Skip to content

Commit 25c7791

Browse files
committed
AutoloadSourceLocator - give all found identifiers in an autoloaded file
1 parent 423fd9b commit 25c7791

File tree

12 files changed

+168
-55
lines changed

12 files changed

+168
-55
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -531,20 +531,20 @@ private function processStmtNode(
531531
if (!$betterReflectionClass instanceof \Roave\BetterReflection\Reflection\ReflectionClass) {
532532
throw new \PHPStan\ShouldNotHappenException();
533533
}
534-
$classScope = $scope->enterClass(
535-
new ClassReflection(
536-
$this->reflectionProvider,
537-
$this->fileTypeMapper,
538-
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
539-
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
540-
$betterReflectionClass->getName(),
541-
new ReflectionClass($betterReflectionClass),
542-
null,
543-
null,
544-
null,
545-
sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine())
546-
)
534+
$classReflection = new ClassReflection(
535+
$this->reflectionProvider,
536+
$this->fileTypeMapper,
537+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
538+
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
539+
$betterReflectionClass->getName(),
540+
new ReflectionClass($betterReflectionClass),
541+
null,
542+
null,
543+
null,
544+
sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine())
547545
);
546+
$this->reflectionProvider->hasClass($classReflection->getName());
547+
$classScope = $scope->enterClass($classReflection);
548548
} elseif ($stmt instanceof Class_) {
549549
if ($stmt->name === null) {
550550
throw new \PHPStan\ShouldNotHappenException();

src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php

Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Roave\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
2121
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
2222
use Roave\BetterReflection\SourceLocator\Type\SourceLocator;
23+
use function array_key_exists;
2324
use function file_exists;
2425
use function restore_error_handler;
2526

@@ -40,6 +41,18 @@ class AutoloadSourceLocator implements SourceLocator
4041

4142
private static ?FileNodesFetcher $currentFileNodesFetcher = null;
4243

44+
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
45+
private array $classNodes = [];
46+
47+
/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
48+
private array $functionNodes = [];
49+
50+
/** @var array<int, FetchedNode<\PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall>> */
51+
private array $constantNodes = [];
52+
53+
/** @var array<string, \Roave\BetterReflection\SourceLocator\Located\LocatedSource> */
54+
private array $locatedSourcesByFile = [];
55+
4356
/**
4457
* Note: the constructor has been made a 0-argument constructor because `\stream_wrapper_register`
4558
* is a piece of trash, and doesn't accept instances, just class names.
@@ -63,6 +76,16 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
6376
{
6477
if ($identifier->isFunction()) {
6578
$functionName = $identifier->getName();
79+
$loweredFunctionName = strtolower($functionName);
80+
if (array_key_exists($loweredFunctionName, $this->functionNodes)) {
81+
$nodeToReflection = new NodeToReflection();
82+
return $nodeToReflection->__invoke(
83+
$reflector,
84+
$this->functionNodes[$loweredFunctionName]->getNode(),
85+
$this->locatedSourcesByFile[$this->functionNodes[$loweredFunctionName]->getFileName()],
86+
$this->functionNodes[$loweredFunctionName]->getNamespace()
87+
);
88+
}
6689
if (!function_exists($functionName)) {
6790
return null;
6891
}
@@ -79,6 +102,51 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
79102

80103
if ($identifier->isConstant()) {
81104
$constantName = $identifier->getName();
105+
$nodeToReflection = new NodeToReflection();
106+
foreach ($this->constantNodes as $stmtConst) {
107+
if ($stmtConst->getNode() instanceof FuncCall) {
108+
$constantReflection = $nodeToReflection->__invoke(
109+
$reflector,
110+
$stmtConst->getNode(),
111+
$this->locatedSourcesByFile[$stmtConst->getFileName()],
112+
$stmtConst->getNamespace()
113+
);
114+
if ($constantReflection === null) {
115+
continue;
116+
}
117+
if (!$constantReflection instanceof ReflectionConstant) {
118+
throw new \PHPStan\ShouldNotHappenException();
119+
}
120+
if ($constantReflection->getName() !== $identifier->getName()) {
121+
continue;
122+
}
123+
124+
return $constantReflection;
125+
}
126+
127+
foreach (array_keys($stmtConst->getNode()->consts) as $i) {
128+
$constantReflection = $nodeToReflection->__invoke(
129+
$reflector,
130+
$stmtConst->getNode(),
131+
$this->locatedSourcesByFile[$stmtConst->getFileName()],
132+
$stmtConst->getNamespace(),
133+
$i
134+
);
135+
if ($constantReflection === null) {
136+
continue;
137+
}
138+
if (!$constantReflection instanceof ReflectionConstant) {
139+
throw new \PHPStan\ShouldNotHappenException();
140+
}
141+
if ($constantReflection->getName() !== $identifier->getName()) {
142+
continue;
143+
}
144+
145+
return $constantReflection;
146+
}
147+
}
148+
149+
82150
if (!defined($constantName)) {
83151
return null;
84152
}
@@ -102,6 +170,17 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
102170
return null;
103171
}
104172

173+
$loweredClassName = strtolower($identifier->getName());
174+
if (array_key_exists($loweredClassName, $this->classNodes)) {
175+
$nodeToReflection = new NodeToReflection();
176+
return $nodeToReflection->__invoke(
177+
$reflector,
178+
$this->classNodes[$loweredClassName]->getNode(),
179+
$this->locatedSourcesByFile[$this->classNodes[$loweredClassName]->getFileName()],
180+
$this->classNodes[$loweredClassName]->getNamespace()
181+
);
182+
}
183+
105184
$locateResult = $this->locateClassByName($identifier->getName());
106185
if ($locateResult === null) {
107186
return null;
@@ -114,36 +193,43 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
114193
private function findReflection(Reflector $reflector, string $file, Identifier $identifier): ?Reflection
115194
{
116195
$result = $this->fileNodesFetcher->fetchNodes($file);
196+
$this->locatedSourcesByFile[$file] = $result->getLocatedSource();
197+
foreach ($result->getClassNodes() as $className => $fetchedClassNode) {
198+
$this->classNodes[$className] = $fetchedClassNode;
199+
}
200+
foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) {
201+
$this->functionNodes[$functionName] = $fetchedFunctionNode;
202+
}
203+
foreach ($result->getConstantNodes() as $fetchedConstantNode) {
204+
$this->constantNodes[] = $fetchedConstantNode;
205+
}
206+
117207
$nodeToReflection = new NodeToReflection();
118208
if ($identifier->isClass()) {
119-
foreach ($result->getClassNodes() as $fetchedFunctionNode) {
120-
$reflection = $nodeToReflection->__invoke(
121-
$reflector,
122-
$fetchedFunctionNode->getNode(),
123-
$result->getLocatedSource(),
124-
$fetchedFunctionNode->getNamespace()
125-
);
126-
if ($reflection === null) {
127-
continue;
128-
}
129-
130-
return $reflection;
209+
$identifierName = strtolower($identifier->getName());
210+
if (!array_key_exists($identifierName, $this->classNodes)) {
211+
return null;
131212
}
213+
214+
return $nodeToReflection->__invoke(
215+
$reflector,
216+
$this->classNodes[$identifierName]->getNode(),
217+
$result->getLocatedSource(),
218+
$this->classNodes[$identifierName]->getNamespace()
219+
);
132220
}
133221
if ($identifier->isFunction()) {
134-
foreach ($result->getFunctionNodes() as $fetchedFunctionNode) {
135-
$reflection = $nodeToReflection->__invoke(
136-
$reflector,
137-
$fetchedFunctionNode->getNode(),
138-
$result->getLocatedSource(),
139-
$fetchedFunctionNode->getNamespace()
140-
);
141-
if ($reflection === null) {
142-
continue;
143-
}
144-
145-
return $reflection;
222+
$identifierName = strtolower($identifier->getName());
223+
if (!array_key_exists($identifierName, $this->functionNodes)) {
224+
return null;
146225
}
226+
227+
return $nodeToReflection->__invoke(
228+
$reflector,
229+
$this->functionNodes[$identifierName]->getNode(),
230+
$result->getLocatedSource(),
231+
$this->functionNodes[$identifierName]->getNamespace()
232+
);
147233
}
148234

149235
return null;

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class OptimizedDirectorySourceLocator implements SourceLocator
3333
private array $functionNodes = [];
3434

3535
/** @var array<string, \Roave\BetterReflection\SourceLocator\Located\LocatedSource> */
36-
private array $locatedSourcesByFile;
36+
private array $locatedSourcesByFile = [];
3737

3838
public function __construct(
3939
FileNodesFetcher $fileNodesFetcher,

src/Testing/TestCase.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use PHPStan\Reflection\BetterReflection\Reflector\MemoizingFunctionReflector;
3939
use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator;
4040
use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker;
41+
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
4142
use PHPStan\Reflection\ClassReflection;
4243
use PHPStan\Reflection\FunctionReflectionFactory;
4344
use PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension;
@@ -429,7 +430,7 @@ public static function getReflectors(): array
429430
});
430431
$reflectionSourceStubber = new ReflectionSourceStubber();
431432
$locators[] = new PhpInternalSourceLocator($astLocator, new PhpStormStubsSourceStubber($phpParser));
432-
$locators[] = new AutoloadSourceLocator($astLocator);
433+
$locators[] = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class));
433434
$locators[] = new PhpInternalSourceLocator($astLocator, $reflectionSourceStubber);
434435
$locators[] = new EvaledCodeSourceLocator($astLocator, $reflectionSourceStubber);
435436
$sourceLocator = new MemoizingSourceLocator(new AggregateSourceLocator($locators));

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ public function testExtendingUnknownClass(): void
6464
{
6565
$errors = $this->runAnalyse(__DIR__ . '/data/extending-unknown-class.php');
6666
$this->assertCount(1, $errors);
67-
$this->assertSame(5, $errors[0]->getLine());
68-
$this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage());
67+
68+
if (self::$useStaticReflectionProvider) {
69+
$this->assertSame(5, $errors[0]->getLine());
70+
$this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage());
71+
} else {
72+
$this->assertNull($errors[0]->getLine());
73+
$this->assertSame('Class ExtendingUnknownClass\Bar not found and could not be autoloaded.', $errors[0]->getMessage());
74+
}
6975
}
7076

