UNPKG

eslint-plugin-arrow-return-style

Version:

Enforce arrow function return style and automatically fix it

274 lines (263 loc) 10.7 kB
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); // package.json var name = "eslint-plugin-arrow-return-style"; var version = "1.3.1"; // src/utils/create-rule.ts import { ESLintUtils } from "@typescript-eslint/utils"; var createRule = ESLintUtils.RuleCreator((rule) => { return `https://github.com/u3u/eslint-plugin-arrow-return-style/tree/v${version}/docs/rules/${rule}.md`; }); // src/utils/define-config.ts var defineConfig = (config) => { return config; }; // src/utils/define-plugin.ts var definePlugin = (plugin) => { return plugin; }; // src/configs/recommended.ts var recommended_default = defineConfig({ plugins: ["arrow-return-style"], rules: { "arrow-body-style": "off", "arrow-return-style/arrow-return-style": "warn", "arrow-return-style/no-export-default-arrow": "warn" } }); // src/rules/arrow-return-style.ts import { AST_NODE_TYPES, AST_TOKEN_TYPES, ASTUtils } from "@typescript-eslint/utils"; var RULE_NAME = "arrow-return-style"; var arrowReturnStyleRule = createRule({ create: (context) => { const sourceCode = context.getSourceCode(); return { ArrowFunctionExpression: (arrowFunction) => { const { body: arrowBody, parent: arrowFunctionParent } = arrowFunction; const { jsxAlwaysUseExplicitReturn, maxLen = 80, namedExportsAlwaysUseExplicitReturn = true } = context.options?.[0] || {}; const isMaxLen = (node = getArrowRoot()) => getTokensLength(node) > maxLen; const isMultiline = (node = arrowBody) => { return node.loc.start.line !== node.loc.end.line; }; const isObjectLiteral = (node = arrowBody) => { return node.type === AST_NODE_TYPES.ObjectExpression; }; const isJsxElement = (node = arrowBody) => { return node.type === AST_NODE_TYPES.JSXElement || node.type === AST_NODE_TYPES.JSXFragment; }; const isNamedExport = () => { return arrowFunctionParent.parent?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration; }; const isCallExpression = (node) => { return node?.type === AST_NODE_TYPES.CallExpression; }; const isVariableDeclaration = (node) => { return node?.type === AST_NODE_TYPES.VariableDeclaration; }; const getArrowVariableDeclaration = () => { return isVariableDeclaration(arrowFunctionParent.parent) ? arrowFunctionParent.parent : void 0; }; const getArrowRoot = () => { return isCallExpression(arrowFunctionParent) ? arrowFunction : getArrowVariableDeclaration() || arrowFunctionParent; }; const getLength = (node) => { return node.loc.end.column - node.loc.start.column; }; const getTokensLength = (node = getArrowRoot()) => { const tokens = sourceCode.getTokens(node); const implicitReturnTokens = tokens.filter(ASTUtils.isNotOpeningBraceToken).filter((x) => !(x.type === AST_TOKEN_TYPES.Keyword && x.value === "return")).filter(ASTUtils.isNotClosingBraceToken).filter(ASTUtils.isNotSemicolonToken); const length = implicitReturnTokens.reduce((acc, token) => acc + getLength(token), 0); return length; }; const getArrowToken = () => { const tokens = sourceCode.getTokens(arrowFunction); const arrowToken = tokens.find(ASTUtils.isArrowToken); return arrowToken; }; const commentsExistBetweenArrowTokenAndArrowBody = () => { const arrowToken = getArrowToken(); return arrowToken && sourceCode.commentsExistBetween(arrowToken, arrowBody); }; if (arrowBody.type === AST_NODE_TYPES.BlockStatement) { const blockBody = arrowBody.body; if (blockBody.length === 1 && blockBody[0].type === AST_NODE_TYPES.ReturnStatement) { const returnStatement = blockBody[0]; const returnValue = returnStatement.argument; if (!returnValue) return; if (isMaxLen()) return; if (isMaxLen(returnValue)) return; if (isMultiline(returnValue)) return; if (jsxAlwaysUseExplicitReturn && isJsxElement(returnValue)) return; if (namedExportsAlwaysUseExplicitReturn && isNamedExport()) return; const openingBrace = sourceCode.getFirstToken(arrowBody); const closingBrace = sourceCode.getLastToken(arrowBody); const firstToken = sourceCode.getFirstToken(returnStatement, 1); const lastToken = sourceCode.getLastToken(returnStatement); const commentsExist = sourceCode.commentsExistBetween(openingBrace, firstToken) || sourceCode.commentsExistBetween(lastToken, closingBrace); if (commentsExist) return; context.report({ fix: (fixer) => { const fixes = []; const returnText = sourceCode.getText(returnValue); fixes.push( fixer.remove(openingBrace), fixer.remove(closingBrace), fixer.replaceText(returnStatement, isObjectLiteral(returnValue) ? `(${returnText})` : returnText) ); return fixes; }, messageId: "useImplicitReturn", node: arrowFunction }); } } else { const commentsExist = commentsExistBetweenArrowTokenAndArrowBody(); if ( // commentsExist || isMaxLen() || isMultiline() || jsxAlwaysUseExplicitReturn && isJsxElement() || namedExportsAlwaysUseExplicitReturn && isNamedExport() ) { context.report({ fix: (fixer) => { const fixes = []; const firstToken = sourceCode.getTokenBefore(arrowBody); const lastToken = sourceCode.getTokenAfter(arrowBody); if (firstToken && lastToken && ASTUtils.isOpeningParenToken(firstToken) && ASTUtils.isClosingParenToken(lastToken)) fixes.push(fixer.remove(firstToken), fixer.remove(lastToken)); if (commentsExist) { const arrowToken = getArrowToken(); fixes.push( fixer.insertTextAfter(arrowToken, " {"), fixer.insertTextBefore(arrowBody, "return "), fixer.insertTextAfter(arrowBody, "\n}") ); } else { fixes.push(fixer.replaceText(arrowBody, `{ return ${sourceCode.getText(arrowBody)} }`)); } return fixes; }, messageId: "useExplicitReturn", node: arrowFunction }); } } } }; }, defaultOptions: [ { jsxAlwaysUseExplicitReturn: false, maxLen: 80, namedExportsAlwaysUseExplicitReturn: true } ], meta: { docs: { description: "Enforce arrow function return style" }, fixable: "code", messages: { useExplicitReturn: "Use explicit return for multiline arrow function bodies.", useImplicitReturn: "Use implicit return for single-line arrow function bodies." }, schema: [ { additionalProperties: true, properties: { jsxAlwaysUseExplicitReturn: { default: false, type: "boolean" }, maxLen: { default: 80, type: "number" }, namedExportsAlwaysUseExplicitReturn: { default: true, type: "boolean" } }, type: "object" } ], type: "suggestion" }, name: RULE_NAME }); // src/rules/no-export-default-arrow.ts import path from "path"; import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils"; import { camelCase, pascalCase } from "scule"; var RULE_NAME2 = "no-export-default-arrow"; var noExportDefaultArrowRule = createRule({ create: (context) => { const sourceCode = context.getSourceCode(); let program; return { ArrowFunctionExpression: (arrowFunction) => { const { body: arrowBody, parent: arrowFunctionParent } = arrowFunction; const isJsxElement = (node) => { return node.type === AST_NODE_TYPES2.JSXElement || node.type === AST_NODE_TYPES2.JSXFragment; }; const getArrowReturnValues = () => { if (arrowBody.type === AST_NODE_TYPES2.BlockStatement) { const blockBody = arrowBody.body; const returnValues = blockBody.filter((node) => { return node.type === AST_NODE_TYPES2.ReturnStatement; }).map((node) => node.argument).filter(Boolean); return returnValues; } return [arrowBody]; }; const arrowReturnIsJsxElement = () => { const returnValues = getArrowReturnValues(); return returnValues.some((node) => isJsxElement(node)); }; if (arrowFunctionParent.type === AST_NODE_TYPES2.ExportDefaultDeclaration) { context.report({ fix: (fixer) => { const fixes = []; const lastToken = sourceCode.getLastToken(program, { includeComments: true }) || arrowFunctionParent; const fileName = context.getPhysicalFilename?.() || context.getFilename() || "namedFunction"; const { name: fileNameWithoutExtension } = path.parse(fileName); const funcName = arrowReturnIsJsxElement() ? pascalCase(fileNameWithoutExtension) : camelCase(fileNameWithoutExtension); fixes.push( fixer.replaceText(arrowFunctionParent, `const ${funcName} = ${sourceCode.getText(arrowFunction)}`), fixer.insertTextAfter(lastToken, ` export default ${funcName}`) ); return fixes; }, messageId: "disallowExportDefaultArrow", node: arrowFunction }); } }, Program: (node) => program = node }; }, defaultOptions: [], meta: { docs: { description: "Disallow export default anonymous arrow function<br/>_**Automatically fix using the current file name.**_" }, fixable: "code", messages: { disallowExportDefaultArrow: "Disallow export default anonymous arrow function" }, schema: [], type: "suggestion" }, name: RULE_NAME2 }); // src/index.ts var src_default = definePlugin({ configs: { recommended: recommended_default }, meta: { name, version }, rules: { [RULE_NAME]: arrowReturnStyleRule, [RULE_NAME2]: noExportDefaultArrowRule } }); export { src_default as default };