123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- "use strict";
- const astUtils = require("./utils/ast-utils");
- const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`;
- const PADDING_LINE_SEQUENCE = new RegExp(
- String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`,
- "u"
- );
- const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
- const CJS_IMPORT = /^require\(/u;
- function newKeywordTester(keyword) {
- return {
- test: (node, sourceCode) =>
- sourceCode.getFirstToken(node).value === keyword
- };
- }
- function newSinglelineKeywordTester(keyword) {
- return {
- test: (node, sourceCode) =>
- node.loc.start.line === node.loc.end.line &&
- sourceCode.getFirstToken(node).value === keyword
- };
- }
- function newMultilineKeywordTester(keyword) {
- return {
- test: (node, sourceCode) =>
- node.loc.start.line !== node.loc.end.line &&
- sourceCode.getFirstToken(node).value === keyword
- };
- }
- function newNodeTypeTester(type) {
- return {
- test: node =>
- node.type === type
- };
- }
- function isIIFEStatement(node) {
- if (node.type === "ExpressionStatement") {
- let call = node.expression;
- if (call.type === "UnaryExpression") {
- call = call.argument;
- }
- return call.type === "CallExpression" && astUtils.isFunction(call.callee);
- }
- return false;
- }
- function isBlockLikeStatement(sourceCode, node) {
-
- if (node.type === "DoWhileStatement" && node.body.type === "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 Boolean(belongingNode) && (
- belongingNode.type === "BlockStatement" ||
- belongingNode.type === "SwitchStatement"
- );
- }
- function isDirective(node, sourceCode) {
- return (
- node.type === "ExpressionStatement" &&
- (
- node.parent.type === "Program" ||
- (
- node.parent.type === "BlockStatement" &&
- astUtils.isFunction(node.parent.parent)
- )
- ) &&
- node.expression.type === "Literal" &&
- typeof node.expression.value === "string" &&
- !astUtils.isParenthesised(sourceCode, node.expression)
- );
- }
- function isDirectivePrologue(node, sourceCode) {
- if (isDirective(node, sourceCode)) {
- for (const sibling of node.parent.body) {
- if (sibling === node) {
- break;
- }
- if (!isDirective(sibling, sourceCode)) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
- function getActualLastToken(sourceCode, node) {
- const semiToken = sourceCode.getLastToken(node);
- const prevToken = sourceCode.getTokenBefore(semiToken);
- const nextToken = sourceCode.getTokenAfter(semiToken);
- const isSemicolonLessStyle = Boolean(
- 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,
- message: "Unexpected blank line before this statement.",
- 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,
- message: "Expected blank line before this statement.",
- fix(fixer) {
- const sourceCode = context.getSourceCode();
- let prevToken = getActualLastToken(sourceCode, prevNode);
- const nextToken = sourceCode.getFirstTokenBetween(
- prevToken,
- nextNode,
- {
- includeComments: true,
-
- 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: (node, sourceCode) => isBlockLikeStatement(sourceCode, node)
- },
- "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))
- },
- directive: {
- test: isDirectivePrologue
- },
- expression: {
- test: (node, sourceCode) =>
- node.type === "ExpressionStatement" &&
- !isDirectivePrologue(node, sourceCode)
- },
- iife: {
- test: isIIFEStatement
- },
- "multiline-block-like": {
- test: (node, sourceCode) =>
- node.loc.start.line !== node.loc.end.line &&
- isBlockLikeStatement(sourceCode, node)
- },
- "multiline-expression": {
- test: (node, sourceCode) =>
- node.loc.start.line !== node.loc.end.line &&
- node.type === "ExpressionStatement" &&
- !isDirectivePrologue(node, sourceCode)
- },
- "multiline-const": newMultilineKeywordTester("const"),
- "multiline-let": newMultilineKeywordTester("let"),
- "multiline-var": newMultilineKeywordTester("var"),
- "singleline-const": newSinglelineKeywordTester("const"),
- "singleline-let": newSinglelineKeywordTester("let"),
- "singleline-var": newSinglelineKeywordTester("var"),
- block: newNodeTypeTester("BlockStatement"),
- empty: newNodeTypeTester("EmptyStatement"),
- function: newNodeTypeTester("FunctionDeclaration"),
- break: newKeywordTester("break"),
- case: newKeywordTester("case"),
- class: newKeywordTester("class"),
- const: newKeywordTester("const"),
- continue: newKeywordTester("continue"),
- debugger: newKeywordTester("debugger"),
- default: newKeywordTester("default"),
- do: newKeywordTester("do"),
- export: newKeywordTester("export"),
- for: newKeywordTester("for"),
- if: newKeywordTester("if"),
- import: newKeywordTester("import"),
- let: newKeywordTester("let"),
- return: newKeywordTester("return"),
- switch: newKeywordTester("switch"),
- throw: newKeywordTester("throw"),
- try: newKeywordTester("try"),
- var: newKeywordTester("var"),
- while: newKeywordTester("while"),
- with: newKeywordTester("with")
- };
- //------------------------------------------------------------------------------
- // Rule Definition
- //------------------------------------------------------------------------------
- module.exports = {
- meta: {
- type: "layout",
- docs: {
- description: "require or disallow padding lines between statements",
- category: "Stylistic Issues",
- recommended: false,
- url: "https://eslint.org/docs/rules/padding-line-between-statements"
- },
- fixable: "whitespace",
- schema: {
- definitions: {
- paddingType: {
- enum: Object.keys(PaddingTypes)
- },
- statementType: {
- anyOf: [
- { enum: Object.keys(StatementTypes) },
- {
- type: "array",
- items: { enum: Object.keys(StatementTypes) },
- minItems: 1,
- uniqueItems: true,
- additionalItems: false
- }
- ]
- }
- },
- type: "array",
- items: {
- type: "object",
- properties: {
- blankLine: { $ref: "#/definitions/paddingType" },
- prev: { $ref: "#/definitions/statementType" },
- next: { $ref: "#/definitions/statementType" }
- },
- additionalProperties: false,
- required: ["blankLine", "prev", "next"]
- },
- additionalItems: false
- }
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- const configureList = context.options || [];
- let scopeInfo = null;
-
- function enterScope() {
- scopeInfo = {
- upper: scopeInfo,
- prevNode: null
- };
- }
-
- function exitScope() {
- scopeInfo = scopeInfo.upper;
- }
-
- function match(node, type) {
- let innerStatementNode = node;
- while (innerStatementNode.type === "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];
- const matched =
- match(prevNode, configure.prev) &&
- match(nextNode, configure.next);
- if (matched) {
- return PaddingTypes[configure.blankLine];
- }
- }
- return PaddingTypes.any;
- }
-
- function getPaddingLineSequences(prevNode, nextNode) {
- const pairs = [];
- let prevToken = getActualLastToken(sourceCode, prevNode);
- 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) {
- const parentType = node.parent.type;
- const validParent =
- astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
- parentType === "SwitchStatement";
- if (!validParent) {
- 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,
- "Program:exit": exitScope,
- "BlockStatement:exit": exitScope,
- "SwitchStatement:exit": exitScope,
- ":statement": verify,
- SwitchCase: verifyThenEnterScope,
- "SwitchCase:exit": exitScope
- };
- }
- };
|