.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / keyword-spacing.js
1 /**
2  * @fileoverview Rule to enforce spacing before and after keywords.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils"),
13     keywords = require("./utils/keywords");
14
15 //------------------------------------------------------------------------------
16 // Constants
17 //------------------------------------------------------------------------------
18
19 const PREV_TOKEN = /^[)\]}>]$/u;
20 const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u;
21 const PREV_TOKEN_M = /^[)\]}>*]$/u;
22 const NEXT_TOKEN_M = /^[{*]$/u;
23 const TEMPLATE_OPEN_PAREN = /\$\{$/u;
24 const TEMPLATE_CLOSE_PAREN = /^\}/u;
25 const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u;
26 const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
27
28 // check duplications.
29 (function() {
30     KEYS.sort();
31     for (let i = 1; i < KEYS.length; ++i) {
32         if (KEYS[i] === KEYS[i - 1]) {
33             throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`);
34         }
35     }
36 }());
37
38 //------------------------------------------------------------------------------
39 // Helpers
40 //------------------------------------------------------------------------------
41
42 /**
43  * Checks whether or not a given token is a "Template" token ends with "${".
44  * @param {Token} token A token to check.
45  * @returns {boolean} `true` if the token is a "Template" token ends with "${".
46  */
47 function isOpenParenOfTemplate(token) {
48     return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value);
49 }
50
51 /**
52  * Checks whether or not a given token is a "Template" token starts with "}".
53  * @param {Token} token A token to check.
54  * @returns {boolean} `true` if the token is a "Template" token starts with "}".
55  */
56 function isCloseParenOfTemplate(token) {
57     return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value);
58 }
59
60 //------------------------------------------------------------------------------
61 // Rule Definition
62 //------------------------------------------------------------------------------
63
64 module.exports = {
65     meta: {
66         type: "layout",
67
68         docs: {
69             description: "enforce consistent spacing before and after keywords",
70             category: "Stylistic Issues",
71             recommended: false,
72             url: "https://eslint.org/docs/rules/keyword-spacing"
73         },
74
75         fixable: "whitespace",
76
77         schema: [
78             {
79                 type: "object",
80                 properties: {
81                     before: { type: "boolean", default: true },
82                     after: { type: "boolean", default: true },
83                     overrides: {
84                         type: "object",
85                         properties: KEYS.reduce((retv, key) => {
86                             retv[key] = {
87                                 type: "object",
88                                 properties: {
89                                     before: { type: "boolean" },
90                                     after: { type: "boolean" }
91                                 },
92                                 additionalProperties: false
93                             };
94                             return retv;
95                         }, {}),
96                         additionalProperties: false
97                     }
98                 },
99                 additionalProperties: false
100             }
101         ],
102         messages: {
103             expectedBefore: "Expected space(s) before \"{{value}}\".",
104             expectedAfter: "Expected space(s) after \"{{value}}\".",
105             unexpectedBefore: "Unexpected space(s) before \"{{value}}\".",
106             unexpectedAfter: "Unexpected space(s) after \"{{value}}\"."
107         }
108     },
109
110     create(context) {
111         const sourceCode = context.getSourceCode();
112
113         /**
114          * Reports a given token if there are not space(s) before the token.
115          * @param {Token} token A token to report.
116          * @param {RegExp} pattern A pattern of the previous token to check.
117          * @returns {void}
118          */
119         function expectSpaceBefore(token, pattern) {
120             const prevToken = sourceCode.getTokenBefore(token);
121
122             if (prevToken &&
123                 (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
124                 !isOpenParenOfTemplate(prevToken) &&
125                 astUtils.isTokenOnSameLine(prevToken, token) &&
126                 !sourceCode.isSpaceBetweenTokens(prevToken, token)
127             ) {
128                 context.report({
129                     loc: token.loc,
130                     messageId: "expectedBefore",
131                     data: token,
132                     fix(fixer) {
133                         return fixer.insertTextBefore(token, " ");
134                     }
135                 });
136             }
137         }
138
139         /**
140          * Reports a given token if there are space(s) before the token.
141          * @param {Token} token A token to report.
142          * @param {RegExp} pattern A pattern of the previous token to check.
143          * @returns {void}
144          */
145         function unexpectSpaceBefore(token, pattern) {
146             const prevToken = sourceCode.getTokenBefore(token);
147
148             if (prevToken &&
149                 (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
150                 !isOpenParenOfTemplate(prevToken) &&
151                 astUtils.isTokenOnSameLine(prevToken, token) &&
152                 sourceCode.isSpaceBetweenTokens(prevToken, token)
153             ) {
154                 context.report({
155                     loc: { start: prevToken.loc.end, end: token.loc.start },
156                     messageId: "unexpectedBefore",
157                     data: token,
158                     fix(fixer) {
159                         return fixer.removeRange([prevToken.range[1], token.range[0]]);
160                     }
161                 });
162             }
163         }
164
165         /**
166          * Reports a given token if there are not space(s) after the token.
167          * @param {Token} token A token to report.
168          * @param {RegExp} pattern A pattern of the next token to check.
169          * @returns {void}
170          */
171         function expectSpaceAfter(token, pattern) {
172             const nextToken = sourceCode.getTokenAfter(token);
173
174             if (nextToken &&
175                 (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
176                 !isCloseParenOfTemplate(nextToken) &&
177                 astUtils.isTokenOnSameLine(token, nextToken) &&
178                 !sourceCode.isSpaceBetweenTokens(token, nextToken)
179             ) {
180                 context.report({
181                     loc: token.loc,
182                     messageId: "expectedAfter",
183                     data: token,
184                     fix(fixer) {
185                         return fixer.insertTextAfter(token, " ");
186                     }
187                 });
188             }
189         }
190
191         /**
192          * Reports a given token if there are space(s) after the token.
193          * @param {Token} token A token to report.
194          * @param {RegExp} pattern A pattern of the next token to check.
195          * @returns {void}
196          */
197         function unexpectSpaceAfter(token, pattern) {
198             const nextToken = sourceCode.getTokenAfter(token);
199
200             if (nextToken &&
201                 (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
202                 !isCloseParenOfTemplate(nextToken) &&
203                 astUtils.isTokenOnSameLine(token, nextToken) &&
204                 sourceCode.isSpaceBetweenTokens(token, nextToken)
205             ) {
206
207                 context.report({
208                     loc: { start: token.loc.end, end: nextToken.loc.start },
209                     messageId: "unexpectedAfter",
210                     data: token,
211                     fix(fixer) {
212                         return fixer.removeRange([token.range[1], nextToken.range[0]]);
213                     }
214                 });
215             }
216         }
217
218         /**
219          * Parses the option object and determines check methods for each keyword.
220          * @param {Object|undefined} options The option object to parse.
221          * @returns {Object} - Normalized option object.
222          *      Keys are keywords (there are for every keyword).
223          *      Values are instances of `{"before": function, "after": function}`.
224          */
225         function parseOptions(options = {}) {
226             const before = options.before !== false;
227             const after = options.after !== false;
228             const defaultValue = {
229                 before: before ? expectSpaceBefore : unexpectSpaceBefore,
230                 after: after ? expectSpaceAfter : unexpectSpaceAfter
231             };
232             const overrides = (options && options.overrides) || {};
233             const retv = Object.create(null);
234
235             for (let i = 0; i < KEYS.length; ++i) {
236                 const key = KEYS[i];
237                 const override = overrides[key];
238
239                 if (override) {
240                     const thisBefore = ("before" in override) ? override.before : before;
241                     const thisAfter = ("after" in override) ? override.after : after;
242
243                     retv[key] = {
244                         before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
245                         after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
246                     };
247                 } else {
248                     retv[key] = defaultValue;
249                 }
250             }
251
252             return retv;
253         }
254
255         const checkMethodMap = parseOptions(context.options[0]);
256
257         /**
258          * Reports a given token if usage of spacing followed by the token is
259          * invalid.
260          * @param {Token} token A token to report.
261          * @param {RegExp} [pattern] Optional. A pattern of the previous
262          *      token to check.
263          * @returns {void}
264          */
265         function checkSpacingBefore(token, pattern) {
266             checkMethodMap[token.value].before(token, pattern || PREV_TOKEN);
267         }
268
269         /**
270          * Reports a given token if usage of spacing preceded by the token is
271          * invalid.
272          * @param {Token} token A token to report.
273          * @param {RegExp} [pattern] Optional. A pattern of the next
274          *      token to check.
275          * @returns {void}
276          */
277         function checkSpacingAfter(token, pattern) {
278             checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN);
279         }
280
281         /**
282          * Reports a given token if usage of spacing around the token is invalid.
283          * @param {Token} token A token to report.
284          * @returns {void}
285          */
286         function checkSpacingAround(token) {
287             checkSpacingBefore(token);
288             checkSpacingAfter(token);
289         }
290
291         /**
292          * Reports the first token of a given node if the first token is a keyword
293          * and usage of spacing around the token is invalid.
294          * @param {ASTNode|null} node A node to report.
295          * @returns {void}
296          */
297         function checkSpacingAroundFirstToken(node) {
298             const firstToken = node && sourceCode.getFirstToken(node);
299
300             if (firstToken && firstToken.type === "Keyword") {
301                 checkSpacingAround(firstToken);
302             }
303         }
304
305         /**
306          * Reports the first token of a given node if the first token is a keyword
307          * and usage of spacing followed by the token is invalid.
308          *
309          * This is used for unary operators (e.g. `typeof`), `function`, and `super`.
310          * Other rules are handling usage of spacing preceded by those keywords.
311          * @param {ASTNode|null} node A node to report.
312          * @returns {void}
313          */
314         function checkSpacingBeforeFirstToken(node) {
315             const firstToken = node && sourceCode.getFirstToken(node);
316
317             if (firstToken && firstToken.type === "Keyword") {
318                 checkSpacingBefore(firstToken);
319             }
320         }
321
322         /**
323          * Reports the previous token of a given node if the token is a keyword and
324          * usage of spacing around the token is invalid.
325          * @param {ASTNode|null} node A node to report.
326          * @returns {void}
327          */
328         function checkSpacingAroundTokenBefore(node) {
329             if (node) {
330                 const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
331
332                 checkSpacingAround(token);
333             }
334         }
335
336         /**
337          * Reports `async` or `function` keywords of a given node if usage of
338          * spacing around those keywords is invalid.
339          * @param {ASTNode} node A node to report.
340          * @returns {void}
341          */
342         function checkSpacingForFunction(node) {
343             const firstToken = node && sourceCode.getFirstToken(node);
344
345             if (firstToken &&
346                 ((firstToken.type === "Keyword" && firstToken.value === "function") ||
347                 firstToken.value === "async")
348             ) {
349                 checkSpacingBefore(firstToken);
350             }
351         }
352
353         /**
354          * Reports `class` and `extends` keywords of a given node if usage of
355          * spacing around those keywords is invalid.
356          * @param {ASTNode} node A node to report.
357          * @returns {void}
358          */
359         function checkSpacingForClass(node) {
360             checkSpacingAroundFirstToken(node);
361             checkSpacingAroundTokenBefore(node.superClass);
362         }
363
364         /**
365          * Reports `if` and `else` keywords of a given node if usage of spacing
366          * around those keywords is invalid.
367          * @param {ASTNode} node A node to report.
368          * @returns {void}
369          */
370         function checkSpacingForIfStatement(node) {
371             checkSpacingAroundFirstToken(node);
372             checkSpacingAroundTokenBefore(node.alternate);
373         }
374
375         /**
376          * Reports `try`, `catch`, and `finally` keywords of a given node if usage
377          * of spacing around those keywords is invalid.
378          * @param {ASTNode} node A node to report.
379          * @returns {void}
380          */
381         function checkSpacingForTryStatement(node) {
382             checkSpacingAroundFirstToken(node);
383             checkSpacingAroundFirstToken(node.handler);
384             checkSpacingAroundTokenBefore(node.finalizer);
385         }
386
387         /**
388          * Reports `do` and `while` keywords of a given node if usage of spacing
389          * around those keywords is invalid.
390          * @param {ASTNode} node A node to report.
391          * @returns {void}
392          */
393         function checkSpacingForDoWhileStatement(node) {
394             checkSpacingAroundFirstToken(node);
395             checkSpacingAroundTokenBefore(node.test);
396         }
397
398         /**
399          * Reports `for` and `in` keywords of a given node if usage of spacing
400          * around those keywords is invalid.
401          * @param {ASTNode} node A node to report.
402          * @returns {void}
403          */
404         function checkSpacingForForInStatement(node) {
405             checkSpacingAroundFirstToken(node);
406             checkSpacingAroundTokenBefore(node.right);
407         }
408
409         /**
410          * Reports `for` and `of` keywords of a given node if usage of spacing
411          * around those keywords is invalid.
412          * @param {ASTNode} node A node to report.
413          * @returns {void}
414          */
415         function checkSpacingForForOfStatement(node) {
416             if (node.await) {
417                 checkSpacingBefore(sourceCode.getFirstToken(node, 0));
418                 checkSpacingAfter(sourceCode.getFirstToken(node, 1));
419             } else {
420                 checkSpacingAroundFirstToken(node);
421             }
422             checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
423         }
424
425         /**
426          * Reports `import`, `export`, `as`, and `from` keywords of a given node if
427          * usage of spacing around those keywords is invalid.
428          *
429          * This rule handles the `*` token in module declarations.
430          *
431          *     import*as A from "./a"; /*error Expected space(s) after "import".
432          *                               error Expected space(s) before "as".
433          * @param {ASTNode} node A node to report.
434          * @returns {void}
435          */
436         function checkSpacingForModuleDeclaration(node) {
437             const firstToken = sourceCode.getFirstToken(node);
438
439             checkSpacingBefore(firstToken, PREV_TOKEN_M);
440             checkSpacingAfter(firstToken, NEXT_TOKEN_M);
441
442             if (node.type === "ExportDefaultDeclaration") {
443                 checkSpacingAround(sourceCode.getTokenAfter(firstToken));
444             }
445
446             if (node.type === "ExportAllDeclaration" && node.exported) {
447                 const asToken = sourceCode.getTokenBefore(node.exported);
448
449                 checkSpacingBefore(asToken, PREV_TOKEN_M);
450             }
451
452             if (node.source) {
453                 const fromToken = sourceCode.getTokenBefore(node.source);
454
455                 checkSpacingBefore(fromToken, PREV_TOKEN_M);
456                 checkSpacingAfter(fromToken, NEXT_TOKEN_M);
457             }
458         }
459
460         /**
461          * Reports `as` keyword of a given node if usage of spacing around this
462          * keyword is invalid.
463          * @param {ASTNode} node A node to report.
464          * @returns {void}
465          */
466         function checkSpacingForImportNamespaceSpecifier(node) {
467             const asToken = sourceCode.getFirstToken(node, 1);
468
469             checkSpacingBefore(asToken, PREV_TOKEN_M);
470         }
471
472         /**
473          * Reports `static`, `get`, and `set` keywords of a given node if usage of
474          * spacing around those keywords is invalid.
475          * @param {ASTNode} node A node to report.
476          * @returns {void}
477          */
478         function checkSpacingForProperty(node) {
479             if (node.static) {
480                 checkSpacingAroundFirstToken(node);
481             }
482             if (node.kind === "get" ||
483                 node.kind === "set" ||
484                 (
485                     (node.method || node.type === "MethodDefinition") &&
486                     node.value.async
487                 )
488             ) {
489                 const token = sourceCode.getTokenBefore(
490                     node.key,
491                     tok => {
492                         switch (tok.value) {
493                             case "get":
494                             case "set":
495                             case "async":
496                                 return true;
497                             default:
498                                 return false;
499                         }
500                     }
501                 );
502
503                 if (!token) {
504                     throw new Error("Failed to find token get, set, or async beside method name");
505                 }
506
507
508                 checkSpacingAround(token);
509             }
510         }
511
512         /**
513          * Reports `await` keyword of a given node if usage of spacing before
514          * this keyword is invalid.
515          * @param {ASTNode} node A node to report.
516          * @returns {void}
517          */
518         function checkSpacingForAwaitExpression(node) {
519             checkSpacingBefore(sourceCode.getFirstToken(node));
520         }
521
522         return {
523
524             // Statements
525             DebuggerStatement: checkSpacingAroundFirstToken,
526             WithStatement: checkSpacingAroundFirstToken,
527
528             // Statements - Control flow
529             BreakStatement: checkSpacingAroundFirstToken,
530             ContinueStatement: checkSpacingAroundFirstToken,
531             ReturnStatement: checkSpacingAroundFirstToken,
532             ThrowStatement: checkSpacingAroundFirstToken,
533             TryStatement: checkSpacingForTryStatement,
534
535             // Statements - Choice
536             IfStatement: checkSpacingForIfStatement,
537             SwitchStatement: checkSpacingAroundFirstToken,
538             SwitchCase: checkSpacingAroundFirstToken,
539
540             // Statements - Loops
541             DoWhileStatement: checkSpacingForDoWhileStatement,
542             ForInStatement: checkSpacingForForInStatement,
543             ForOfStatement: checkSpacingForForOfStatement,
544             ForStatement: checkSpacingAroundFirstToken,
545             WhileStatement: checkSpacingAroundFirstToken,
546
547             // Statements - Declarations
548             ClassDeclaration: checkSpacingForClass,
549             ExportNamedDeclaration: checkSpacingForModuleDeclaration,
550             ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
551             ExportAllDeclaration: checkSpacingForModuleDeclaration,
552             FunctionDeclaration: checkSpacingForFunction,
553             ImportDeclaration: checkSpacingForModuleDeclaration,
554             VariableDeclaration: checkSpacingAroundFirstToken,
555
556             // Expressions
557             ArrowFunctionExpression: checkSpacingForFunction,
558             AwaitExpression: checkSpacingForAwaitExpression,
559             ClassExpression: checkSpacingForClass,
560             FunctionExpression: checkSpacingForFunction,
561             NewExpression: checkSpacingBeforeFirstToken,
562             Super: checkSpacingBeforeFirstToken,
563             ThisExpression: checkSpacingBeforeFirstToken,
564             UnaryExpression: checkSpacingBeforeFirstToken,
565             YieldExpression: checkSpacingBeforeFirstToken,
566
567             // Others
568             ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
569             MethodDefinition: checkSpacingForProperty,
570             Property: checkSpacingForProperty
571         };
572     }
573 };