Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Skipped
388 lines
15 KiB
Plaintext
388 lines
15 KiB
Plaintext
import { K as isSingleLine, Y as isTopLevelExpressionStatement, Z as skipChainExpression, f as createRule, g as import_ast_utils, m as AST_NODE_TYPES, x as LINEBREAKS } from "../utils.js";
|
|
const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
|
|
const CJS_IMPORT = /^require\(/u;
|
|
const LT = `[${Array.from(LINEBREAKS).join("")}]`;
|
|
const PADDING_LINE_SEQUENCE = new RegExp(String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, "u");
|
|
function isSelectorOption(option) {
|
|
return typeof option === "object" && !Array.isArray(option);
|
|
}
|
|
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 newNodeTypeTester(type) {
|
|
return { test: (node) => node.type === type };
|
|
}
|
|
function isIIFEStatement(node) {
|
|
if (node.type === AST_NODE_TYPES.ExpressionStatement) {
|
|
let expression = skipChainExpression(node.expression);
|
|
if (expression.type === AST_NODE_TYPES.UnaryExpression) expression = skipChainExpression(expression.argument);
|
|
if (expression.type === AST_NODE_TYPES.CallExpression) {
|
|
let node = expression.callee;
|
|
while (node.type === AST_NODE_TYPES.SequenceExpression) node = node.expressions[node.expressions.length - 1];
|
|
return (0, import_ast_utils.isFunction)(node);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function isCJSRequire(node) {
|
|
if (node.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
const declaration = node.declarations[0];
|
|
if (declaration?.init) {
|
|
let call = declaration?.init;
|
|
while (call.type === AST_NODE_TYPES.MemberExpression) call = call.object;
|
|
if (call.type === AST_NODE_TYPES.CallExpression && call.callee.type === AST_NODE_TYPES.Identifier) return call.callee.name === "require";
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function isBlockLikeStatement(node, sourceCode) {
|
|
if (node.type === AST_NODE_TYPES.DoWhileStatement && node.body.type === AST_NODE_TYPES.BlockStatement) return true;
|
|
if (isIIFEStatement(node)) return true;
|
|
const lastToken = sourceCode.getLastToken(node, import_ast_utils.isNotSemicolonToken);
|
|
const belongingNode = lastToken && (0, import_ast_utils.isClosingBraceToken)(lastToken) ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) : null;
|
|
return !!belongingNode && (belongingNode.type === AST_NODE_TYPES.BlockStatement || belongingNode.type === AST_NODE_TYPES.SwitchStatement);
|
|
}
|
|
function isDirective(node, sourceCode) {
|
|
return isTopLevelExpressionStatement(node) && node.expression.type === AST_NODE_TYPES.Literal && typeof node.expression.value === "string" && !(0, import_ast_utils.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 === AST_NODE_TYPES.ExpressionStatement) {
|
|
const expression = node.expression;
|
|
if (expression.type === AST_NODE_TYPES.AssignmentExpression) {
|
|
let left = expression.left;
|
|
if (left.type === AST_NODE_TYPES.MemberExpression) {
|
|
while (left.object.type === AST_NODE_TYPES.MemberExpression) left = left.object;
|
|
return left.object.type === AST_NODE_TYPES.Identifier && (left.object.name === "exports" || left.object.name === "module" && left.property.type === AST_NODE_TYPES.Identifier && left.property.name === "exports");
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function isExpression(node, sourceCode) {
|
|
return node.type === 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);
|
|
return prevToken && nextToken && prevToken.range[0] >= node.range[0] && (0, import_ast_utils.isSemicolonToken)(semiToken) && !(0, import_ast_utils.isTokenOnSameLine)(prevToken, semiToken) && (0, import_ast_utils.isTokenOnSameLine)(semiToken, nextToken) ? prevToken : semiToken;
|
|
}
|
|
function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
|
|
return trailingSpaces + indentSpaces;
|
|
}
|
|
function getReportLoc(node, sourceCode) {
|
|
if (isSingleLine(node)) return node.loc;
|
|
const line = node.loc.start.line;
|
|
return {
|
|
start: node.loc.start,
|
|
end: {
|
|
line,
|
|
column: sourceCode.lines[line - 1].length
|
|
}
|
|
};
|
|
}
|
|
function verifyForAny() {}
|
|
function verifyForNever(context, _, nextNode, paddingLines) {
|
|
if (paddingLines.length === 0) return;
|
|
context.report({
|
|
node: nextNode,
|
|
messageId: "unexpectedBlankLine",
|
|
loc: getReportLoc(nextNode, context.sourceCode),
|
|
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.sourceCode.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",
|
|
loc: getReportLoc(nextNode, context.sourceCode),
|
|
fix(fixer) {
|
|
const sourceCode = context.sourceCode;
|
|
let prevToken = getActualLastToken(prevNode, sourceCode);
|
|
const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, {
|
|
includeComments: true,
|
|
filter(token) {
|
|
if ((0, import_ast_utils.isTokenOnSameLine)(prevToken, token)) {
|
|
prevToken = token;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}) || nextNode;
|
|
const insertText = (0, import_ast_utils.isTokenOnSameLine)(prevToken, nextToken) ? "\n\n" : "\n";
|
|
return fixer.insertTextAfter(prevToken, insertText);
|
|
}
|
|
});
|
|
}
|
|
const PaddingTypes = {
|
|
any: { verify: verifyForAny },
|
|
never: { verify: verifyForNever },
|
|
always: { verify: verifyForAlways }
|
|
};
|
|
const MaybeMultilineStatementType = {
|
|
"block-like": { test: isBlockLikeStatement },
|
|
"expression": { test: isExpression },
|
|
"return": newKeywordTester(AST_NODE_TYPES.ReturnStatement, "return"),
|
|
"export": newKeywordTester([
|
|
AST_NODE_TYPES.ExportAllDeclaration,
|
|
AST_NODE_TYPES.ExportDefaultDeclaration,
|
|
AST_NODE_TYPES.ExportNamedDeclaration
|
|
], "export"),
|
|
"var": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "var"),
|
|
"let": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "let"),
|
|
"const": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "const"),
|
|
"using": { test: (node) => node.type === "VariableDeclaration" && (node.kind === "using" || node.kind === "await using") },
|
|
"type": newKeywordTester(AST_NODE_TYPES.TSTypeAliasDeclaration, "type")
|
|
};
|
|
const StatementTypes = {
|
|
"*": { test: () => true },
|
|
"exports": { test: isCJSExport },
|
|
"require": { test: isCJSRequire },
|
|
"directive": { test: isDirectivePrologue },
|
|
"iife": { test: isIIFEStatement },
|
|
"block": newNodeTypeTester(AST_NODE_TYPES.BlockStatement),
|
|
"empty": newNodeTypeTester(AST_NODE_TYPES.EmptyStatement),
|
|
"function": newNodeTypeTester(AST_NODE_TYPES.FunctionDeclaration),
|
|
"ts-method": newNodeTypeTester(AST_NODE_TYPES.TSMethodSignature),
|
|
"break": newKeywordTester(AST_NODE_TYPES.BreakStatement, "break"),
|
|
"case": newKeywordTester(AST_NODE_TYPES.SwitchCase, "case"),
|
|
"class": newKeywordTester(AST_NODE_TYPES.ClassDeclaration, "class"),
|
|
"continue": newKeywordTester(AST_NODE_TYPES.ContinueStatement, "continue"),
|
|
"debugger": newKeywordTester(AST_NODE_TYPES.DebuggerStatement, "debugger"),
|
|
"default": newKeywordTester([AST_NODE_TYPES.SwitchCase, AST_NODE_TYPES.ExportDefaultDeclaration], "default"),
|
|
"do": newKeywordTester(AST_NODE_TYPES.DoWhileStatement, "do"),
|
|
"for": newKeywordTester([
|
|
AST_NODE_TYPES.ForStatement,
|
|
AST_NODE_TYPES.ForInStatement,
|
|
AST_NODE_TYPES.ForOfStatement
|
|
], "for"),
|
|
"if": newKeywordTester(AST_NODE_TYPES.IfStatement, "if"),
|
|
"import": newKeywordTester(AST_NODE_TYPES.ImportDeclaration, "import"),
|
|
"switch": newKeywordTester(AST_NODE_TYPES.SwitchStatement, "switch"),
|
|
"throw": newKeywordTester(AST_NODE_TYPES.ThrowStatement, "throw"),
|
|
"try": newKeywordTester(AST_NODE_TYPES.TryStatement, "try"),
|
|
"while": newKeywordTester([AST_NODE_TYPES.WhileStatement, AST_NODE_TYPES.DoWhileStatement], "while"),
|
|
"with": newKeywordTester(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)) },
|
|
"enum": newKeywordTester(AST_NODE_TYPES.TSEnumDeclaration, "enum"),
|
|
"interface": newKeywordTester(AST_NODE_TYPES.TSInterfaceDeclaration, "interface"),
|
|
"function-overload": newNodeTypeTester(AST_NODE_TYPES.TSDeclareFunction),
|
|
...Object.fromEntries(Object.entries(MaybeMultilineStatementType).flatMap(([key, value]) => [
|
|
[key, value],
|
|
[`singleline-${key}`, {
|
|
...value,
|
|
test: (node, sourceCode) => value.test(node, sourceCode) && isSingleLine(node)
|
|
}],
|
|
[`multiline-${key}`, {
|
|
...value,
|
|
test: (node, sourceCode) => value.test(node, sourceCode) && !isSingleLine(node)
|
|
}]
|
|
]))
|
|
};
|
|
var padding_line_between_statements_default = createRule({
|
|
name: "padding-line-between-statements",
|
|
meta: {
|
|
type: "layout",
|
|
docs: { description: "Require or disallow padding lines between statements" },
|
|
fixable: "whitespace",
|
|
hasSuggestions: false,
|
|
schema: {
|
|
$defs: {
|
|
paddingType: {
|
|
type: "string",
|
|
enum: Object.keys(PaddingTypes)
|
|
},
|
|
statementType: {
|
|
type: "string",
|
|
enum: Object.keys(StatementTypes)
|
|
},
|
|
selectorOption: {
|
|
type: "object",
|
|
properties: {
|
|
selector: { type: "string" },
|
|
lineMode: {
|
|
type: "string",
|
|
enum: [
|
|
"any",
|
|
"singleline",
|
|
"multiline"
|
|
]
|
|
}
|
|
},
|
|
required: ["selector"],
|
|
additionalProperties: false
|
|
},
|
|
statementMatcher: { anyOf: [{ $ref: "#/$defs/statementType" }, { $ref: "#/$defs/selectorOption" }] },
|
|
statementOption: { anyOf: [{ $ref: "#/$defs/statementMatcher" }, {
|
|
type: "array",
|
|
items: { $ref: "#/$defs/statementMatcher" },
|
|
minItems: 1,
|
|
uniqueItems: true,
|
|
additionalItems: false
|
|
}] }
|
|
},
|
|
type: "array",
|
|
additionalItems: false,
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
blankLine: { $ref: "#/$defs/paddingType" },
|
|
prev: { $ref: "#/$defs/statementOption" },
|
|
next: { $ref: "#/$defs/statementOption" }
|
|
},
|
|
additionalProperties: false,
|
|
required: [
|
|
"blankLine",
|
|
"prev",
|
|
"next"
|
|
]
|
|
}
|
|
},
|
|
defaultOptions: [],
|
|
messages: {
|
|
unexpectedBlankLine: "Unexpected blank line before this statement.",
|
|
expectedBlankLine: "Expected blank line before this statement."
|
|
}
|
|
},
|
|
create(context, options) {
|
|
const sourceCode = context.sourceCode;
|
|
const selectorMatchedNodes = /* @__PURE__ */ new Map();
|
|
const pendingPairs = [];
|
|
function collectSelectorOption(option) {
|
|
if (Array.isArray(option)) {
|
|
for (const item of option) collectSelectorOption(item);
|
|
return;
|
|
}
|
|
if (!isSelectorOption(option)) return;
|
|
selectorMatchedNodes.set(option.selector, /* @__PURE__ */ new Set());
|
|
}
|
|
for (const configure of options) {
|
|
collectSelectorOption(configure.prev);
|
|
collectSelectorOption(configure.next);
|
|
}
|
|
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 === AST_NODE_TYPES.LabeledStatement) innerStatementNode = innerStatementNode.body;
|
|
if (Array.isArray(type)) return type.some(match.bind(null, innerStatementNode));
|
|
if (isSelectorOption(type)) {
|
|
if (!selectorMatchedNodes.get(type.selector)?.has(innerStatementNode)) return false;
|
|
const lineMode = type.lineMode;
|
|
if (lineMode === "singleline") return isSingleLine(innerStatementNode);
|
|
else if (lineMode === "multiline") return !isSingleLine(innerStatementNode);
|
|
return true;
|
|
} else return StatementTypes[type].test(innerStatementNode, sourceCode);
|
|
}
|
|
function getPaddingType(prevNode, nextNode) {
|
|
for (let i = options.length - 1; i >= 0; --i) {
|
|
const configure = options[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 || ![
|
|
AST_NODE_TYPES.BlockStatement,
|
|
AST_NODE_TYPES.Program,
|
|
AST_NODE_TYPES.StaticBlock,
|
|
AST_NODE_TYPES.SwitchCase,
|
|
AST_NODE_TYPES.SwitchStatement,
|
|
AST_NODE_TYPES.TSInterfaceBody,
|
|
AST_NODE_TYPES.TSModuleBlock,
|
|
AST_NODE_TYPES.TSTypeLiteral
|
|
].includes(node.parent.type)) return;
|
|
const prevNode = scopeInfo.prevNode;
|
|
if (prevNode) pendingPairs.push({
|
|
prevNode,
|
|
nextNode: node
|
|
});
|
|
scopeInfo.prevNode = node;
|
|
}
|
|
function verifyPendingPairs() {
|
|
for (const { prevNode, nextNode } of pendingPairs) {
|
|
const type = getPaddingType(prevNode, nextNode);
|
|
const paddingLines = getPaddingLineSequences(prevNode, nextNode);
|
|
type.verify(context, prevNode, nextNode, paddingLines);
|
|
}
|
|
}
|
|
function verifyThenEnterScope(node) {
|
|
verify(node);
|
|
enterScope();
|
|
}
|
|
const selectorMatchListeners = Object.fromEntries(Array.from(selectorMatchedNodes.keys(), (selector) => [selector, (node) => {
|
|
selectorMatchedNodes.get(selector)?.add(node);
|
|
}]));
|
|
return {
|
|
"Program": enterScope,
|
|
"Program:exit": () => {
|
|
verifyPendingPairs();
|
|
exitScope();
|
|
},
|
|
"BlockStatement": enterScope,
|
|
"BlockStatement:exit": exitScope,
|
|
"SwitchStatement": enterScope,
|
|
"SwitchStatement:exit": exitScope,
|
|
"SwitchCase": verifyThenEnterScope,
|
|
"SwitchCase:exit": exitScope,
|
|
"StaticBlock": enterScope,
|
|
"StaticBlock:exit": exitScope,
|
|
"TSInterfaceBody": enterScope,
|
|
"TSInterfaceBody:exit": exitScope,
|
|
"TSModuleBlock": enterScope,
|
|
"TSModuleBlock:exit": exitScope,
|
|
"TSTypeLiteral": enterScope,
|
|
"TSTypeLiteral:exit": exitScope,
|
|
"TSDeclareFunction": verifyThenEnterScope,
|
|
"TSDeclareFunction:exit": exitScope,
|
|
"TSMethodSignature": verifyThenEnterScope,
|
|
"TSMethodSignature:exit": exitScope,
|
|
":statement": verify,
|
|
...selectorMatchListeners
|
|
};
|
|
}
|
|
});
|
|
export { padding_line_between_statements_default as t };
|