blob: e9212001a341564dcf61878ea3efbd9d279a88fe [file] [log] [blame]
/**
* @fileoverview Specify the maximum number of statements allowed per line.
* @author Kenneth Williams
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce a maximum number of statements allowed per line",
category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-statements-per-line"
},
schema: [
{
type: "object",
properties: {
max: {
type: "integer",
minimum: 1,
default: 1
}
},
additionalProperties: false
}
],
messages: {
exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}."
}
},
create(context) {
const sourceCode = context.getSourceCode(),
options = context.options[0] || {},
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
let lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
firstExtraStatement;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u;
/**
* Reports with the first extra statement, and clears it.
*
* @returns {void}
*/
function reportFirstExtraStatementAndClear() {
if (firstExtraStatement) {
context.report({
node: firstExtraStatement,
messageId: "exceed",
data: {
numberOfStatementsOnThisLine,
maxStatementsPerLine,
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
}
});
}
firstExtraStatement = null;
}
/**
* Gets the actual last token of a given node.
*
* @param {ASTNode} node - A node to get. This is a node except EmptyStatement.
* @returns {Token} The actual last token.
*/
function getActualLastToken(node) {
return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
}
/**
* Addresses a given node.
* It updates the state of this rule, then reports the node if the node violated this rule.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
*/
function enterStatement(node) {
const line = node.loc.start.line;
/*
* Skip to allow non-block statements if this is direct child of control statements.
* `if (a) foo();` is counted as 1.
* But `if (a) foo(); else foo();` should be counted as 2.
*/
if (SINGLE_CHILD_ALLOWED.test(node.parent.type) &&
node.parent.alternate !== node
) {
return;
}
// Update state.
if (line === lastStatementLine) {
numberOfStatementsOnThisLine += 1;
} else {
reportFirstExtraStatementAndClear();
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
// Reports if the node violated this rule.
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
firstExtraStatement = firstExtraStatement || node;
}
}
/**
* Updates the state of this rule with the end line of leaving node to check with the next statement.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
*/
function leaveStatement(node) {
const line = getActualLastToken(node).loc.end.line;
// Update state.
if (line !== lastStatementLine) {
reportFirstExtraStatementAndClear();
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
BreakStatement: enterStatement,
ClassDeclaration: enterStatement,
ContinueStatement: enterStatement,
DebuggerStatement: enterStatement,
DoWhileStatement: enterStatement,
ExpressionStatement: enterStatement,
ForInStatement: enterStatement,
ForOfStatement: enterStatement,
ForStatement: enterStatement,
FunctionDeclaration: enterStatement,
IfStatement: enterStatement,
ImportDeclaration: enterStatement,
LabeledStatement: enterStatement,
ReturnStatement: enterStatement,
SwitchStatement: enterStatement,
ThrowStatement: enterStatement,
TryStatement: enterStatement,
VariableDeclaration: enterStatement,
WhileStatement: enterStatement,
WithStatement: enterStatement,
ExportNamedDeclaration: enterStatement,
ExportDefaultDeclaration: enterStatement,
ExportAllDeclaration: enterStatement,
"BreakStatement:exit": leaveStatement,
"ClassDeclaration:exit": leaveStatement,
"ContinueStatement:exit": leaveStatement,
"DebuggerStatement:exit": leaveStatement,
"DoWhileStatement:exit": leaveStatement,
"ExpressionStatement:exit": leaveStatement,
"ForInStatement:exit": leaveStatement,
"ForOfStatement:exit": leaveStatement,
"ForStatement:exit": leaveStatement,
"FunctionDeclaration:exit": leaveStatement,
"IfStatement:exit": leaveStatement,
"ImportDeclaration:exit": leaveStatement,
"LabeledStatement:exit": leaveStatement,
"ReturnStatement:exit": leaveStatement,
"SwitchStatement:exit": leaveStatement,
"ThrowStatement:exit": leaveStatement,
"TryStatement:exit": leaveStatement,
"VariableDeclaration:exit": leaveStatement,
"WhileStatement:exit": leaveStatement,
"WithStatement:exit": leaveStatement,
"ExportNamedDeclaration:exit": leaveStatement,
"ExportDefaultDeclaration:exit": leaveStatement,
"ExportAllDeclaration:exit": leaveStatement,
"Program:exit": reportFirstExtraStatementAndClear
};
}
};