.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / curly.js
1 /**
2  * @fileoverview Rule to flag statements without curly braces
3  * @author Nicholas C. Zakas
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: "suggestion",
20
21         docs: {
22             description: "enforce consistent brace style for all control statements",
23             category: "Best Practices",
24             recommended: false,
25             url: "https://eslint.org/docs/rules/curly"
26         },
27
28         schema: {
29             anyOf: [
30                 {
31                     type: "array",
32                     items: [
33                         {
34                             enum: ["all"]
35                         }
36                     ],
37                     minItems: 0,
38                     maxItems: 1
39                 },
40                 {
41                     type: "array",
42                     items: [
43                         {
44                             enum: ["multi", "multi-line", "multi-or-nest"]
45                         },
46                         {
47                             enum: ["consistent"]
48                         }
49                     ],
50                     minItems: 0,
51                     maxItems: 2
52                 }
53             ]
54         },
55
56         fixable: "code",
57
58         messages: {
59             missingCurlyAfter: "Expected { after '{{name}}'.",
60             missingCurlyAfterCondition: "Expected { after '{{name}}' condition.",
61             unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.",
62             unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition."
63         }
64     },
65
66     create(context) {
67
68         const multiOnly = (context.options[0] === "multi");
69         const multiLine = (context.options[0] === "multi-line");
70         const multiOrNest = (context.options[0] === "multi-or-nest");
71         const consistent = (context.options[1] === "consistent");
72
73         const sourceCode = context.getSourceCode();
74
75         //--------------------------------------------------------------------------
76         // Helpers
77         //--------------------------------------------------------------------------
78
79         /**
80          * Determines if a given node is a one-liner that's on the same line as it's preceding code.
81          * @param {ASTNode} node The node to check.
82          * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
83          * @private
84          */
85         function isCollapsedOneLiner(node) {
86             const before = sourceCode.getTokenBefore(node);
87             const last = sourceCode.getLastToken(node);
88             const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
89
90             return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
91         }
92
93         /**
94          * Determines if a given node is a one-liner.
95          * @param {ASTNode} node The node to check.
96          * @returns {boolean} True if the node is a one-liner.
97          * @private
98          */
99         function isOneLiner(node) {
100             if (node.type === "EmptyStatement") {
101                 return true;
102             }
103
104             const first = sourceCode.getFirstToken(node);
105             const last = sourceCode.getLastToken(node);
106             const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
107
108             return first.loc.start.line === lastExcludingSemicolon.loc.end.line;
109         }
110
111         /**
112          * Determines if the given node is a lexical declaration (let, const, function, or class)
113          * @param {ASTNode} node The node to check
114          * @returns {boolean} True if the node is a lexical declaration
115          * @private
116          */
117         function isLexicalDeclaration(node) {
118             if (node.type === "VariableDeclaration") {
119                 return node.kind === "const" || node.kind === "let";
120             }
121
122             return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
123         }
124
125         /**
126          * Checks if the given token is an `else` token or not.
127          * @param {Token} token The token to check.
128          * @returns {boolean} `true` if the token is an `else` token.
129          */
130         function isElseKeywordToken(token) {
131             return token.value === "else" && token.type === "Keyword";
132         }
133
134         /**
135          * Gets the `else` keyword token of a given `IfStatement` node.
136          * @param {ASTNode} node A `IfStatement` node to get.
137          * @returns {Token} The `else` keyword token.
138          */
139         function getElseKeyword(node) {
140             return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken);
141         }
142
143         /**
144          * Determines whether the given node has an `else` keyword token as the first token after.
145          * @param {ASTNode} node The node to check.
146          * @returns {boolean} `true` if the node is followed by an `else` keyword token.
147          */
148         function isFollowedByElseKeyword(node) {
149             const nextToken = sourceCode.getTokenAfter(node);
150
151             return Boolean(nextToken) && isElseKeywordToken(nextToken);
152         }
153
154         /**
155          * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError.
156          * @param {Token} closingBracket The } token
157          * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block.
158          */
159         function needsSemicolon(closingBracket) {
160             const tokenBefore = sourceCode.getTokenBefore(closingBracket);
161             const tokenAfter = sourceCode.getTokenAfter(closingBracket);
162             const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
163
164             if (astUtils.isSemicolonToken(tokenBefore)) {
165
166                 // If the last statement already has a semicolon, don't add another one.
167                 return false;
168             }
169
170             if (!tokenAfter) {
171
172                 // If there are no statements after this block, there is no need to add a semicolon.
173                 return false;
174             }
175
176             if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") {
177
178                 /*
179                  * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression),
180                  * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause
181                  * a SyntaxError if it was followed by `else`.
182                  */
183                 return false;
184             }
185
186             if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) {
187
188                 // If the next token is on the same line, insert a semicolon.
189                 return true;
190             }
191
192             if (/^[([/`+-]/u.test(tokenAfter.value)) {
193
194                 // If the next token starts with a character that would disrupt ASI, insert a semicolon.
195                 return true;
196             }
197
198             if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) {
199
200                 // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI.
201                 return true;
202             }
203
204             // Otherwise, do not insert a semicolon.
205             return false;
206         }
207
208         /**
209          * Determines whether the code represented by the given node contains an `if` statement
210          * that would become associated with an `else` keyword directly appended to that code.
211          *
212          * Examples where it returns `true`:
213          *
214          *    if (a)
215          *        foo();
216          *
217          *    if (a) {
218          *        foo();
219          *    }
220          *
221          *    if (a)
222          *        foo();
223          *    else if (b)
224          *        bar();
225          *
226          *    while (a)
227          *        if (b)
228          *            if(c)
229          *                foo();
230          *            else
231          *                bar();
232          *
233          * Examples where it returns `false`:
234          *
235          *    if (a)
236          *        foo();
237          *    else
238          *        bar();
239          *
240          *    while (a) {
241          *        if (b)
242          *            if(c)
243          *                foo();
244          *            else
245          *                bar();
246          *    }
247          *
248          *    while (a)
249          *        if (b) {
250          *            if(c)
251          *                foo();
252          *        }
253          *        else
254          *            bar();
255          * @param {ASTNode} node Node representing the code to check.
256          * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code.
257          */
258         function hasUnsafeIf(node) {
259             switch (node.type) {
260                 case "IfStatement":
261                     if (!node.alternate) {
262                         return true;
263                     }
264                     return hasUnsafeIf(node.alternate);
265                 case "ForStatement":
266                 case "ForInStatement":
267                 case "ForOfStatement":
268                 case "LabeledStatement":
269                 case "WithStatement":
270                 case "WhileStatement":
271                     return hasUnsafeIf(node.body);
272                 default:
273                     return false;
274             }
275         }
276
277         /**
278          * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code.
279          * The braces, which make the given block body, are necessary in either of the following situations:
280          *
281          * 1. The statement is a lexical declaration.
282          * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace:
283          *
284          *     if (a) {
285          *         if (b)
286          *             foo();
287          *     }
288          *     else
289          *         bar();
290          *
291          *     if (a)
292          *         while (b)
293          *             while (c) {
294          *                 while (d)
295          *                     if (e)
296          *                         while(f)
297          *                             foo();
298          *            }
299          *     else
300          *         bar();
301          * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements.
302          * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content)
303          * would change the semantics of the code or produce a syntax error.
304          */
305         function areBracesNecessary(node) {
306             const statement = node.body[0];
307
308             return isLexicalDeclaration(statement) ||
309                 hasUnsafeIf(statement) && isFollowedByElseKeyword(node);
310         }
311
312         /**
313          * Prepares to check the body of a node to see if it's a block statement.
314          * @param {ASTNode} node The node to report if there's a problem.
315          * @param {ASTNode} body The body node to check for blocks.
316          * @param {string} name The name to report if there's a problem.
317          * @param {{ condition: boolean }} opts Options to pass to the report functions
318          * @returns {Object} a prepared check object, with "actual", "expected", "check" properties.
319          *   "actual" will be `true` or `false` whether the body is already a block statement.
320          *   "expected" will be `true` or `false` if the body should be a block statement or not, or
321          *   `null` if it doesn't matter, depending on the rule options. It can be modified to change
322          *   the final behavior of "check".
323          *   "check" will be a function reporting appropriate problems depending on the other
324          *   properties.
325          */
326         function prepareCheck(node, body, name, opts) {
327             const hasBlock = (body.type === "BlockStatement");
328             let expected = null;
329
330             if (hasBlock && (body.body.length !== 1 || areBracesNecessary(body))) {
331                 expected = true;
332             } else if (multiOnly) {
333                 expected = false;
334             } else if (multiLine) {
335                 if (!isCollapsedOneLiner(body)) {
336                     expected = true;
337                 }
338
339                 // otherwise, the body is allowed to have braces or not to have braces
340
341             } else if (multiOrNest) {
342                 if (hasBlock) {
343                     const statement = body.body[0];
344                     const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement);
345
346                     expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0;
347                 } else {
348                     expected = !isOneLiner(body);
349                 }
350             } else {
351
352                 // default "all"
353                 expected = true;
354             }
355
356             return {
357                 actual: hasBlock,
358                 expected,
359                 check() {
360                     if (this.expected !== null && this.expected !== this.actual) {
361                         if (this.expected) {
362                             context.report({
363                                 node,
364                                 loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
365                                 messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
366                                 data: {
367                                     name
368                                 },
369                                 fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`)
370                             });
371                         } else {
372                             context.report({
373                                 node,
374                                 loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
375                                 messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
376                                 data: {
377                                     name
378                                 },
379                                 fix(fixer) {
380
381                                     /*
382                                      * `do while` expressions sometimes need a space to be inserted after `do`.
383                                      * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
384                                      */
385                                     const needsPrecedingSpace = node.type === "DoWhileStatement" &&
386                                         sourceCode.getTokenBefore(body).range[1] === body.range[0] &&
387                                         !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 }));
388
389                                     const openingBracket = sourceCode.getFirstToken(body);
390                                     const closingBracket = sourceCode.getLastToken(body);
391                                     const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket);
392
393                                     if (needsSemicolon(closingBracket)) {
394
395                                         /*
396                                          * If removing braces would cause a SyntaxError due to multiple statements on the same line (or
397                                          * change the semantics of the code due to ASI), don't perform a fix.
398                                          */
399                                         return null;
400                                     }
401
402                                     const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) +
403                                         sourceCode.getText(lastTokenInBlock) +
404                                         sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]);
405
406                                     return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText);
407                                 }
408                             });
409                         }
410                     }
411                 }
412             };
413         }
414
415         /**
416          * Prepares to check the bodies of a "if", "else if" and "else" chain.
417          * @param {ASTNode} node The first IfStatement node of the chain.
418          * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more
419          *   information.
420          */
421         function prepareIfChecks(node) {
422             const preparedChecks = [];
423
424             for (let currentNode = node; currentNode; currentNode = currentNode.alternate) {
425                 preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true }));
426                 if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") {
427                     preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else"));
428                     break;
429                 }
430             }
431
432             if (consistent) {
433
434                 /*
435                  * If any node should have or already have braces, make sure they
436                  * all have braces.
437                  * If all nodes shouldn't have braces, make sure they don't.
438                  */
439                 const expected = preparedChecks.some(preparedCheck => {
440                     if (preparedCheck.expected !== null) {
441                         return preparedCheck.expected;
442                     }
443                     return preparedCheck.actual;
444                 });
445
446                 preparedChecks.forEach(preparedCheck => {
447                     preparedCheck.expected = expected;
448                 });
449             }
450
451             return preparedChecks;
452         }
453
454         //--------------------------------------------------------------------------
455         // Public
456         //--------------------------------------------------------------------------
457
458         return {
459             IfStatement(node) {
460                 const parent = node.parent;
461                 const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
462
463                 if (!isElseIf) {
464
465                     // This is a top `if`, check the whole `if-else-if` chain
466                     prepareIfChecks(node).forEach(preparedCheck => {
467                         preparedCheck.check();
468                     });
469                 }
470
471                 // Skip `else if`, it's already checked (when the top `if` was visited)
472             },
473
474             WhileStatement(node) {
475                 prepareCheck(node, node.body, "while", { condition: true }).check();
476             },
477
478             DoWhileStatement(node) {
479                 prepareCheck(node, node.body, "do").check();
480             },
481
482             ForStatement(node) {
483                 prepareCheck(node, node.body, "for", { condition: true }).check();
484             },
485
486             ForInStatement(node) {
487                 prepareCheck(node, node.body, "for-in").check();
488             },
489
490             ForOfStatement(node) {
491                 prepareCheck(node, node.body, "for-of").check();
492             }
493         };
494     }
495 };