• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Specify the maximum number of statements allowed per line.
3 * @author Kenneth Williams
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = {
18    meta: {
19        type: "layout",
20
21        docs: {
22            description: "enforce a maximum number of statements allowed per line",
23            category: "Stylistic Issues",
24            recommended: false,
25            url: "https://eslint.org/docs/rules/max-statements-per-line"
26        },
27
28        schema: [
29            {
30                type: "object",
31                properties: {
32                    max: {
33                        type: "integer",
34                        minimum: 1,
35                        default: 1
36                    }
37                },
38                additionalProperties: false
39            }
40        ],
41        messages: {
42            exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}."
43        }
44    },
45
46    create(context) {
47
48        const sourceCode = context.getSourceCode(),
49            options = context.options[0] || {},
50            maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
51
52        let lastStatementLine = 0,
53            numberOfStatementsOnThisLine = 0,
54            firstExtraStatement;
55
56        //--------------------------------------------------------------------------
57        // Helpers
58        //--------------------------------------------------------------------------
59
60        const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u;
61
62        /**
63         * Reports with the first extra statement, and clears it.
64         * @returns {void}
65         */
66        function reportFirstExtraStatementAndClear() {
67            if (firstExtraStatement) {
68                context.report({
69                    node: firstExtraStatement,
70                    messageId: "exceed",
71                    data: {
72                        numberOfStatementsOnThisLine,
73                        maxStatementsPerLine,
74                        statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
75                    }
76                });
77            }
78            firstExtraStatement = null;
79        }
80
81        /**
82         * Gets the actual last token of a given node.
83         * @param {ASTNode} node A node to get. This is a node except EmptyStatement.
84         * @returns {Token} The actual last token.
85         */
86        function getActualLastToken(node) {
87            return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
88        }
89
90        /**
91         * Addresses a given node.
92         * It updates the state of this rule, then reports the node if the node violated this rule.
93         * @param {ASTNode} node A node to check.
94         * @returns {void}
95         */
96        function enterStatement(node) {
97            const line = node.loc.start.line;
98
99            /*
100             * Skip to allow non-block statements if this is direct child of control statements.
101             * `if (a) foo();` is counted as 1.
102             * But `if (a) foo(); else foo();` should be counted as 2.
103             */
104            if (SINGLE_CHILD_ALLOWED.test(node.parent.type) &&
105                node.parent.alternate !== node
106            ) {
107                return;
108            }
109
110            // Update state.
111            if (line === lastStatementLine) {
112                numberOfStatementsOnThisLine += 1;
113            } else {
114                reportFirstExtraStatementAndClear();
115                numberOfStatementsOnThisLine = 1;
116                lastStatementLine = line;
117            }
118
119            // Reports if the node violated this rule.
120            if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
121                firstExtraStatement = firstExtraStatement || node;
122            }
123        }
124
125        /**
126         * Updates the state of this rule with the end line of leaving node to check with the next statement.
127         * @param {ASTNode} node A node to check.
128         * @returns {void}
129         */
130        function leaveStatement(node) {
131            const line = getActualLastToken(node).loc.end.line;
132
133            // Update state.
134            if (line !== lastStatementLine) {
135                reportFirstExtraStatementAndClear();
136                numberOfStatementsOnThisLine = 1;
137                lastStatementLine = line;
138            }
139        }
140
141        //--------------------------------------------------------------------------
142        // Public API
143        //--------------------------------------------------------------------------
144
145        return {
146            BreakStatement: enterStatement,
147            ClassDeclaration: enterStatement,
148            ContinueStatement: enterStatement,
149            DebuggerStatement: enterStatement,
150            DoWhileStatement: enterStatement,
151            ExpressionStatement: enterStatement,
152            ForInStatement: enterStatement,
153            ForOfStatement: enterStatement,
154            ForStatement: enterStatement,
155            FunctionDeclaration: enterStatement,
156            IfStatement: enterStatement,
157            ImportDeclaration: enterStatement,
158            LabeledStatement: enterStatement,
159            ReturnStatement: enterStatement,
160            SwitchStatement: enterStatement,
161            ThrowStatement: enterStatement,
162            TryStatement: enterStatement,
163            VariableDeclaration: enterStatement,
164            WhileStatement: enterStatement,
165            WithStatement: enterStatement,
166            ExportNamedDeclaration: enterStatement,
167            ExportDefaultDeclaration: enterStatement,
168            ExportAllDeclaration: enterStatement,
169
170            "BreakStatement:exit": leaveStatement,
171            "ClassDeclaration:exit": leaveStatement,
172            "ContinueStatement:exit": leaveStatement,
173            "DebuggerStatement:exit": leaveStatement,
174            "DoWhileStatement:exit": leaveStatement,
175            "ExpressionStatement:exit": leaveStatement,
176            "ForInStatement:exit": leaveStatement,
177            "ForOfStatement:exit": leaveStatement,
178            "ForStatement:exit": leaveStatement,
179            "FunctionDeclaration:exit": leaveStatement,
180            "IfStatement:exit": leaveStatement,
181            "ImportDeclaration:exit": leaveStatement,
182            "LabeledStatement:exit": leaveStatement,
183            "ReturnStatement:exit": leaveStatement,
184            "SwitchStatement:exit": leaveStatement,
185            "ThrowStatement:exit": leaveStatement,
186            "TryStatement:exit": leaveStatement,
187            "VariableDeclaration:exit": leaveStatement,
188            "WhileStatement:exit": leaveStatement,
189            "WithStatement:exit": leaveStatement,
190            "ExportNamedDeclaration:exit": leaveStatement,
191            "ExportDefaultDeclaration:exit": leaveStatement,
192            "ExportAllDeclaration:exit": leaveStatement,
193            "Program:exit": reportFirstExtraStatementAndClear
194        };
195    }
196};
197