1 | /**
|
---|
2 | * @fileoverview enforce consistent line breaks inside function parentheses
|
---|
3 | * @author Teddy Katz
|
---|
4 | * @deprecated in ESLint v8.53.0
|
---|
5 | */
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | //------------------------------------------------------------------------------
|
---|
9 | // Requirements
|
---|
10 | //------------------------------------------------------------------------------
|
---|
11 |
|
---|
12 | const astUtils = require("./utils/ast-utils");
|
---|
13 |
|
---|
14 | //------------------------------------------------------------------------------
|
---|
15 | // Rule Definition
|
---|
16 | //------------------------------------------------------------------------------
|
---|
17 |
|
---|
18 | /** @type {import('../shared/types').Rule} */
|
---|
19 | module.exports = {
|
---|
20 | meta: {
|
---|
21 | deprecated: true,
|
---|
22 | replacedBy: [],
|
---|
23 | type: "layout",
|
---|
24 |
|
---|
25 | docs: {
|
---|
26 | description: "Enforce consistent line breaks inside function parentheses",
|
---|
27 | recommended: false,
|
---|
28 | url: "https://eslint.org/docs/latest/rules/function-paren-newline"
|
---|
29 | },
|
---|
30 |
|
---|
31 | fixable: "whitespace",
|
---|
32 |
|
---|
33 | schema: [
|
---|
34 | {
|
---|
35 | oneOf: [
|
---|
36 | {
|
---|
37 | enum: ["always", "never", "consistent", "multiline", "multiline-arguments"]
|
---|
38 | },
|
---|
39 | {
|
---|
40 | type: "object",
|
---|
41 | properties: {
|
---|
42 | minItems: {
|
---|
43 | type: "integer",
|
---|
44 | minimum: 0
|
---|
45 | }
|
---|
46 | },
|
---|
47 | additionalProperties: false
|
---|
48 | }
|
---|
49 | ]
|
---|
50 | }
|
---|
51 | ],
|
---|
52 |
|
---|
53 | messages: {
|
---|
54 | expectedBefore: "Expected newline before ')'.",
|
---|
55 | expectedAfter: "Expected newline after '('.",
|
---|
56 | expectedBetween: "Expected newline between arguments/params.",
|
---|
57 | unexpectedBefore: "Unexpected newline before ')'.",
|
---|
58 | unexpectedAfter: "Unexpected newline after '('."
|
---|
59 | }
|
---|
60 | },
|
---|
61 |
|
---|
62 | create(context) {
|
---|
63 | const sourceCode = context.sourceCode;
|
---|
64 | const rawOption = context.options[0] || "multiline";
|
---|
65 | const multilineOption = rawOption === "multiline";
|
---|
66 | const multilineArgumentsOption = rawOption === "multiline-arguments";
|
---|
67 | const consistentOption = rawOption === "consistent";
|
---|
68 | let minItems;
|
---|
69 |
|
---|
70 | if (typeof rawOption === "object") {
|
---|
71 | minItems = rawOption.minItems;
|
---|
72 | } else if (rawOption === "always") {
|
---|
73 | minItems = 0;
|
---|
74 | } else if (rawOption === "never") {
|
---|
75 | minItems = Infinity;
|
---|
76 | } else {
|
---|
77 | minItems = null;
|
---|
78 | }
|
---|
79 |
|
---|
80 | //----------------------------------------------------------------------
|
---|
81 | // Helpers
|
---|
82 | //----------------------------------------------------------------------
|
---|
83 |
|
---|
84 | /**
|
---|
85 | * Determines whether there should be newlines inside function parens
|
---|
86 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
87 | * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
|
---|
88 | * @returns {boolean} `true` if there should be newlines inside the function parens
|
---|
89 | */
|
---|
90 | function shouldHaveNewlines(elements, hasLeftNewline) {
|
---|
91 | if (multilineArgumentsOption && elements.length === 1) {
|
---|
92 | return hasLeftNewline;
|
---|
93 | }
|
---|
94 | if (multilineOption || multilineArgumentsOption) {
|
---|
95 | return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
|
---|
96 | }
|
---|
97 | if (consistentOption) {
|
---|
98 | return hasLeftNewline;
|
---|
99 | }
|
---|
100 | return elements.length >= minItems;
|
---|
101 | }
|
---|
102 |
|
---|
103 | /**
|
---|
104 | * Validates parens
|
---|
105 | * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
---|
106 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
107 | * @returns {void}
|
---|
108 | */
|
---|
109 | function validateParens(parens, elements) {
|
---|
110 | const leftParen = parens.leftParen;
|
---|
111 | const rightParen = parens.rightParen;
|
---|
112 | const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
---|
113 | const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
|
---|
114 | const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
---|
115 | const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
|
---|
116 | const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
---|
117 |
|
---|
118 | if (hasLeftNewline && !needsNewlines) {
|
---|
119 | context.report({
|
---|
120 | node: leftParen,
|
---|
121 | messageId: "unexpectedAfter",
|
---|
122 | fix(fixer) {
|
---|
123 | return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
|
---|
124 |
|
---|
125 | // If there is a comment between the ( and the first element, don't do a fix.
|
---|
126 | ? null
|
---|
127 | : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
|
---|
128 | }
|
---|
129 | });
|
---|
130 | } else if (!hasLeftNewline && needsNewlines) {
|
---|
131 | context.report({
|
---|
132 | node: leftParen,
|
---|
133 | messageId: "expectedAfter",
|
---|
134 | fix: fixer => fixer.insertTextAfter(leftParen, "\n")
|
---|
135 | });
|
---|
136 | }
|
---|
137 |
|
---|
138 | if (hasRightNewline && !needsNewlines) {
|
---|
139 | context.report({
|
---|
140 | node: rightParen,
|
---|
141 | messageId: "unexpectedBefore",
|
---|
142 | fix(fixer) {
|
---|
143 | return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
|
---|
144 |
|
---|
145 | // If there is a comment between the last element and the ), don't do a fix.
|
---|
146 | ? null
|
---|
147 | : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
|
---|
148 | }
|
---|
149 | });
|
---|
150 | } else if (!hasRightNewline && needsNewlines) {
|
---|
151 | context.report({
|
---|
152 | node: rightParen,
|
---|
153 | messageId: "expectedBefore",
|
---|
154 | fix: fixer => fixer.insertTextBefore(rightParen, "\n")
|
---|
155 | });
|
---|
156 | }
|
---|
157 | }
|
---|
158 |
|
---|
159 | /**
|
---|
160 | * Validates a list of arguments or parameters
|
---|
161 | * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
---|
162 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
163 | * @returns {void}
|
---|
164 | */
|
---|
165 | function validateArguments(parens, elements) {
|
---|
166 | const leftParen = parens.leftParen;
|
---|
167 | const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
---|
168 | const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
---|
169 | const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
---|
170 |
|
---|
171 | for (let i = 0; i <= elements.length - 2; i++) {
|
---|
172 | const currentElement = elements[i];
|
---|
173 | const nextElement = elements[i + 1];
|
---|
174 | const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line;
|
---|
175 |
|
---|
176 | if (!hasNewLine && needsNewlines) {
|
---|
177 | context.report({
|
---|
178 | node: currentElement,
|
---|
179 | messageId: "expectedBetween",
|
---|
180 | fix: fixer => fixer.insertTextBefore(nextElement, "\n")
|
---|
181 | });
|
---|
182 | }
|
---|
183 | }
|
---|
184 | }
|
---|
185 |
|
---|
186 | /**
|
---|
187 | * Gets the left paren and right paren tokens of a node.
|
---|
188 | * @param {ASTNode} node The node with parens
|
---|
189 | * @throws {TypeError} Unexpected node type.
|
---|
190 | * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
|
---|
191 | * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
|
---|
192 | * with a single parameter)
|
---|
193 | */
|
---|
194 | function getParenTokens(node) {
|
---|
195 | switch (node.type) {
|
---|
196 | case "NewExpression":
|
---|
197 | if (!node.arguments.length &&
|
---|
198 | !(
|
---|
199 | astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
|
---|
200 | astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
|
---|
201 | node.callee.range[1] < node.range[1]
|
---|
202 | )
|
---|
203 | ) {
|
---|
204 |
|
---|
205 | // If the NewExpression does not have parens (e.g. `new Foo`), return null.
|
---|
206 | return null;
|
---|
207 | }
|
---|
208 |
|
---|
209 | // falls through
|
---|
210 |
|
---|
211 | case "CallExpression":
|
---|
212 | return {
|
---|
213 | leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
|
---|
214 | rightParen: sourceCode.getLastToken(node)
|
---|
215 | };
|
---|
216 |
|
---|
217 | case "FunctionDeclaration":
|
---|
218 | case "FunctionExpression": {
|
---|
219 | const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
|
---|
220 | const rightParen = node.params.length
|
---|
221 | ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
---|
222 | : sourceCode.getTokenAfter(leftParen);
|
---|
223 |
|
---|
224 | return { leftParen, rightParen };
|
---|
225 | }
|
---|
226 |
|
---|
227 | case "ArrowFunctionExpression": {
|
---|
228 | const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
|
---|
229 |
|
---|
230 | if (!astUtils.isOpeningParenToken(firstToken)) {
|
---|
231 |
|
---|
232 | // If the ArrowFunctionExpression has a single param without parens, return null.
|
---|
233 | return null;
|
---|
234 | }
|
---|
235 |
|
---|
236 | const rightParen = node.params.length
|
---|
237 | ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
---|
238 | : sourceCode.getTokenAfter(firstToken);
|
---|
239 |
|
---|
240 | return {
|
---|
241 | leftParen: firstToken,
|
---|
242 | rightParen
|
---|
243 | };
|
---|
244 | }
|
---|
245 |
|
---|
246 | case "ImportExpression": {
|
---|
247 | const leftParen = sourceCode.getFirstToken(node, 1);
|
---|
248 | const rightParen = sourceCode.getLastToken(node);
|
---|
249 |
|
---|
250 | return { leftParen, rightParen };
|
---|
251 | }
|
---|
252 |
|
---|
253 | default:
|
---|
254 | throw new TypeError(`unexpected node with type ${node.type}`);
|
---|
255 | }
|
---|
256 | }
|
---|
257 |
|
---|
258 | //----------------------------------------------------------------------
|
---|
259 | // Public
|
---|
260 | //----------------------------------------------------------------------
|
---|
261 |
|
---|
262 | return {
|
---|
263 | [[
|
---|
264 | "ArrowFunctionExpression",
|
---|
265 | "CallExpression",
|
---|
266 | "FunctionDeclaration",
|
---|
267 | "FunctionExpression",
|
---|
268 | "ImportExpression",
|
---|
269 | "NewExpression"
|
---|
270 | ]](node) {
|
---|
271 | const parens = getParenTokens(node);
|
---|
272 | let params;
|
---|
273 |
|
---|
274 | if (node.type === "ImportExpression") {
|
---|
275 | params = [node.source];
|
---|
276 | } else if (astUtils.isFunction(node)) {
|
---|
277 | params = node.params;
|
---|
278 | } else {
|
---|
279 | params = node.arguments;
|
---|
280 | }
|
---|
281 |
|
---|
282 | if (parens) {
|
---|
283 | validateParens(parens, params);
|
---|
284 |
|
---|
285 | if (multilineArgumentsOption) {
|
---|
286 | validateArguments(parens, params);
|
---|
287 | }
|
---|
288 | }
|
---|
289 | }
|
---|
290 | };
|
---|
291 | }
|
---|
292 | };
|
---|