From e5118b26506c0f1d61ca122b9950615886005051 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 30 Oct 2025 02:15:32 +0800 Subject: [PATCH] Convert node types `Set` to type check function (#18149) --- .../prefer-create-type-check-function.js | 119 +++++++++++++++++- .../test.js | 13 ++ src/language-js/comments/handle-comments.js | 20 +-- src/language-js/print/jsx.js | 5 +- src/language-js/print/misc.js | 11 +- .../utils/create-type-check-function.js | 11 ++ 6 files changed, 161 insertions(+), 18 deletions(-) diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/prefer-create-type-check-function.js b/scripts/tools/eslint-plugin-prettier-internal-rules/prefer-create-type-check-function.js index 366f7d877..61cf6124b 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/prefer-create-type-check-function.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/prefer-create-type-check-function.js @@ -126,13 +126,27 @@ function isTopLevelFunction(node) { ); } -const selector = [ +const functionSelector = [ ":function", "[params.length=1]", "[async!=true]", "[generator!=true]", ].join(""); +const setDeclareSelector = [ + "VariableDeclaration", + '[kind="const"]', + "[declarations.length=1]", + " > ", + "VariableDeclarator.declarations", + '[init.type="NewExpression"]', + '[init.callee.type="Identifier"]', + '[init.callee.name="Set"]', + "[init.arguments.length=1]", + '[init.arguments.0.type="ArrayExpression"]', + " > Identifier.id", +].join(""); + export default { meta: { type: "suggestion", @@ -165,7 +179,7 @@ export default { const sourceCode = context.getSourceCode(); return { - [selector](functionNode) { + [functionSelector](functionNode) { if (onlyTopLevelFunctions && !isTopLevelFunction(functionNode)) { return; } @@ -252,6 +266,107 @@ export default { context.report(problem); }, + [setDeclareSelector](identifier) { + const variable = sourceCode + .getDeclaredVariables(identifier.parent) + .find( + (variable) => + variable.identifiers.length === 1 && + variable.identifiers[0] === identifier, + ); + + if (!variable) { + return; + } + + const { references } = variable; + + if (references.length === 0) { + return; + } + + const nodes = []; + + for (const { identifier: referenceIdentifier } of references) { + if (referenceIdentifier === identifier) { + continue; + } + + if ( + !( + referenceIdentifier.parent.type === "MemberExpression" && + referenceIdentifier.parent.object === referenceIdentifier && + !referenceIdentifier.parent.optional && + !referenceIdentifier.parent.computed && + referenceIdentifier.parent.property.type === "Identifier" && + referenceIdentifier.parent.property.name === "has" && + referenceIdentifier.parent.parent.type === "CallExpression" && + referenceIdentifier.parent.parent.callee === + referenceIdentifier.parent && + !referenceIdentifier.parent.parent.optional && + referenceIdentifier.parent.parent.arguments.length === 1 + ) + ) { + return; + } + + const [typeNode] = referenceIdentifier.parent.parent.arguments; + const memberExpression = + typeNode.type === "ChainExpression" + ? typeNode.expression + : typeNode; + + if ( + !( + memberExpression.type === "MemberExpression" && + !memberExpression.computed && + memberExpression.property.type === "Identifier" && + memberExpression.property.name === "type" + ) + ) { + return; + } + + nodes.push({ + setHasNode: referenceIdentifier.parent, + typeNode, + nodeNode: memberExpression.object, + }); + } + + if (nodes.length === 0) { + return; + } + + context.report({ + node: identifier, + messageId: MESSAGE_ID, + *fix(fixer) { + const { name } = identifier; + const replacement = `__please_rename_this_function_${name}`; + + // Rename variable + yield fixer.replaceText(identifier, replacement); + + // `new Set()` -> `createTypeCheckFunction()` + const newSet = identifier.parent.init; + const newToken = sourceCode.getFirstToken(newSet); + yield fixer.remove(newToken); + yield fixer.replaceText(newSet.callee, "createTypeCheckFunction"); + + for (const { setHasNode, typeNode, nodeNode } of nodes) { + // `set.has` -> `typeCheckFunction` + yield fixer.replaceText(setHasNode, replacement); + + // `foo.type` -> `foo` + yield fixer.replaceText( + typeNode, + `(${sourceCode.getText(nodeNode)})`, + ); + } + }, + }); + }, }; }, }; diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js index d99ea7ca1..33f64c771 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js @@ -570,6 +570,19 @@ test("prefer-create-type-check-function", { 'const isClassProperty = createTypeCheckFunction(["ClassProperty", "PropertyDefinition"]);', errors: 1, }, + { + code: outdent` + const types = new Set(["ClassProperty", "PropertyDefinition"]); + if (types.has(node?.type)) {} + if (types.has((0, foo).type)) {} + `, + output: outdent` + const __please_rename_this_function_types = createTypeCheckFunction(["ClassProperty", "PropertyDefinition"]); + if (__please_rename_this_function_types((node))) {} + if (__please_rename_this_function_types((0, foo))) {} + `, + errors: 1, + }, ], }); diff --git a/src/language-js/comments/handle-comments.js b/src/language-js/comments/handle-comments.js index 7467059be..df4ae1ee8 100644 --- a/src/language-js/comments/handle-comments.js +++ b/src/language-js/comments/handle-comments.js @@ -446,7 +446,7 @@ function handleConditionalExpressionComments({ return false; } -const classLikeNodeTypes = new Set([ +const isClassLikeNode = createTypeCheckFunction([ "ClassDeclaration", "ClassExpression", "DeclareClass", @@ -460,7 +460,7 @@ function handleClassComments({ enclosingNode, followingNode, }) { - if (classLikeNodeTypes.has(enclosingNode?.type)) { + if (isClassLikeNode(enclosingNode)) { if ( isNonEmptyArray(enclosingNode.decorators) && !(followingNode?.type === "Decorator") @@ -508,7 +508,7 @@ function handleClassComments({ return false; } -const propertyLikeNodeTypes = new Set([ +const isPropertyLikeNode = createTypeCheckFunction([ "ClassMethod", "ClassProperty", "PropertyDefinition", @@ -552,7 +552,7 @@ function handleMethodNameComments({ // on the decorator node instead of the method node if ( precedingNode?.type === "Decorator" && - propertyLikeNodeTypes.has(enclosingNode?.type) && + isPropertyLikeNode(enclosingNode) && (isLineComment(comment) || comment.placement === "ownLine") ) { addTrailingComment(precedingNode, comment); @@ -562,7 +562,7 @@ function handleMethodNameComments({ return false; } -const functionLikeNodeTypes = new Set([ +const isFunctionLikeNode = createTypeCheckFunction([ "FunctionDeclaration", "FunctionExpression", "ClassMethod", @@ -578,7 +578,7 @@ function handleFunctionNameComments({ if (getNextNonSpaceNonCommentCharacter(text, locEnd(comment)) !== "(") { return false; } - if (precedingNode && functionLikeNodeTypes.has(enclosingNode?.type)) { + if (precedingNode && isFunctionLikeNode(enclosingNode)) { addTrailingComment(precedingNode, comment); return true; } @@ -891,13 +891,13 @@ function handleAssignmentPatternComments({ comment, enclosingNode }) { return false; } -const assignmentLikeNodeTypes = new Set([ +const isAssignmentLikeNode = createTypeCheckFunction([ "VariableDeclarator", "AssignmentExpression", "TypeAlias", "TSTypeAliasDeclaration", ]); -const complexExprNodeTypes = new Set([ +const isComplexExprNode = createTypeCheckFunction([ "ObjectExpression", "ArrayExpression", "TemplateLiteral", @@ -911,9 +911,9 @@ function handleVariableDeclaratorComments({ followingNode, }) { if ( - assignmentLikeNodeTypes.has(enclosingNode?.type) && + isAssignmentLikeNode(enclosingNode) && followingNode && - (complexExprNodeTypes.has(followingNode.type) || isBlockComment(comment)) + (isComplexExprNode(followingNode) || isBlockComment(comment)) ) { addLeadingComment(followingNode, comment); return true; diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index 20e8eeb3f..65cfa0a7e 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -28,6 +28,7 @@ import pathNeedsParens from "../needs-parens.js"; import getRaw from "../utils/get-raw.js"; import { CommentCheckFlags, + createTypeCheckFunction, hasComment, hasNodeIgnoreComment, isArrayExpression, @@ -485,7 +486,7 @@ function separatorWithWhitespace( return hardline; } -const NO_WRAP_PARENTS = new Set([ +const isNoWrapParent = createTypeCheckFunction([ "ArrayExpression", "JSXAttribute", "JSXElement", @@ -501,7 +502,7 @@ const NO_WRAP_PARENTS = new Set([ function maybeWrapJsxElementInParens(path, elem, options) { const { parent } = path; - if (NO_WRAP_PARENTS.has(parent.type)) { + if (isNoWrapParent(parent)) { return elem; } diff --git a/src/language-js/print/misc.js b/src/language-js/print/misc.js index e8ad0e422..9f17cb698 100644 --- a/src/language-js/print/misc.js +++ b/src/language-js/print/misc.js @@ -1,6 +1,7 @@ import { indent, line } from "../../document/builders.js"; import { CommentCheckFlags, + createTypeCheckFunction, hasComment, isCallExpression, isMemberExpression, @@ -51,7 +52,7 @@ function printDefiniteToken(path) { : ""; } -const flowDeclareNodeTypes = new Set([ +const isFlowDeclareNode = createTypeCheckFunction([ "DeclareClass", "DeclareComponent", "DeclareFunction", @@ -64,6 +65,7 @@ const flowDeclareNodeTypes = new Set([ "DeclareEnum", "DeclareInterface", ]); + /** * @param {AstPath} path * @returns {Doc} @@ -75,24 +77,25 @@ function printDeclareToken(path) { // TypeScript node.declare || // Flow - (flowDeclareNodeTypes.has(node.type) && + (isFlowDeclareNode(node) && path.parent.type !== "DeclareExportDeclaration") ? "declare " : "" ); } -const tsAbstractNodeTypes = new Set([ +const isTsAbstractNode = createTypeCheckFunction([ "TSAbstractMethodDefinition", "TSAbstractPropertyDefinition", "TSAbstractAccessorProperty", ]); + /** * @param {AstPath} param0 * @returns {Doc} */ function printAbstractToken({ node }) { - return node.abstract || tsAbstractNodeTypes.has(node.type) ? "abstract " : ""; + return node.abstract || isTsAbstractNode(node) ? "abstract " : ""; } function printBindExpressionCallee(path, options, print) { diff --git a/src/language-js/utils/create-type-check-function.js b/src/language-js/utils/create-type-check-function.js index dbcc243da..5491983e9 100644 --- a/src/language-js/utils/create-type-check-function.js +++ b/src/language-js/utils/create-type-check-function.js @@ -1,3 +1,5 @@ +import * as assert from "#universal/assert"; + /** @import {Node, Comment} from "../types/estree.js" */ /** @@ -6,6 +8,15 @@ */ function createTypeCheckFunction(typesArray) { const types = new Set(typesArray); + + if (process.env.NODE_ENV !== "production") { + assert.equal( + typesArray.length, + types.size, + "'typesArray' should be unique.", + ); + } + return (node) => types.has(node?.type); }