UNPKG

cognitive-complexity-ts

Version:

This program analyses TypeScript and JavaScript code according to the [Cognitive Complexity metric](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). It produces a JSON summary and a GUI for exploring the complexity of your codebase.

186 lines 8.08 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.fileCost = void 0; const ts = __importStar(require("typescript")); const util_1 = require("../util/util"); const node_naming_1 = require("./node-naming"); const depth_1 = require("./depth"); const node_inspection_1 = require("./node-inspection"); const Scope_1 = require("./Scope"); function fileCost(file) { return nodeCost(file, true); } exports.fileCost = fileCost; function aggregateCostOfChildren(children, childDepth, topLevel, scope, variableBeingDefined) { let score = 0; // The inner containers of a node is defined as the concat of: // * all child nodes that are functions/namespaces/classes // * all containers declared directly under a non-container child node const inner = []; for (const child of children) { const childCost = nodeCost(child, topLevel, childDepth, scope, variableBeingDefined); score += childCost.score; // a function/class/namespace/type is part of the inner scope we want to output const name = (0, node_naming_1.chooseContainerName)(child, variableBeingDefined); if (name !== undefined) { inner.push({ ...(0, node_inspection_1.getColumnAndLine)(child), ...childCost, name, }); } else { // the child's inner is all part of this node's direct inner scope inner.push(...childCost.inner); } } return { score, inner }; } function costOfDepth(node, depth) { // increment for nesting level if (depth > 0) { if (ts.isCatchClause(node) || ts.isConditionalExpression(node) || ts.isConditionalTypeNode(node) || ts.isDoStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isForStatement(node) || ts.isMappedTypeNode(node) || ts.isSwitchStatement(node) || ts.isWhileStatement(node) || ( // increment for `if`, but not `else if` // The parent of the `if` within an `else if` // is the `if` the `else` belongs to. // However `if (...) if (...)` is treated as false here // even though technically there should be 2 increments. // This quirky syntax produces the same score as using `&&`, // so maybe it doesn't matter. ts.isIfStatement(node) && !ts.isIfStatement(node.parent))) { return depth; } } return 0; } function inherentCost(node, scope) { // certain language features carry and inherent cost if ((0, node_inspection_1.isSequenceOfDifferentBooleanOperations)(node) || ts.isCatchClause(node) || ts.isConditionalExpression(node) || ts.isConditionalTypeNode(node) || ts.isDoStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isForStatement(node) || ts.isMappedTypeNode(node) || ts.isSwitchStatement(node) || ts.isWhileStatement(node) || (0, node_inspection_1.isBreakOrContinueToLabel)(node)) { return 1; } const calledName = (0, node_naming_1.getNameIfCalledNode)(node); if (calledName !== undefined) { return scope.includes(calledName) ? 1 : 0; } // An `if` may contain an else keyword followed by else code. // An `else if` is just the else keyword followed by an if statement. // Therefore this block is entered for both `if` and `else if`. if (ts.isIfStatement(node)) { // increment for `if` and `else if` let score = 1; // increment for solo else const children = node.getChildren(); const elseIndex = children.findIndex(child => child.kind === ts.SyntaxKind.ElseKeyword); if (elseIndex !== -1) { const elseIf = ts.isIfStatement(children[elseIndex + 1]); if (!elseIf) { score += 1; } } return score; } if ((0, node_inspection_1.isBinaryTypeOperator)(node)) { // This node naturally represents a sequence of binary type operators. // (unlike normal binary operators) let score = 1; // However, this sequence can contain nodes that are a different binary operator. // We can assume that children of the internal syntax list that are binary operators // are not the same kind as this node. // Binary sub-expressions at either end of the syntax list // do not break this sequence of operators in the code; they merely bookend it. const syntaxList = node.getChildren()[0]; const numOfSequenceInterrupts = (0, util_1.countNotAtTheEnds)(syntaxList.getChildren(), node_inspection_1.isBinaryTypeOperator); score += numOfSequenceInterrupts; return score; } return 0; } /** * @param node The node whose cost we want * @param topLevel Whether the node is at the top level of a file * @param depth The depth the node is at * @param scope The scope at the node */ function nodeCost(node, topLevel, depth = 0, scope = new Scope_1.Scope([], []), variableBeingDefined = undefined) { let score = inherentCost(node, scope); score += costOfDepth(node, depth); // get the ancestors container names from the perspective of this node's children const namedAncestorsOfChildren = scope .maybeAdd(node, variableBeingDefined); const { same, below } = (0, depth_1.whereAreChildren)(node); /** * The name being introduced (if there is one) * for a variable whose declaration this scope is directly inside of. * It is used to give names to anonymous functions and classes. * let a = $a$ () => {}; * let a = ( $a$ () => {} ); * let a = f( $undefined$ () => {} ); * let a = () => { $undefined$ }; */ let newVariableBeingDefined = (0, node_naming_1.getNameOfAssignment)(node); if (newVariableBeingDefined === undefined && (0, node_inspection_1.passThroughNameBeingAssigned)(node)) { newVariableBeingDefined = variableBeingDefined; } const costOfSameDepthChildren = aggregateCostOfChildren(same, depth, topLevel, namedAncestorsOfChildren, newVariableBeingDefined); // The nodes below this node have the same depth number, // iff this node is top level and it is a container. const container = (0, node_inspection_1.isContainer)(node); const depthOfBelow = depth + (topLevel && container ? 0 : 1); const costOfBelowChildren = aggregateCostOfChildren(below, depthOfBelow, false, namedAncestorsOfChildren, newVariableBeingDefined); score += costOfSameDepthChildren.score; score += costOfBelowChildren.score; const inner = [...costOfSameDepthChildren.inner, ...costOfBelowChildren.inner]; return { inner, score, }; } //# sourceMappingURL=cognitive-complexity.js.map