From 7f86df81899e2b12b70bd66790d8d15abc490b06 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 27 Aug 2021 14:39:23 +0200 Subject: [PATCH] Use code path analysis --- lib/rules/complexity.js | 130 +++++++++++++++------------------------- 1 file changed, 48 insertions(+), 82 deletions(-) diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js index ebf24e10adf..377c54a0291 100644 --- a/lib/rules/complexity.js +++ b/lib/rules/complexity.js @@ -74,73 +74,16 @@ module.exports = { // Helpers //-------------------------------------------------------------------------- - // Using a stack to store complexity (handling nested functions) - const fns = []; + // Using a stack to store complexity per code path + const complexities = []; /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - fns.push(1); - } - - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate. If it is a `PropertyDefinition` node, its initializer is being evaluated. - * @returns {void} - * @private - */ - function endFunction(node) { - const complexity = fns.pop(); - - if (complexity > THRESHOLD) { - let evaluatedNode, name; - - if (node.type === "PropertyDefinition") { - evaluatedNode = node.value; - name = "class field initializer"; - } else { - evaluatedNode = node; - name = astUtils.getFunctionNameWithKind(node); - } - - context.report({ - node: evaluatedNode, - messageId: "complex", - data: { - name: upperCaseFirst(name), - complexity, - max: THRESHOLD - } - }); - } - } - - /** - * Increase the complexity of the function in context + * Increase the complexity of the code path in context * @returns {void} * @private */ function increaseComplexity() { - if (fns.length) { - fns[fns.length - 1]++; - } - } - - /** - * Increase the switch complexity in context - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function increaseSwitchComplexity(node) { - - // Avoiding `default` - if (node.test) { - increaseComplexity(); - } + complexities[complexities.length - 1]++; } //-------------------------------------------------------------------------- @@ -148,27 +91,14 @@ module.exports = { //-------------------------------------------------------------------------- return { - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - - /* - * Class field initializers are implicit functions. Therefore, they shouldn't contribute - * to the enclosing function's complexity, but their own complexity should be evaluated. - * We're using `*.key:exit` here in order to make sure that `startFunction()` is called - * before entering the `.value` node, and thus certainly before other listeners - * (e.g., if the initializer is `a || b`, due to a higher selector specificity - * `PropertyDefinition > *.value` would be called after `LogicalExpression`). - * We're passing the `PropertyDefinition` node instead of `PropertyDefinition.value` node - * to `endFunction(node)` in order to disambiguate between evaluating implicit initializer - * functions and "regular" functions, which may be the `.value` itself, e.g., `x = () => {};`. - */ - "PropertyDefinition[value] > *.key:exit": startFunction, - "PropertyDefinition[value]:exit": endFunction, + onCodePathStart() { + + // The initial complexity is 1, representing one execution path in the CodePath + complexities.push(1); + }, + + // Each branching in the code adds 1 to the complexity CatchClause: increaseComplexity, ConditionalExpression: increaseComplexity, LogicalExpression: increaseComplexity, @@ -176,14 +106,50 @@ module.exports = { ForInStatement: increaseComplexity, ForOfStatement: increaseComplexity, IfStatement: increaseComplexity, - SwitchCase: increaseSwitchComplexity, WhileStatement: increaseComplexity, DoWhileStatement: increaseComplexity, + // Avoid `default` + "SwitchCase[test]": increaseComplexity, + + // Logical assignment operators have short-circuiting behavior AssignmentExpression(node) { if (astUtils.isLogicalAssignmentOperator(node.operator)) { increaseComplexity(); } + }, + + onCodePathEnd(codePath, node) { + const complexity = complexities.pop(); + + /* + * This rule only evaluates complexity of functions, so "program" is excluded. + * Class field initializers are implicit functions. Therefore, they shouldn't contribute + * to the enclosing function's complexity, but their own complexity should be evaluated. + */ + if ( + codePath.origin !== "function" && + codePath.origin !== "class-field-initializer" + ) { + return; + } + + + if (complexity > THRESHOLD) { + const name = codePath.origin === "class-field-initializer" + ? "class field initializer" + : astUtils.getFunctionNameWithKind(node); + + context.report({ + node, + messageId: "complex", + data: { + name: upperCaseFirst(name), + complexity, + max: THRESHOLD + } + }); + } } };