Skip to content

Commit

Permalink
AutoloadSourceLocator - select the correct class node based on the line
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 13, 2020
1 parent b28368a commit e2fe1ef
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ class AutoloadSourceLocator implements SourceLocator

private FileNodesFetcher $fileNodesFetcher;

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

/** @var array<string, Reflection|null> */
private array $classReflections = [];

/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\Function_>> */
private array $functionNodes = [];

Expand Down Expand Up @@ -82,7 +85,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
return null;
}

return $this->findReflection($reflector, $reflectionFileName, $identifier);
return $this->findReflection($reflector, $reflectionFileName, $identifier, null);
}

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

$loweredClassName = strtolower($identifier->getName());
if (array_key_exists($loweredClassName, $this->classNodes)) {
$nodeToReflection = new NodeToReflection();
return $nodeToReflection->__invoke(
$reflector,
$this->classNodes[$loweredClassName]->getNode(),
$this->locatedSourcesByFile[$this->classNodes[$loweredClassName]->getFileName()],
$this->classNodes[$loweredClassName]->getNamespace()
);
if (array_key_exists($loweredClassName, $this->classReflections)) {
return $this->classReflections[$loweredClassName];
}

$locateResult = $this->locateClassByName($identifier->getName());
if ($locateResult === null) {
return null;
}
[$potentiallyLocatedFile, $className] = $locateResult;
[$potentiallyLocatedFile, $className, $startLine] = $locateResult;

return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()));
return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()), $startLine);
}

