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
JavaScript
;
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