Skip to content

Commit e2fe1ef

Browse files
committed
AutoloadSourceLocator - select the correct class node based on the line
1 parent b28368a commit e2fe1ef

File tree

7 files changed

+87
-43
lines changed

7 files changed

+87
-43
lines changed

src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ class AutoloadSourceLocator implements SourceLocator
3535

3636
private FileNodesFetcher $fileNodesFetcher;
3737

38-
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
38+
/** @var array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> */
3939
private array $classNodes = [];
4040

41+
/** @var array<string, Reflection|null> */
42+
private array $classReflections = [];
43+
4144
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
4245
private array $functionNodes = [];
4346

@@ -82,7 +85,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
8285
return null;
8386
}
8487

85-
return $this->findReflection($reflector, $reflectionFileName, $identifier);
88+
return $this->findReflection($reflector, $reflectionFileName, $identifier, null);
8689
}
8790

8891
if ($identifier->isConstant()) {
@@ -155,32 +158,28 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
155158
}
156159

157160
$loweredClassName = strtolower($identifier->getName());
158-
if (array_key_exists($loweredClassName, $this->classNodes)) {
159-
$nodeToReflection = new NodeToReflection();
160-
return $nodeToReflection->__invoke(
161-
$reflector,
162-
$this->classNodes[$loweredClassName]->getNode(),
163-
$this->locatedSourcesByFile[$this->classNodes[$loweredClassName]->getFileName()],
164-
$this->classNodes[$loweredClassName]->getNamespace()
165-
);
161+
if (array_key_exists($loweredClassName, $this->classReflections)) {
162+
return $this->classReflections[$loweredClassName];
166163
}
167164

168165
$locateResult = $this->locateClassByName($identifier->getName());
169166
if ($locateResult === null) {
170167
return null;
171168
}
172-
[$potentiallyLocatedFile, $className] = $locateResult;
169+
[$potentiallyLocatedFile, $className, $startLine] = $locateResult;
173170

174-
return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()));
171+
return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()), $startLine);
175172
}
176173