private function findReflection(Reflector $reflector, string $file, Identifier $identifier): ?Reflection
private function findReflection(Reflector $reflector, string $file, Identifier $identifier, ?int $startLine): ?Reflection
{
if (!array_key_exists($file, $this->locatedSourcesByFile)) {
$result = $this->fileNodesFetcher->fetchNodes($file);
$this->locatedSourcesByFile[$file] = $result->getLocatedSource();
foreach ($result->getClassNodes() as $className => $fetchedClassNode) {
$this->classNodes[$className] = $fetchedClassNode;
foreach ($result->getClassNodes() as $className => $fetchedClassNodes) {
foreach ($fetchedClassNodes as $fetchedClassNode) {
$this->classNodes[$className][] = $fetchedClassNode;
}
}
foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) {
$this->functionNodes[$functionName] = $fetchedFunctionNode;
Expand All @@ -196,16 +195,27 @@ private function findReflection(Reflector $reflector, string $file, Identifier $
$nodeToReflection = new NodeToReflection();
if ($identifier->isClass()) {
$identifierName = strtolower($identifier->getName());
if (array_key_exists($identifierName, $this->classReflections)) {
return $this->classReflections[$identifierName];
}
if (!array_key_exists($identifierName, $this->classNodes)) {
return null;
}

return $nodeToReflection->__invoke(
$reflector,
$this->classNodes[$identifierName]->getNode(),
$locatedSource,
$this->classNodes[$identifierName]->getNamespace()
);
foreach ($this->classNodes[$identifierName] as $classNode) {
if ($startLine !== null && $startLine !== $classNode->getNode()->getStartLine()) {
continue;
}

return $this->classReflections[$identifierName] = $nodeToReflection->__invoke(
$reflector,
$classNode->getNode(),
$locatedSource,
$classNode->getNamespace()
);
}

return null;
}
if ($identifier->isFunction()) {
$identifierName = strtolower($identifier->getName());
Expand Down Expand Up @@ -243,7 +253,7 @@ public function locateIdentifiersByType(Reflector $reflector, IdentifierType $id
* error handler temporarily.
*
* @throws ReflectionException
* @return array{string, string}|null
* @return array{string, string, int|null}|null
*/
private function locateClassByName(string $className): ?array
{
Expand All @@ -259,13 +269,13 @@ private function locateClassByName(string $className): ?array
return null;
}

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

$this->silenceErrors();

try {
/** @var array{string, string}|null */
/** @var array{string, string, null}|null */
return FileReadTrapStreamWrapper::withStreamWrapperOverride(
static function () use ($className): ?array {
$functions = spl_autoload_functions();
Expand All @@ -283,7 +293,7 @@ static function () use ($className): ?array {
* This will not be `null` when the autoloader tried to read a file.
*/
if (FileReadTrapStreamWrapper::$autoloadLocatedFile !== null) {
return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className];
return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className, null];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CachingVisitor extends NodeVisitorAbstract

private string $fileName;

/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
/** @var array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> */
private array $classNodes;

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

if ($node instanceof \PhpParser\Node\Stmt\ClassLike) {
if ($node->name !== null) {
$this->classNodes[strtolower($node->namespacedName->toString())] = new FetchedNode(
$this->classNodes[strtolower($node->namespacedName->toString())][] = new FetchedNode(
$node,
$this->currentNamespaceNode,
$this->fileName
Expand Down Expand Up @@ -106,7 +106,7 @@ public function leaveNode(\PhpParser\Node $node)
}

/**
* @return array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>>
* @return array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>>
*/
public function getClassNodes(): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class FetchedNodesResult
{

/** @var array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>> */
/** @var array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>> */
private array $classNodes;

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

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

/**
* @return array<string, FetchedNode<\PhpParser\Node\Stmt\ClassLike>>
* @return array<string, array<FetchedNode<\PhpParser\Node\Stmt\ClassLike>>>
*/
public function getClassNodes(): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
$fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file);
$locatedSource = $fetchedNodesResult->getLocatedSource();
$this->locatedSourcesByFile[$file] = $locatedSource;
foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNode) {
$this->classNodes[$identifierName] = $fetchedClassNode;
foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNodes) {
foreach ($fetchedClassNodes as $fetchedClassNode) {
$this->classNodes[$identifierName] = $fetchedClassNode;
break;
}
}

if (!array_key_exists($className, $this->classNodes)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
return null;
}

$classReflection = $nodeToReflection->__invoke(
$reflector,
$classNodes[$className]->getNode(),
$this->fetchedNodesResult->getLocatedSource(),
$classNodes[$className]->getNamespace()
);
if (!$classReflection instanceof ReflectionClass) {
throw new \PHPStan\ShouldNotHappenException();
}
foreach ($classNodes[$className] as $classNode) {
$classReflection = $nodeToReflection->__invoke(
$reflector,
$classNode->getNode(),
$this->fetchedNodesResult->getLocatedSource(),
$classNode->getNamespace()
);
if (!$classReflection instanceof ReflectionClass) {
throw new \PHPStan\ShouldNotHappenException();
}

return $classReflection;
return $classReflection;
}
}

if ($identifier->isFunction()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace PHPStan\Reflection\BetterReflection\SourceLocator;

use PHPStan\Testing\TestCase;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\Reflector\ConstantReflector;
use Roave\BetterReflection\Reflector\FunctionReflector;
use TestSingleFileSourceLocator\AFoo;
use TestSingleFileSourceLocator\InCondition;

function testFunctionForLocator(): void // phpcs:disable
{
Expand Down Expand Up @@ -45,6 +47,16 @@ public function testAutoloadEverythingInFile(): void
$this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName());
$this->assertNotNull($doFooFunctionReflection->getFileName());
$this->assertSame('a.php', basename($doFooFunctionReflection->getFileName()));

class_exists(InCondition::class);
$classInCondition = $classReflector->reflect(InCondition::class);
$classInConditionFilename = $classInCondition->getFileName();
$this->assertNotNull($classInConditionFilename);
$this->assertSame('a.php', basename($classInConditionFilename));
$this->assertSame(InCondition::class, $classInCondition->getName());
$this->assertSame(25, $classInCondition->getStartLine());
$this->assertInstanceOf(ReflectionClass::class, $classInCondition->getParentClass());
$this->assertSame(AFoo::class, $classInCondition->getParentClass()->getName());
}

}
17 changes: 17 additions & 0 deletions tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,20 @@ function doFoo()
define('TestSingleFileSourceLocator\\SOME_CONSTANT', 1);

const ANOTHER_CONSTANT = 2;

if (false) {
class InCondition
{

}
} elseif (true) {
class InCondition extends AFoo
{

}
} else {
class InCondition extends \stdClass
{

}
}

0 comments on commit e2fe1ef

Please sign in to comment.