Skip to content

Commit

Permalink
feat: add CyclomaticComplexityCountableNode for TypeScript.
Browse files Browse the repository at this point in the history
  • Loading branch information
ytetsuro committed Aug 14, 2022
1 parent d473edd commit 3d2cb07
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 15 deletions.
4 changes: 2 additions & 2 deletions src/Language/TypeScript/ASTNode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
import { ComplexityCountableNode } from './ComplexityCountableNode';
import { CognitiveComplexityCountableNode } from './CognitiveComplexityCountableNode';
import { ASTNode as ASTNodeInterface } from '../../Analyzer/Adapter/ASTNode';
import { injectable } from 'inversify';

Expand Down Expand Up @@ -60,7 +60,7 @@ export class ASTNode implements ASTNodeInterface {
return (
new ASTNode((<ts.FunctionExpression>this.node).body, this.sourceFile)
.getChildren()
.filter((row) => new ComplexityCountableNode(row).isIncrement() && !row.isFunction()).length === 0
.filter((row) => new CognitiveComplexityCountableNode(row).isIncrement() && !row.isFunction()).length === 0
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ComplexityCountableNode as ComplexityCountableNodeInterface } from '../
import { ASTNode } from './ASTNode';

@injectable()
export class ComplexityCountableNode implements ComplexityCountableNodeInterface {
export class CognitiveComplexityCountableNode implements ComplexityCountableNodeInterface {
private static readonly nestLevelUpKinds = [
ts.SyntaxKind.IfStatement,
ts.SyntaxKind.SwitchStatement,
Expand Down Expand Up @@ -46,16 +46,16 @@ export class ComplexityCountableNode implements ComplexityCountableNodeInterface
}

isNestLevelUp() {
return !this.isElse() && ComplexityCountableNode.nestLevelUpKinds.includes(this.pureNode.kind);
return !this.isElse() && CognitiveComplexityCountableNode.nestLevelUpKinds.includes(this.pureNode.kind);
}

isIncrement() {
if (this.isElse()) {
return true;
}

const allIncrementSyntaxKinds = ComplexityCountableNode.incrementSyntaxKinds.concat(
...ComplexityCountableNode.nestingIncrementSyntaxKinds
const allIncrementSyntaxKinds = CognitiveComplexityCountableNode.incrementSyntaxKinds.concat(
...CognitiveComplexityCountableNode.nestingIncrementSyntaxKinds
);

return allIncrementSyntaxKinds.includes(this.pureNode.kind);
Expand All @@ -66,7 +66,7 @@ export class ComplexityCountableNode implements ComplexityCountableNodeInterface
return false;
}

return ComplexityCountableNode.nestingIncrementSyntaxKinds.includes(this.pureNode.kind);
return CognitiveComplexityCountableNode.nestingIncrementSyntaxKinds.includes(this.pureNode.kind);
}

private isElse() {
Expand All @@ -75,6 +75,6 @@ export class ComplexityCountableNode implements ComplexityCountableNodeInterface
}

getChildren() {
return this.node.getChildren().map((node) => new ComplexityCountableNode(node));
return this.node.getChildren().map((node) => new CognitiveComplexityCountableNode(node));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { injectable } from 'inversify';
import { ASTNode } from '../ASTNode';
import { ComplexityCountableNode } from '../ComplexityCountableNode';
import { CognitiveComplexityCountableNode } from '../CognitiveComplexityCountableNode';

@injectable()
export class Complexity {
convert(astNode: ASTNode) {
return new ComplexityCountableNode(astNode);
return new CognitiveComplexityCountableNode(astNode);
}
}
10 changes: 10 additions & 0 deletions src/Language/TypeScript/Converter/CyclomaticComplexity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { injectable } from 'inversify';
import { ASTNode } from '../ASTNode';
import { CyclomaticComplexityCountableNode } from '../CyclomaticComplexityCountableNode';

@injectable()
export class Complexity {
convert(astNode: ASTNode) {
return new CyclomaticComplexityCountableNode(astNode);
}
}
42 changes: 42 additions & 0 deletions src/Language/TypeScript/CyclomaticComplexityCountableNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { injectable } from 'inversify';
import * as ts from 'typescript';
import { ComplexityCountableNode as ComplexityCountableNodeInterface } from '../../Analyzer/CodeMetricsCalculator/CyclomaticComplexity/Adapter/ComplexityCountableNode';
import { ASTNode } from './ASTNode';

@injectable()
export class CyclomaticComplexityCountableNode implements ComplexityCountableNodeInterface {
private static readonly incrementSyntaxKinds = [
ts.SyntaxKind.IfStatement,
ts.SyntaxKind.SwitchStatement,
ts.SyntaxKind.ForInStatement,
ts.SyntaxKind.ForOfStatement,
ts.SyntaxKind.ForStatement,
ts.SyntaxKind.WhileStatement,
ts.SyntaxKind.CatchClause,
ts.SyntaxKind.ConditionalExpression,
ts.SyntaxKind.AmpersandAmpersandToken,
ts.SyntaxKind.BarBarToken,
ts.SyntaxKind.LabeledStatement,
ts.SyntaxKind.QuestionQuestionToken,
ts.SyntaxKind.QuestionDotToken,
];

private readonly node: ASTNode;

private readonly pureNode: ts.Node;

constructor(node: ASTNode) {
this.node = node;
this.pureNode = node.node;
}

isIncrement() {
const allIncrementSyntaxKinds = CyclomaticComplexityCountableNode.incrementSyntaxKinds;

return allIncrementSyntaxKinds.includes(this.pureNode.kind);
}

getChildren() {
return this.node.getChildren().map((node) => new CyclomaticComplexityCountableNode(node));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { readFileSync } from 'fs';
import ts from 'typescript';
import { ASTNode } from '../ASTNode';
import { ComplexityCountableNode } from '../ComplexityCountableNode';
import { CognitiveComplexityCountableNode } from '../CognitiveComplexityCountableNode';

describe('ComplexityCountableNode', () => {
describe('CognitiveComplexityCountableNode', () => {
const parent = ts.createSourceFile(
`${__dirname}/fixtures/example.ts`,
readFileSync(`${__dirname}/fixtures/example.ts`).toString(),
Expand All @@ -27,7 +27,7 @@ describe('ComplexityCountableNode', () => {
['arrowFunction', (<ts.ReturnStatement>map.get('arrowFunction')!.statements[0]).expression!],
])('should %s is nest level up.', (_, statement) => {
const astNode = new ASTNode(statement, parent);
const actual = new ComplexityCountableNode(astNode);
const actual = new CognitiveComplexityCountableNode(astNode);

expect(actual.isNestLevelUp()).toBe(true);
});
Expand All @@ -45,7 +45,7 @@ describe('ComplexityCountableNode', () => {
['conditional', (<ts.ReturnStatement>map.get('conditional')!.statements[0]).expression!],
])('should %s is nesting increment.', (_, statement) => {
const astNode = new ASTNode(statement, parent);
const actual = new ComplexityCountableNode(astNode);
const actual = new CognitiveComplexityCountableNode(astNode);

expect(actual.isNestingIncrement()).toBe(true);
});
Expand All @@ -72,7 +72,7 @@ describe('ComplexityCountableNode', () => {
['label', map.get('label')!.statements[0]],
])('should %s is increment.', (_, statement) => {
const astNode = new ASTNode(statement, parent);
const actual = new ComplexityCountableNode(astNode);
const actual = new CognitiveComplexityCountableNode(astNode);

expect(actual.isIncrement()).toBe(true);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { readFileSync } from 'fs';
import ts from 'typescript';
import { ASTNode } from '../ASTNode';
import { CyclomaticComplexityCountableNode } from '../CyclomaticComplexityCountableNode';

describe('CyclomaticComplexityCountableNode', () => {
const parent = ts.createSourceFile(
`${__dirname}/fixtures/example.ts`,
readFileSync(`${__dirname}/fixtures/example.ts`).toString(),
ts.ScriptTarget.ES2016,
true
);
const members = <ts.NodeArray<ts.MethodDeclaration>>(<ts.ClassDeclaration>parent.statements[0]).members;
const map = [...members].reduce((map, node) => map.set(node.name.getText(), node.body!), new Map<string, ts.Block>());

describe('.isIncrement()', () => {
it.each([
['if', map.get('if')!.statements[0]],
['switch', map.get('switch')!.statements[0]],
['for', map.get('for')!.statements[0]],
['forIn', map.get('forIn')!.statements[0]],
['forOf', map.get('forOf')!.statements[0]],
['while', map.get('while')!.statements[0]],
['catch', (<ts.TryStatement>map.get('try')!.statements[0]).catchClause!],
['conditional', (<ts.ReturnStatement>map.get('conditional')!.statements[0]).expression!],
[
'ampersand',
(<ts.BinaryExpression>(<ts.ReturnStatement>map.get('ampersand')!.statements[0]).expression!).operatorToken,
],
[
'barbar',
(<ts.BinaryExpression>(<ts.ReturnStatement>map.get('barbar')!.statements[0]).expression!).operatorToken,
],
[
'nullishCoalescingOperator',
(<ts.BinaryExpression>(<ts.ReturnStatement>map.get('nullishCoalescingOperator')!.statements[0]).expression!)
.operatorToken,
],
[
'optionalChaining',
(<ts.CallExpression>(<ts.ReturnStatement>map.get('optionalChaining')!.statements[0]).expression!)
.questionDotToken,
],
])('should %s is increment.', (_, statement) => {
const astNode = new ASTNode(statement, parent);
const actual = new CyclomaticComplexityCountableNode(astNode);

expect(actual.isIncrement()).toBe(true);
});
});
});
8 changes: 8 additions & 0 deletions src/Language/TypeScript/__tests__/fixtures/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ class Class {
arrowFunction() {
return () => {};
}

nullishCoalescingOperator(args?: number) {
return args ?? 0;
}

optionalChaining(args?: number) {
return args?.toString?.();
}
}

/**
Expand Down

0 comments on commit 3d2cb07

Please sign in to comment.