codtracker-js/node_modules/eslint/lib/rules/function-paren-newline.js
2025-04-19 23:12:19 -04:00

363 lines
11 KiB
JavaScript

/**
* @fileoverview enforce consistent line breaks inside function parentheses
* @author Teddy Katz
* @deprecated in ESLint v8.53.0
*/
'use strict';
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require('./utils/ast-utils');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: {
message: 'Formatting rules are being moved out of ESLint core.',
url: 'https://eslint.org/blog/2023/10/deprecating-formatting-rules/',
deprecatedSince: '8.53.0',
availableUntil: '10.0.0',
replacedBy: [
{
message:
'ESLint Stylistic now maintains deprecated stylistic core rules.',
url: 'https://eslint.style/guide/migration',
plugin: {
name: '@stylistic/eslint-plugin-js',
url: 'https://eslint.style/packages/js',
},
rule: {
name: 'function-paren-newline',
url: 'https://eslint.style/rules/js/function-paren-newline',
},
},
],
},
type: 'layout',
docs: {
description: 'Enforce consistent line breaks inside function parentheses',
recommended: false,
url: 'https://eslint.org/docs/latest/rules/function-paren-newline',
},
fixable: 'whitespace',
schema: [
{
oneOf: [
{
enum: [
'always',
'never',
'consistent',
'multiline',
'multiline-arguments',
],
},
{
type: 'object',
properties: {
minItems: {
type: 'integer',
minimum: 0,
},
},
additionalProperties: false,
},
],
},
],
messages: {
expectedBefore: "Expected newline before ')'.",
expectedAfter: "Expected newline after '('.",
expectedBetween: 'Expected newline between arguments/params.',
unexpectedBefore: "Unexpected newline before ')'.",
unexpectedAfter: "Unexpected newline after '('.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const rawOption = context.options[0] || 'multiline';
const multilineOption = rawOption === 'multiline';
const multilineArgumentsOption = rawOption === 'multiline-arguments';
const consistentOption = rawOption === 'consistent';
let minItems;
if (typeof rawOption === 'object') {
minItems = rawOption.minItems;
} else if (rawOption === 'always') {
minItems = 0;
} else if (rawOption === 'never') {
minItems = Infinity;
} else {
minItems = null;
}
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
/**
* Determines whether there should be newlines inside function parens
* @param {ASTNode[]} elements The arguments or parameters in the list
* @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
* @returns {boolean} `true` if there should be newlines inside the function parens
*/
function shouldHaveNewlines(elements, hasLeftNewline) {
if (multilineArgumentsOption && elements.length === 1) {
return hasLeftNewline;
}
if (multilineOption || multilineArgumentsOption) {
return elements.some(
(element, index) =>
index !== elements.length - 1 &&
element.loc.end.line !== elements[index + 1].loc.start.line
);
}
if (consistentOption) {
return hasLeftNewline;
}
return elements.length >= minItems;
}
/**
* Validates parens
* @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
* @param {ASTNode[]} elements The arguments or parameters in the list
* @returns {void}
*/
function validateParens(parens, elements) {
const leftParen = parens.leftParen;
const rightParen = parens.rightParen;
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
const hasLeftNewline = !astUtils.isTokenOnSameLine(
leftParen,
tokenAfterLeftParen
);
const hasRightNewline = !astUtils.isTokenOnSameLine(
tokenBeforeRightParen,
rightParen
);
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
if (hasLeftNewline && !needsNewlines) {
context.report({
node: leftParen,
messageId: 'unexpectedAfter',
fix(fixer) {
return (
sourceCode
.getText()
.slice(leftParen.range[1], tokenAfterLeftParen.range[0])
.trim()
) ?
// If there is a comment between the ( and the first element, don't do a fix.
null
: fixer.removeRange([
leftParen.range[1],
tokenAfterLeftParen.range[0],
]);
},
});
} else if (!hasLeftNewline && needsNewlines) {
context.report({
node: leftParen,
messageId: 'expectedAfter',
fix: (fixer) => fixer.insertTextAfter(leftParen, '\n'),
});
}
if (hasRightNewline && !needsNewlines) {
context.report({
node: rightParen,
messageId: 'unexpectedBefore',
fix(fixer) {
return (
sourceCode
.getText()
.slice(tokenBeforeRightParen.range[1], rightParen.range[0])
.trim()
) ?
// If there is a comment between the last element and the ), don't do a fix.
null
: fixer.removeRange([
tokenBeforeRightParen.range[1],
rightParen.range[0],
]);
},
});
} else if (!hasRightNewline && needsNewlines) {
context.report({
node: rightParen,
messageId: 'expectedBefore',
fix: (fixer) => fixer.insertTextBefore(rightParen, '\n'),
});
}
}
/**
* Validates a list of arguments or parameters
* @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
* @param {ASTNode[]} elements The arguments or parameters in the list
* @returns {void}
*/
function validateArguments(parens, elements) {
const leftParen = parens.leftParen;
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
const hasLeftNewline = !astUtils.isTokenOnSameLine(
leftParen,
tokenAfterLeftParen
);
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
for (let i = 0; i <= elements.length - 2; i++) {
const currentElement = elements[i];
const nextElement = elements[i + 1];
const hasNewLine =
currentElement.loc.end.line !== nextElement.loc.start.line;
if (!hasNewLine && needsNewlines) {
context.report({
node: currentElement,
messageId: 'expectedBetween',
fix: (fixer) => fixer.insertTextBefore(nextElement, '\n'),
});
}
}
}
/**
* Gets the left paren and right paren tokens of a node.
* @param {ASTNode} node The node with parens
* @throws {TypeError} Unexpected node type.
* @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
* Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
* with a single parameter)
*/
function getParenTokens(node) {
switch (node.type) {
case 'NewExpression':
if (
!node.arguments.length &&
!(
astUtils.isOpeningParenToken(
sourceCode.getLastToken(node, { skip: 1 })
) &&
astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
node.callee.range[1] < node.range[1]
)
) {
// If the NewExpression does not have parens (e.g. `new Foo`), return null.
return null;
}
// falls through
case 'CallExpression':
return {
leftParen: sourceCode.getTokenAfter(
node.callee,
astUtils.isOpeningParenToken
),
rightParen: sourceCode.getLastToken(node),
};
case 'FunctionDeclaration':
case 'FunctionExpression': {
const leftParen = sourceCode.getFirstToken(
node,
astUtils.isOpeningParenToken
);
const rightParen =
node.params.length ?
sourceCode.getTokenAfter(
node.params.at(-1),
astUtils.isClosingParenToken
)
: sourceCode.getTokenAfter(leftParen);
return { leftParen, rightParen };
}
case 'ArrowFunctionExpression': {
const firstToken = sourceCode.getFirstToken(node, {
skip: node.async ? 1 : 0,
});
if (!astUtils.isOpeningParenToken(firstToken)) {
// If the ArrowFunctionExpression has a single param without parens, return null.
return null;
}
const rightParen =
node.params.length ?
sourceCode.getTokenAfter(
node.params.at(-1),
astUtils.isClosingParenToken
)
: sourceCode.getTokenAfter(firstToken);
return {
leftParen: firstToken,
rightParen,
};
}
case 'ImportExpression': {
const leftParen = sourceCode.getFirstToken(node, 1);
const rightParen = sourceCode.getLastToken(node);
return { leftParen, rightParen };
}
default:
throw new TypeError(`unexpected node with type ${node.type}`);
}
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
[[
'ArrowFunctionExpression',
'CallExpression',
'FunctionDeclaration',
'FunctionExpression',
'ImportExpression',
'NewExpression',
]](node) {
const parens = getParenTokens(node);
let params;
if (node.type === 'ImportExpression') {
params = [node.source];
} else if (astUtils.isFunction(node)) {
params = node.params;
} else {
params = node.arguments;
}
if (parens) {
validateParens(parens, params);
if (multilineArgumentsOption) {
validateArguments(parens, params);
}
}
},
};
},
};