Add prefer-create-type-check-function rule (#14071)

This commit is contained in:
fisker Cheung authored and GitHub committed 2022-12-29 17:17:19 +08:00
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"),
@@ -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))",