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.
236 lines • 9.83 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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.fileCost = fileCost;
const ts = __importStar(require("typescript"));
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) {
const initialContext = {
depth: 0,
scope: new Scope_1.Scope([], []),
topLevel: true,
variableBeingDefined: undefined,
};
const initialMutableContext = {
precedingOperator: undefined,
precedingTypeOperator: undefined,
};
return {
kind: "file",
...nodeCost(file, initialContext, initialMutableContext),
};
}
function aggregateCostOfChildren(children, ctx, mutCtx) {
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, ctx, mutCtx);
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, ctx.variableBeingDefined);
if (name !== undefined) {
inner.push({
kind: (0, node_inspection_1.getNodeKind)(child),
...(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, mutCtx) {
// certain language features carry and inherent cost
if ((0, node_inspection_1.isNewSequenceOfBinaryOperators)(node, mutCtx.precedingOperator)
|| 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)
|| (0, node_inspection_1.isNewSequenceOfBinaryTypeOperators)(node, mutCtx.precedingTypeOperator)) {
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;
}
return 0;
}
/**
* @param node The node whose cost we want
* @param ctx Information about the context a node is in, which is needed to know the node cost.
* @param mutCtx Same as above, but this information can be changed while traversing,
* which will provide information up the call stack (where this function is called and higher).
*/
function nodeCost(node, ctx, mutCtx) {
const { depth, topLevel, scope, variableBeingDefined } = ctx;
// get the ancestors container names from the perspective of this node's children
const scopeForChildren = scope.maybeAdd(node, variableBeingDefined);
const { sameDepth, 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$ () => { $anonymous$ };
* let a = ( $a$ () => { $anonymous$ } );
* let a = f( $anonymous$ () => { $anonymous$ } );
*/
let newVariableBeingDefined = (0, node_naming_1.getNameOfAssignment)(node);
if (newVariableBeingDefined === undefined
&& variableBeingDefined !== undefined
&& (0, node_inspection_1.passThroughNameBeingAssigned)(node)) {
newVariableBeingDefined = variableBeingDefined;
}
const opSequenceInProgress = mutCtx.precedingOperator;
const typeOpSequenceInProgress = mutCtx.precedingTypeOperator;
const pauseOpSequence = (0, node_inspection_1.pausesASequenceOfBinaryOperators)(node);
const pauseTypeOpSequence = (0, node_inspection_1.pausesASequenceOfBinaryTypeOperators)(node);
// Check if the node ends any ongoing sequence of binary operators
if ((0, node_inspection_1.breaksASequenceOfBinaryOperators)(node)) {
mutCtx.precedingTypeOperator = undefined;
mutCtx.precedingOperator = undefined;
}
// Score for the current node
let score = inherentCost(node, scope, mutCtx);
score += costOfDepth(node, depth);
// If this is a binary operator, there won't be any children.
// Pass along the operator info
if ((0, node_inspection_1.isChainableBinaryOperator)(node)) {
mutCtx.precedingOperator = node.kind;
}
// If this is a binary type operator, there won't be any children.
// Pass along the operator info
else if ((0, node_inspection_1.isChainableBinaryTypeOperator)(node)) {
mutCtx.precedingTypeOperator = node.kind;
}
else if (pauseOpSequence) {
mutCtx.precedingOperator = undefined;
}
else if (pauseTypeOpSequence) {
mutCtx.precedingTypeOperator = undefined;
}
const ctxForChildrenSameDepth = {
depth,
topLevel,
precedingOperator: mutCtx.precedingOperator,
scope: scopeForChildren,
variableBeingDefined: newVariableBeingDefined,
};
const costOfSameDepthChildren = aggregateCostOfChildren(sameDepth, ctxForChildrenSameDepth, mutCtx);
// The nodes below this node have an increased depth number,
// unless this node is top level and it is a container.
const depthOfBelow = depth + (topLevel && (0, node_inspection_1.isContainer)(node) ? 0 : 1);
const ctxForChildrenBelow = {
...ctxForChildrenSameDepth,
depth: depthOfBelow,
topLevel: false,
};
const costOfBelowChildren = aggregateCostOfChildren(below, ctxForChildrenBelow, mutCtx);
// continue a paused sequence
if (pauseOpSequence) {
mutCtx.precedingOperator = opSequenceInProgress;
}
else if (pauseTypeOpSequence) {
mutCtx.precedingTypeOperator = typeOpSequenceInProgress;
}
score += costOfSameDepthChildren.score;
score += costOfBelowChildren.score;
const inner = [
...costOfSameDepthChildren.inner,
...costOfBelowChildren.inner
];
return { inner, score };
}
//# sourceMappingURL=cognitive-complexity.js.map