Skip to content

Commit bd0ed24

Browse files
committed
[Feature] AST-preserving node name resolution, implements #136
1 parent fe2b2b0 commit bd0ed24

8 files changed

+94
-93
lines changed

src/ReflectionAttribute.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ public function getNode(): Node\Attribute
4949
$nodeExpressionResolver = new NodeExpressionResolver($this);
5050
foreach ($node->attrGroups as $attrGroup) {
5151
foreach ($attrGroup->attrs as $attr) {
52-
if ($attr->name->toString() !== $this->attributeName) {
52+
$attributeNodeName = $attr->name;
53+
// Unpack fully-resolved class name from attribute if we have it
54+
if ($attributeNodeName->hasAttribute('resolvedName')) {
55+
$attributeNodeName = $attributeNodeName->getAttribute('resolvedName');
56+
}
57+
if ($attributeNodeName->toString() !== $this->attributeName) {
5358
continue;
5459
}
5560

src/ReflectionClass.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Go\ParserReflection\Traits\AttributeResolverTrait;
1616
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
1717
use Go\ParserReflection\Traits\ReflectionClassLikeTrait;
18+
use PhpParser\Node\Name;
1819
use PhpParser\Node\Name\FullyQualified;
1920
use PhpParser\Node\Stmt\ClassLike;
2021
use PhpParser\Node\Stmt\Enum_;
@@ -68,8 +69,8 @@ public static function collectInterfacesFromClassNode(ClassLike $classLikeNode):
6869

6970
if (count($implementsList) > 0) {
7071
foreach ($implementsList as $implementNode) {
71-
if ($implementNode instanceof FullyQualified) {
72-
$implementName = $implementNode->toString();
72+
if ($implementNode instanceof Name && $implementNode->getAttribute('resolvedName') instanceof FullyQualified) {
73+
$implementName = $implementNode->getAttribute('resolvedName')->toString();
7374
$interface = interface_exists($implementName, false)
7475
? new parent($implementName)
7576
: new static($implementName);
@@ -108,8 +109,8 @@ public static function collectTraitsFromClassNode(ClassLike $classLikeNode, arra
108109
foreach ($classLikeNode->stmts as $classLevelNode) {
109110
if ($classLevelNode instanceof TraitUse) {
110111
foreach ($classLevelNode->traits as $classTraitName) {
111-
if ($classTraitName instanceof FullyQualified) {
112-
$traitName = $classTraitName->toString();
112+
if ($classTraitName instanceof Name && $classTraitName->getAttribute('resolvedName') instanceof FullyQualified) {
113+
$traitName = $classTraitName->getAttribute('resolvedName')->toString();
113114
$trait = trait_exists($traitName, false)
114115
? new parent($traitName)
115116
: new static($traitName);

src/ReflectionEngine.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ public static function init(LocatorInterface $locator): void
5151
self::$parser = (new ParserFactory())->createForHostVersion();
5252

5353
self::$traverser = $traverser = new NodeTraverser();
54-
$traverser->addVisitor(new NameResolver());
54+
$traverser->addVisitor(new NameResolver(
55+
null,
56+
[
57+
'preserveOriginalNames' => true,
58+
'replaceNodes' => false,
59+
]
60+
));
5561
$traverser->addVisitor(new RootNamespaceNormalizer());
5662

5763
self::$locator = $locator;

src/ReflectionParameter.php

+5
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ public function getClass(): ?\ReflectionClass
204204
$parameterType = new Name\FullyQualified(\Traversable::class);
205205
}
206206
if ($parameterType instanceof Name) {
207+
// If we have resolved type name, we should use it instead
208+
if ($parameterType->hasAttribute('resolvedName')) {
209+
$parameterType = $parameterType->getAttribute('resolvedName');
210+
}
211+
207212
if (!$parameterType instanceof Name\FullyQualified) {
208213
$parameterTypeName = $parameterType->toString();
209214

src/Resolver/NodeExpressionResolver.php

+48-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Go\ParserReflection\ReflectionClass;
1616
use Go\ParserReflection\ReflectionException;
1717
use Go\ParserReflection\ReflectionFileNamespace;
18+
use Go\ParserReflection\ReflectionNamedType;
1819
use PhpParser\Node;
1920
use PhpParser\Node\Const_;
2021
use PhpParser\Node\Expr;
@@ -112,12 +113,17 @@ public function getConstExpression(): ?string
112113
// Clone node to avoid possible side-effects
113114
$node = clone $this->nodeStack[$this->nodeLevel];
114115
if ($node instanceof Expr\ConstFetch) {
115-
if ($node->name->isFullyQualified()) {
116+
$constantNodeName = $node->name;
117+
// Unpack fully-resolved name if we have it inside attribute
118+
if ($constantNodeName->hasAttribute('resolvedName')) {
119+
$constantNodeName = $constantNodeName->getAttribute('resolvedName');
120+
}
121+
if ($constantNodeName->isFullyQualified()) {
116122
// For full-qualified names we would like to remove leading "\"
117-
$node->name = new Name(ltrim($node->name->toString(), '\\'));
123+
$node->name = new Name(ltrim($constantNodeName->toString(), '\\'));
118124
} else {
119125
// For relative names we would like to add namespace prefix
120-
$node->name = new Name($this->resolveScalarMagicConstNamespace() . '\\' . $node->name->toString());
126+
$node->name = new Name($this->resolveScalarMagicConstNamespace() . '\\' . $constantNodeName->toString());
121127
}
122128
}
123129
// All long array nodes are pretty-printed by PHP in short format
@@ -185,6 +191,15 @@ protected function resolveNameFullyQualified(Name\FullyQualified $node): string
185191
return $node->toString();
186192
}
187193

194+
private function resolveName(Name $node): string
195+
{
196+
if ($node->hasAttribute('resolvedName')) {
197+
return $node->getAttribute('resolvedName')->toString();
198+
}
199+
200+
return $node->toString();
201+
}
202+
188203
protected function resolveIdentifier(Node\Identifier $node): string
189204
{
190205
return $node->toString();
@@ -332,8 +347,13 @@ protected function resolveExprConstFetch(Expr\ConstFetch $node)
332347
$constantValue = null;
333348
$isResolved = false;
334349

335-
$isFQNConstant = $node->name instanceof Node\Name\FullyQualified;
336-
$constantName = $node->name->toString();
350+
$nodeConstantName = $node->name;
351+
// If we have resolved type name
352+
if ($nodeConstantName->hasAttribute('resolvedName')) {
353+
$nodeConstantName = $nodeConstantName->getAttribute('resolvedName');
354+
}
355+
$isFQNConstant = $nodeConstantName instanceof Node\Name\FullyQualified;
356+
$constantName = $nodeConstantName->toString();
337357

338358
if (!$isFQNConstant && method_exists($this->context, 'getFileName')) {
339359
$fileName = $this->context->getFileName();
@@ -365,18 +385,29 @@ protected function resolveExprConstFetch(Expr\ConstFetch $node)
365385

366386
protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node)
367387
{
368-
$classToReflect = $node->class;
369-
if (!($classToReflect instanceof Node\Name)) {
370-
$classToReflect = $this->resolve($classToReflect);
371-
if (!is_string($classToReflect)) {
388+
$classToReflectNodeName = $node->class;
389+
if (!($classToReflectNodeName instanceof Node\Name)) {
390+
$classToReflectNodeName = $this->resolve($classToReflectNodeName);
391+
if (!is_string($classToReflectNodeName)) {
372392
throw new ReflectionException("Unable to resolve class constant.");
373393
}
374394
// Strings evaluated as class names are always treated as fully
375395
// qualified.
376-
$classToReflect = new Node\Name\FullyQualified(ltrim($classToReflect, '\\'));
396+
$classToReflectNodeName = new Node\Name\FullyQualified(ltrim($classToReflectNodeName, '\\'));
397+
}
398+
// Unwrap resolved class name if we have it inside attributes
399+
if ($classToReflectNodeName->hasAttribute('resolvedName')) {
400+
$classToReflectNodeName = $classToReflectNodeName->getAttribute('resolvedName');
401+
}
402+
$refClass = $this->fetchReflectionClass($classToReflectNodeName);
403+
if (($node->name instanceof Expr\Error)) {
404+
$constantName = '';
405+
} else {
406+
$constantName = match (true) {
407+
$node->name->hasAttribute('resolvedName') => $node->name->getAttribute('resolvedName')->toString(),
408+
default => $node->name->toString(),
409+
};
377410
}
378-
$refClass = $this->fetchReflectionClass($classToReflect);
379-
$constantName = ($node->name instanceof Expr\Error) ? '' : $node->name->toString();
380411

381412
// special handling of ::class constants
382413
if ('class' === $constantName) {
@@ -385,7 +416,7 @@ protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node)
385416

386417
$this->isConstant = true;
387418
$this->isConstExpr = true;
388-
$this->constantName = $classToReflect . '::' . $constantName;
419+
$this->constantName = $classToReflectNodeName . '::' . $constantName;
389420

390421
return $refClass->getConstant($constantName);
391422
}
@@ -581,6 +612,10 @@ private function getDispatchMethodFor(Node $node): string
581612
*/
582613
private function fetchReflectionClass(Node\Name $node)
583614
{
615+
// If we have already resolved node name, we should use it instead
616+
if ($node->hasAttribute('resolvedName')) {
617+
$node = $node->getAttribute('resolvedName');
618+
}
584619
$className = $node->toString();
585620
$isFQNClass = $node instanceof Node\Name\FullyQualified;
586621
if ($isFQNClass) {

src/Resolver/TypeExpressionResolver.php

+4-66
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ private function resolveIdentifier(Node\Identifier $node): ReflectionNamedType
143143

144144
private function resolveName(Name $node): ReflectionNamedType
145145
{
146+
if ($node->hasAttribute('resolvedName')) {
147+
$node = $node->getAttribute('resolvedName');
148+
}
149+
146150
return new ReflectionNamedType($node->toString(), $this->hasDefaultNull, false);
147151
}
148152

@@ -157,70 +161,4 @@ private function getDispatchMethodFor(Node $node): string
157161

158162
return 'resolve' . str_replace('_', '', $nodeType);
159163
}
160-
161-
/**
162-
* Utility method to fetch reflection class instance by name
163-
*
164-
* Supports:
165-
* 'self' keyword
166-
* 'parent' keyword
167-
* not-FQN class names
168-
*
169-
* @param Node\Name $node Class name node
170-
*
171-
* @return bool|\ReflectionClass
172-
*
173-
* @throws ReflectionException
174-
*/
175-
private function fetchReflectionClass(Node\Name $node)
176-
{
177-
$className = $node->toString();
178-
$isFQNClass = $node instanceof Node\Name\FullyQualified;
179-
if ($isFQNClass) {
180-
// check to see if the class is already loaded and is safe to use
181-
// PHP's ReflectionClass to determine if the class is user defined
182-
if (class_exists($className, false)) {
183-
$refClass = new \ReflectionClass($className);
184-
if (!$refClass->isUserDefined()) {
185-
return $refClass;
186-
}
187-
}
188-
189-
return new ReflectionClass($className);
190-
}
191-
192-
if ('self' === $className) {
193-
if ($this->context instanceof \ReflectionClass) {
194-
return $this->context;
195-
}
196-
197-
if (method_exists($this->context, 'getDeclaringClass')) {
198-
return $this->context->getDeclaringClass();
199-
}
200-
}
201-
202-
if ('parent' === $className) {
203-
if ($this->context instanceof \ReflectionClass) {
204-
return $this->context->getParentClass();
205-
}
206-
207-
if (method_exists($this->context, 'getDeclaringClass')) {
208-
return $this->context->getDeclaringClass()
209-
->getParentClass()
210-
;
211-
}
212-
}
213-
214-
if (method_exists($this->context, 'getFileName')) {
215-
/** @var ReflectionFileNamespace|null $fileNamespace */
216-
$fileName = $this->context->getFileName();
217-
$namespaceName = $this->resolveScalarMagicConstNamespace();
218-
219-
$fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
220-
221-
return $fileNamespace->getClass($className);
222-
}
223-
224-
throw new ReflectionException("Can not resolve class $className");
225-
}
226164
}

src/Traits/AttributeResolverTrait.php

+14-3
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,18 @@ public function getAttributes(?string $name = null, int $flags = 0): array
4040
$arguments[] = $nodeExpressionResolver->getValue();
4141
}
4242

43+
$attributeNameNode = $attr->name;
44+
// If we have resoled node name, then we should use it instead
45+
if ($attributeNameNode->hasAttribute('resolvedName')) {
46+
$attributeNameNode = $attributeNameNode->getAttribute('resolvedName');
47+
}
4348
if ($name === null) {
44-
$attributes[] = new ReflectionAttribute($attr->name->toString(), $this, $arguments, $this->isAttributeRepeated($attr->name->toString(), $node->attrGroups));
49+
$attributes[] = new ReflectionAttribute($attributeNameNode->toString(), $this, $arguments, $this->isAttributeRepeated($attributeNameNode->toString(), $node->attrGroups));
4550

4651
continue;
4752
}
4853

49-
if ($name !== $attr->name->toString()) {
54+
if ($name !== $attributeNameNode->toString()) {
5055
continue;
5156
}
5257

@@ -63,7 +68,13 @@ private function isAttributeRepeated(string $attributeName, array $attrGroups):
6368

6469
foreach ($attrGroups as $attrGroup) {
6570
foreach ($attrGroup->attrs as $attr) {
66-
if ($attr->name->toString() === $attributeName) {
71+
$attributeNameNode = $attr->name;
72+
// If we have resoled node name, then we should use it instead
73+
if ($attributeNameNode->hasAttribute('resolvedName')) {
74+
$attributeNameNode = $attributeNameNode->getAttribute('resolvedName');
75+
}
76+
77+
if ($attributeNameNode->toString() === $attributeName) {
6778
++$count;
6879
}
6980
}

src/Traits/ReflectionClassLikeTrait.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Go\ParserReflection\ReflectionMethod;
2020
use Go\ParserReflection\ReflectionProperty;
2121
use Go\ParserReflection\Resolver\NodeExpressionResolver;
22+
use PhpParser\Node\Name;
2223
use PhpParser\Node\Name\FullyQualified;
2324
use PhpParser\Node\Stmt\Class_;
2425
use PhpParser\Node\Stmt\ClassConst;
@@ -450,13 +451,12 @@ public function getNamespaceName(): string
450451
public function getParentClass(): \ReflectionClass|false
451452
{
452453
if (!isset($this->parentClass)) {
453-
static $extendsField = 'extends';
454454

455455
$parentClass = false;
456-
$hasExtends = in_array($extendsField, $this->classLikeNode->getSubNodeNames(), true);
457-
$extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
458-
if ($extendsNode instanceof FullyQualified) {
459-
$extendsName = $extendsNode->toString();
456+
$extendsNode = $this->classLikeNode->extends ?? null;
457+
458+
if ($extendsNode instanceof Name && $extendsNode->getAttribute('resolvedName') instanceof FullyQualified) {
459+
$extendsName = $extendsNode->getAttribute('resolvedName')->toString();
460460
$parentClass = $this->createReflectionForClass($extendsName);
461461
}
462462
$this->parentClass = $parentClass;

0 commit comments

Comments
 (0)