Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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") { |
Tim van der Lippe | 16aca39 | 2020-11-13 11:37:13 +0000 | [diff] [blame] | 88 | let call = astUtils.skipChainExpression(node.expression); |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 89 | |
| 90 | if (call.type === "UnaryExpression") { |
Tim van der Lippe | 16aca39 | 2020-11-13 11:37:13 +0000 | [diff] [blame] | 91 | call = astUtils.skipChainExpression(call.argument); |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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) |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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, |
Tim van der Lippe | 16aca39 | 2020-11-13 11:37:13 +0000 | [diff] [blame] | 246 | messageId: "unexpectedBlankLine", |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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, |
Tim van der Lippe | 16aca39 | 2020-11-13 11:37:13 +0000 | [diff] [blame] | 285 | messageId: "expectedBlankLine", |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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(); |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 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", |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 434 | recommended: false, |
| 435 | url: "https://eslint.org/docs/rules/padding-line-between-statements" |
| 436 | }, |
| 437 | |
| 438 | fixable: "whitespace", |
| 439 | |
| 440 | schema: { |
| 441 | definitions: { |
| 442 | paddingType: { |
| 443 | enum: Object.keys(PaddingTypes) |
| 444 | }, |
| 445 | statementType: { |
| 446 | anyOf: [ |
| 447 | { enum: Object.keys(StatementTypes) }, |
| 448 | { |
| 449 | type: "array", |
| 450 | items: { enum: Object.keys(StatementTypes) }, |
| 451 | minItems: 1, |
| 452 | uniqueItems: true, |
| 453 | additionalItems: false |
| 454 | } |
| 455 | ] |
| 456 | } |
| 457 | }, |
| 458 | type: "array", |
| 459 | items: { |
| 460 | type: "object", |
| 461 | properties: { |
| 462 | blankLine: { $ref: "#/definitions/paddingType" }, |
| 463 | prev: { $ref: "#/definitions/statementType" }, |
| 464 | next: { $ref: "#/definitions/statementType" } |
| 465 | }, |
| 466 | additionalProperties: false, |
| 467 | required: ["blankLine", "prev", "next"] |
| 468 | }, |
| 469 | additionalItems: false |
Tim van der Lippe | 16aca39 | 2020-11-13 11:37:13 +0000 | [diff] [blame] | 470 | }, |
| 471 | |
| 472 | messages: { |
| 473 | unexpectedBlankLine: "Unexpected blank line before this statement.", |
| 474 | expectedBlankLine: "Expected blank line before this statement." |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 475 | } |
| 476 | }, |
| 477 | |
| 478 | create(context) { |
| 479 | const sourceCode = context.getSourceCode(); |
| 480 | const configureList = context.options || []; |
| 481 | let scopeInfo = null; |
| 482 | |
| 483 | /** |
| 484 | * Processes to enter to new scope. |
| 485 | * This manages the current previous statement. |
| 486 | * @returns {void} |
| 487 | * @private |
| 488 | */ |
| 489 | function enterScope() { |
| 490 | scopeInfo = { |
| 491 | upper: scopeInfo, |
| 492 | prevNode: null |
| 493 | }; |
| 494 | } |
| 495 | |
| 496 | /** |
| 497 | * Processes to exit from the current scope. |
| 498 | * @returns {void} |
| 499 | * @private |
| 500 | */ |
| 501 | function exitScope() { |
| 502 | scopeInfo = scopeInfo.upper; |
| 503 | } |
| 504 | |
| 505 | /** |
| 506 | * Checks whether the given node matches the given type. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 507 | * @param {ASTNode} node The statement node to check. |
| 508 | * @param {string|string[]} type The statement type to check. |
| 509 | * @returns {boolean} `true` if the statement node matched the type. |
| 510 | * @private |
| 511 | */ |
| 512 | function match(node, type) { |
| 513 | let innerStatementNode = node; |
| 514 | |
| 515 | while (innerStatementNode.type === "LabeledStatement") { |
| 516 | innerStatementNode = innerStatementNode.body; |
| 517 | } |
| 518 | if (Array.isArray(type)) { |
| 519 | return type.some(match.bind(null, innerStatementNode)); |
| 520 | } |
| 521 | return StatementTypes[type].test(innerStatementNode, sourceCode); |
| 522 | } |
| 523 | |
| 524 | /** |
| 525 | * Finds the last matched configure from configureList. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 526 | * @param {ASTNode} prevNode The previous statement to match. |
| 527 | * @param {ASTNode} nextNode The current statement to match. |
| 528 | * @returns {Object} The tester of the last matched configure. |
| 529 | * @private |
| 530 | */ |
| 531 | function getPaddingType(prevNode, nextNode) { |
| 532 | for (let i = configureList.length - 1; i >= 0; --i) { |
| 533 | const configure = configureList[i]; |
| 534 | const matched = |
| 535 | match(prevNode, configure.prev) && |
| 536 | match(nextNode, configure.next); |
| 537 | |
| 538 | if (matched) { |
| 539 | return PaddingTypes[configure.blankLine]; |
| 540 | } |
| 541 | } |
| 542 | return PaddingTypes.any; |
| 543 | } |
| 544 | |
| 545 | /** |
| 546 | * Gets padding line sequences between the given 2 statements. |
| 547 | * Comments are separators of the padding line sequences. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 548 | * @param {ASTNode} prevNode The previous statement to count. |
| 549 | * @param {ASTNode} nextNode The current statement to count. |
| 550 | * @returns {Array<Token[]>} The array of token pairs. |
| 551 | * @private |
| 552 | */ |
| 553 | function getPaddingLineSequences(prevNode, nextNode) { |
| 554 | const pairs = []; |
| 555 | let prevToken = getActualLastToken(sourceCode, prevNode); |
| 556 | |
| 557 | if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { |
| 558 | do { |
| 559 | const token = sourceCode.getTokenAfter( |
| 560 | prevToken, |
| 561 | { includeComments: true } |
| 562 | ); |
| 563 | |
| 564 | if (token.loc.start.line - prevToken.loc.end.line >= 2) { |
| 565 | pairs.push([prevToken, token]); |
| 566 | } |
| 567 | prevToken = token; |
| 568 | |
| 569 | } while (prevToken.range[0] < nextNode.range[0]); |
| 570 | } |
| 571 | |
| 572 | return pairs; |
| 573 | } |
| 574 | |
| 575 | /** |
| 576 | * Verify padding lines between the given node and the previous node. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 577 | * @param {ASTNode} node The node to verify. |
| 578 | * @returns {void} |
| 579 | * @private |
| 580 | */ |
| 581 | function verify(node) { |
| 582 | const parentType = node.parent.type; |
| 583 | const validParent = |
| 584 | astUtils.STATEMENT_LIST_PARENTS.has(parentType) || |
| 585 | parentType === "SwitchStatement"; |
| 586 | |
| 587 | if (!validParent) { |
| 588 | return; |
| 589 | } |
| 590 | |
| 591 | // Save this node as the current previous statement. |
| 592 | const prevNode = scopeInfo.prevNode; |
| 593 | |
| 594 | // Verify. |
| 595 | if (prevNode) { |
| 596 | const type = getPaddingType(prevNode, node); |
| 597 | const paddingLines = getPaddingLineSequences(prevNode, node); |
| 598 | |
| 599 | type.verify(context, prevNode, node, paddingLines); |
| 600 | } |
| 601 | |
| 602 | scopeInfo.prevNode = node; |
| 603 | } |
| 604 | |
| 605 | /** |
| 606 | * Verify padding lines between the given node and the previous node. |
| 607 | * Then process to enter to new scope. |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 608 | * @param {ASTNode} node The node to verify. |
| 609 | * @returns {void} |
| 610 | * @private |
| 611 | */ |
| 612 | function verifyThenEnterScope(node) { |
| 613 | verify(node); |
| 614 | enterScope(); |
| 615 | } |
| 616 | |
| 617 | return { |
| 618 | Program: enterScope, |
| 619 | BlockStatement: enterScope, |
| 620 | SwitchStatement: enterScope, |
Tim van der Lippe | 0124c68 | 2021-11-23 15:12:10 +0000 | [diff] [blame^] | 621 | StaticBlock: enterScope, |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 622 | "Program:exit": exitScope, |
| 623 | "BlockStatement:exit": exitScope, |
| 624 | "SwitchStatement:exit": exitScope, |
Tim van der Lippe | 0124c68 | 2021-11-23 15:12:10 +0000 | [diff] [blame^] | 625 | "StaticBlock:exit": exitScope, |
Yang Guo | 4fd355c | 2019-09-19 10:59:03 +0200 | [diff] [blame] | 626 | |
| 627 | ":statement": verify, |
| 628 | |
| 629 | SwitchCase: verifyThenEnterScope, |
| 630 | "SwitchCase:exit": exitScope |
| 631 | }; |
| 632 | } |
| 633 | }; |