From de74013c423a8c76ae0eae7fb08601d7d94c3ad0 Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Thu, 28 Sep 2023 09:54:13 +0200 Subject: [PATCH] Handle anonymous classes --- ChangeLog.md | 4 ++ src/Visitor/ComplexityCalculatingVisitor.php | 5 ++ tests/_fixture/anonymous_class.php | 45 ++++++++++++++++++ .../unit/ComplexityCalculatingVisitorTest.php | 47 ++++++++++++++++++- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/_fixture/anonymous_class.php diff --git a/ChangeLog.md b/ChangeLog.md index 112624d..03be722 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,10 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * `ComplexityCollection::isFunction()` and `ComplexityCollection::isMethod()` * `ComplexityCollection::mergeWith()` +### Fixed + +* Anonymous classes are not processed correctly + ## [3.0.1] - 2023-08-31 ### Fixed diff --git a/src/Visitor/ComplexityCalculatingVisitor.php b/src/Visitor/ComplexityCalculatingVisitor.php index cfda990..be15e00 100644 --- a/src/Visitor/ComplexityCalculatingVisitor.php +++ b/src/Visitor/ComplexityCalculatingVisitor.php @@ -103,6 +103,11 @@ private function classMethodName(ClassMethod $node): string $parent = $node->getAttribute('parent'); assert($parent instanceof Class_ || $parent instanceof Trait_); + + if ($parent->getAttribute('parent') instanceof Node\Expr\New_) { + return 'anonymous class'; + } + assert(isset($parent->namespacedName)); assert($parent->namespacedName instanceof Name); diff --git a/tests/_fixture/anonymous_class.php b/tests/_fixture/anonymous_class.php new file mode 100644 index 0000000..76f3857 --- /dev/null +++ b/tests/_fixture/anonymous_class.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity\TestFixture; + +$o = new class { + public function method(): void + { + if (true || false) { + if (true && false) { + for ($i = 0; $i <= 1; $i++) { + $a = true ? 'foo' : 'bar'; + } + + foreach (range(0, 1) as $i) { + switch ($i) { + case 0: + break; + + case 1: + break; + + default: + } + } + } + } elseif (null) { + try { + } catch (Throwable $t) { + } + } + + while (true) { + } + + do { + } while (false); + } +}; diff --git a/tests/unit/ComplexityCalculatingVisitorTest.php b/tests/unit/ComplexityCalculatingVisitorTest.php index e4e5e3d..ea90a17 100644 --- a/tests/unit/ComplexityCalculatingVisitorTest.php +++ b/tests/unit/ComplexityCalculatingVisitorTest.php @@ -41,7 +41,7 @@ public static function shortCircuitTraversalProvider(): array } #[DataProvider('shortCircuitTraversalProvider')] - public function testCalculatesComplexityForAbstractSyntaxTree(bool $shortCircuitTraversal): void + public function testCalculatesComplexityForAbstractSyntaxTreeOfClass(bool $shortCircuitTraversal): void { $nodes = $this->parser()->parse( file_get_contents(__DIR__ . '/../_fixture/ExampleClass.php'), @@ -84,7 +84,50 @@ public function numberOfNodesVisited(): int } #[DataProvider('shortCircuitTraversalProvider')] - public function testCalculatesComplexityForAbstractSyntaxTreeInterface(bool $shortCircuitTraversal): void + public function testCalculatesComplexityForAbstractSyntaxTreeOfAnonymousClass(bool $shortCircuitTraversal): void + { + $nodes = $this->parser()->parse( + file_get_contents(__DIR__ . '/../_fixture/anonymous_class.php'), + ); + + $traverser = new NodeTraverser; + + $complexityCalculatingVisitor = new ComplexityCalculatingVisitor($shortCircuitTraversal); + + $shortCircuitVisitor = new class extends NodeVisitorAbstract + { + private int $numberOfNodesVisited = 0; + + public function enterNode(Node $node): void + { + $this->numberOfNodesVisited++; + } + + public function numberOfNodesVisited(): int + { + return $this->numberOfNodesVisited; + } + }; + + $traverser->addVisitor(new NameResolver); + $traverser->addVisitor(new ParentConnectingVisitor); + $traverser->addVisitor($complexityCalculatingVisitor); + $traverser->addVisitor($shortCircuitVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + + $this->assertSame(14, $complexityCalculatingVisitor->result()->cyclomaticComplexity()); + + if ($shortCircuitTraversal) { + $this->assertSame(12, $shortCircuitVisitor->numberOfNodesVisited()); + } else { + $this->assertSame(73, $shortCircuitVisitor->numberOfNodesVisited()); + } + } + + #[DataProvider('shortCircuitTraversalProvider')] + public function testCalculatesComplexityForAbstractSyntaxTreeOfInterface(bool $shortCircuitTraversal): void { $nodes = $this->parser()->parse( file_get_contents(__DIR__ . '/../_fixture/ExampleInterface.php'),