1/** 2 * @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity. 3 * Counts the number of if, conditional, for, while, try, switch/case, 4 * @author Patrick Brosset 5 */ 6 7"use strict"; 8 9//------------------------------------------------------------------------------ 10// Requirements 11//------------------------------------------------------------------------------ 12 13const lodash = require("lodash"); 14 15const astUtils = require("./utils/ast-utils"); 16 17//------------------------------------------------------------------------------ 18// Rule Definition 19//------------------------------------------------------------------------------ 20 21module.exports = { 22 meta: { 23 type: "suggestion", 24 25 docs: { 26 description: "enforce a maximum cyclomatic complexity allowed in a program", 27 category: "Best Practices", 28 recommended: false, 29 url: "https://eslint.org/docs/rules/complexity" 30 }, 31 32 schema: [ 33 { 34 oneOf: [ 35 { 36 type: "integer", 37 minimum: 0 38 }, 39 { 40 type: "object", 41 properties: { 42 maximum: { 43 type: "integer", 44 minimum: 0 45 }, 46 max: { 47 type: "integer", 48 minimum: 0 49 } 50 }, 51 additionalProperties: false 52 } 53 ] 54 } 55 ], 56 57 messages: { 58 complex: "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}." 59 } 60 }, 61 62 create(context) { 63 const option = context.options[0]; 64 let THRESHOLD = 20; 65 66 if ( 67 typeof option === "object" && 68 (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) 69 ) { 70 THRESHOLD = option.maximum || option.max; 71 } else if (typeof option === "number") { 72 THRESHOLD = option; 73 } 74 75 //-------------------------------------------------------------------------- 76 // Helpers 77 //-------------------------------------------------------------------------- 78 79 // Using a stack to store complexity (handling nested functions) 80 const fns = []; 81 82 /** 83 * When parsing a new function, store it in our function stack 84 * @returns {void} 85 * @private 86 */ 87 function startFunction() { 88 fns.push(1); 89 } 90 91 /** 92 * Evaluate the node at the end of function 93 * @param {ASTNode} node node to evaluate 94 * @returns {void} 95 * @private 96 */ 97 function endFunction(node) { 98 const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node)); 99 const complexity = fns.pop(); 100 101 if (complexity > THRESHOLD) { 102 context.report({ 103 node, 104 messageId: "complex", 105 data: { name, complexity, max: THRESHOLD } 106 }); 107 } 108 } 109 110 /** 111 * Increase the complexity of the function in context 112 * @returns {void} 113 * @private 114 */ 115 function increaseComplexity() { 116 if (fns.length) { 117 fns[fns.length - 1]++; 118 } 119 } 120 121 /** 122 * Increase the switch complexity in context 123 * @param {ASTNode} node node to evaluate 124 * @returns {void} 125 * @private 126 */ 127 function increaseSwitchComplexity(node) { 128 129 // Avoiding `default` 130 if (node.test) { 131 increaseComplexity(); 132 } 133 } 134 135 //-------------------------------------------------------------------------- 136 // Public API 137 //-------------------------------------------------------------------------- 138 139 return { 140 FunctionDeclaration: startFunction, 141 FunctionExpression: startFunction, 142 ArrowFunctionExpression: startFunction, 143 "FunctionDeclaration:exit": endFunction, 144 "FunctionExpression:exit": endFunction, 145 "ArrowFunctionExpression:exit": endFunction, 146 147 CatchClause: increaseComplexity, 148 ConditionalExpression: increaseComplexity, 149 LogicalExpression: increaseComplexity, 150 ForStatement: increaseComplexity, 151 ForInStatement: increaseComplexity, 152 ForOfStatement: increaseComplexity, 153 IfStatement: increaseComplexity, 154 SwitchCase: increaseSwitchComplexity, 155 WhileStatement: increaseComplexity, 156 DoWhileStatement: increaseComplexity 157 }; 158 159 } 160}; 161