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