7177
public function testExtendingKnownClassWithCheck(): void
@@ -201,11 +207,11 @@ public function testTwoSameClassesInSingleFile(): void
201207

202208
$error = $errors[1];
203209
$this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage());
204-
$this->assertSame(17, $error->getLine());
210+
$this->assertSame(18, $error->getLine());
205211

206212
$error = $errors[2];
207213
$this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage());
208-
$this->assertSame(20, $error->getLine());
214+
$this->assertSame(21, $error->getLine());
209215
}
210216

211217
/**

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9018,7 +9018,6 @@ public function testDynamicConstants(
90189018
string $expression
90199019
): void
90209020
{
9021-
require_once __DIR__ . '/data/dynamic-constant.php';
90229021
$this->assertTypes(
90239022
__DIR__ . '/data/dynamic-constant.php',
90249023
$description,

tests/PHPStan/Analyser/data/two-same-classes.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ class Foo
1010

1111
}
1212

13-
class Foo
14-
{
13+
if (rand(0, 0)) {
14+
class Foo
15+
{
1516

16-
/** @var int */
17-
private $prop = 'str';
17+
/** @var int */
18+
private $prop = 'str';
1819

19-
/** @var int */
20-
private $prop2 = 'str';
20+
/** @var int */
21+
private $prop2 = 'str';
2122

