source: imaps-frontend/node_modules/eslint/lib/rules/padding-line-between-statements.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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