codtracker-js/node_modules/eslint/lib/rules/arrow-body-style.js
2025-04-19 23:12:19 -04:00

357 lines
11 KiB
JavaScript

/**
* @fileoverview Rule to require braces in arrow function body.
* @author Alberto Rodríguez
*/
'use strict';
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require('./utils/ast-utils');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
defaultOptions: ['as-needed'],
docs: {
description: 'Require braces around arrow function bodies',
recommended: false,
frozen: true,
url: 'https://eslint.org/docs/latest/rules/arrow-body-style',
},
schema: {
anyOf: [
{
type: 'array',
items: [
{
enum: ['always', 'never'],
},
],
minItems: 0,
maxItems: 1,
},
{
type: 'array',
items: [
{
enum: ['as-needed'],
},
{
type: 'object',
properties: {
requireReturnForObjectLiteral: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
minItems: 0,
maxItems: 2,
},
],
},
fixable: 'code',
messages: {
unexpectedOtherBlock:
'Unexpected block statement surrounding arrow body.',
unexpectedEmptyBlock:
'Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.',
unexpectedObjectBlock:
'Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.',
unexpectedSingleBlock:
'Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.',
expectedBlock: 'Expected block statement surrounding arrow body.',
},
},
create(context) {
const options = context.options;
const always = options[0] === 'always';
const asNeeded = options[0] === 'as-needed';
const never = options[0] === 'never';
const requireReturnForObjectLiteral =
options[1] && options[1].requireReturnForObjectLiteral;
const sourceCode = context.sourceCode;
let funcInfo = null;
/**
* Checks whether the given node has ASI problem or not.
* @param {Token} token The token to check.
* @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed.
*/
function hasASIProblem(token) {
return (
token && token.type === 'Punctuator' && /^[([/`+-]/u.test(token.value)
);
}
/**
* Gets the closing parenthesis by the given node.
* @param {ASTNode} node first node after an opening parenthesis.
* @returns {Token} The found closing parenthesis token.
*/
function findClosingParen(node) {
let nodeToCheck = node;
while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) {
nodeToCheck = nodeToCheck.parent;
}
return sourceCode.getTokenAfter(nodeToCheck);
}
/**
* Check whether the node is inside of a for loop's init
* @param {ASTNode} node node is inside for loop
* @returns {boolean} `true` if the node is inside of a for loop, else `false`
*/
function isInsideForLoopInitializer(node) {
if (node && node.parent) {
if (node.parent.type === 'ForStatement' && node.parent.init === node) {
return true;
}
return isInsideForLoopInitializer(node.parent);
}
return false;
}
/**
* Determines whether a arrow function body needs braces
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function validate(node) {
const arrowBody = node.body;
if (arrowBody.type === 'BlockStatement') {
const blockBody = arrowBody.body;
if (blockBody.length !== 1 && !never) {
return;
}
if (
asNeeded &&
requireReturnForObjectLiteral &&
blockBody[0].type === 'ReturnStatement' &&
blockBody[0].argument &&
blockBody[0].argument.type === 'ObjectExpression'
) {
return;
}
if (never || (asNeeded && blockBody[0].type === 'ReturnStatement')) {
let messageId;
if (blockBody.length === 0) {
messageId = 'unexpectedEmptyBlock';
} else if (
blockBody.length > 1 ||
blockBody[0].type !== 'ReturnStatement'
) {
messageId = 'unexpectedOtherBlock';
} else if (blockBody[0].argument === null) {
messageId = 'unexpectedSingleBlock';
} else if (
astUtils.isOpeningBraceToken(
sourceCode.getFirstToken(blockBody[0], { skip: 1 })
)
) {
messageId = 'unexpectedObjectBlock';
} else {
messageId = 'unexpectedSingleBlock';
}
context.report({
node,
loc: arrowBody.loc,
messageId,
fix(fixer) {
const fixes = [];
if (
blockBody.length !== 1 ||
blockBody[0].type !== 'ReturnStatement' ||
!blockBody[0].argument ||
hasASIProblem(sourceCode.getTokenAfter(arrowBody))
) {
return fixes;
}
const openingBrace = sourceCode.getFirstToken(arrowBody);
const closingBrace = sourceCode.getLastToken(arrowBody);
const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1);
const lastValueToken = sourceCode.getLastToken(blockBody[0]);
const commentsExist =
sourceCode.commentsExistBetween(
openingBrace,
firstValueToken
) ||
sourceCode.commentsExistBetween(lastValueToken, closingBrace);
/*
* Remove tokens around the return value.
* If comments don't exist, remove extra spaces as well.
*/
if (commentsExist) {
fixes.push(
fixer.remove(openingBrace),
fixer.remove(closingBrace),
fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword
);
} else {
fixes.push(
fixer.removeRange([
openingBrace.range[0],
firstValueToken.range[0],
]),
fixer.removeRange([
lastValueToken.range[1],
closingBrace.range[1],
])
);
}
/*
* If the first token of the return value is `{` or the return value is a sequence expression,
* enclose the return value by parentheses to avoid syntax error.
*/
if (
astUtils.isOpeningBraceToken(firstValueToken) ||
blockBody[0].argument.type === 'SequenceExpression' ||
(funcInfo.hasInOperator && isInsideForLoopInitializer(node))
) {
if (
!astUtils.isParenthesised(sourceCode, blockBody[0].argument)
) {
fixes.push(
fixer.insertTextBefore(firstValueToken, '('),
fixer.insertTextAfter(lastValueToken, ')')
);
}
}
/*
* If the last token of the return statement is semicolon, remove it.
* Non-block arrow body is an expression, not a statement.
*/
if (astUtils.isSemicolonToken(lastValueToken)) {
fixes.push(fixer.remove(lastValueToken));
}
return fixes;
},
});
}
} else {
if (
always ||
(asNeeded &&
requireReturnForObjectLiteral &&
arrowBody.type === 'ObjectExpression')
) {
context.report({
node,
loc: arrowBody.loc,
messageId: 'expectedBlock',
fix(fixer) {
const fixes = [];
const arrowToken = sourceCode.getTokenBefore(
arrowBody,
astUtils.isArrowToken
);
const [firstTokenAfterArrow, secondTokenAfterArrow] =
sourceCode.getTokensAfter(arrowToken, {
count: 2,
});
const lastToken = sourceCode.getLastToken(node);
let parenthesisedObjectLiteral = null;
if (
astUtils.isOpeningParenToken(firstTokenAfterArrow) &&
astUtils.isOpeningBraceToken(secondTokenAfterArrow)
) {
const braceNode = sourceCode.getNodeByRangeIndex(
secondTokenAfterArrow.range[0]
);
if (braceNode.type === 'ObjectExpression') {
parenthesisedObjectLiteral = braceNode;
}
}
// If the value is object literal, remove parentheses which were forced by syntax.
if (parenthesisedObjectLiteral) {
const openingParenToken = firstTokenAfterArrow;
const openingBraceToken = secondTokenAfterArrow;
if (
astUtils.isTokenOnSameLine(
openingParenToken,
openingBraceToken
)
) {
fixes.push(fixer.replaceText(openingParenToken, '{return '));
} else {
// Avoid ASI
fixes.push(
fixer.replaceText(openingParenToken, '{'),
fixer.insertTextBefore(openingBraceToken, 'return ')
);
}
// Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo()
fixes.push(
fixer.remove(findClosingParen(parenthesisedObjectLiteral))
);
fixes.push(fixer.insertTextAfter(lastToken, '}'));
} else {
fixes.push(
fixer.insertTextBefore(firstTokenAfterArrow, '{return ')
);
fixes.push(fixer.insertTextAfter(lastToken, '}'));
}
return fixes;
},
});
}
}
}
return {
"BinaryExpression[operator='in']"() {
let info = funcInfo;
while (info) {
info.hasInOperator = true;
info = info.upper;
}
},
ArrowFunctionExpression() {
funcInfo = {
upper: funcInfo,
hasInOperator: false,
};
},
'ArrowFunctionExpression:exit'(node) {
validate(node);
funcInfo = funcInfo.upper;
},
};
},
};