eslint-plugin-unicorn
Version:
More than 300 powerful ESLint rules
127 lines (113 loc) • 3.71 kB
JavaScript
import getShortBodyProblem from './shared/short-body-conditional.js';
import {
canRewriteToEarlyExit,
getConsequentStatementCount,
getEarlyExitReplacementText,
getEarlyExitOppositeComparisonFix,
} from './shared/early-exit.js';
const MESSAGE_ID = 'prefer-early-return';
const SUGGESTION_MESSAGE_ID = 'prefer-early-return/suggestion';
const OPPOSITE_COMPARISON_MESSAGE_ID = 'prefer-early-return/opposite-comparison';
const SHORT_BODY_MESSAGE_ID = 'prefer-early-return/short-body';
const messages = {
[SHORT_BODY_MESSAGE_ID]: 'Prefer conditional wrapping over an early return for a short body.',
[MESSAGE_ID]: 'Prefer an early return over wrapping the remainder of the function body in an `if` statement.',
[SUGGESTION_MESSAGE_ID]: 'Rewrite to an early return.',
[OPPOSITE_COMPARISON_MESSAGE_ID]: 'Rewrite to an early return using the opposite comparison (changes behavior for NaN and values coerced to NaN).',
};
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
maximumStatements: {
type: 'integer',
minimum: 0,
description: 'Maximum number of statements allowed in a conditional wrapping the remainder of the function body.',
},
checkShortBodies: {
type: 'boolean',
description: 'Enforce conditional wrapping when the body after a guard, or its else body, has between one and maximumStatements statements.',
},
},
},
];
const getFix = (ifStatement, context) => {
if (
!canRewriteToEarlyExit(ifStatement, context)
|| context.sourceCode.getCommentsAfter(ifStatement).length > 0
) {
return;
}
return fixer => fixer.replaceText(
ifStatement,
getEarlyExitReplacementText(ifStatement, 'return', context),
);
};
const getSuggestion = (ifStatement, context) => {
if (!canRewriteToEarlyExit(ifStatement, context)) {
return;
}
return [
{
messageId: SUGGESTION_MESSAGE_ID,
fix: fixer => fixer.replaceText(ifStatement, getEarlyExitReplacementText(ifStatement, 'return', context)),
},
];
};
/**
@param {import('eslint').Rule.RuleContext} context
*/
const create = context => {
const {maximumStatements} = context.options[0];
context.on(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'], node => {
if (node.body.type !== 'BlockStatement') {
return;
}
const {body} = node.body;
const statement = body.at(-1);
if (
statement?.type !== 'IfStatement'
|| statement.alternate
|| getConsequentStatementCount(statement) <= maximumStatements
) {
// Checked after the wrapper so a guard before a long wrapper becomes a guard chain instead of nested `if` statements.
const shortBodyProblem = getShortBodyProblem(node.body, context, 'ReturnStatement');
return shortBodyProblem && {...shortBodyProblem, messageId: SHORT_BODY_MESSAGE_ID};
}
const fix = getFix(statement, context);
const oppositeComparisonFix = fix && getEarlyExitOppositeComparisonFix(statement, 'return', context);
let suggest = fix ? undefined : getSuggestion(statement, context);
if (oppositeComparisonFix) {
suggest = [{messageId: OPPOSITE_COMPARISON_MESSAGE_ID, fix: oppositeComparisonFix}];
}
return {
node: statement,
messageId: MESSAGE_ID,
...(fix && {fix}),
...(suggest && {suggest}),
};
});
};
/**
@type {import('eslint').Rule.RuleModule}
*/
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer early returns over conditionals wrapping the remainder of the function body.',
recommended: 'unopinionated',
},
fixable: 'code',
hasSuggestions: true,
schema,
defaultOptions: [{maximumStatements: 1, checkShortBodies: false}],
messages,
languages: [
'js/js',
],
},
};
export default config;