diff --git a/src/Language/PHP/CyclomaticComplexityCountableNode.ts b/src/Language/PHP/CyclomaticComplexityCountableNode.ts new file mode 100644 index 0000000..a86c903 --- /dev/null +++ b/src/Language/PHP/CyclomaticComplexityCountableNode.ts @@ -0,0 +1,49 @@ +import * as PHPParser from 'php-parser'; +import { ComplexityCountableNode as ComplexityCountableNodeInterface } from '../../Analyzer/CodeMetricsCalculator/CyclomaticComplexity/Adapter/ComplexityCountableNode'; +import { ASTNode } from './ASTNode'; +import { ASTKind } from './ASTKind'; +import { injectable } from 'inversify'; + +type BinNode = PHPParser.Node & { type: string }; +type AssignNode = PHPParser.Node & { operator: string }; + +@injectable() +export class CyclomaticComplexityCountableNode implements ComplexityCountableNodeInterface { + private static readonly incrementSyntaxKinds = [ + ASTKind.IF, + ASTKind.CATCH, + ASTKind.SWITCH, + ASTKind.FOR, + ASTKind.WHILE, + ASTKind.RETURN_IF, + ASTKind.MATCH_ARM, + ASTKind.NULL_SAFE_PROPERTY_LOOKUP, + ]; + + private readonly node: ASTNode; + + constructor(node: ASTNode) { + this.node = node; + } + + isIncrement() { + if (CyclomaticComplexityCountableNode.incrementSyntaxKinds.includes(this.node.kind)) { + return true; + } else if (this.node.kind === ASTKind.LABEL) { + return true; + } else if (this.node.kind === ASTKind.ASSIGN && ['??='].includes((this.node.node).operator)) { + return true; + } else if ( + this.node.kind === ASTKind.BIN && + ['and', 'or', '&&', '||', 'xor', '??'].includes((this.node.node).type) + ) { + return true; + } + + return false; + } + + getChildren() { + return this.node.getChildren().map((node) => new CyclomaticComplexityCountableNode(node)); + } +} diff --git a/src/Language/PHP/__tests__/ComplexityCountableNode.test.ts b/src/Language/PHP/__tests__/CognitiveComplexityCountableNode.test.ts similarity index 100% rename from src/Language/PHP/__tests__/ComplexityCountableNode.test.ts rename to src/Language/PHP/__tests__/CognitiveComplexityCountableNode.test.ts diff --git a/src/Language/PHP/__tests__/CyclomaticComplexityCountableNode.test.ts b/src/Language/PHP/__tests__/CyclomaticComplexityCountableNode.test.ts new file mode 100644 index 0000000..6556b5f --- /dev/null +++ b/src/Language/PHP/__tests__/CyclomaticComplexityCountableNode.test.ts @@ -0,0 +1,48 @@ +import { Engine } from 'php-parser'; +import { readFileSync } from 'fs'; +import { ASTNode } from '../ASTNode'; +import { CyclomaticComplexityCountableNode } from '../CyclomaticComplexityCountableNode'; + +describe('CyclomaticComplexityCountableNode', () => { + const engine = new Engine({ + parser: { + extractDoc: true, + }, + ast: { + withPositions: true, + withSource: true, + }, + }); + const node = engine.parseCode( + readFileSync(`${__dirname}/fixtures/example.php`).toString(), + `${__dirname}/fixtures/example.php` + ); + + const [_, ...members] = new ASTNode(node.children[0], node).getChildren(); + + const map = [...members].reduce( + (map, node) => map.set((node.node).name.name, node.getChildren()[1]), + new Map() + ); + + describe('.isIncrement()', () => { + it.each([ + ['if', map.get('if')!.getChildren()[0]], + ['switch', map.get('switch')!.getChildren()[0]], + ['for', map.get('for')!.getChildren()[0]], + ['while', map.get('while')!.getChildren()[0]], + ['catch', map.get('try')!.getChildren()[0].getChildren().pop()], + ['conditional', map.get('conditional')!.getChildren()[0].getChildren()[0]], + ['ampersand', map.get('ampersand')!.getChildren()[0].getChildren()[0]], + ['barbar', map.get('barbar')!.getChildren()[0].getChildren()[0]], + ['label', map.get('label')!.getChildren()[1]], + ['match', map.get('match')!.getChildren()[0].getChildren()[0].getChildren()[1]], + ['nullishCoalescingOperator', map.get('nullishCoalescingOperator')!.getChildren()[0].getChildren()[0]], + ['optionalChaining', map.get('optionalChaining')!.getChildren()[1].getChildren()[0]], + ])('should %s is increment.', (_, astNode) => { + const actual = new CyclomaticComplexityCountableNode(astNode); + + expect(actual.isIncrement()).toBe(true); + }); + }); +});