.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / function-paren-newline.js
1 /**
2  * @fileoverview enforce consistent line breaks inside function parentheses
3  * @author Teddy Katz
4  */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const astUtils = require("./utils/ast-utils");
12
13 //------------------------------------------------------------------------------
14 // Rule Definition
15 //------------------------------------------------------------------------------
16
17 module.exports = {
18     meta: {
19         type: "layout",
20
21         docs: {
22             description: "enforce consistent line breaks inside function parentheses",
23             category: "Stylistic Issues",
24             recommended: false,
25             url: "https://eslint.org/docs/rules/function-paren-newline"
26         },
27
28         fixable: "whitespace",
29
30         schema: [
31             {
32                 oneOf: [
33                     {
34                         enum: ["always", "never", "consistent", "multiline", "multiline-arguments"]
35                     },
36                     {
37                         type: "object",
38                         properties: {
39                             minItems: {
40                                 type: "integer",
41                                 minimum: 0
42                             }
43                         },
44                         additionalProperties: false
45                     }
46                 ]
47             }
48         ],
49
50         messages: {
51             expectedBefore: "Expected newline before ')'.",
52             expectedAfter: "Expected newline after '('.",
53             expectedBetween: "Expected newline between arguments/params.",
54             unexpectedBefore: "Unexpected newline before ')'.",
55             unexpectedAfter: "Unexpected newline after '('."
56         }
57     },
58
59     create(context) {
60         const sourceCode = context.getSourceCode();
61         const rawOption = context.options[0] || "multiline";
62         const multilineOption = rawOption === "multiline";
63         const multilineArgumentsOption = rawOption === "multiline-arguments";
64         const consistentOption = rawOption === "consistent";
65         let minItems;
66
67         if (typeof rawOption === "object") {
68             minItems = rawOption.minItems;
69         } else if (rawOption === "always") {
70             minItems = 0;
71         } else if (rawOption === "never") {
72             minItems = Infinity;
73         } else {
74             minItems = null;
75         }
76
77         //----------------------------------------------------------------------
78         // Helpers
79         //----------------------------------------------------------------------
80
81         /**
82          * Determines whether there should be newlines inside function parens
83          * @param {ASTNode[]} elements The arguments or parameters in the list
84          * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
85          * @returns {boolean} `true` if there should be newlines inside the function parens
86          */
87         function shouldHaveNewlines(elements, hasLeftNewline) {
88             if (multilineArgumentsOption && elements.length === 1) {
89                 return hasLeftNewline;
90             }
91             if (multilineOption || multilineArgumentsOption) {
92                 return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
93             }
94             if (consistentOption) {
95                 return hasLeftNewline;
96             }
97             return elements.length >= minItems;
98         }
99
100         /**
101          * Validates parens
102          * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
103          * @param {ASTNode[]} elements The arguments or parameters in the list
104          * @returns {void}
105          */
106         function validateParens(parens, elements) {
107             const leftParen = parens.leftParen;
108             const rightParen = parens.rightParen;
109             const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
110             const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
111             const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
112             const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
113             const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
114
115             if (hasLeftNewline && !needsNewlines) {
116                 context.report({
117                     node: leftParen,
118                     messageId: "unexpectedAfter",
119                     fix(fixer) {
120                         return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
121
122                             // If there is a comment between the ( and the first element, don't do a fix.
123                             ? null
124                             : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
125                     }
126                 });
127             } else if (!hasLeftNewline && needsNewlines) {
128                 context.report({
129                     node: leftParen,
130                     messageId: "expectedAfter",
131                     fix: fixer => fixer.insertTextAfter(leftParen, "\n")
132                 });
133             }
134
135             if (hasRightNewline && !needsNewlines) {
136                 context.report({
137                     node: rightParen,
138                     messageId: "unexpectedBefore",
139                     fix(fixer) {
140                         return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
141
142                             // If there is a comment between the last element and the ), don't do a fix.
143                             ? null
144                             : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
145                     }
146                 });
147             } else if (!hasRightNewline && needsNewlines) {
148                 context.report({
149                     node: rightParen,
150                     messageId: "expectedBefore",
151                     fix: fixer => fixer.insertTextBefore(rightParen, "\n")
152                 });
153             }
154         }
155
156         /**
157          * Validates a list of arguments or parameters
158          * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
159          * @param {ASTNode[]} elements The arguments or parameters in the list
160          * @returns {void}
161          */
162         function validateArguments(parens, elements) {
163             const leftParen = parens.leftParen;
164             const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
165             const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
166             const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
167
168             for (let i = 0; i <= elements.length - 2; i++) {
169                 const currentElement = elements[i];
170                 const nextElement = elements[i + 1];
171                 const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line;
172
173                 if (!hasNewLine && needsNewlines) {
174                     context.report({
175                         node: currentElement,
176                         messageId: "expectedBetween",
177                         fix: fixer => fixer.insertTextBefore(nextElement, "\n")
178                     });
179                 }
180             }
181         }
182
183         /**
184          * Gets the left paren and right paren tokens of a node.
185          * @param {ASTNode} node The node with parens
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": {
221                     const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
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 };