From 7fd33d1dd9c4f124fa9c8896c0617156b60edba1 Mon Sep 17 00:00:00 2001 From: Sander Muller <9074391+SanderMuller@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:45:30 +0200 Subject: [PATCH] Fix ReflectionParser picking the first class-like in a multi-class-like file --- composer.json | 3 +- src/NodeFinder/TypeAwareNodeFinder.php | 12 ++++++++ src/Reflection/ReflectionParser.php | 27 ++++++++++-------- tests/Reflection/Fixture/TwoClassLikes.php | 19 +++++++++++++ tests/Reflection/ReflectionParserTest.php | 32 ++++++++++++++++++++++ 5 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 tests/Reflection/Fixture/TwoClassLikes.php create mode 100644 tests/Reflection/ReflectionParserTest.php diff --git a/composer.json b/composer.json index 034dc09d3..01e338bd6 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ ], "files": [ "vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php", - "tests/Rules/Rector/PhpUpgradeImplementsMinPhpVersionInterfaceRule/Fixture/SomePhpFeatureRector.php" + "tests/Rules/Rector/PhpUpgradeImplementsMinPhpVersionInterfaceRule/Fixture/SomePhpFeatureRector.php", + "tests/Reflection/Fixture/TwoClassLikes.php" ] }, "config": { diff --git a/src/NodeFinder/TypeAwareNodeFinder.php b/src/NodeFinder/TypeAwareNodeFinder.php index 214007d99..7832c7305 100644 --- a/src/NodeFinder/TypeAwareNodeFinder.php +++ b/src/NodeFinder/TypeAwareNodeFinder.php @@ -30,4 +30,16 @@ public function findFirstInstanceOf(array|Node $nodes, string $type): ?Node { return $this->nodeFinder->findFirstInstanceOf($nodes, $type); } + + /** + * @template TNode as Node + * + * @param Node[]|Node $nodes + * @param class-string $type + * @return TNode[] + */ + public function findInstanceOf(array|Node $nodes, string $type): array + { + return $this->nodeFinder->findInstanceOf($nodes, $type); + } } diff --git a/src/Reflection/ReflectionParser.php b/src/Reflection/ReflectionParser.php index 8889615fe..58c4e8db8 100644 --- a/src/Reflection/ReflectionParser.php +++ b/src/Reflection/ReflectionParser.php @@ -23,7 +23,7 @@ final class ReflectionParser /** * @var array */ - private array $classesByFilename = []; + private array $classLikesByName = []; private readonly Parser $parser; @@ -54,7 +54,7 @@ public function parseClassReflection(ClassReflection $classReflection): ?ClassLi return null; } - return $this->parseFilenameToClass($fileName); + return $this->parseFilenameToClass($fileName, $classReflection->getName()); } private function parseNativeClassReflection(ReflectionClass|ClassReflection $reflectionClass): ?ClassLike @@ -68,13 +68,13 @@ private function parseNativeClassReflection(ReflectionClass|ClassReflection $ref return null; } - return $this->parseFilenameToClass($fileName); + return $this->parseFilenameToClass($fileName, $reflectionClass->getName()); } - private function parseFilenameToClass(string $fileName): ClassLike|null + private function parseFilenameToClass(string $fileName, string $className): ClassLike|null { - if (isset($this->classesByFilename[$fileName])) { - return $this->classesByFilename[$fileName]; + if (isset($this->classLikesByName[$className])) { + return $this->classLikesByName[$className]; } try { @@ -92,13 +92,16 @@ private function parseFilenameToClass(string $fileName): ClassLike|null return null; } - $classLike = $this->typeAwareNodeFinder->findFirstInstanceOf($stmts, ClassLike::class); - if (! $classLike instanceof ClassLike) { - return null; - } + foreach ($this->typeAwareNodeFinder->findInstanceOf($stmts, ClassLike::class) as $classLike) { + if ($classLike->namespacedName?->toString() !== $className) { + continue; + } + + $this->classLikesByName[$className] = $classLike; - $this->classesByFilename[$fileName] = $classLike; + return $classLike; + } - return $classLike; + return null; } } diff --git a/tests/Reflection/Fixture/TwoClassLikes.php b/tests/Reflection/Fixture/TwoClassLikes.php new file mode 100644 index 000000000..cdfb8127b --- /dev/null +++ b/tests/Reflection/Fixture/TwoClassLikes.php @@ -0,0 +1,19 @@ +reflectionParser = new ReflectionParser(new TypeAwareNodeFinder()); + } + + public function testParseMethodOfSecondClassLikeInFile(): void + { + $reflectionMethod = new ReflectionMethod(SecondClassLike::class, 'secondMethod'); + + $classMethod = $this->reflectionParser->parseMethodReflection($reflectionMethod); + + $this->assertInstanceOf(ClassMethod::class, $classMethod); + $this->assertSame('secondMethod', $classMethod->name->toString()); + } +}