Skip to content

Commit

Permalink
Merge c0f82e8 into 9c92998
Browse files Browse the repository at this point in the history
  • Loading branch information
jimjenkins5 committed Feb 6, 2019
2 parents 9c92998 + c0f82e8 commit 4854d1b
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -17,6 +17,7 @@ var fluentChaining = require('./rules/fluent-chaining'),
modulesOnly = require('./rules/module-files-only'),
blockScopeCase = require('./rules/block-scope-case'),
braceStyle = require('./rules/brace-style'),
maxStatementsPerLine = require('./rules/max-statements-per-line'),
indent = require('./rules/indent');

module.exports.rules = {
Expand All @@ -34,4 +35,5 @@ module.exports.rules = {
'no-arrow-for-class-property': noArrowProperties,
'module-files-only': modulesOnly,
'block-scope-case': blockScopeCase,
'max-statements-per-line': maxStatementsPerLine,
};
224 changes: 224 additions & 0 deletions lib/rules/max-statements-per-line.js
@@ -0,0 +1,224 @@
/**
* Forked from:
* https://github.com/eslint/eslint/blob/v5.13.0/lib/rules/max-statements-per-line.js
*
* This modification changes the rule to allow statements in single line arrow functions.
* For example:
*
* myArray.forEach((x) => { someNumber = x + 1; });
*
* Example configuration:
*
* "@silvermine/silvermine/max-statements-per-line": [ "error", { "max": 1 } ]
*/

'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const astUtils = require('eslint/lib/util/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,
},
},
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' ? 1 : options.max;

let lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
inOneLineArrowFunction = false,
firstExtraStatement;

// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------

const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/;

/**
* 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;
inOneLineArrowFunction = false;
}

/**
* 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;
}

// Skip to allow expressions in single-line, block-style arrow functions
const isParentArrow = node.parent.type === 'BlockStatement' && node.parent.parent.type === 'ArrowFunctionExpression',
isParentOneLine = astUtils.isTokenOnSameLine(node.parent, node.parent);

if (isParentArrow && isParentOneLine) {
inOneLineArrowFunction = true;
}

// Update state.
if (line === lastStatementLine) {
numberOfStatementsOnThisLine += 1;
} else {
reportFirstExtraStatementAndClear();
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}

// We need to subtract one statement if this statement is inside a one line arrow
// function. For example:
// myArray.forEach((x) => { y = y + x; }); - Should count as 1, not 2, statements
const statementsThatCount = numberOfStatementsOnThisLine - (inOneLineArrowFunction ? 1 : 0);

// Reports if the node violated this rule.
if (statementsThatCount === 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,
};
},
};
96 changes: 96 additions & 0 deletions tests/lib/rules/max-statements-per-line.test.js
@@ -0,0 +1,96 @@
/**
* @fileoverview Check maximum statelemtns on a single line
*/
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/max-statements-per-line'),
formatCode = require('../../code-helper'),
ruleTester = require('../../ruleTesters').es6(),
invalidExamples, validExample, validExampleMax2;

invalidExamples = [
{
code: formatCode(
'x = 4; y = 2;',
'x = 3 + 1; y = x;',
'let x = 1; let y = 2;',
'var foo = function foo() { var bar = 1; };',
'if (condition) { var bar = 1; var baz = 2; }',
'function foo() {',
' let x = 1; return x;',
'}',
'myArray.forEach((x) => { y = y + x; z = y * x });'
),
errors: [
{
message: 'This line has 2 statements. Maximum allowed is 1.',
type: 'ExpressionStatement',
},
{
message: 'This line has 2 statements. Maximum allowed is 1.',
type: 'ExpressionStatement',
},
{
message: 'This line has 2 statements. Maximum allowed is 1.',
type: 'VariableDeclaration',
},
{
message: 'This line has 2 statements. Maximum allowed is 1.',
type: 'VariableDeclaration',
},
{
message: 'This line has 3 statements. Maximum allowed is 1.',
type: 'VariableDeclaration',
},
{
message: 'This line has 2 statements. Maximum allowed is 1.',
type: 'ReturnStatement',
},
{
message: 'This line has 3 statements. Maximum allowed is 1.',
type: 'ExpressionStatement',
},
],
options: [ { 'max': 1 } ],
},
];

validExample = formatCode(
'if (condition) var bar = 1;',
'let z = 1',
'for (let i = 1; i < 1000; i++) {',
' console.log(i);',
'}',
'myArray.forEach((x) => { y = y + x });',
'myArray.forEach((x) => y = y + x);'
);

validExampleMax2 = formatCode(
'let a = 1; let b = 2;',
'function foo() {',
' let y = 1; return y;',
'}',
);

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

ruleTester.run('max-statements-per-line', rule, {
valid: [
{
code: validExample,
options: [ { 'max': 1 } ],
},
{
code: validExampleMax2,
options: [ { 'max': 2 } ],
},
],

invalid: invalidExamples,
});

0 comments on commit 4854d1b

Please sign in to comment.