eslint-plugin-code-quality-rules
Version:
code-quality-rules
169 lines (149 loc) • 5.16 kB
JavaScript
/**
* @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity.
* Counts the number of if, conditional, for, whilte, try, switch/case,
* @author Patrick Brosset
*/
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
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 cyclomatic complexity allowed in a program',
category: 'Best Practices',
recommended: false,
url: 'https://eslint.org/docs/rules/complexity',
},
schema: [
{
oneOf: [
{
type: 'integer',
minimum: 0,
},
{
type: 'object',
properties: {
maximum: {
type: 'integer',
minimum: 0,
},
max: {
type: 'integer',
minimum: 0,
},
},
additionalProperties: false,
},
],
},
],
messages: {
complex: '<%={{name}}=%> has a complexity of <%={{complexity}}=%>. Maximum allowed is <%={{max}}=%>.Detail is <%={{detail}}=%>',
},
},
create(context) {
const option = context.options[0];
let THRESHOLD = 10;
if (
typeof option === 'object'
&& (Object.prototype.hasOwnProperty.call(option, 'maximum') || Object.prototype.hasOwnProperty.call(option, 'max'))
) {
THRESHOLD = option.maximum || option.max;
} else if (typeof option === 'number') {
THRESHOLD = option;
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// Using a stack to store complexity (handling nested functions)
const fns = [];
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push({
complexity: 1,
detail: {},
});
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
const { complexity, detail } = fns.pop();
if (complexity > THRESHOLD) {
context.report({
node,
messageId: 'complex',
data: {
name, complexity, max: THRESHOLD, detail: JSON.stringify(detail),
},
});
}
}
/**
* Increase the complexity of the function in context
* @returns {void}
* @private
*/
function increaseComplexity(type) {
if (fns.length) {
(fns[fns.length - 1].complexity)++;
const { detail } = fns[fns.length - 1];
const typeCount = detail[type];
if (!typeCount) {
detail[type] = 1;
} else {
detail[type] = typeCount + 1;
}
}
}
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node, type) {
// console.log('increaseSwitchComplexity');
// Avoiding `default`
if (node.test) {
increaseComplexity(type);
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
'FunctionDeclaration:exit': endFunction,
'FunctionExpression:exit': endFunction,
'ArrowFunctionExpression:exit': endFunction,
CatchClause: () => increaseComplexity('CatchClause'),
ConditionalExpression: () => increaseComplexity('ConditionalExpression'),
LogicalExpression: () => increaseComplexity('LogicalExpression'),
ForStatement: () => increaseComplexity('ForStatement'),
ForInStatement: () => increaseComplexity('ForInStatement'),
ForOfStatement: () => increaseComplexity('ForOfStatement'),
IfStatement: () => increaseComplexity('IfStatement'),
SwitchCase: (node) => increaseSwitchComplexity(node, 'SwitchCase'),
WhileStatement: () => increaseComplexity('WhileStatement'),
DoWhileStatement: () => increaseComplexity('DoWhileStatement'),
};
},
};