Add prefer-create-type-check-function rule (#14071)
This commit is contained in:
1 parent
b6027f891f
commit
4176837f75
8 files changed
+462
-83
No files matched your search
@@ -15,6 +15,7 @@ module.exports = {
|
||||
"no-node-comments": require("./no-node-comments.js"),
|
||||
"no-unnecessary-ast-path-call": require("./no-unnecessary-ast-path-call.js"),
|
||||
"prefer-ast-path-each": require("./prefer-ast-path-each.js"),
|
||||
"prefer-create-type-check-function": require("./prefer-create-type-check-function.js"),
|
||||
"prefer-indent-if-break": require("./prefer-indent-if-break.js"),
|
||||
"prefer-is-non-empty-array": require("./prefer-is-non-empty-array.js"),
|
||||
"prefer-fs-promises-submodule": require("./prefer-fs-promises-submodule.js"),
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
"use strict";
|
||||
|
||||
const MESSAGE_ID = "prefer-create-type-check-function";
|
||||
|
||||
const isTypeAccess = (node, parameterName) => {
|
||||
if (node.type === "ChainExpression") {
|
||||
node = node.expression;
|
||||
}
|
||||
|
||||
return (
|
||||
node.type === "MemberExpression" &&
|
||||
!node.computed &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.object.name === parameterName &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === "type"
|
||||
);
|
||||
};
|
||||
|
||||
const isTypeCheck = (node, parameterName) =>
|
||||
node.type === "BinaryExpression" &&
|
||||
node.operator === "===" &&
|
||||
isTypeAccess(node.left, parameterName) &&
|
||||
node.right.type === "Literal" &&
|
||||
typeof node.right.value === "string";
|
||||
function getTypes(node, parameterName) {
|
||||
if (isTypeCheck(node, parameterName)) {
|
||||
return [node.right.value];
|
||||
}
|
||||
|
||||
if (node.type === "LogicalExpression" && node.operator === "||") {
|
||||
const left = getTypes(node.left, parameterName);
|
||||
|
||||
if (!left) {
|
||||
return;
|
||||
}
|
||||
const right = getTypes(node.right, parameterName);
|
||||
|
||||
if (!right) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [...left, ...right];
|
||||
}
|
||||
}
|
||||
|
||||
function isTopLevelFunction(node) {
|
||||
return (
|
||||
node.parent.type === "Program" ||
|
||||
(node.parent.type === "VariableDeclarator" &&
|
||||
node.parent.parent.type === "VariableDeclaration" &&
|
||||
node.parent.parent.parent.type === "Program") ||
|
||||
node.parent.type === "ExportDefaultDeclaration"
|
||||
);
|
||||
}
|
||||
|
||||
const selector = [
|
||||
":function",
|
||||
"[params.length=1]",
|
||||
'[params.0.type="Identifier"]',
|
||||
"[async!=true]",
|
||||
"[generator!=true]",
|
||||
].join("");
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url: "https://github.com/prettier/prettier/blob/main/scripts/tools/eslint-plugin-prettier-internal-rules/prefer-create-type-check-function.js",
|
||||
},
|
||||
messages: {
|
||||
[MESSAGE_ID]:
|
||||
"Prefer use `createTypeCheckFunction` to create this function",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
ignoreSingleType: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
onlyTopLevelFunctions: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
const { ignoreSingleType, onlyTopLevelFunctions } = {
|
||||
ignoreSingleType: false,
|
||||
onlyTopLevelFunctions: false,
|
||||
...context.options[0],
|
||||
};
|
||||
|
||||
return {
|
||||
[selector](functionNode) {
|
||||
if (onlyTopLevelFunctions && !isTopLevelFunction(functionNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let returnStatementArgument = functionNode.body;
|
||||
if (functionNode.body.type === "BlockStatement") {
|
||||
const { body } = functionNode;
|
||||
if (
|
||||
body.body.length !== 1 ||
|
||||
body.body[0].type !== "ReturnStatement" ||
|
||||
!body.body[0].argument
|
||||
) {
|
||||
return;
|
||||
}
|
||||
returnStatementArgument = body.body[0].argument;
|
||||
}
|
||||
|
||||
const parameterName = functionNode.params[0].name;
|
||||
const types = getTypes(returnStatementArgument, parameterName);
|
||||
if (!types) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoreSingleType && types.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const problem = {
|
||||
node: functionNode,
|
||||
messageId: MESSAGE_ID,
|
||||
};
|
||||
|
||||
if (
|
||||
context.getSourceCode().getCommentsInside(functionNode).length === 0
|
||||
) {
|
||||
problem.fix = (fixer) => {
|
||||
let text = `createTypeCheckFunction(${JSON.stringify(
|
||||
types,
|
||||
undefined,
|
||||
2
|
||||
)})`;
|
||||
if (functionNode.type === "FunctionDeclaration") {
|
||||
const functionName =
|
||||
functionNode.id?.name ?? "__please_name_this_function";
|
||||
text = `const ${functionName} = ${text};`;
|
||||
|
||||
if (
|
||||
functionNode.parent.type === "ExportDefaultDeclaration" &&
|
||||
functionNode.parent.declaration === functionNode
|
||||
) {
|
||||
return fixer.replaceText(
|
||||
functionNode.parent,
|
||||
`${text}\nexport default ${functionName};`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fixer.replaceText(functionNode, text);
|
||||
};
|
||||
}
|
||||
|
||||
context.report(problem);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -340,6 +340,213 @@ test("prefer-ast-path-each", {
|
||||
],
|
||||
});
|
||||
|
||||
test("prefer-create-type-check-function", {
|
||||
valid: [
|
||||
'node.type === "Identifier"',
|
||||
'node.type === "Identifier" || node.type === "FunctionExpression"',
|
||||
"const isIdentifier = node => {}",
|
||||
'const isIdentifier = async node => node.type === "Identifier"',
|
||||
outdent`
|
||||
function * isIdentifier(node){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
async function isIdentifier(node){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
async function * isIdentifier(node){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return;
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node, extraParameter){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier({node}){
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return node.type === "Identifier" && node.type === "FunctionExpression";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return node.type === Identifier;
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return node.type !== "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return node[type] === "Identifier";
|
||||
}
|
||||
`,
|
||||
outdent`
|
||||
function isIdentifier(node){
|
||||
return node.type === "Identifier" || node.type === "FunctionExpression" || notTypeChecking();
|
||||
}
|
||||
`,
|
||||
{
|
||||
code: 'const isIdentifier = node => node.type === "Identifier";',
|
||||
options: [{ ignoreSingleType: true }],
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
function foo() {
|
||||
use(node => node.type === "Identifier" || node.type === "FunctionExpression");
|
||||
}
|
||||
`,
|
||||
options: [{ onlyTopLevelFunctions: true }],
|
||||
},
|
||||
outdent`
|
||||
function isGetterOrSetter(node) {
|
||||
return node.kind === "get" || node.kind === "set";
|
||||
}
|
||||
`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: outdent`
|
||||
function isIdentifier(node) {
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
output: outdent`
|
||||
const isIdentifier = createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]);
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
export default function isIdentifier(node) {
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
output: outdent`
|
||||
const isIdentifier = createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]);
|
||||
export default isIdentifier;
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
export default function (node) {
|
||||
return node.type === "Identifier";
|
||||
}
|
||||
`,
|
||||
output: outdent`
|
||||
const __please_name_this_function = createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]);
|
||||
export default __please_name_this_function;
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
use(function isIdentifier(node) {
|
||||
return node.type === "Identifier";
|
||||
})
|
||||
`,
|
||||
output: outdent`
|
||||
use(createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]))
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
const foo = node => node.type === "Identifier";
|
||||
`,
|
||||
output: outdent`
|
||||
const foo = createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]);
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
const foo = node => {
|
||||
return node.type === "Identifier";
|
||||
};
|
||||
`,
|
||||
output: outdent`
|
||||
const foo = createTypeCheckFunction([
|
||||
"Identifier"
|
||||
]);
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
const foo = node =>
|
||||
node.type === "Identifier" || node.type === "FunctionExpression";
|
||||
`,
|
||||
output: outdent`
|
||||
const foo = createTypeCheckFunction([
|
||||
"Identifier",
|
||||
"FunctionExpression"
|
||||
]);
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: outdent`
|
||||
const foo = node =>
|
||||
node.type === "Identifier" || node?.type === "FunctionExpression";
|
||||
`,
|
||||
output: outdent`
|
||||
const foo = createTypeCheckFunction([
|
||||
"Identifier",
|
||||
"FunctionExpression"
|
||||
]);
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
// Skip fix if comments inside
|
||||
{
|
||||
code: outdent`
|
||||
const foo = node =>
|
||||
node.type === "Identifier" || /* comment */ node.type === "FunctionExpression";
|
||||
`,
|
||||
output: outdent`
|
||||
const foo = node =>
|
||||
node.type === "Identifier" || /* comment */ node.type === "FunctionExpression";
|
||||
`,
|
||||
errors: 1,
|
||||
},
|
||||
].map((testCase) => ({
|
||||
...testCase,
|
||||
parserOptions: { sourceType: "module" },
|
||||
})),
|
||||
});
|
||||
|
||||
test("prefer-indent-if-break", {
|
||||
valid: [
|
||||
"ifBreak(indent(doc))",
|
||||
|
||||
Reference in new issue
Block a user