'use strict'; var utils$1 = require('../utils.js'); var utils = require('@typescript-eslint/utils'); var astUtils = require('@typescript-eslint/utils/ast-utils'); require('eslint-visitor-keys'); require('espree'); require('estraverse'); const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u; const CJS_IMPORT = /^require\(/u; const LT = `[${Array.from( /* @__PURE__ */ new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]) ).join("")}]`; const PADDING_LINE_SEQUENCE = new RegExp( String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, "u" ); function newKeywordTester(type, keyword) { return { test(node, sourceCode) { const isSameKeyword = sourceCode.getFirstToken(node)?.value === keyword; const isSameType = Array.isArray(type) ? type.includes(node.type) : type === node.type; return isSameKeyword && isSameType; } }; } function newSinglelineKeywordTester(keyword) { return { test(node, sourceCode) { return node.loc.start.line === node.loc.end.line && sourceCode.getFirstToken(node).value === keyword; } }; } function newMultilineKeywordTester(keyword) { return { test(node, sourceCode) { return node.loc.start.line !== node.loc.end.line && sourceCode.getFirstToken(node).value === keyword; } }; } function newNodeTypeTester(type) { return { test: (node) => node.type === type }; } function skipChainExpression(node) { return node && node.type === utils.AST_NODE_TYPES.ChainExpression ? node.expression : node; } function isIIFEStatement(node) { if (node.type === utils.AST_NODE_TYPES.ExpressionStatement) { let expression = skipChainExpression(node.expression); if (expression.type === utils.AST_NODE_TYPES.UnaryExpression) expression = skipChainExpression(expression.argument); if (expression.type === utils.AST_NODE_TYPES.CallExpression) { let node2 = expression.callee; while (node2.type === utils.AST_NODE_TYPES.SequenceExpression) node2 = node2.expressions[node2.expressions.length - 1]; return astUtils.isFunction(node2); } } return false; } function isCJSRequire(node) { if (node.type === utils.AST_NODE_TYPES.VariableDeclaration) { const declaration = node.declarations[0]; if (declaration?.init) { let call = declaration?.init; while (call.type === utils.AST_NODE_TYPES.MemberExpression) call = call.object; if (call.type === utils.AST_NODE_TYPES.CallExpression && call.callee.type === utils.AST_NODE_TYPES.Identifier) { return call.callee.name === "require"; } } } return false; } function isBlockLikeStatement(node, sourceCode) { if (node.type === utils.AST_NODE_TYPES.DoWhileStatement && node.body.type === utils.AST_NODE_TYPES.BlockStatement) { return true; } if (isIIFEStatement(node)) return true; const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken) ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) : null; return !!belongingNode && (belongingNode.type === utils.AST_NODE_TYPES.BlockStatement || belongingNode.type === utils.AST_NODE_TYPES.SwitchStatement); } function isDirective(node, sourceCode) { return node.type === utils.AST_NODE_TYPES.ExpressionStatement && (node.parent?.type === utils.AST_NODE_TYPES.Program || node.parent?.type === utils.AST_NODE_TYPES.BlockStatement && astUtils.isFunction(node.parent.parent)) && node.expression.type === utils.AST_NODE_TYPES.Literal && typeof node.expression.value === "string" && !astUtils.isParenthesized(node.expression, sourceCode); } function isDirectivePrologue(node, sourceCode) { if (isDirective(node, sourceCode) && node.parent && "body" in node.parent && Array.isArray(node.parent.body)) { for (const sibling of node.parent.body) { if (sibling === node) break; if (!isDirective(sibling, sourceCode)) return false; } return true; } return false; } function isCJSExport(node) { if (node.type === utils.AST_NODE_TYPES.ExpressionStatement) { const expression = node.expression; if (expression.type === utils.AST_NODE_TYPES.AssignmentExpression) { let left = expression.left; if (left.type === utils.AST_NODE_TYPES.MemberExpression) { while (left.object.type === utils.AST_NODE_TYPES.MemberExpression) left = left.object; return left.object.type === utils.AST_NODE_TYPES.Identifier && (left.object.name === "exports" || left.object.name === "module" && left.property.type === utils.AST_NODE_TYPES.Identifier && left.property.name === "exports"); } } } return false; } function isExpression(node, sourceCode) { return node.type === utils.AST_NODE_TYPES.ExpressionStatement && !isDirectivePrologue(node, sourceCode); } function getActualLastToken(node, sourceCode) { const semiToken = sourceCode.getLastToken(node); const prevToken = sourceCode.getTokenBefore(semiToken); const nextToken = sourceCode.getTokenAfter(semiToken); const isSemicolonLessStyle = prevToken && nextToken && prevToken.range[0] >= node.range[0] && astUtils.isSemicolonToken(semiToken) && semiToken.loc.start.line !== prevToken.loc.end.line && semiToken.loc.end.line === nextToken.loc.start.line; return isSemicolonLessStyle ? prevToken : semiToken; } function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) { return trailingSpaces + indentSpaces; } function verifyForAny() { } function verifyForNever(context, _, nextNode, paddingLines) { if (paddingLines.length === 0) return; context.report({ node: nextNode, messageId: "unexpectedBlankLine", fix(fixer) { if (paddingLines.length >= 2) return null; const prevToken = paddingLines[0][0]; const nextToken = paddingLines[0][1]; const start = prevToken.range[1]; const end = nextToken.range[0]; const text = context.getSourceCode().text.slice(start, end).replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); return fixer.replaceTextRange([start, end], text); } }); } function verifyForAlways(context, prevNode, nextNode, paddingLines) { if (paddingLines.length > 0) return; context.report({ node: nextNode, messageId: "expectedBlankLine", fix(fixer) { const sourceCode = context.sourceCode; let prevToken = getActualLastToken(prevNode, sourceCode); const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, { includeComments: true, /** * Skip the trailing comments of the previous node. * This inserts a blank line after the last trailing comment. * * For example: * * foo(); // trailing comment. * // comment. * bar(); * * Get fixed to: * * foo(); // trailing comment. * * // comment. * bar(); * @param token The token to check. * @returns `true` if the token is not a trailing comment. * @private */ filter(token) { if (astUtils.isTokenOnSameLine(prevToken, token)) { prevToken = token; return false; } return true; } }) || nextNode; const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken) ? "\n\n" : "\n"; return fixer.insertTextAfter(prevToken, insertText); } }); } const PaddingTypes = { any: { verify: verifyForAny }, never: { verify: verifyForNever }, always: { verify: verifyForAlways } }; const StatementTypes = { "*": { test: () => true }, "block-like": { test: isBlockLikeStatement }, "exports": { test: isCJSExport }, "require": { test: isCJSRequire }, "directive": { test: isDirectivePrologue }, "expression": { test: isExpression }, "iife": { test: isIIFEStatement }, "multiline-block-like": { test: (node, sourceCode) => node.loc.start.line !== node.loc.end.line && isBlockLikeStatement(node, sourceCode) }, "multiline-expression": { test: (node, sourceCode) => node.loc.start.line !== node.loc.end.line && node.type === utils.AST_NODE_TYPES.ExpressionStatement && !isDirectivePrologue(node, sourceCode) }, "multiline-const": newMultilineKeywordTester("const"), "multiline-export": newMultilineKeywordTester("export"), "multiline-let": newMultilineKeywordTester("let"), "multiline-var": newMultilineKeywordTester("var"), "singleline-const": newSinglelineKeywordTester("const"), "singleline-export": newSinglelineKeywordTester("export"), "singleline-let": newSinglelineKeywordTester("let"), "singleline-var": newSinglelineKeywordTester("var"), "block": newNodeTypeTester(utils.AST_NODE_TYPES.BlockStatement), "empty": newNodeTypeTester(utils.AST_NODE_TYPES.EmptyStatement), "function": newNodeTypeTester(utils.AST_NODE_TYPES.FunctionDeclaration), "ts-method": newNodeTypeTester(utils.AST_NODE_TYPES.TSMethodSignature), "break": newKeywordTester(utils.AST_NODE_TYPES.BreakStatement, "break"), "case": newKeywordTester(utils.AST_NODE_TYPES.SwitchCase, "case"), "class": newKeywordTester(utils.AST_NODE_TYPES.ClassDeclaration, "class"), "const": newKeywordTester(utils.AST_NODE_TYPES.VariableDeclaration, "const"), "continue": newKeywordTester(utils.AST_NODE_TYPES.ContinueStatement, "continue"), "debugger": newKeywordTester(utils.AST_NODE_TYPES.DebuggerStatement, "debugger"), "default": newKeywordTester( [utils.AST_NODE_TYPES.SwitchCase, utils.AST_NODE_TYPES.ExportDefaultDeclaration], "default" ), "do": newKeywordTester(utils.AST_NODE_TYPES.DoWhileStatement, "do"), "export": newKeywordTester( [ utils.AST_NODE_TYPES.ExportAllDeclaration, utils.AST_NODE_TYPES.ExportDefaultDeclaration, utils.AST_NODE_TYPES.ExportNamedDeclaration ], "export" ), "for": newKeywordTester( [ utils.AST_NODE_TYPES.ForStatement, utils.AST_NODE_TYPES.ForInStatement, utils.AST_NODE_TYPES.ForOfStatement ], "for" ), "if": newKeywordTester(utils.AST_NODE_TYPES.IfStatement, "if"), "import": newKeywordTester(utils.AST_NODE_TYPES.ImportDeclaration, "import"), "let": newKeywordTester(utils.AST_NODE_TYPES.VariableDeclaration, "let"), "return": newKeywordTester(utils.AST_NODE_TYPES.ReturnStatement, "return"), "switch": newKeywordTester(utils.AST_NODE_TYPES.SwitchStatement, "switch"), "throw": newKeywordTester(utils.AST_NODE_TYPES.ThrowStatement, "throw"), "try": newKeywordTester(utils.AST_NODE_TYPES.TryStatement, "try"), "var": newKeywordTester(utils.AST_NODE_TYPES.VariableDeclaration, "var"), "while": newKeywordTester( [utils.AST_NODE_TYPES.WhileStatement, utils.AST_NODE_TYPES.DoWhileStatement], "while" ), "with": newKeywordTester(utils.AST_NODE_TYPES.WithStatement, "with"), "cjs-export": { test: (node, sourceCode) => node.type === "ExpressionStatement" && node.expression.type === "AssignmentExpression" && CJS_EXPORT.test(sourceCode.getText(node.expression.left)) }, "cjs-import": { test: (node, sourceCode) => node.type === "VariableDeclaration" && node.declarations.length > 0 && Boolean(node.declarations[0].init) && CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) }, // Additional Typescript constructs "enum": newKeywordTester( utils.AST_NODE_TYPES.TSEnumDeclaration, "enum" ), "interface": newKeywordTester( utils.AST_NODE_TYPES.TSInterfaceDeclaration, "interface" ), "type": newKeywordTester( utils.AST_NODE_TYPES.TSTypeAliasDeclaration, "type" ), "function-overload": { test: (node) => node.type === "TSDeclareFunction" } }; var paddingLineBetweenStatements = utils$1.createRule({ name: "padding-line-between-statements", package: "ts", meta: { type: "layout", docs: { description: "Require or disallow padding lines between statements" }, fixable: "whitespace", hasSuggestions: false, // This is intentionally an array schema as you can pass 0..n config objects schema: { $defs: { paddingType: { type: "string", enum: Object.keys(PaddingTypes) }, statementType: { anyOf: [ { type: "string", enum: Object.keys(StatementTypes) }, { type: "array", items: { type: "string", enum: Object.keys(StatementTypes) }, minItems: 1, uniqueItems: true, additionalItems: false } ] } }, type: "array", additionalItems: false, items: { type: "object", properties: { blankLine: { $ref: "#/$defs/paddingType" }, prev: { $ref: "#/$defs/statementType" }, next: { $ref: "#/$defs/statementType" } }, additionalProperties: false, required: ["blankLine", "prev", "next"] } }, messages: { unexpectedBlankLine: "Unexpected blank line before this statement.", expectedBlankLine: "Expected blank line before this statement." } }, defaultOptions: [], create(context) { const sourceCode = context.sourceCode; const configureList = context.options || []; let scopeInfo = null; function enterScope() { scopeInfo = { upper: scopeInfo, prevNode: null }; } function exitScope() { if (scopeInfo) scopeInfo = scopeInfo.upper; } function match(node, type) { let innerStatementNode = node; while (innerStatementNode.type === utils.AST_NODE_TYPES.LabeledStatement) innerStatementNode = innerStatementNode.body; if (Array.isArray(type)) return type.some(match.bind(null, innerStatementNode)); return StatementTypes[type].test(innerStatementNode, sourceCode); } function getPaddingType(prevNode, nextNode) { for (let i = configureList.length - 1; i >= 0; --i) { const configure = configureList[i]; if (match(prevNode, configure.prev) && match(nextNode, configure.next)) { return PaddingTypes[configure.blankLine]; } } return PaddingTypes.any; } function getPaddingLineSequences(prevNode, nextNode) { const pairs = []; let prevToken = getActualLastToken(prevNode, sourceCode); if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { do { const token = sourceCode.getTokenAfter(prevToken, { includeComments: true }); if (token.loc.start.line - prevToken.loc.end.line >= 2) pairs.push([prevToken, token]); prevToken = token; } while (prevToken.range[0] < nextNode.range[0]); } return pairs; } function verify(node) { if (!node.parent || ![ utils.AST_NODE_TYPES.BlockStatement, utils.AST_NODE_TYPES.Program, utils.AST_NODE_TYPES.StaticBlock, utils.AST_NODE_TYPES.SwitchCase, utils.AST_NODE_TYPES.SwitchStatement, utils.AST_NODE_TYPES.TSInterfaceBody, utils.AST_NODE_TYPES.TSModuleBlock, utils.AST_NODE_TYPES.TSTypeLiteral ].includes(node.parent.type)) { return; } const prevNode = scopeInfo.prevNode; if (prevNode) { const type = getPaddingType(prevNode, node); const paddingLines = getPaddingLineSequences(prevNode, node); type.verify(context, prevNode, node, paddingLines); } scopeInfo.prevNode = node; } function verifyThenEnterScope(node) { verify(node); enterScope(); } return { "Program": enterScope, "BlockStatement": enterScope, "SwitchStatement": enterScope, "StaticBlock": enterScope, "TSInterfaceBody": enterScope, "TSModuleBlock": enterScope, "TSTypeLiteral": enterScope, "Program:exit": exitScope, "BlockStatement:exit": exitScope, "SwitchStatement:exit": exitScope, "StaticBlock:exit": exitScope, "TSInterfaceBody:exit": exitScope, "TSModuleBlock:exit": exitScope, "TSTypeLiteral:exit": exitScope, ":statement": verify, "SwitchCase": verifyThenEnterScope, "TSDeclareFunction": verifyThenEnterScope, "TSMethodSignature": verifyThenEnterScope, "SwitchCase:exit": exitScope, "TSDeclareFunction:exit": exitScope, "TSMethodSignature:exit": exitScope }; } }); module.exports = paddingLineBetweenStatements;