.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / semi.js
1 /**
2  * @fileoverview Rule to flag missing semicolons.
3  * @author Nicholas C. Zakas
4  */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const FixTracker = require("./utils/fix-tracker");
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Rule Definition
16 //------------------------------------------------------------------------------
17
18 module.exports = {
19     meta: {
20         type: "layout",
21
22         docs: {
23             description: "require or disallow semicolons instead of ASI",
24             category: "Stylistic Issues",
25             recommended: false,
26             url: "https://eslint.org/docs/rules/semi"
27         },
28
29         fixable: "code",
30
31         schema: {
32             anyOf: [
33                 {
34                     type: "array",
35                     items: [
36                         {
37                             enum: ["never"]
38                         },
39                         {
40                             type: "object",
41                             properties: {
42                                 beforeStatementContinuationChars: {
43                                     enum: ["always", "any", "never"]
44                                 }
45                             },
46                             additionalProperties: false
47                         }
48                     ],
49                     minItems: 0,
50                     maxItems: 2
51                 },
52                 {
53                     type: "array",
54                     items: [
55                         {
56                             enum: ["always"]
57                         },
58                         {
59                             type: "object",
60                             properties: {
61                                 omitLastInOneLineBlock: { type: "boolean" }
62                             },
63                             additionalProperties: false
64                         }
65                     ],
66                     minItems: 0,
67                     maxItems: 2
68                 }
69             ]
70         },
71
72         messages: {
73             missingSemi: "Missing semicolon.",
74             extraSemi: "Extra semicolon."
75         }
76     },
77
78     create(context) {
79
80         const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-`
81         const options = context.options[1];
82         const never = context.options[0] === "never";
83         const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
84         const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
85         const sourceCode = context.getSourceCode();
86
87         //--------------------------------------------------------------------------
88         // Helpers
89         //--------------------------------------------------------------------------
90
91         /**
92          * Reports a semicolon error with appropriate location and message.
93          * @param {ASTNode} node The node with an extra or missing semicolon.
94          * @param {boolean} missing True if the semicolon is missing.
95          * @returns {void}
96          */
97         function report(node, missing) {
98             const lastToken = sourceCode.getLastToken(node);
99             let messageId,
100                 fix,
101                 loc;
102
103             if (!missing) {
104                 messageId = "missingSemi";
105                 loc = {
106                     start: lastToken.loc.end,
107                     end: astUtils.getNextLocation(sourceCode, lastToken.loc.end)
108                 };
109                 fix = function(fixer) {
110                     return fixer.insertTextAfter(lastToken, ";");
111                 };
112             } else {
113                 messageId = "extraSemi";
114                 loc = lastToken.loc;
115                 fix = function(fixer) {
116
117                     /*
118                      * Expand the replacement range to include the surrounding
119                      * tokens to avoid conflicting with no-extra-semi.
120                      * https://github.com/eslint/eslint/issues/7928
121                      */
122                     return new FixTracker(fixer, sourceCode)
123                         .retainSurroundingTokens(lastToken)
124                         .remove(lastToken);
125                 };
126             }
127
128             context.report({
129                 node,
130                 loc,
131                 messageId,
132                 fix
133             });
134
135         }
136
137         /**
138          * Check whether a given semicolon token is redundant.
139          * @param {Token} semiToken A semicolon token to check.
140          * @returns {boolean} `true` if the next token is `;` or `}`.
141          */
142         function isRedundantSemi(semiToken) {
143             const nextToken = sourceCode.getTokenAfter(semiToken);
144
145             return (
146                 !nextToken ||
147                 astUtils.isClosingBraceToken(nextToken) ||
148                 astUtils.isSemicolonToken(nextToken)
149             );
150         }
151
152         /**
153          * Check whether a given token is the closing brace of an arrow function.
154          * @param {Token} lastToken A token to check.
155          * @returns {boolean} `true` if the token is the closing brace of an arrow function.
156          */
157         function isEndOfArrowBlock(lastToken) {
158             if (!astUtils.isClosingBraceToken(lastToken)) {
159                 return false;
160             }
161             const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
162
163             return (
164                 node.type === "BlockStatement" &&
165                 node.parent.type === "ArrowFunctionExpression"
166             );
167         }
168
169         /**
170          * Check whether a given node is on the same line with the next token.
171          * @param {Node} node A statement node to check.
172          * @returns {boolean} `true` if the node is on the same line with the next token.
173          */
174         function isOnSameLineWithNextToken(node) {
175             const prevToken = sourceCode.getLastToken(node, 1);
176             const nextToken = sourceCode.getTokenAfter(node);
177
178             return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken);
179         }
180
181         /**
182          * Check whether a given node can connect the next line if the next line is unreliable.
183          * @param {Node} node A statement node to check.
184          * @returns {boolean} `true` if the node can connect the next line.
185          */
186         function maybeAsiHazardAfter(node) {
187             const t = node.type;
188
189             if (t === "DoWhileStatement" ||
190                 t === "BreakStatement" ||
191                 t === "ContinueStatement" ||
192                 t === "DebuggerStatement" ||
193                 t === "ImportDeclaration" ||
194                 t === "ExportAllDeclaration"
195             ) {
196                 return false;
197             }
198             if (t === "ReturnStatement") {
199                 return Boolean(node.argument);
200             }
201             if (t === "ExportNamedDeclaration") {
202                 return Boolean(node.declaration);
203             }
204             if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
205                 return false;
206             }
207
208             return true;
209         }
210
211         /**
212          * Check whether a given token can connect the previous statement.
213          * @param {Token} token A token to check.
214          * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`.
215          */
216         function maybeAsiHazardBefore(token) {
217             return (
218                 Boolean(token) &&
219                 OPT_OUT_PATTERN.test(token.value) &&
220                 token.value !== "++" &&
221                 token.value !== "--"
222             );
223         }
224
225         /**
226          * Check if the semicolon of a given node is unnecessary, only true if:
227          *   - next token is a valid statement divider (`;` or `}`).
228          *   - next token is on a new line and the node is not connectable to the new line.
229          * @param {Node} node A statement node to check.
230          * @returns {boolean} whether the semicolon is unnecessary.
231          */
232         function canRemoveSemicolon(node) {
233             if (isRedundantSemi(sourceCode.getLastToken(node))) {
234                 return true; // `;;` or `;}`
235             }
236             if (isOnSameLineWithNextToken(node)) {
237                 return false; // One liner.
238             }
239             if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) {
240                 return true; // ASI works. This statement doesn't connect to the next.
241             }
242             if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
243                 return true; // ASI works. The next token doesn't connect to this statement.
244             }
245
246             return false;
247         }
248
249         /**
250          * Checks a node to see if it's in a one-liner block statement.
251          * @param {ASTNode} node The node to check.
252          * @returns {boolean} whether the node is in a one-liner block statement.
253          */
254         function isOneLinerBlock(node) {
255             const parent = node.parent;
256             const nextToken = sourceCode.getTokenAfter(node);
257
258             if (!nextToken || nextToken.value !== "}") {
259                 return false;
260             }
261             return (
262                 !!parent &&
263                 parent.type === "BlockStatement" &&
264                 parent.loc.start.line === parent.loc.end.line
265             );
266         }
267
268         /**
269          * Checks a node to see if it's followed by a semicolon.
270          * @param {ASTNode} node The node to check.
271          * @returns {void}
272          */
273         function checkForSemicolon(node) {
274             const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node));
275
276             if (never) {
277                 if (isSemi && canRemoveSemicolon(node)) {
278                     report(node, true);
279                 } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
280                     report(node);
281                 }
282             } else {
283                 const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node));
284
285                 if (isSemi && oneLinerBlock) {
286                     report(node, true);
287                 } else if (!isSemi && !oneLinerBlock) {
288                     report(node);
289                 }
290             }
291         }
292
293         /**
294          * Checks to see if there's a semicolon after a variable declaration.
295          * @param {ASTNode} node The node to check.
296          * @returns {void}
297          */
298         function checkForSemicolonForVariableDeclaration(node) {
299             const parent = node.parent;
300
301             if ((parent.type !== "ForStatement" || parent.init !== node) &&
302                 (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node)
303             ) {
304                 checkForSemicolon(node);
305             }
306         }
307
308         //--------------------------------------------------------------------------
309         // Public API
310         //--------------------------------------------------------------------------
311
312         return {
313             VariableDeclaration: checkForSemicolonForVariableDeclaration,
314             ExpressionStatement: checkForSemicolon,
315             ReturnStatement: checkForSemicolon,
316             ThrowStatement: checkForSemicolon,
317             DoWhileStatement: checkForSemicolon,
318             DebuggerStatement: checkForSemicolon,
319             BreakStatement: checkForSemicolon,
320             ContinueStatement: checkForSemicolon,
321             ImportDeclaration: checkForSemicolon,
322             ExportAllDeclaration: checkForSemicolon,
323             ExportNamedDeclaration(node) {
324                 if (!node.declaration) {
325                     checkForSemicolon(node);
326                 }
327             },
328             ExportDefaultDeclaration(node) {
329                 if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) {
330                     checkForSemicolon(node);
331                 }
332             }
333         };
334
335     }
336 };