UNPKG

eslint-plugin-arrow-return-style-x

Version:
924 lines (915 loc) 37.3 kB
import { ASTUtils, AST_NODE_TYPES } from "@typescript-eslint/utils"; import detectIndent from "detect-indent"; import { RuleCreator } from "@typescript-eslint/utils/eslint-utils"; import path, { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { createSyncFn } from "synckit"; //#region package.json var name = "eslint-plugin-arrow-return-style-x"; var version = "1.2.6"; var repository = { "url": "git+https://github.com/christopher-buss/eslint-plugin-arrow-return-style-x.git", "type": "git" }; //#endregion //#region src/util.ts const createEslintRule = RuleCreator((name$1) => { const repoUrl = repository.url.replace(/^git\+/, "").replace(/\.git$/, ""); return `${repoUrl}/blob/v${version}/src/rules/${name$1}/documentation.md`; }); //#endregion //#region src/utils/prettier-format.ts const currentFilename = fileURLToPath(import.meta.url); const currentDirname = dirname(currentFilename); const workerPath = process.env.PRETTIER_WORKER_PATH !== void 0 && process.env.PRETTIER_WORKER_PATH !== "" ? resolve(process.env.PRETTIER_WORKER_PATH) : resolve(currentDirname, "../../dist/workers/prettier-worker.mjs"); const prettierWorkerSync = createSyncFn(workerPath); const formatCache = /* @__PURE__ */ new Map(); function formatWithPrettier(code, context, configOverride) { const filePath = context.physicalFilename || context.filename || "file.ts"; const configKey = configOverride ? JSON.stringify(configOverride) : ""; const cacheKey = `${code}:${filePath}:${configKey}`; const cached = formatCache.get(cacheKey); if (cached) return cached; try { const workerResult = prettierWorkerSync({ code, configOverride, filePath, type: "format" }); const result = processWorkerResult(workerResult, code); formatCache.set(cacheKey, result); return result; } catch (err) { const result = handleFormattingError(err, code); formatCache.set(cacheKey, result); return result; } } /** * Checks if Prettier integration is available. * * @returns True if Prettier is available, false otherwise. */ function isPrettierAvailable() { try { const result = prettierWorkerSync({ filePath: "package.json", type: "resolveConfig" }); return result.success; } catch { return false; } } function isPrettierEnabled(usePrettier) { return usePrettier === true || typeof usePrettier === "object" && usePrettier !== null; } /** * Resolve Prettier configuration options for the project. * * @param filePath - The file path to resolve config for. * @returns The Prettier configuration options, or an empty object if none * found. */ function resolvePrettierConfigOptions(filePath = "package.json") { try { const result = prettierWorkerSync({ filePath, type: "resolveConfig" }); return result.success && "config" in result ? result.config : {}; } catch { return {}; } } function shouldUsePrettier(context, options) { const filePath = context.physicalFilename || context.filename || "file.ts"; const { usePrettier } = options; if (usePrettier === false) return false; if (usePrettier === true) return resolvePrettierConfigOptions(filePath); if (typeof usePrettier === "object" && usePrettier !== null) return usePrettier; const isAvailable = isPrettierAvailable(); if (isAvailable) return resolvePrettierConfigOptions(filePath); return false; } /** * Creates a fallback result when Prettier formatting fails. * * @param code - The code to format. * @param error - Optional error message. * @returns Formatting result with line length and multiline info. */ function createFallbackResult(code, error) { return { error, formatted: code, isMultiline: code.includes("\n"), lineLength: code.replace(/\n.*$/s, "").length }; } /** * Handles errors during prettier formatting and returns fallback result. * * @param err - The error that occurred. * @param code - The original code to format. * @returns Fallback result with error information. */ function handleFormattingError(err, code) { const errorMessage = err instanceof Error ? `Worker error: ${err.message}` : "Worker communication failed"; return createFallbackResult(code, errorMessage); } /** * Processes worker result and returns a PrettierFormatResult. * * @param workerResult - The result from the prettier worker. * @param code - The original code that was formatted. * @returns Processed format result. */ function processWorkerResult(workerResult, code) { if (workerResult.success && "formatted" in workerResult) return { formatted: workerResult.formatted, isMultiline: workerResult.isMultiline, lineLength: workerResult.lineLength }; const errorMessage = workerResult.success ? "Invalid worker response" : workerResult.error; return createFallbackResult(code, errorMessage); } //#endregion //#region src/rules/arrow-return-style/rule.ts const indentCache = /* @__PURE__ */ new WeakMap(); const ObjectReturnStyle = { AlwaysExplicit: "always-explicit", ComplexExplicit: "complex-explicit", Off: "off" }; const RULE_NAME$1 = "arrow-return-style"; const IMPLICIT_RETURN_VIOLATION = "use-implicit-return"; const EXPLICIT_RETURN_VIOLATION = "use-explicit-return"; const EXPLICIT_RETURN_COMPLEX = "use-explicit-return-complex"; const messages$1 = { [EXPLICIT_RETURN_COMPLEX]: "Use explicit return for complex {{type}} expressions.", [EXPLICIT_RETURN_VIOLATION]: "Use explicit return for arrow function bodies.", [IMPLICIT_RETURN_VIOLATION]: "Use implicit return for arrow function bodies." }; function adjustJsxIndentation(bodyText, indentUnit) { const bodyLines = bodyText.split("\n"); if (bodyLines.length <= 1) return bodyText; const adjustedLines = bodyLines.map((line, index) => { if (index === 0) return line; return line.trim() === "" ? line : indentUnit + line; }); return adjustedLines.join("\n"); } function buildCallExpressionContext({ arrowNode, callExpression, implicitReturnText, parameters, sourceCode }) { const contextNode = callExpression.parent.type === AST_NODE_TYPES.VariableDeclarator ? callExpression.parent : callExpression; const contextText = sourceCode.getText(contextNode); const arrowFunctionText = sourceCode.getText(arrowNode); const implicitArrowFunction = `${parameters} => ${implicitReturnText}`; return contextText.replace(arrowFunctionText, implicitArrowFunction); } function buildConvertedArrowFunction(node, returnValue, sourceCode) { const nodeText = sourceCode.getText(node); const arrowIndex = nodeText.indexOf(" => "); if (arrowIndex === -1) return null; const parameters = nodeText.substring(0, arrowIndex); const returnValueText = sourceCode.getText(returnValue); const implicitReturnText = isObjectLiteral(returnValue) ? `(${returnValueText})` : returnValueText; return `${parameters} => ${implicitReturnText}`; } function buildConvertedForReturn(node, returnValue, sourceCode) { return buildConvertedArrowFunction(node, returnValue, sourceCode); } function buildIsolatedArrowFunction(returnValue, sourceCode, node) { const returnValueText = sourceCode.getText(returnValue); const nodeText = sourceCode.getText(node); const arrowIndex = nodeText.indexOf(" => "); if (arrowIndex === -1) return null; const parameters = nodeText.substring(0, arrowIndex); let implicitReturnText = returnValueText; if (isObjectLiteral(returnValue)) implicitReturnText = `(${returnValueText})`; return `${parameters} => ${implicitReturnText}`; } function buildPrettierCode(returnValue, sourceCode, node) { const returnValueText = sourceCode.getText(returnValue); const nodeText = sourceCode.getText(node); const arrowIndex = nodeText.indexOf(" => "); if (arrowIndex === -1) return null; const parameters = nodeText.substring(0, arrowIndex); let implicitReturnText = returnValueText; if (isObjectLiteral(returnValue)) implicitReturnText = `(${returnValueText})`; if (isPartOfComplexExpression(node)) { const fullContext = getFullExpressionContext(node, sourceCode, parameters, implicitReturnText); if (fullContext) return fullContext; } return `${parameters} => ${implicitReturnText}`; } function calcMethodChainImplicitLength(returnValue, context, node, prettierOptions) { const { sourceCode } = context; const isolatedArrowFunction = buildIsolatedArrowFunction(returnValue, sourceCode, node); if (isolatedArrowFunction === null) return createPrettierFallbackResult(returnValue, sourceCode, node); const prettierResult = formatWithPrettier(isolatedArrowFunction, context, prettierOptions); if (prettierResult.error !== void 0) return createPrettierFallbackResult(returnValue, sourceCode, node); return { isMultiline: prettierResult.isMultiline, length: prettierResult.lineLength }; } function calcPrettierImplicitLength(returnValue, context, node, prettierOptions) { const methodChainResult = calculateMethodChainLength({ context, node, prettierOptions, returnValue, sourceCode: context.sourceCode }); if (methodChainResult) return methodChainResult; const returnStatementResult = returnStatementCalculation(returnValue, context, node); if (returnStatementResult) return returnStatementResult; return performStandardCalculation(returnValue, context, node, prettierOptions); } function calculateDirectImplicitLength(node, sourceCode, returnValue) { const arrowToken = getArrowToken(node, sourceCode); const beforeArrow = arrowToken ? sourceCode.text.substring(0, arrowToken.range[1]) : ""; const lastLineBeforeArrow = beforeArrow.split("\n").pop() ?? ""; const returnValueText = sourceCode.getText(returnValue); let estimatedSingleLineText = returnValueText; if (returnValue.type === AST_NODE_TYPES.ObjectExpression) estimatedSingleLineText = `(${returnValueText})`; if (isMultiline(returnValue) && (returnValue.type === AST_NODE_TYPES.ArrayExpression || returnValue.type === AST_NODE_TYPES.ObjectExpression)) estimatedSingleLineText = convertMultilineToSingleLine(returnValueText); return lastLineBeforeArrow.length + 1 + estimatedSingleLineText.length; } function calculateFullLength(lineStart, returnToken, convertedArrowFunction, sourceCode) { const beforeReturn = sourceCode.text.substring(lineStart, returnToken.range[0]); const returnKeyword = sourceCode.getText(returnToken); const spaceAfterReturn = " "; return beforeReturn.length + returnKeyword.length + 1 + convertedArrowFunction.length; } function calculateImplicitLength(returnValue, sourceCode, node) { return calculateDirectImplicitLength(node, sourceCode, returnValue); } function calculateLineStart(returnToken, sourceCode) { const lineStart = sourceCode.getIndexFromLoc({ column: 0, line: returnToken.loc.start.line }); return typeof lineStart === "number" ? lineStart : null; } function calculateMethodChainLength({ context, node, prettierOptions, returnValue, sourceCode }) { const isExistingImplicit = returnValue === node.body && node.body.type !== AST_NODE_TYPES.BlockStatement; const isInMethodChain = isPartOfMethodChain(node); if (isInMethodChain && isExistingImplicit) return calcMethodChainImplicitLength(returnValue, context, node, prettierOptions); const isEvaluatingBlockForConversion = node.body.type === AST_NODE_TYPES.BlockStatement; if (isInMethodChain && isEvaluatingBlockForConversion) { const methodChainCheck = checkMethodChainConversion(node, returnValue, sourceCode, context); if (methodChainCheck) return methodChainCheck; } return null; } function calculateMethodChainLineLength(node, convertedArrowFunction, sourceCode) { const arrowToken = getArrowToken(node, sourceCode); if (!arrowToken) return null; const lineStart = sourceCode.getIndexFromLoc({ column: 0, line: arrowToken.loc.start.line }); if (typeof lineStart !== "number") return null; const beforeArrow = sourceCode.text.substring(lineStart, arrowToken.range[0]); return beforeArrow.length + convertedArrowFunction.length; } function checkForceExplicitForObject(body, options) { if (body.type === AST_NODE_TYPES.ObjectExpression) return shouldForceObjectExplicit(body, options); if (body.type === AST_NODE_TYPES.ArrayExpression) return shouldForceArrayExplicit(body, options); return false; } function checkMethodChainConversion(node, returnValue, sourceCode, context) { const { maxLen } = getRuleOptions(context); const convertedArrowFunction = buildConvertedArrowFunction(node, returnValue, sourceCode); if (convertedArrowFunction === null) return null; const estimatedLineLength = calculateMethodChainLineLength(node, convertedArrowFunction, sourceCode); if (estimatedLineLength === null) return null; if (estimatedLineLength > maxLen) return { isMultiline: false, length: estimatedLineLength }; return null; } function checkReturnStatementLength(node, returnValue, sourceCode, context) { const { maxLen } = getRuleOptions(context); const convertedArrowFunction = buildConvertedForReturn(node, returnValue, sourceCode); if (convertedArrowFunction === null) return null; const returnToken = sourceCode.getFirstToken(node.parent); if (!returnToken) return null; const lineStart = calculateLineStart(returnToken, sourceCode); if (lineStart === null) return null; const fullLineLength = calculateFullLength(lineStart, returnToken, convertedArrowFunction, sourceCode); return validateReturnConversion(fullLineLength, maxLen); } function commentsExistBetweenTokens(node, body, sourceCode) { const arrowToken = getArrowToken(node, sourceCode); return Boolean(arrowToken && sourceCode.commentsExistBetween(arrowToken, body)); } function convertMultilineToSingleLine(returnValue) { return returnValue.replace(/\s+/g, " ").replace(/,\s*([}\]])/g, "$1").replace(/^\s+|\s+$/g, "").replace(/\[\s+/g, "[").replace(/\s+\]/g, "]").replace(/{\s+/g, "{").replace(/\s+}/g, "}"); } function countObjectProperties(node) { return node.properties.length; } function create$1(context) { return { ArrowFunctionExpression: (node) => { if (node.body.type === AST_NODE_TYPES.BlockStatement) handleBlockStatement(context, node); else handleExpressionBody(context, node); } }; } function createExplicitReturnText(body, sourceCode, node) { const indentUnit = getIndentStyle(sourceCode); let returnValue = normalizeParentheses(body, sourceCode); if (isMultiline(body) && (body.type === AST_NODE_TYPES.ArrayExpression || body.type === AST_NODE_TYPES.ObjectExpression)) returnValue = convertMultilineToSingleLine(returnValue); if (isJsxElement$1(body) && isMultiline(body)) { const bodyText = sourceCode.getText(body); returnValue = adjustJsxIndentation(bodyText, indentUnit); } const arrowLine = sourceCode.lines[node.loc.start.line - 1]; const baseIndentMatch = arrowLine?.match(/^(\s*)/); const baseIndent = baseIndentMatch?.[1] ?? ""; const returnIndent = determineReturnIndentation(body, node, baseIndent, indentUnit); return `{\n${returnIndent}return ${returnValue};\n${baseIndent}}`; } function createImplicitReturnFix(options) { const { closingBrace, openingBrace, returnValue, sourceCode } = options; return (fixer) => { const returnText = sourceCode.getText(returnValue); const replacement = isObjectLiteral(returnValue) ? `(${returnText})` : returnText; const tokenAfterClosingBrace = sourceCode.getTokenAfter(closingBrace); if (tokenAfterClosingBrace && (ASTUtils.isSemicolonToken(tokenAfterClosingBrace) || tokenAfterClosingBrace.value === ",")) return [fixer.replaceTextRange([openingBrace.range[0], tokenAfterClosingBrace.range[1]], replacement + tokenAfterClosingBrace.value)]; return [fixer.replaceTextRange([openingBrace.range[0], closingBrace.range[1]], replacement)]; }; } /** * Creates a fallback result when prettier formatting fails. * * @param returnValue - The return value expression. * @param sourceCode - ESLint source code object. * @param node - The arrow function node. * @returns Object with multiline status and length information. */ function createPrettierFallbackResult(returnValue, sourceCode, node) { return { isMultiline: isMultiline(returnValue), length: calculateImplicitLength(returnValue, sourceCode, node) }; } function determineReturnIndentation(body, node, baseIndent, indentUnit) { if (isJsxElement$1(body)) { if (isMultiline(body)) return baseIndent === "" ? indentUnit : baseIndent + indentUnit; return baseIndent + indentUnit; } if (isMultiline(body) && baseIndent === "") return indentUnit; if (node.parent.type === AST_NODE_TYPES.Property) return baseIndent + indentUnit; return getMethodChainIndentation(node, baseIndent, indentUnit); } function generateReturnCommentFixes(fixer, node, body, sourceCode) { const fixes = []; const arrowToken = getArrowToken(node, sourceCode); if (arrowToken) fixes.push(fixer.insertTextAfter(arrowToken, " {"), fixer.insertTextBefore(body, "return "), fixer.insertTextAfter(body, "\n}")); return fixes; } function getArrowToken(node, sourceCode) { const tokens = sourceCode.getTokens(node); return tokens.find(ASTUtils.isArrowToken); } function getBlockStatementTokens(sourceCode, body, returnStatement) { return { closingBrace: sourceCode.getLastToken(body), firstToken: sourceCode.getFirstToken(returnStatement, 1), lastToken: sourceCode.getLastToken(returnStatement), openingBrace: sourceCode.getFirstToken(body) }; } /** * Gets the contextual prefix for arrow functions in complex expressions. This * replaces fragile string splitting with safer source text extraction. * * @param node - The arrow function node. * @param sourceCode - ESLint source code object. * @param parameters - The function parameters text. * @param implicitReturnText - The implicit return value text. * @returns The contextual prefix string. */ function getContextualPrefix(node, sourceCode, parameters, implicitReturnText) { const arrowToken = getArrowToken(node, sourceCode); if (!arrowToken) return ""; const lineStart = sourceCode.getIndexFromLoc({ column: 0, line: arrowToken.loc.start.line }); const arrowEnd = arrowToken.range[1]; if (typeof lineStart !== "number") return ""; const lineText = sourceCode.text.substring(lineStart, arrowEnd); return `${lineText.trim()} ${parameters} => ${implicitReturnText}`; } function getExplicitReturnMessageId(body) { return isMultiline(body) && (body.type === AST_NODE_TYPES.ArrayExpression || body.type === AST_NODE_TYPES.ObjectExpression) ? IMPLICIT_RETURN_VIOLATION : EXPLICIT_RETURN_VIOLATION; } /** * Gets the full expression context for arrow functions in complex expressions. * This builds the complete statement that prettier should format. * * @param node - The arrow function node. * @param sourceCode - ESLint source code object. * @param parameters - The function parameters text. * @param implicitReturnText - The implicit return value text. * @returns The full expression context string. */ function getFullExpressionContext(node, sourceCode, parameters, implicitReturnText) { const { parent } = node; if (parent.type === AST_NODE_TYPES.CallExpression) return buildCallExpressionContext({ arrowNode: node, callExpression: parent, implicitReturnText, parameters, sourceCode }); return getContextualPrefix(node, sourceCode, parameters, implicitReturnText); } function getImplicitReturnMetrics(context, node, returnValue) { const { sourceCode } = context; const { usePrettier } = getRuleOptions(context); if (isPrettierEnabled(usePrettier)) { const prettierResult = calcPrettierImplicitLength(returnValue, context, node, usePrettier); return { estimatedLength: prettierResult.length, wouldBeMultiline: prettierResult.isMultiline }; } const estimatedLength = calculateImplicitLength(returnValue, sourceCode, node); const wouldBeMultiline = isMultiline(returnValue) && returnValue.type !== AST_NODE_TYPES.ArrayExpression && returnValue.type !== AST_NODE_TYPES.ObjectExpression; return { estimatedLength, wouldBeMultiline }; } function getIndentStyle(sourceCode) { const cached = indentCache.get(sourceCode); if (cached !== void 0) return cached; const detected = detectIndent(sourceCode.text); const indentStyle = detected.indent || " "; indentCache.set(sourceCode, indentStyle); return indentStyle; } function getMethodChainIndentation(node, baseIndent, indentUnit) { let parent = node.parent; while (parent) { if (parent.type === AST_NODE_TYPES.CallExpression) { const { callee } = parent; if (callee.type === AST_NODE_TYPES.MemberExpression) return baseIndent + indentUnit; } ({parent} = parent); } return baseIndent + indentUnit; } function getReportData(body, isObjectArrayRule) { return isObjectArrayRule ? { type: body.type === AST_NODE_TYPES.ObjectExpression ? "object" : "array" } : void 0; } function getReportMessageId(body, isObjectArrayRule) { return isObjectArrayRule ? EXPLICIT_RETURN_COMPLEX : getExplicitReturnMessageId(body); } function getRuleOptions(context) { const [options] = context.options; return { ...options, usePrettier: shouldUsePrettier(context, { usePrettier: options.usePrettier }) }; } function getTrailingPunctuation(tokenAfterBody, isWrappedInParens) { return !isWrappedInParens && (ASTUtils.isSemicolonToken(tokenAfterBody) || tokenAfterBody.value === ",") ? tokenAfterBody.value : ""; } function handleBlockStatement(context, node) { const blockBody = node.body.body; if (blockBody.length !== 1) return; const returnStatement = blockBody[0]; if (returnStatement?.type !== AST_NODE_TYPES.ReturnStatement) return; const returnValue = returnStatement.argument; if (!returnValue) return; processImplicitReturn(context, node, returnStatement, returnValue); } function handleExplicitReturnFix(context) { const { arrowToken, body, fixer, node, sourceCode } = context; const returnText = createExplicitReturnText(body, sourceCode, node); const tokenAfterBody = sourceCode.getTokenAfter(body); const tokenBeforeBody = sourceCode.getTokenBefore(body); const isWrappedInParens = Boolean(tokenBeforeBody && tokenAfterBody && ASTUtils.isOpeningParenToken(tokenBeforeBody) && ASTUtils.isClosingParenToken(tokenAfterBody)); if (tokenAfterBody && (ASTUtils.isSemicolonToken(tokenAfterBody) || tokenAfterBody.value === "," || isWrappedInParens)) { const trailingPunctuation = getTrailingPunctuation(tokenAfterBody, isWrappedInParens); return [fixer.replaceTextRange([arrowToken.range[1], tokenAfterBody.range[1]], ` ${returnText}${trailingPunctuation}`)]; } return [fixer.replaceTextRange([arrowToken.range[1], body.range[1]], ` ${returnText}`)]; } function handleExpressionBody(context, node) { const shouldUseExplicit = shouldUseExplicitReturn(context, node); if (shouldUseExplicit) { const { body } = node; const { maxObjectProperties, objectReturnStyle } = getRuleOptions(context); const isObjectArrayRule = checkForceExplicitForObject(body, { maxObjectProperties, objectReturnStyle }); reportExplicitReturn(context, node, { isObjectArrayRule }); } } function handleParenthesesRemoval(fixer, firstToken, lastToken) { const fixes = []; if (firstToken && lastToken && ASTUtils.isOpeningParenToken(firstToken) && ASTUtils.isClosingParenToken(lastToken)) fixes.push(fixer.remove(firstToken), fixer.remove(lastToken)); return fixes; } function hasCommentsInBlock(sourceCode, tokens) { const { closingBrace, firstToken, lastToken, openingBrace } = tokens; return sourceCode.commentsExistBetween(openingBrace, firstToken) || sourceCode.commentsExistBetween(lastToken, closingBrace); } function isComplexArray(node) { let hasSpread = false; let nonSpreadCount = 0; let functionCallCount = 0; for (const element of node.elements) { if (!element) continue; if (element.type === AST_NODE_TYPES.SpreadElement) { hasSpread = true; continue; } nonSpreadCount++; if (element.type === AST_NODE_TYPES.CallExpression) functionCallCount++; } return hasSpread && nonSpreadCount >= 1 || functionCallCount >= 2; } function isComplexObject(node) { let hasSpread = false; let hasComputed = false; let functionCallCount = 0; for (const property of node.properties) { if (property.type === AST_NODE_TYPES.SpreadElement) { hasSpread = true; continue; } if (property.computed) hasComputed = true; if (property.value.type === AST_NODE_TYPES.CallExpression) functionCallCount++; } return hasSpread && hasComputed || functionCallCount >= 2 || hasComputed && functionCallCount >= 1; } function isJsxElement$1(node) { return node.type === AST_NODE_TYPES.JSXElement || node.type === AST_NODE_TYPES.JSXFragment; } function isMultiline(node) { return node.loc.start.line !== node.loc.end.line; } function isNamedExport(node) { return node.parent?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration; } function isObjectLiteral(node) { return node.type === AST_NODE_TYPES.ObjectExpression; } /** * Determines if arrow function is part of a method chain or complex expression. * This helps determine whether to use contextual formatting for prettier. * * @param node - The arrow function node. * @returns True if the arrow function is part of a complex expression. */ function isPartOfComplexExpression(node) { const { parent } = node; if (parent.type === AST_NODE_TYPES.CallExpression) { const grandparent = parent.parent; if (grandparent.type === AST_NODE_TYPES.MemberExpression) return true; } if (parent.type === AST_NODE_TYPES.CallExpression) return parent.arguments.includes(node); return false; } /** * Determines if arrow function is specifically part of a method chain. This is * a subset of complex expressions that helps determine when to use isolated * formatting. * * @param node - The arrow function node. * @returns True if the arrow function is part of a method chain. */ function isPartOfMethodChain(node) { const { parent } = node; if (parent.type === AST_NODE_TYPES.CallExpression) return parent.callee.type === AST_NODE_TYPES.MemberExpression; return false; } function normalizeParentheses(body, sourceCode) { const bodyText = sourceCode.getText(body); const firstBodyToken = sourceCode.getFirstToken(body); const lastBodyToken = sourceCode.getLastToken(body); if (firstBodyToken && lastBodyToken && ASTUtils.isOpeningParenToken(firstBodyToken) && ASTUtils.isClosingParenToken(lastBodyToken)) return bodyText.slice(1, -1); return bodyText; } function performStandardCalculation(returnValue, context, node, prettierOptions) { const { sourceCode } = context; const arrowFunctionCode = buildPrettierCode(returnValue, sourceCode, node); if (arrowFunctionCode === null) return createPrettierFallbackResult(returnValue, sourceCode, node); const prettierResult = formatWithPrettier(arrowFunctionCode, context, prettierOptions); if (prettierResult.error !== void 0) return createPrettierFallbackResult(returnValue, sourceCode, node); return { isMultiline: prettierResult.isMultiline, length: prettierResult.lineLength }; } function processImplicitReturn(context, node, returnStatement, returnValue) { if (shouldSkipImplicitReturn(context, node, returnValue)) return; const { sourceCode } = context; const body = node.body; const tokens = getBlockStatementTokens(sourceCode, body, returnStatement); if (!validateImplicitReturnTokens(tokens)) return; if (hasCommentsInBlock(sourceCode, tokens)) return; context.report({ fix: createImplicitReturnFix({ closingBrace: tokens.closingBrace, openingBrace: tokens.openingBrace, returnStatement, returnValue, sourceCode }), messageId: IMPLICIT_RETURN_VIOLATION, node }); } function reportExplicitReturn(context, node, options) { const { body } = node; const { sourceCode } = context; const isObjectArrayRule = options?.isObjectArrayRule === true; const firstToken = sourceCode.getTokenBefore(body); const lastToken = sourceCode.getTokenAfter(body); const commentsExist = commentsExistBetweenTokens(node, body, sourceCode); context.report({ data: getReportData(body, isObjectArrayRule), fix: (fixer) => { if (commentsExist) return [...handleParenthesesRemoval(fixer, firstToken, lastToken), ...generateReturnCommentFixes(fixer, node, body, sourceCode)]; const arrowToken = getArrowToken(node, sourceCode); if (!arrowToken) return []; return handleExplicitReturnFix({ arrowToken, body, fixer, node, sourceCode }); }, messageId: getReportMessageId(body, isObjectArrayRule), node }); } function returnStatementCalculation(returnValue, context, node) { const { sourceCode } = context; if (node.parent.type === AST_NODE_TYPES.ReturnStatement) return checkReturnStatementLength(node, returnValue, sourceCode, context); return null; } function shouldForceArrayExplicit(node, options) { const { objectReturnStyle } = options; switch (objectReturnStyle) { case ObjectReturnStyle.AlwaysExplicit: return true; case ObjectReturnStyle.ComplexExplicit: return isComplexArray(node); case ObjectReturnStyle.Off: return false; default: return false; } } function shouldForceObjectExplicit(node, options) { const { maxObjectProperties, objectReturnStyle } = options; switch (objectReturnStyle) { case ObjectReturnStyle.AlwaysExplicit: return true; case ObjectReturnStyle.ComplexExplicit: { const propertyCount = countObjectProperties(node); return propertyCount > maxObjectProperties || isComplexObject(node); } case ObjectReturnStyle.Off: return false; default: return false; } } function shouldSkipForObjectLogic(returnValue, options) { if (returnValue.type === AST_NODE_TYPES.ObjectExpression) return shouldForceObjectExplicit(returnValue, options); if (returnValue.type === AST_NODE_TYPES.ArrayExpression) return shouldForceArrayExplicit(returnValue, options); return false; } function shouldSkipImplicitReturn(context, node, returnValue) { const { jsxAlwaysUseExplicitReturn, maxLen, maxObjectProperties, namedExportsAlwaysUseExplicitReturn, objectReturnStyle } = getRuleOptions(context); const { estimatedLength, wouldBeMultiline } = getImplicitReturnMetrics(context, node, returnValue); if (shouldSkipForObjectLogic(returnValue, { maxObjectProperties, objectReturnStyle })) return true; return estimatedLength > maxLen || wouldBeMultiline || Boolean(jsxAlwaysUseExplicitReturn) && isJsxElement$1(returnValue) || namedExportsAlwaysUseExplicitReturn && isNamedExport(node.parent); } function shouldUseExplicitReturn(context, node) { const { sourceCode } = context; const { body, parent } = node; const commentsExist = commentsExistBetweenTokens(node, body, sourceCode); const { jsxAlwaysUseExplicitReturn, maxLen, maxObjectProperties, namedExportsAlwaysUseExplicitReturn, objectReturnStyle } = getRuleOptions(context); const { estimatedLength, wouldBeMultiline } = getImplicitReturnMetrics(context, node, body); const isExceedingMaxLength = estimatedLength > maxLen; if (checkForceExplicitForObject(body, { maxObjectProperties, objectReturnStyle })) return true; return commentsExist || isExceedingMaxLength || wouldBeMultiline || Boolean(jsxAlwaysUseExplicitReturn) && isJsxElement$1(body) || namedExportsAlwaysUseExplicitReturn && isNamedExport(parent); } function validateImplicitReturnTokens(tokens) { const { closingBrace, firstToken, lastToken, openingBrace } = tokens; return !!(openingBrace && closingBrace && firstToken && lastToken); } function validateReturnConversion(fullLineLength, maxLength) { if (fullLineLength > maxLength) return { isMultiline: false, length: fullLineLength }; return null; } const defaultOptions = [{ jsxAlwaysUseExplicitReturn: false, maxLen: 80, maxObjectProperties: 2, namedExportsAlwaysUseExplicitReturn: true, objectReturnStyle: ObjectReturnStyle.ComplexExplicit, usePrettier: void 0 }]; const arrowReturnStyleRule = createEslintRule({ create: create$1, defaultOptions, meta: { defaultOptions: [defaultOptions[0]], docs: { description: "Enforce consistent arrow function return style based on length, multiline expressions, JSX usage, and export context", recommended: true, requiresTypeChecking: false }, fixable: "code", messages: messages$1, schema: [{ additionalProperties: true, properties: { jsxAlwaysUseExplicitReturn: { description: "Always use explicit return for JSX elements", type: "boolean" }, maxLen: { description: "Maximum line length before requiring explicit return", type: "number" }, maxObjectProperties: { description: "Maximum number of object properties before requiring explicit return (only applies when objectReturnStyle is 'complex-explicit')", minimum: 1, type: "number" }, namedExportsAlwaysUseExplicitReturn: { description: "Always use explicit return for named exports", type: "boolean" }, objectReturnStyle: { description: "Control when object and array returns should use explicit syntax", enum: [ ObjectReturnStyle.AlwaysExplicit, ObjectReturnStyle.ComplexExplicit, ObjectReturnStyle.Off ], type: "string" }, usePrettier: { description: "Prettier integration: false=disabled, true=auto-resolve config, object=explicit config, undefined=auto-detect availability", oneOf: [ { description: "Enable or disable Prettier integration", type: "boolean" }, { description: "Explicit Prettier configuration object", type: "object" }, { description: "Auto-detect Prettier availability", type: "null" } ] } }, type: "object" }], type: "suggestion" }, name: RULE_NAME$1 }); //#endregion //#region src/rules/no-export-default-arrow/rule.ts function toCamelCase(str) { return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^[A-Z]/, (char) => char.toLowerCase()); } function toPascalCase(str) { return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^[a-z]/, (char) => char.toUpperCase()); } const RULE_NAME = "no-export-default-arrow"; const EXPORT_DEFAULT_ARROW_VIOLATION = "disallowExportDefaultArrow"; const messages = { [EXPORT_DEFAULT_ARROW_VIOLATION]: "Disallow export default anonymous arrow function" }; function arrowReturnIsJsxElement(arrowBody) { const returnValues = getArrowReturnValues(arrowBody); return returnValues.some((node) => isJsxElement(node)); } function create(context) { const { sourceCode } = context; let program; return { ArrowFunctionExpression: (arrowFunction) => { const { parent: arrowFunctionParent } = arrowFunction; if (arrowFunctionParent.type === AST_NODE_TYPES.ExportDefaultDeclaration) context.report({ fix: createFixFunction({ arrowFunction, arrowFunctionParent, context, program, sourceCode }), messageId: "disallowExportDefaultArrow", node: arrowFunction }); }, Program: (node) => { program = node; } }; } function createFixFunction(options) { const { arrowFunction, arrowFunctionParent, context, program, sourceCode } = options; return (fixer) => { const fixes = []; const lastToken = sourceCode.getLastToken(program, { includeComments: true }) ?? arrowFunctionParent; const fileName = context.physicalFilename || context.filename || "namedFunction"; const { name: fileNameWithoutExtension } = path.parse(fileName); const funcName = arrowReturnIsJsxElement(arrowFunction.body) ? toPascalCase(fileNameWithoutExtension) : toCamelCase(fileNameWithoutExtension); fixes.push(fixer.replaceText(arrowFunctionParent, `const ${funcName} = ${sourceCode.getText(arrowFunction)}`), fixer.insertTextAfter(lastToken, `\n\nexport default ${funcName}`)); return fixes; }; } function getArrowReturnValues(arrowBody) { if (arrowBody.type === AST_NODE_TYPES.BlockStatement) { const blockBody = arrowBody.body; return blockBody.filter((node) => node.type === AST_NODE_TYPES.ReturnStatement).map((node) => node.argument).filter((argument) => Boolean(argument)); } return [arrowBody]; } function isJsxElement(node) { return node.type === AST_NODE_TYPES.JSXElement || node.type === AST_NODE_TYPES.JSXFragment; } const noExportDefaultArrowRule = createEslintRule({ create, defaultOptions: [], meta: { docs: { description: "Disallow anonymous arrow functions as export default declarations", recommended: true, requiresTypeChecking: true }, fixable: "code", hasSuggestions: false, messages, schema: [], type: "suggestion" }, name: RULE_NAME }); //#endregion //#region src/plugin.ts const PLUGIN_NAME = name.replace(/^eslint-plugin-/, ""); /** * Generates a rules record where all plugin rules are set to "error". * * @param pluginName - The plugin identifier used to prefix rule names. * @param rules - The rules record to transform. * @returns A Linter.RulesRecord with all rules enabled. */ function getRules(pluginName, rules) { return Object.fromEntries(Object.keys(rules).map((ruleName) => [`${pluginName}/${ruleName}`, "error"])); } const plugin = { meta: { name: PLUGIN_NAME, version }, rules: { "arrow-return-style": arrowReturnStyleRule, "no-export-default-arrow": noExportDefaultArrowRule } }; const allRules = getRules(PLUGIN_NAME, plugin.rules); //#endregion //#region src/configs/index.ts const configs = { recommended: { plugins: { [PLUGIN_NAME]: plugin }, rules: { "arrow-body-style": "off", "arrow-return-style/arrow-return-style": "error", "arrow-return-style/no-export-default-arrow": "error" } } }; //#endregion //#region src/index.ts var src_default = { ...plugin, configs }; //#endregion export { src_default as default };