diff --git a/src/NodeFinder/TypeAwareNodeFinder.php b/src/NodeFinder/TypeAwareNodeFinder.php index 214007d99..85b6b61df 100644 --- a/src/NodeFinder/TypeAwareNodeFinder.php +++ b/src/NodeFinder/TypeAwareNodeFinder.php @@ -30,4 +30,13 @@ public function findFirstInstanceOf(array|Node $nodes, string $type): ?Node { return $this->nodeFinder->findFirstInstanceOf($nodes, $type); } + + /** + * @param Node[]|Node $nodes + * @param callable(Node): bool $filter + */ + public function findFirst(array|Node $nodes, callable $filter): ?Node + { + return $this->nodeFinder->findFirst($nodes, $filter); + } } diff --git a/src/Reflection/ReflectionParser.php b/src/Reflection/ReflectionParser.php index 8889615fe..add4251b7 100644 --- a/src/Reflection/ReflectionParser.php +++ b/src/Reflection/ReflectionParser.php @@ -4,6 +4,7 @@ namespace Symplify\PHPStanRules\Reflection; +use PhpParser\Node; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\NodeTraverser; @@ -23,7 +24,7 @@ final class ReflectionParser /** * @var array */ - private array $classesByFilename = []; + private array $classesByName = []; private readonly Parser $parser; @@ -54,7 +55,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 +69,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->classesByName[$className])) { + return $this->classesByName[$className]; } try { @@ -92,12 +93,17 @@ private function parseFilenameToClass(string $fileName): ClassLike|null return null; } - $classLike = $this->typeAwareNodeFinder->findFirstInstanceOf($stmts, ClassLike::class); + // a file can hold multiple class-likes, match the requested one by name + $classLike = $this->typeAwareNodeFinder->findFirst( + $stmts, + static fn (Node $node): bool => $node instanceof ClassLike && (string) $node->namespacedName === $className + ); + if (! $classLike instanceof ClassLike) { return null; } - $this->classesByFilename[$fileName] = $classLike; + $this->classesByName[$className] = $classLike; return $classLike; } diff --git a/tests/Rules/NoReferenceRule/Fixture/SkipParentMethodWithReferenceAfterException.php b/tests/Rules/NoReferenceRule/Fixture/SkipParentMethodWithReferenceAfterException.php new file mode 100644 index 000000000..49398e9eb --- /dev/null +++ b/tests/Rules/NoReferenceRule/Fixture/SkipParentMethodWithReferenceAfterException.php @@ -0,0 +1,14 @@ +