Convert node types Set to type check function (#18149)

This commit is contained in:
fisker Cheung authored and GitHub committed 2025-10-30 02:15:32 +08:00
1 parent e51ff9ad41
commit e5118b2650
6 files changed
+161 -18

No files matched your search

@@ -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)})`,
);
}
},
});
},
};
},
};
@@ -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,
},
],
});
+10 -10
View File
@@ -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;
+3 -2
View File
@@ -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;
}
+7 -4
View File
@@ -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) {
@@ -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);
}