From 6894d85aaf2907d72db443f1a8b0a8d52f640900 Mon Sep 17 00:00:00 2001 From: Phil Nelson Date: Tue, 9 Jan 2018 20:38:18 +1100 Subject: [PATCH] fix(DefinitionResolver): resolve self correctly for docblock @return self (#576) --- src/DefinitionResolver.php | 30 +++++++++++++++---- tests/Validation/cases/methodReturnType.php | 3 ++ .../cases/methodReturnType.php.expected.json | 25 ++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index f5e5f7b..990c196 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -526,6 +526,20 @@ class DefinitionResolver return (string)$classNode->getNamespacedName(); } + /** + * Returns the type of the class a node is contained in + * Returns null if the class is anonymous or the node is not contained in a class + * + * @param Node $node The node used to find the containing class + * + * @return Types\Object_|null + */ + private function getContainingClassType(Node $node) + { + $classFqn = $this->getContainingClassFqn($node); + return $classFqn ? new Types\Object_(new Fqsen('\\' . $classFqn)) : null; + } + /** * Returns the assignment or parameter node where a variable was defined * @@ -1110,7 +1124,14 @@ class DefinitionResolver && $returnTags[0]->getType() !== null ) { // Use @return tag - return $returnTags[0]->getType(); + $returnType = $returnTags[0]->getType(); + if ($returnType instanceof Types\Self_) { + $selfType = $this->getContainingClassType($node); + if ($selfType) { + return $selfType; + } + } + return $returnType; } if ($node->returnType !== null && !($node->returnType instanceof PhpParser\MissingToken)) { // Use PHP7 return type hint @@ -1118,10 +1139,9 @@ class DefinitionResolver // Resolve a string like "bool" to a type object return $this->typeResolver->resolve($node->returnType->getText($node->getFileContents())); } elseif ($node->returnType->getResolvedName() === 'self') { - $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); - if ($classNode) { - $classFqn = (string)$classNode->getNamespacedName(); - return new Types\Object_(new Fqsen('\\' . $classFqn)); + $selfType = $this->getContainingClassType($node); + if ($selfType !== null) { + return $selfType; } } return new Types\Object_(new Fqsen('\\' . (string)$node->returnType->getResolvedName())); diff --git a/tests/Validation/cases/methodReturnType.php b/tests/Validation/cases/methodReturnType.php index b4b937d..824314a 100644 --- a/tests/Validation/cases/methodReturnType.php +++ b/tests/Validation/cases/methodReturnType.php @@ -4,4 +4,7 @@ class FooClass { public function foo(): FooClass { return $this; } + + /** @return self */ + public function bar() { } } diff --git a/tests/Validation/cases/methodReturnType.php.expected.json b/tests/Validation/cases/methodReturnType.php.expected.json index 0d537c5..4abbb07 100644 --- a/tests/Validation/cases/methodReturnType.php.expected.json +++ b/tests/Validation/cases/methodReturnType.php.expected.json @@ -49,6 +49,31 @@ "documentation": null, "parameters": [] } + }, + "FooClass->bar()": { + "fqn": "FooClass->bar()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "bar", + "kind": 6, + "location": { + "uri": "./methodReturnType.php" + }, + "containerName": "FooClass" + }, + "type__tostring": "\\FooClass", + "type": {}, + "declarationLine": "public function bar() { }", + "documentation": "", + "signatureInformation": { + "label": "()", + "documentation": "", + "parameters": [] + } } } } \ No newline at end of file