UNPKG

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