Skip to content

Commit

Permalink
Merge pull request #5091 from ohader/namespace-resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Sep 30, 2021
2 parents 377d3bd + 51333e8 commit 97a3d67
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 21 deletions.
53 changes: 42 additions & 11 deletions src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php
Expand Up @@ -28,17 +28,8 @@ class SimpleNameResolver extends NodeVisitorAbstract
private $end_change;

/**
* Constructs a name resolution visitor.
*
* Options:
* * preserveOriginalNames (default false): An "originalName" attribute will be added to
* all name nodes that underwent resolution.
* * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
* resolvedName attribute is added. (Names that cannot be statically resolved receive a
* namespacedName attribute, as usual.)
*
* @param ErrorHandler $errorHandler Error handler
* @param array<int, array{int, int, int, int}> $offset_map
* @param null|array<int, array{int, int, int, int, int, string}> $offset_map
*/
public function __construct(ErrorHandler $errorHandler, ?array $offset_map = null)
{
Expand Down Expand Up @@ -74,6 +65,17 @@ public function enterNode(Node $node): ?int
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, $node->prefix);
}
} elseif ($node instanceof Stmt\Class_) {
if (null !== $node->extends) {
$node->extends = $this->resolveClassName($node->extends);
}
foreach ($node->implements as &$interface) {
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
}

if ($node instanceof Stmt\ClassMethod
Expand Down Expand Up @@ -190,6 +192,9 @@ private function resolveType(?Node $node): ?Node
/**
* Resolve name, according to name resolver options.
*
* CAVE: Attribute values are of type `string`, this is
* different to PhpParser's `NameResolver` using objects.
*
* @param Name $name Function or constant name to resolve
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
*
Expand All @@ -200,8 +205,16 @@ protected function resolveName(Name $name, int $type): Name
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
$name->setAttribute('resolvedName', $resolvedName->toString());
} else {
$namespaceName = Name\FullyQualified::concat(
$this->nameContext->getNamespace(),
$name,
$name->getAttributes()
);
if ($namespaceName instanceof Name) {
$name->setAttribute('namespacedName', $namespaceName->toString());
}
}

return $name;
}

Expand All @@ -210,6 +223,24 @@ protected function resolveClassName(Name $name): Name
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
}

protected function addNamespacedName(Stmt\Class_ $node): void
{
/** @psalm-suppress UndefinedPropertyAssignment */
$node->namespacedName = Name::concat(
$this->nameContext->getNamespace(),
(string)$node->name
);
}

protected function resolveAttrGroups(Stmt\Class_ $node): void
{
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$attr->name = $this->resolveClassName($attr->name);
}
}
}

protected function resolveTrait(Stmt\Trait_ $node): void
{
$resolvedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name);
Expand Down
42 changes: 32 additions & 10 deletions tests/CodebaseTest.php
@@ -1,6 +1,8 @@
<?php
namespace Psalm\Tests;

use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
Expand All @@ -9,6 +11,7 @@
use Psalm\Tests\Internal\Provider\ClassLikeStorageInstanceCacheProvider;
use Psalm\Type;

use function array_map;
use function array_values;
use function get_class;

Expand Down Expand Up @@ -126,7 +129,11 @@ public function customMetadataIsPersisted(): void
$this->addFile(
'somefile.php',
'<?php
class C {
namespace Psalm\CurrentTest;
abstract class A {}
interface I {}
class C extends A implements I
{
/** @var string */
private $prop = "";
Expand All @@ -141,9 +148,20 @@ public function m(int $_i = 1) {}
*/
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
{
$stmt = $event->getStmt();
$storage = $event->getStorage();
$codebase = $event->getCodebase();
if ($storage->name === 'C') {
if ($storage->name === 'Psalm\\CurrentTest\\C' && $stmt instanceof Class_) {
$storage->custom_metadata['fqcn'] = (string)($stmt->namespacedName ?? $stmt->name);
$storage->custom_metadata['extends'] = $stmt->extends instanceof Name
? (string)$stmt->extends->getAttribute('resolvedName')
: '';
$storage->custom_metadata['implements'] = array_map(
function (Name $aspect): string {
return (string)$aspect->getAttribute('resolvedName');
},
$stmt->implements
);
$storage->custom_metadata['a'] = 'b';
$storage->methods['m']->custom_metadata['c'] = 'd';
$storage->properties['prop']->custom_metadata['e'] = 'f';
Expand All @@ -158,17 +176,21 @@ public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)

$this->analyzeFile('somefile.php', new Context);

$this->codebase->classlike_storage_provider->remove('C');
$this->codebase->exhumeClassLikeStorage('C', 'somefile.php');
$fixtureNamespace = 'Psalm\\CurrentTest\\';
$this->codebase->classlike_storage_provider->remove($fixtureNamespace . 'C');
$this->codebase->exhumeClassLikeStorage($fixtureNamespace . 'C', 'somefile.php');

$class_storage = $this->codebase->classlike_storage_provider->get('C');
$class_storage = $this->codebase->classlike_storage_provider->get($fixtureNamespace . 'C');
$file_storage = $this->codebase->file_storage_provider->get('somefile.php');

$this->assertSame('b', $class_storage->custom_metadata['a']);
$this->assertSame('d', $class_storage->methods['m']->custom_metadata['c']);
$this->assertSame('f', $class_storage->properties['prop']->custom_metadata['e']);
$this->assertSame('h', $class_storage->methods['m']->params[0]->custom_metadata['g']);
$this->assertSame('j', $file_storage->custom_metadata['i']);
self::assertSame($fixtureNamespace . 'C', $class_storage->custom_metadata['fqcn']);
self::assertSame($fixtureNamespace . 'A', $class_storage->custom_metadata['extends']);
self::assertSame([$fixtureNamespace . 'I'], $class_storage->custom_metadata['implements']);
self::assertSame('b', $class_storage->custom_metadata['a']);
self::assertSame('d', $class_storage->methods['m']->custom_metadata['c']);
self::assertSame('f', $class_storage->properties['prop']->custom_metadata['e']);
self::assertSame('h', $class_storage->methods['m']->params[0]->custom_metadata['g']);
self::assertSame('j', $file_storage->custom_metadata['i']);
}

/**
Expand Down

0 comments on commit 97a3d67

Please sign in to comment.