23+
}
2224
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
use PHPStan\Testing\TestCase;
66
use Roave\BetterReflection\Reflector\ClassReflector;
7+
use Roave\BetterReflection\Reflector\ConstantReflector;
78
use Roave\BetterReflection\Reflector\FunctionReflector;
89
use TestSingleFileSourceLocator\AFoo;
910

10-
function testFunctionForLocator()
11+
function testFunctionForLocator(): void // phpcs:disable
1112
{
1213

1314
}
@@ -22,11 +23,28 @@ public function testAutoloadEverythingInFile(): void
2223
$locator = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class));
2324
$classReflector = new ClassReflector($locator);
2425
$functionReflector = new FunctionReflector($locator, $classReflector);
26+
$constantReflector = new ConstantReflector($locator, $classReflector);
2527
$aFoo = $classReflector->reflect(AFoo::class);
28+
$this->assertNotNull($aFoo->getFileName());
2629
$this->assertSame('a.php', basename($aFoo->getFileName()));
2730

2831
$testFunctionReflection = $functionReflector->reflect('PHPStan\\Reflection\\BetterReflection\\SourceLocator\testFunctionForLocator');
2932
$this->assertSame(__FILE__, $testFunctionReflection->getFileName());
33+
34+
$someConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\SOME_CONSTANT');
35+
$this->assertNotNull($someConstant->getFileName());
36+
$this->assertSame('a.php', basename($someConstant->getFileName()));
37+
$this->assertSame(1, $someConstant->getValue());
38+
39+
$anotherConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\ANOTHER_CONSTANT');
40+
$this->assertNotNull($anotherConstant->getFileName());
41+
$this->assertSame('a.php', basename($anotherConstant->getFileName()));
42+
$this->assertSame(2, $anotherConstant->getValue());
43+
44+
$doFooFunctionReflection = $functionReflector->reflect('TestSingleFileSourceLocator\\doFoo');
45+
$this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName());
46+
$this->assertNotNull($doFooFunctionReflection->getFileName());
47+
$this->assertSame('a.php', basename($doFooFunctionReflection->getFileName()));
3048
}
3149

3250
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ function doFoo()
1111
{
1212

1313
}
14+
15+
define('TestSingleFileSourceLocator\\SOME_CONSTANT', 1);
16+
17+
const ANOTHER_CONSTANT = 2;

tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ protected function getRule(): Rule
2222

2323
public function testClassWithWrongCase(): void
2424
{
25-
require_once __DIR__ . '/data/trait-use.php';
2625
$this->analyse([__DIR__ . '/data/trait-use.php'], [
2726
[
2827
'Trait TraitUseCase\FooTrait referenced with incorrect case: TraitUseCase\FOOTrait.',

0 commit comments

Comments
 (0)