.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / padding-line-between-statements.js
1 /**
2  * @fileoverview Rule to require or disallow newlines between statements
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`;
19 const PADDING_LINE_SEQUENCE = new RegExp(
20     String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`,
21     "u"
22 );
23 const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
24 const CJS_IMPORT = /^require\(/u;
25
26 /**
27  * Creates tester which check if a node starts with specific keyword.
28  * @param {string} keyword The keyword to test.
29  * @returns {Object} the created tester.
30  * @private
31  */
32 function newKeywordTester(keyword) {
33     return {
34         test: (node, sourceCode) =>
35             sourceCode.getFirstToken(node).value === keyword
36     };
37 }
38
39 /**
40  * Creates tester which check if a node starts with specific keyword and spans a single line.
41  * @param {string} keyword The keyword to test.
42  * @returns {Object} the created tester.
43  * @private
44  */
45 function newSinglelineKeywordTester(keyword) {
46     return {
47         test: (node, sourceCode) =>
48             node.loc.start.line === node.loc.end.line &&
49             sourceCode.getFirstToken(node).value === keyword
50     };
51 }
52
53 /**
54  * Creates tester which check if a node starts with specific keyword and spans multiple lines.
55  * @param {string} keyword The keyword to test.
56  * @returns {Object} the created tester.
57  * @private
58  */
59 function newMultilineKeywordTester(keyword) {
60     return {
61         test: (node, sourceCode) =>
62             node.loc.start.line !== node.loc.end.line &&
63             sourceCode.getFirstToken(node).value === keyword
64     };
65 }
66
67 /**
68  * Creates tester which check if a node is specific type.
69  * @param {string} type The node type to test.
70  * @returns {Object} the created tester.
71  * @private
72  */
73 function newNodeTypeTester(type) {
74     return {
75         test: node =>
76             node.type === type
77     };
78 }
79
80 /**
81  * Checks the given node is an expression statement of IIFE.
82  * @param {ASTNode} node The node to check.
83  * @returns {boolean} `true` if the node is an expression statement of IIFE.
84  * @private
85  */
86 function isIIFEStatement(node) {
87     if (node.type === "ExpressionStatement") {
88         let call = astUtils.skipChainExpression(node.expression);
89
90         if (call.type === "UnaryExpression") {
91             call = astUtils.skipChainExpression(call.argument);
92         }
93         return call.type === "CallExpression" && astUtils.isFunction(call.callee);
94     }
95     return false;
96 }
97
98 /**
99  * Checks whether the given node is a block-like statement.
100  * This checks the last token of the node is the closing brace of a block.
101  * @param {SourceCode} sourceCode The source code to get tokens.
102  * @param {ASTNode} node The node to check.
103  * @returns {boolean} `true` if the node is a block-like statement.
104  * @private
105  */
106 function isBlockLikeStatement(sourceCode, node) {
107
108     // do-while with a block is a block-like statement.
109     if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") {
110         return true;
111     }
112
113     /*
114      * IIFE is a block-like statement specially from
115      * JSCS#disallowPaddingNewLinesAfterBlocks.
116      */
117     if (isIIFEStatement(node)) {
118         return true;
119     }
120
121     // Checks the last token is a closing brace of blocks.
122     const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
123     const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken)
124         ? sourceCode.getNodeByRangeIndex(lastToken.range[0])
125         : null;
126
127     return Boolean(belongingNode) && (
128         belongingNode.type === "BlockStatement" ||
129         belongingNode.type === "SwitchStatement"
130     );
131 }
132
133 /**
134  * Check whether the given node is a directive or not.
135  * @param {ASTNode} node The node to check.
136  * @param {SourceCode} sourceCode The source code object to get tokens.
137  * @returns {boolean} `true` if the node is a directive.
138  */
139 function isDirective(node, sourceCode) {
140     return (
141         node.type === "ExpressionStatement" &&
142         (
143             node.parent.type === "Program" ||
144             (
145                 node.parent.type === "BlockStatement" &&
146                 astUtils.isFunction(node.parent.parent)
147             )
148         ) &&
149         node.expression.type === "Literal" &&
150         typeof node.expression.value === "string" &&
151         !astUtils.isParenthesised(sourceCode, node.expression)
152     );
153 }
154
155 /**
156  * Check whether the given node is a part of directive prologue or not.
157  * @param {ASTNode} node The node to check.
158  * @param {SourceCode} sourceCode The source code object to get tokens.
159  * @returns {boolean} `true` if the node is a part of directive prologue.
160  */
161 function isDirectivePrologue(node, sourceCode) {
162     if (isDirective(node, sourceCode)) {
163         for (const sibling of node.parent.body) {
164             if (sibling === node) {
165                 break;
166             }
167             if (!isDirective(sibling, sourceCode)) {
168                 return false;
169             }
170         }
171         return true;
172     }
173     return false;
174 }
175
176 /**
177  * Gets the actual last token.
178  *
179  * If a semicolon is semicolon-less style's semicolon, this ignores it.
180  * For example:
181  *
182  *     foo()
183  *     ;[1, 2, 3].forEach(bar)
184  * @param {SourceCode} sourceCode The source code to get tokens.
185  * @param {ASTNode} node The node to get.
186  * @returns {Token} The actual last token.
187  * @private
188  */
189 function getActualLastToken(sourceCode, node) {
190     const semiToken = sourceCode.getLastToken(node);
191     const prevToken = sourceCode.getTokenBefore(semiToken);
192     const nextToken = sourceCode.getTokenAfter(semiToken);
193     const isSemicolonLessStyle = Boolean(
194         prevToken &&
195         nextToken &&
196         prevToken.range[0] >= node.range[0] &&
197         astUtils.isSemicolonToken(semiToken) &&
198         semiToken.loc.start.line !== prevToken.loc.end.line &&
199         semiToken.loc.end.line === nextToken.loc.start.line
200     );
201
202     return isSemicolonLessStyle ? prevToken : semiToken;
203 }
204
205 /**
206  * This returns the concatenation of the first 2 captured strings.
207  * @param {string} _ Unused. Whole matched string.
208  * @param {string} trailingSpaces The trailing spaces of the first line.
209  * @param {string} indentSpaces The indentation spaces of the last line.
210  * @returns {string} The concatenation of trailingSpaces and indentSpaces.
211  * @private
212  */
213 function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
214     return trailingSpaces + indentSpaces;
215 }
216
217 /**
218  * Check and report statements for `any` configuration.
219  * It does nothing.
220  * @returns {void}
221  * @private
222  */
223 function verifyForAny() {
224 }
225
226 /**
227  * Check and report statements for `never` configuration.
228  * This autofix removes blank lines between the given 2 statements.
229  * However, if comments exist between 2 blank lines, it does not remove those
230  * blank lines automatically.
231  * @param {RuleContext} context The rule context to report.
232  * @param {ASTNode} _ Unused. The previous node to check.
233  * @param {ASTNode} nextNode The next node to check.
234  * @param {Array<Token[]>} paddingLines The array of token pairs that blank
235  * lines exist between the pair.
236  * @returns {void}
237  * @private
238  */
239 function verifyForNever(context, _, nextNode, paddingLines) {
240     if (paddingLines.length === 0) {
241         return;
242     }
243
244     context.report({
245         node: nextNode,
246         messageId: "unexpectedBlankLine",
247         fix(fixer) {
248             if (paddingLines.length >= 2) {
249                 return null;
250             }
251
252             const prevToken = paddingLines[0][0];
253             const nextToken = paddingLines[0][1];
254             const start = prevToken.range[1];
255             const end = nextToken.range[0];
256             const text = context.getSourceCode().text
257                 .slice(start, end)
258                 .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
259
260             return fixer.replaceTextRange([start, end], text);
261         }
262     });
263 }
264
265 /**
266  * Check and report statements for `always` configuration.
267  * This autofix inserts a blank line between the given 2 statements.
268  * If the `prevNode` has trailing comments, it inserts a blank line after the
269  * trailing comments.
270  * @param {RuleContext} context The rule context to report.
271  * @param {ASTNode} prevNode The previous node to check.
272  * @param {ASTNode} nextNode The next node to check.
273  * @param {Array<Token[]>} paddingLines The array of token pairs that blank
274  * lines exist between the pair.
275  * @returns {void}
276  * @private
277  */
278 function verifyForAlways(context, prevNode, nextNode, paddingLines) {
279     if (paddingLines.length > 0) {
280         return;
281     }
282
283     context.report({
284         node: nextNode,
285         messageId: "expectedBlankLine",
286         fix(fixer) {
287             const sourceCode = context.getSourceCode();
288             let prevToken = getActualLastToken(sourceCode, prevNode);
289             const nextToken = sourceCode.getFirstTokenBetween(
290                 prevToken,
291                 nextNode,
292                 {
293                     includeComments: true,
294
295                     /**
296                      * Skip the trailing comments of the previous node.
297                      * This inserts a blank line after the last trailing comment.
298                      *
299                      * For example:
300                      *
301                      *     foo(); // trailing comment.
302                      *     // comment.
303                      *     bar();
304                      *
305                      * Get fixed to:
306                      *
307                      *     foo(); // trailing comment.
308                      *
309                      *     // comment.
310                      *     bar();
311                      * @param {Token} token The token to check.
312                      * @returns {boolean} `true` if the token is not a trailing comment.
313                      * @private
314                      */
315                     filter(token) {
316                         if (astUtils.isTokenOnSameLine(prevToken, token)) {
317                             prevToken = token;
318                             return false;
319                         }
320                         return true;
321                     }
322                 }
323             ) || nextNode;
324             const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken)
325                 ? "\n\n"
326                 : "\n";
327
328             return fixer.insertTextAfter(prevToken, insertText);
329         }
330     });
331 }
332
333 /**
334  * Types of blank lines.
335  * `any`, `never`, and `always` are defined.
336  * Those have `verify` method to check and report statements.
337  * @private
338  */
339 const PaddingTypes = {
340     any: { verify: verifyForAny },
341     never: { verify: verifyForNever },
342     always: { verify: verifyForAlways }
343 };
344
345 /**
346  * Types of statements.
347  * Those have `test` method to check it matches to the given statement.
348  * @private
349  */
350 const StatementTypes = {
351     "*": { test: () => true },
352     "block-like": {
353         test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node)
354     },
355     "cjs-export": {
356         test: (node, sourceCode) =>
357             node.type === "ExpressionStatement" &&
358             node.expression.type === "AssignmentExpression" &&
359             CJS_EXPORT.test(sourceCode.getText(node.expression.left))
360     },
361     "cjs-import": {
362         test: (node, sourceCode) =>
363             node.type === "VariableDeclaration" &&
364             node.declarations.length > 0 &&
365             Boolean(node.declarations[0].init) &&
366             CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
367     },
368     directive: {
369         test: isDirectivePrologue
370     },
371     expression: {
372         test: (node, sourceCode) =>
373             node.type === "ExpressionStatement" &&
374             !isDirectivePrologue(node, sourceCode)
375     },
376     iife: {
377         test: isIIFEStatement
378     },
379     "multiline-block-like": {
380         test: (node, sourceCode) =>
381             node.loc.start.line !== node.loc.end.line &&
382             isBlockLikeStatement(sourceCode, node)
383     },
384     "multiline-expression": {
385         test: (node, sourceCode) =>
386             node.loc.start.line !== node.loc.end.line &&
387             node.type === "ExpressionStatement" &&
388             !isDirectivePrologue(node, sourceCode)
389     },
390
391     "multiline-const": newMultilineKeywordTester("const"),
392     "multiline-let": newMultilineKeywordTester("let"),
393     "multiline-var": newMultilineKeywordTester("var"),
394     "singleline-const": newSinglelineKeywordTester("const"),
395     "singleline-let": newSinglelineKeywordTester("let"),
396     "singleline-var": newSinglelineKeywordTester("var"),
397
398     block: newNodeTypeTester("BlockStatement"),
399     empty: newNodeTypeTester("EmptyStatement"),
400     function: newNodeTypeTester("FunctionDeclaration"),
401
402     break: newKeywordTester("break"),
403     case: newKeywordTester("case"),
404     class: newKeywordTester("class"),
405     const: newKeywordTester("const"),
406     continue: newKeywordTester("continue"),
407     debugger: newKeywordTester("debugger"),
408     default: newKeywordTester("default"),
409     do: newKeywordTester("do"),
410     export: newKeywordTester("export"),
411     for: newKeywordTester("for"),
412     if: newKeywordTester("if"),
413     import: newKeywordTester("import"),
414     let: newKeywordTester("let"),
415     return: newKeywordTester("return"),
416     switch: newKeywordTester("switch"),
417     throw: newKeywordTester("throw"),
418     try: newKeywordTester("try"),
419     var: newKeywordTester("var"),
420     while: newKeywordTester("while"),
421     with: newKeywordTester("with")
422 };
423
424 //------------------------------------------------------------------------------
425 // Rule Definition
426 //------------------------------------------------------------------------------
427
428 module.exports = {
429     meta: {
430         type: "layout",
431
432         docs: {
433             description: "require or disallow padding lines between statements",
434             category: "Stylistic Issues",
435             recommended: false,
436             url: "https://eslint.org/docs/rules/padding-line-between-statements"
437         },
438
439         fixable: "whitespace",
440
441         schema: {
442             definitions: {
443                 paddingType: {
444                     enum: Object.keys(PaddingTypes)
445                 },
446                 statementType: {
447                     anyOf: [
448                         { enum: Object.keys(StatementTypes) },
449                         {
450                             type: "array",
451                             items: { enum: Object.keys(StatementTypes) },
452                             minItems: 1,
453                             uniqueItems: true,
454                             additionalItems: false
455                         }
456                     ]
457                 }
458             },
459             type: "array",
460             items: {
461                 type: "object",
462                 properties: {
463                     blankLine: { $ref: "#/definitions/paddingType" },
464                     prev: { $ref: "#/definitions/statementType" },
465                     next: { $ref: "#/definitions/statementType" }
466                 },
467                 additionalProperties: false,
468                 required: ["blankLine", "prev", "next"]
469             },
470             additionalItems: false
471         },
472
473         messages: {
474             unexpectedBlankLine: "Unexpected blank line before this statement.",
475             expectedBlankLine: "Expected blank line before this statement."
476         }
477     },
478
479     create(context) {
480         const sourceCode = context.getSourceCode();
481         const configureList = context.options || [];
482         let scopeInfo = null;
483
484         /**
485          * Processes to enter to new scope.
486          * This manages the current previous statement.
487          * @returns {void}
488          * @private
489          */
490         function enterScope() {
491             scopeInfo = {
492                 upper: scopeInfo,
493                 prevNode: null
494             };
495         }
496
497         /**
498          * Processes to exit from the current scope.
499          * @returns {void}
500          * @private
501          */
502         function exitScope() {
503             scopeInfo = scopeInfo.upper;
504         }
505
506         /**
507          * Checks whether the given node matches the given type.
508          * @param {ASTNode} node The statement node to check.
509          * @param {string|string[]} type The statement type to check.
510          * @returns {boolean} `true` if the statement node matched the type.
511          * @private
512          */
513         function match(node, type) {
514             let innerStatementNode = node;
515
516             while (innerStatementNode.type === "LabeledStatement") {
517                 innerStatementNode = innerStatementNode.body;
518             }
519             if (Array.isArray(type)) {
520                 return type.some(match.bind(null, innerStatementNode));
521             }
522             return StatementTypes[type].test(innerStatementNode, sourceCode);
523         }
524
525         /**
526          * Finds the last matched configure from configureList.
527          * @param {ASTNode} prevNode The previous statement to match.
528          * @param {ASTNode} nextNode The current statement to match.
529          * @returns {Object} The tester of the last matched configure.
530          * @private
531          */
532         function getPaddingType(prevNode, nextNode) {
533             for (let i = configureList.length - 1; i >= 0; --i) {
534                 const configure = configureList[i];
535                 const matched =
536                     match(prevNode, configure.prev) &&
537                     match(nextNode, configure.next);
538
539                 if (matched) {
540                     return PaddingTypes[configure.blankLine];
541                 }
542             }
543             return PaddingTypes.any;
544         }
545
546         /**
547          * Gets padding line sequences between the given 2 statements.
548          * Comments are separators of the padding line sequences.
549          * @param {ASTNode} prevNode The previous statement to count.
550          * @param {ASTNode} nextNode The current statement to count.
551          * @returns {Array<Token[]>} The array of token pairs.
552          * @private
553          */
554         function getPaddingLineSequences(prevNode, nextNode) {
555             const pairs = [];
556             let prevToken = getActualLastToken(sourceCode, prevNode);
557
558             if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
559                 do {
560                     const token = sourceCode.getTokenAfter(
561                         prevToken,
562                         { includeComments: true }
563                     );
564
565                     if (token.loc.start.line - prevToken.loc.end.line >= 2) {
566                         pairs.push([prevToken, token]);
567                     }
568                     prevToken = token;
569
570                 } while (prevToken.range[0] < nextNode.range[0]);
571             }
572
573             return pairs;
574         }
575
576         /**
577          * Verify padding lines between the given node and the previous node.
578          * @param {ASTNode} node The node to verify.
579          * @returns {void}
580          * @private
581          */
582         function verify(node) {
583             const parentType = node.parent.type;
584             const validParent =
585                 astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
586                 parentType === "SwitchStatement";
587
588             if (!validParent) {
589                 return;
590             }
591
592             // Save this node as the current previous statement.
593             const prevNode = scopeInfo.prevNode;
594
595             // Verify.
596             if (prevNode) {
597                 const type = getPaddingType(prevNode, node);
598                 const paddingLines = getPaddingLineSequences(prevNode, node);
599
600                 type.verify(context, prevNode, node, paddingLines);
601             }
602
603             scopeInfo.prevNode = node;
604         }
605
606         /**
607          * Verify padding lines between the given node and the previous node.
608          * Then process to enter to new scope.
609          * @param {ASTNode} node The node to verify.
610          * @returns {void}
611          * @private
612          */
613         function verifyThenEnterScope(node) {
614             verify(node);
615             enterScope();
616         }
617
618         return {
619             Program: enterScope,
620             BlockStatement: enterScope,
621             SwitchStatement: enterScope,
622             "Program:exit": exitScope,
623             "BlockStatement:exit": exitScope,
624             "SwitchStatement:exit": exitScope,
625
626             ":statement": verify,
627
628             SwitchCase: verifyThenEnterScope,
629             "SwitchCase:exit": exitScope
630         };
631     }
632 };