first commit
This commit is contained in:
632
app_vue/node_modules/eslint/lib/rules/padding-line-between-statements.js
generated
vendored
Normal file
632
app_vue/node_modules/eslint/lib/rules/padding-line-between-statements.js
generated
vendored
Normal file
@ -0,0 +1,632 @@
|
||||
/**
|
||||
* @fileoverview Rule to require or disallow newlines between statements
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node starts with specific keyword.
|
||||
* @param {string} keyword The keyword to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newKeywordTester(keyword) {
|
||||
return {
|
||||
test: (node, sourceCode) =>
|
||||
sourceCode.getFirstToken(node).value === keyword
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node starts with specific keyword and spans a single line.
|
||||
* @param {string} keyword The keyword to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newSinglelineKeywordTester(keyword) {
|
||||
return {
|
||||
test: (node, sourceCode) =>
|
||||
node.loc.start.line === node.loc.end.line &&
|
||||
sourceCode.getFirstToken(node).value === keyword
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node starts with specific keyword and spans multiple lines.
|
||||
* @param {string} keyword The keyword to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newMultilineKeywordTester(keyword) {
|
||||
return {
|
||||
test: (node, sourceCode) =>
|
||||
node.loc.start.line !== node.loc.end.line &&
|
||||
sourceCode.getFirstToken(node).value === keyword
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tester which check if a node is specific type.
|
||||
* @param {string} type The node type to test.
|
||||
* @returns {Object} the created tester.
|
||||
* @private
|
||||
*/
|
||||
function newNodeTypeTester(type) {
|
||||
return {
|
||||
test: node =>
|
||||
node.type === type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given node is an expression statement of IIFE.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} `true` if the node is an expression statement of IIFE.
|
||||
* @private
|
||||
*/
|
||||
function isIIFEStatement(node) {
|
||||
if (node.type === "ExpressionStatement") {
|
||||
let call = astUtils.skipChainExpression(node.expression);
|
||||
|
||||
if (call.type === "UnaryExpression") {
|
||||
call = astUtils.skipChainExpression(call.argument);
|
||||
}
|
||||
return call.type === "CallExpression" && astUtils.isFunction(call.callee);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given node is a block-like statement.
|
||||
* This checks the last token of the node is the closing brace of a block.
|
||||
* @param {SourceCode} sourceCode The source code to get tokens.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} `true` if the node is a block-like statement.
|
||||
* @private
|
||||
*/
|
||||
function isBlockLikeStatement(sourceCode, node) {
|
||||
|
||||
// do-while with a block is a block-like statement.
|
||||
if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* IIFE is a block-like statement specially from
|
||||
* JSCS#disallowPaddingNewLinesAfterBlocks.
|
||||
*/
|
||||
if (isIIFEStatement(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks the last token is a closing brace of blocks.
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given node is a directive or not.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @param {SourceCode} sourceCode The source code object to get tokens.
|
||||
* @returns {boolean} `true` if the node is a directive.
|
||||
*/
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given node is a part of directive prologue or not.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @param {SourceCode} sourceCode The source code object to get tokens.
|
||||
* @returns {boolean} `true` if the node is a part of directive prologue.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actual last token.
|
||||
*
|
||||
* If a semicolon is semicolon-less style's semicolon, this ignores it.
|
||||
* For example:
|
||||
*
|
||||
* foo()
|
||||
* ;[1, 2, 3].forEach(bar)
|
||||
* @param {SourceCode} sourceCode The source code to get tokens.
|
||||
* @param {ASTNode} node The node to get.
|
||||
* @returns {Token} The actual last token.
|
||||
* @private
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the concatenation of the first 2 captured strings.
|
||||
* @param {string} _ Unused. Whole matched string.
|
||||
* @param {string} trailingSpaces The trailing spaces of the first line.
|
||||
* @param {string} indentSpaces The indentation spaces of the last line.
|
||||
* @returns {string} The concatenation of trailingSpaces and indentSpaces.
|
||||
* @private
|
||||
*/
|
||||
function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
|
||||
return trailingSpaces + indentSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `any` configuration.
|
||||
* It does nothing.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForAny() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `never` configuration.
|
||||
* This autofix removes blank lines between the given 2 statements.
|
||||
* However, if comments exist between 2 blank lines, it does not remove those
|
||||
* blank lines automatically.
|
||||
* @param {RuleContext} context The rule context to report.
|
||||
* @param {ASTNode} _ Unused. The previous node to check.
|
||||
* @param {ASTNode} nextNode The next node to check.
|
||||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
||||
* lines exist between the pair.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and report statements for `always` configuration.
|
||||
* This autofix inserts a blank line between the given 2 statements.
|
||||
* If the `prevNode` has trailing comments, it inserts a blank line after the
|
||||
* trailing comments.
|
||||
* @param {RuleContext} context The rule context to report.
|
||||
* @param {ASTNode} prevNode The previous node to check.
|
||||
* @param {ASTNode} nextNode The next node to check.
|
||||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
||||
* lines exist between the pair.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForAlways(context, prevNode, nextNode, paddingLines) {
|
||||
if (paddingLines.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: nextNode,
|
||||
messageId: "expectedBlankLine",
|
||||
fix(fixer) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
let prevToken = getActualLastToken(sourceCode, prevNode);
|
||||
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} token The token to check.
|
||||
* @returns {boolean} `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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of blank lines.
|
||||
* `any`, `never`, and `always` are defined.
|
||||
* Those have `verify` method to check and report statements.
|
||||
* @private
|
||||
*/
|
||||
const PaddingTypes = {
|
||||
any: { verify: verifyForAny },
|
||||
never: { verify: verifyForNever },
|
||||
always: { verify: verifyForAlways }
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of statements.
|
||||
* Those have `test` method to check it matches to the given statement.
|
||||
* @private
|
||||
*/
|
||||
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
|
||||
},
|
||||
|
||||
messages: {
|
||||
unexpectedBlankLine: "Unexpected blank line before this statement.",
|
||||
expectedBlankLine: "Expected blank line before this statement."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
const configureList = context.options || [];
|
||||
let scopeInfo = null;
|
||||
|
||||
/**
|
||||
* Processes to enter to new scope.
|
||||
* This manages the current previous statement.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function enterScope() {
|
||||
scopeInfo = {
|
||||
upper: scopeInfo,
|
||||
prevNode: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes to exit from the current scope.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function exitScope() {
|
||||
scopeInfo = scopeInfo.upper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given node matches the given type.
|
||||
* @param {ASTNode} node The statement node to check.
|
||||
* @param {string|string[]} type The statement type to check.
|
||||
* @returns {boolean} `true` if the statement node matched the type.
|
||||
* @private
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the last matched configure from configureList.
|
||||
* @param {ASTNode} prevNode The previous statement to match.
|
||||
* @param {ASTNode} nextNode The current statement to match.
|
||||
* @returns {Object} The tester of the last matched configure.
|
||||
* @private
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets padding line sequences between the given 2 statements.
|
||||
* Comments are separators of the padding line sequences.
|
||||
* @param {ASTNode} prevNode The previous statement to count.
|
||||
* @param {ASTNode} nextNode The current statement to count.
|
||||
* @returns {Array<Token[]>} The array of token pairs.
|
||||
* @private
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify padding lines between the given node and the previous node.
|
||||
* @param {ASTNode} node The node to verify.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verify(node) {
|
||||
const parentType = node.parent.type;
|
||||
const validParent =
|
||||
astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
|
||||
parentType === "SwitchStatement";
|
||||
|
||||
if (!validParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save this node as the current previous statement.
|
||||
const prevNode = scopeInfo.prevNode;
|
||||
|
||||
// Verify.
|
||||
if (prevNode) {
|
||||
const type = getPaddingType(prevNode, node);
|
||||
const paddingLines = getPaddingLineSequences(prevNode, node);
|
||||
|
||||
type.verify(context, prevNode, node, paddingLines);
|
||||
}
|
||||
|
||||
scopeInfo.prevNode = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify padding lines between the given node and the previous node.
|
||||
* Then process to enter to new scope.
|
||||
* @param {ASTNode} node The node to verify.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
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
|
||||
};
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user