eslint-plugin-code-quality-rules
Version:
code-quality-rules
235 lines (216 loc) • 6.17 kB
JavaScript
/**
* @fileoverview A rule to set the maximum depth block can be nested in a function.
* @author Ian Christian Myers
*/
const lodash = require('lodash');
const astUtils = require('eslint/lib/rules/utils/ast-utils');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce a maximum depth that blocks can be nested',
category: 'Stylistic Issues',
recommended: false,
url: 'https://eslint.org/docs/rules/max-depth',
},
schema: [
{
oneOf: [
{
type: 'integer',
minimum: 0,
},
{
type: 'object',
properties: {
maximum: {
type: 'integer',
minimum: 0,
},
max: {
type: 'integer',
minimum: 0,
},
},
additionalProperties: false,
},
],
},
],
messages: {
tooDeeply: 'Maximum allowed is {{maxDepth}}.Detail is {{detail}}',
},
},
create(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
const functionStack = [];
const option = context.options[0];
let maxDepth = 2;
if (
typeof option === 'object'
&& (Object.prototype.hasOwnProperty.call(option, 'maximum') || Object.prototype.hasOwnProperty.call(option, 'max'))
) {
maxDepth = option.maximum || option.max;
}
if (typeof option === 'number') {
maxDepth = option;
}
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
* @param node
*/
function startFunction(node) {
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
functionStack.push({
len: 0,
name,
block: [],
});
}
/**
* When parsing is done then pop out the reference
* @returns {void}
* @private
*/
function endFunction() {
functionStack.pop();
}
/**
* parse result
* @param {*} block
*/
function parseResult(block) {
const result = [];
if (block.length >= 2) {
let bk = 0;
let index = 0;
result.push({
id: index,
start: block[bk].start,
end: block[bk].end,
name: block[bk].name,
children: [],
});
for (let i = 1; i < block.length; i++) {
const { start, end } = block[i];
if (start > block[bk].start && end < block[bk].end) {
result[index].children.push(block[i]);
} else {
bk = i;
index++;
result.push({
id: index,
start: block[bk].start,
end: block[bk].end,
children: [],
});
}
}
} else {
result.push({
start: block[0].start,
end: block[0].end,
name: block[0].name,
children: [block[0]],
});
}
console.log(result);
return result.map((elem) => {
const { children, name } = elem;
const typeCount = {};
typeCount[name] = 1;
children.forEach((child) => {
const type = child.name;
if (!typeCount[type]) {
typeCount[type] = 1;
} else {
typeCount[type]++;
}
});
return {
start: elem.start,
end: elem.end,
...typeCount,
};
});
}
/**
* Save the block and Evaluate the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function pushBlock(node) {
const func = functionStack[functionStack.length - 1];
if(!func)return;
const len = ++func.len;
const { type } = node;
func.block.push({
start: node.loc.start.line,
end: node.loc.end.line,
name: type,
});
if (len > maxDepth) {
context.report({
node,
messageId: 'tooDeeply',
data: {
maxDepth,
name: func.name,
detail: JSON.stringify(parseResult(func.block)),
},
});
}
}
/**
* Pop the saved block
* @returns {void}
* @private
*/
function popBlock() {
const func = functionStack[functionStack.length - 1];
if(!func)return;
func.len--;
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
IfStatement(node) {
if (node.parent.type !== 'IfStatement') {
pushBlock(node);
}
},
SwitchStatement: pushBlock,
TryStatement: pushBlock,
DoWhileStatement: pushBlock,
WhileStatement: pushBlock,
WithStatement: pushBlock,
ForStatement: pushBlock,
ForInStatement: pushBlock,
ForOfStatement: pushBlock,
'IfStatement:exit': popBlock,
'SwitchStatement:exit': popBlock,
'TryStatement:exit': popBlock,
'DoWhileStatement:exit': popBlock,
'WhileStatement:exit': popBlock,
'WithStatement:exit': popBlock,
'ForStatement:exit': popBlock,
'ForInStatement:exit': popBlock,
'ForOfStatement:exit': popBlock,
'FunctionDeclaration:exit': endFunction,
'FunctionExpression:exit': endFunction,
'ArrowFunctionExpression:exit': endFunction,
};
},
};