eslint-plugin-arrow-return-style-x
Version:
Enforce arrow function return style and automatically fix it
924 lines (915 loc) • 37.3 kB
JavaScript
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 };