UNPKG

eslint-plugin-vtex

Version:
276 lines (265 loc) 9.38 kB
"use strict"; // src/configs/recommended.ts var recommended = { rules: { // Prefer an early return to prevent nesting and improve code readability. "vtex/prefer-early-return": [ "warn", { maxStatements: 2 } ], "vtex/consistent-props-type": "off" } }; // src/utils/react/isComponentName.ts var isComponentName = (name) => name.length > 0 && name[0] === name[0].toUpperCase(); // src/createRule.ts var import_utils = require("@typescript-eslint/utils"); var createRule = import_utils.ESLintUtils.RuleCreator( (name) => `https://github.com/vtex/typescript/tree/main/packages/eslint-plugin-vtex/docs/rules/${name}.md` ); // src/utils/estree/isFunctionNode.ts var isFunctionNode = (node) => { return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration"; }; // src/utils/estree/isInsideAnotherFunction.ts var isInsideAnotherFunction = (context) => { const functions = context.getAncestors().filter(isFunctionNode); return functions.length > 1; }; // src/utils/estree/getFunctionNodeName.ts var getFunctionNodeName = (node) => { var _a, _b; if (node.type === "FunctionDeclaration") { return ((_a = node.id) == null ? void 0 : _a.name) ?? ""; } if (((_b = node.parent) == null ? void 0 : _b.type) === "VariableDeclarator" && node.parent.id.type === "Identifier") { return node.parent.id.name; } return ""; }; // src/rules/consistent-props-type.ts var consistentPropsType = createRule({ name: "consistent-props-type", meta: { type: "suggestion", docs: { recommended: false, description: "Consistent way to define props type" }, messages: { invalidPropsTypeName: "Invalid props type name. Must be: {{expectedPropsTypeName}}", propsTypeIncomplete: "Invalid inline props type. {{expectedPropsTypeName}} must define all the props.", invalidInlinePropsType: "Invalid inline props type. You must to create an type {{expectedPropsTypeName}}", genericsPropsTypeName: "Invalid generics props type name. Must be: Props" }, schema: [] }, defaultOptions: [], create(context) { return { ":function > :matches(Identifier, ObjectPattern) > TSTypeAnnotation": function(node) { var _a, _b, _c; if (isInsideAnotherFunction(context)) return; const functionNode = context.getAncestors().reverse().find(isFunctionNode); const functionName = getFunctionNodeName(functionNode); const [typeParameter] = ((_a = functionNode.typeParameters) == null ? void 0 : _a.params) ?? []; if (!isComponentName(functionName)) return; const expectedPropsTypeName = `${functionName}Props`; if (typeParameter) { if (typeParameter.name.name !== "Props") { context.report({ node: typeParameter.name, messageId: "genericsPropsTypeName" }); } if (typeParameter.constraint && ((_b = typeParameter.constraint) == null ? void 0 : _b.type) !== "TSTypeReference") { context.report({ node: typeParameter.constraint, messageId: "propsTypeIncomplete", data: { expectedPropsTypeName } }); } if (((_c = typeParameter.constraint) == null ? void 0 : _c.type) === "TSTypeReference" && typeParameter.constraint.typeName.type === "Identifier" && typeParameter.constraint.typeName.name !== expectedPropsTypeName) { context.report({ node: typeParameter.constraint, messageId: "invalidPropsTypeName", data: { expectedPropsTypeName } }); } return; } if (node.typeAnnotation.type !== "TSTypeReference") { context.report({ node: node.typeAnnotation, messageId: "invalidInlinePropsType", data: { expectedPropsTypeName } }); return; } if (node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name !== expectedPropsTypeName) { context.report({ node: node.typeAnnotation, messageId: "invalidPropsTypeName", data: { expectedPropsTypeName } }); } }, "VariableDeclarator > Identifier > TSTypeAnnotation": function(node) { if (isInsideAnotherFunction(context)) return; const expectedPropsTypeName = `${node.parent.name}Props`; if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "FC" && node.typeAnnotation.typeParameters) { const [typeParameter] = node.typeAnnotation.typeParameters.params; if (typeParameter.type === "TSTypeReference" && typeParameter.typeName.type === "Identifier" && typeParameter.typeName.name !== expectedPropsTypeName) { context.report({ node: typeParameter, messageId: "invalidPropsTypeName", data: { expectedPropsTypeName } }); } if (typeParameter.type !== "TSTypeReference") { context.report({ node: typeParameter, messageId: "propsTypeIncomplete", data: { expectedPropsTypeName } }); } } } }; } }); // src/rules/prefer-early-return.ts function isLonelyIfStatement(node) { return node.type === "IfStatement" && node.alternate == null; } var preferEarlyReturn = createRule({ name: "prefer-early-return", meta: { type: "layout", docs: { recommended: "error", description: "Prefer early returns over full-body conditional wrapping in function declarations." }, schema: [ { type: "object", properties: { maxStatements: { type: "integer" } }, additionalProperties: false } ], messages: { default: "Prefer an early return to prevent nesting and improve code readability" } }, defaultOptions: [{ maxStatements: 1 }], create(context) { const options = { ...context.options[0] }; const { maxStatements } = options; function isOffendingConsequent(consequentNode) { return consequentNode.type === "ExpressionStatement" && maxStatements === 0 || consequentNode.type === "BlockStatement" && consequentNode.body.length > maxStatements; } function isOffendingIfStatement(ifNode) { return isLonelyIfStatement(ifNode) && isOffendingConsequent(ifNode.consequent); } function checkFunctionBody(fnNode) { const bodyNode = fnNode.body; if (bodyNode.type !== "BlockStatement" || bodyNode.body.length === 0) { return; } const lastNode = bodyNode.body[bodyNode.body.length - 1]; if (!isOffendingIfStatement(lastNode)) { return; } context.report({ node: lastNode, messageId: "default" }); } return { FunctionDeclaration: checkFunctionBody, FunctionExpression: checkFunctionBody, ArrowFunctionExpression: checkFunctionBody }; } }); // src/rules/prefer-use-effect-named-callback.ts var USE_EFFECT_NAME = "useEffect"; function checkCallExpressionIsUseEffect(node) { if (node.type !== "CallExpression") { return false; } if (!node.callee) { return false; } if (node.callee.type === "Identifier" && node.callee.name === USE_EFFECT_NAME) { return true; } if (node.callee.type === "MemberExpression" && node.callee.property && node.callee.property.type === "Identifier" && node.callee.property.name === USE_EFFECT_NAME) { return true; } return false; } var preferUseEffectNamedCallback = createRule({ name: "prefer-use-effect-named-callback", meta: { type: "layout", docs: { recommended: "error", description: "Prefer useEffect with named function or constant callbacks." }, messages: { default: "Prefer useEffect with named function or constant callbacks." }, schema: [] }, defaultOptions: [], create(context) { function checkUseEffectCallExpression(callExpressionNode) { if (!checkCallExpressionIsUseEffect(callExpressionNode)) { return; } const [useEffectFirstArgument] = callExpressionNode.arguments; let hasError = false; if (useEffectFirstArgument.type === "ArrowFunctionExpression" && useEffectFirstArgument.id === null) { hasError = true; } if (useEffectFirstArgument.type === "FunctionExpression" && useEffectFirstArgument.id === null) { hasError = true; } if (hasError) { context.report({ node: useEffectFirstArgument, messageId: "default" }); } } return { CallExpression: checkUseEffectCallExpression }; } }); // src/index.ts module.exports = { configs: { recommended }, // TODO: these rules could be auto-generated using fs+path, // but tsdx doesn't works well with dynamic imports, // so we need to change our build system first rules: { "consistent-props-type": consistentPropsType, "prefer-early-return": preferEarlyReturn, "prefer-use-effect-named-callback": preferUseEffectNamedCallback } };