177-
private function findReflection(Reflector $reflector, string $file, Identifier $identifier): ?Reflection
174+
private function findReflection(Reflector $reflector, string $file, Identifier $identifier, ?int $startLine): ?Reflection
178175
{
179176
if (!array_key_exists($file, $this->locatedSourcesByFile)) {
180177
$result = $this->fileNodesFetcher->fetchNodes($file);
181178
$this->locatedSourcesByFile[$file] = $result->getLocatedSource();
182-
foreach ($result->getClassNodes() as $className => $fetchedClassNode) {
183-
$this->classNodes[$className] = $fetchedClassNode;
179+
foreach ($result->getClassNodes() as $className => $fetchedClassNodes) {
180+
foreach ($fetchedClassNodes as $fetchedClassNode) {
181+
$this->classNodes[$className][] = $fetchedClassNode;
182+
}
184183
}
185184
foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) {
186185
$this->functionNodes[$functionName] = $fetchedFunctionNode;
@@ -196,16 +195,27 @@ private function findReflection(Reflector $reflector, string $file, Identifier $
196195
$nodeToReflection = new NodeToReflection();
197196
if ($identifier->isClass()) {
198197
$identifierName = strtolower($identifier->getName());
198+
if (array_key_exists($identifierName, $this->classReflections)) {
199+
return $this->classReflections[$identifierName];
200+
}
199201
if (!array_key_exists($identifierName, $this->classNodes)) {
200202
return null;
201203
}
202204

203-
return $nodeToReflection->__invoke(
204-
$reflector,
205-
$this->classNodes[$identifierName]->getNode(),
206-
$locatedSource,
207-
$this->classNodes[$identifierName]->getNamespace()
208-
);
205+
foreach ($this->classNodes[$identifierName] as $classNode) {
206+
if ($startLine !== null && $startLine !== $classNode->getNode()->getStartLine()) {
207+
continue;
208+
}
209+
210+
return $this->classReflections[$identifierName] = $nodeToReflection->__invoke(
211+
$reflector,
212+
$classNode->getNode(),
213+
$locatedSource,
214+
$classNode->getNamespace()
215+
);
216+
}
217+
218+
return null;
209219
}
210220
if ($identifier->isFunction()) {
211221
$identifierName = strtolower($identifier->getName());
@@ -243,7 +253,7 @@ public function locateIdentifiersByType(Reflector $reflector, IdentifierType $id
243253
* error handler temporarily.
244254
*
245255
* @throws ReflectionException
246-
* @return array{string, string}|null
256+
* @return array{string, string, int|null}|null
247257
*/
248258
private function locateClassByName(string $className): ?array
249259
{
@@ -259,13 +269,13 @@ private function locateClassByName(string $className): ?array
259269
return null;
260270
}
261271

262-
return [$filename, $reflection->getName()];
272+
return [$filename, $reflection->getName(), $reflection->getStartLine() !== false ? $reflection->getStartLine() : null];
263273
}
264274

265275
$this->silenceErrors();
266276

267277
try {
268-
/** @var array{string, string}|null */
278+
/** @var array{string, string, null}|null */
269279
return FileReadTrapStreamWrapper::withStreamWrapperOverride(
270280
static function () use ($className): ?array {
271281
$functions = spl_autoload_functions();
@@ -283,7 +293,7 @@ static function () use ($className): ?array {
283293
* This will not be `null` when the autoloader tried to read a file.
284294
*/
285295
if (FileReadTrapStreamWrapper::$autoloadLocatedFile !== null) {
286-
return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className];
296+
return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className, null];
287297
}
288298
}
289299

src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class CachingVisitor extends NodeVisitorAbstract
1313

1414
private string $fileName;
1515

16-
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
16+
/** @var array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> */
1717
private array $classNodes;
1818

1919
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
@@ -32,7 +32,7 @@ public function enterNode(\PhpParser\Node $node): ?int
3232

3333
if ($node instanceof \PhpParser\Node\Stmt\ClassLike) {
3434
if ($node->name !== null) {
35-
$this->classNodes[strtolower($node->namespacedName->toString())] = new FetchedNode(
35+
$this->classNodes[strtolower($node->namespacedName->toString())][] = new FetchedNode(
3636
$node,
3737
$this->currentNamespaceNode,
3838
$this->fileName
@@ -106,7 +106,7 @@ public function leaveNode(\PhpParser\Node $node)
106106
}
107107

108108
/**
109-
* @return array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>>
109+
* @return array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>>
110110
*/
111111
public function getClassNodes(): array
112112
{

src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class FetchedNodesResult
88
{
99

10-
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
10+
/** @var array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> */
1111
private array $classNodes;
1212

1313
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
@@ -19,7 +19,7 @@ class FetchedNodesResult
1919
private \Roave\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource;
2020

2121
/**
22-
* @param array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> $classNodes
22+
* @param array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> $classNodes
2323
* @param array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> $functionNodes
2424
* @param array<int, FetchedNode<\PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall>> $constantNodes
2525
* @param \Roave\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource
@@ -38,7 +38,7 @@ public function __construct(
3838
}
3939

4040
/**
41-
* @return array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>>
41+
* @return array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>>
4242
*/
4343
public function getClassNodes(): array
4444
{

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
6262
$fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file);
6363
$locatedSource = $fetchedNodesResult->getLocatedSource();
6464
$this->locatedSourcesByFile[$file] = $locatedSource;
65-
foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNode) {
66-
$this->classNodes[$identifierName] = $fetchedClassNode;
65+
foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNodes) {
66+
foreach ($fetchedClassNodes as $fetchedClassNode) {
67+
$this->classNodes[$identifierName] = $fetchedClassNode;
68+
break;
69+
}
6770
}
6871

6972
if (!array_key_exists($className, $this->classNodes)) {

src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
4444
return null;
4545
}
4646

47-
$classReflection = $nodeToReflection->__invoke(
48-
$reflector,
49-
$classNodes[$className]->getNode(),
50-
$this->fetchedNodesResult->getLocatedSource(),
51-
$classNodes[$className]->getNamespace()
52-
);
53-
if (!$classReflection instanceof ReflectionClass) {
54-
throw new \PHPStan\ShouldNotHappenException();
55-
}
47+
foreach ($classNodes[$className] as $classNode) {
48+
$classReflection = $nodeToReflection->__invoke(
49+
$reflector,
50+
$classNode->getNode(),
51+
$this->fetchedNodesResult->getLocatedSource(),
52+
$classNode->getNamespace()
53+
);
54+
if (!$classReflection instanceof ReflectionClass) {
55+
throw new \PHPStan\ShouldNotHappenException();
56+
}
5657

57-
return $classReflection;
58+
return $classReflection;
59+
}
5860
}
5961

6062
if ($identifier->isFunction()) {

tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
namespace PHPStan\Reflection\BetterReflection\SourceLocator;
44

55
use PHPStan\Testing\TestCase;
6+
use Roave\BetterReflection\Reflection\ReflectionClass;
67
use Roave\BetterReflection\Reflector\ClassReflector;
78
use Roave\BetterReflection\Reflector\ConstantReflector;
89
use Roave\BetterReflection\Reflector\FunctionReflector;
910
use TestSingleFileSourceLocator\AFoo;
11+
use TestSingleFileSourceLocator\InCondition;
1012

1113
function testFunctionForLocator(): void // phpcs:disable
1214
{
@@ -45,6 +47,16 @@ public function testAutoloadEverythingInFile(): void
4547
$this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName());
4648
$this->assertNotNull($doFooFunctionReflection->getFileName());
4749
$this->assertSame('a.php', basename($doFooFunctionReflection->getFileName()));
50+
51+
class_exists(InCondition::class);
52+
$classInCondition = $classReflector->reflect(InCondition::class);
53+
$classInConditionFilename = $classInCondition->getFileName();
54+
$this->assertNotNull($classInConditionFilename);
55+
$this->assertSame('a.php', basename($classInConditionFilename));
56+
$this->assertSame(InCondition::class, $classInCondition->getName());
57+
$this->assertSame(25, $classInCondition->getStartLine());
58+
$this->assertInstanceOf(ReflectionClass::class, $classInCondition->getParentClass());
59+
$this->assertSame(AFoo::class, $classInCondition->getParentClass()->getName());
4860
}
4961

5062
}

tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,20 @@ function doFoo()
1515
define('TestSingleFileSourceLocator\\SOME_CONSTANT', 1);
1616

1717
const ANOTHER_CONSTANT = 2;
18+
19+
if (false) {
20+
class InCondition
21+
{
22+
23+
}
24+
} elseif (true) {
25+
class InCondition extends AFoo
26+
{
27+
28+
}
29+
} else {
30+
class InCondition extends \stdClass
31+
{
32+
33+
}
34+
}

0 commit comments

Comments
 (0)