UNPKG

eslint-plugin-code-quality-rules

Version:

code-quality-rules

235 lines (216 loc) 6.17 kB
/** * @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, }; }, };