diff --git a/fixtures/completion/property.php b/fixtures/completion/property.php index 677bcbb..17ae95d 100644 --- a/fixtures/completion/property.php +++ b/fixtures/completion/property.php @@ -1,4 +1,4 @@ t +$obj-> diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index a8d4020..f13e343 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -56,12 +56,36 @@ class CompletionProvider || $node instanceof Node\Expr\StaticPropertyFetch || $node instanceof Node\Expr\ClassConstFetch ) { - /** The FQN to be completed */ - $prefix = $this->definitionResolver->resolveReferenceNodeToFqn($node) ?? ''; - $prefixLen = strlen($prefix); + $nodeToResolve = $node; + if (!is_string($node->name)) { + // If the name is an Error node, just filter by the class + if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { + $nodeToResolve = $node->var; + } else { + $nodeToResolve = $node->class; + } + } + $prefixes = DefinitionResolver::getFqnsFromType( + $this->definitionResolver->resolveExpressionNodeToType($nodeToResolve) + ); + if (!is_string($node->name)) { + // If we are just filtering by the class, add the appropiate operator to the prefix + // to filter the type of symbol + foreach ($prefixes as &$prefix) { + if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { + $prefix .= '->'; + } else if ($node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\ClassConstFetch) { + $prefix .= '::'; + } else if ($node instanceof Node\Expr\StaticPropertyFetch) { + $prefix .= '::$'; + } + } + } foreach ($this->project->getDefinitions() as $fqn => $def) { - if (substr($fqn, 0, $prefixLen) === $prefix && !$def->isGlobal) { - $items[] = CompletionItem::fromDefinition($def); + foreach ($prefixes as $prefix) { + if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) { + $items[] = CompletionItem::fromDefinition($def); + } } } } else if ( diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index e9e5eac..bda1e8b 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -146,6 +146,31 @@ class DefinitionResolver return $this->project->getDefinition($fqn, $globalFallback); } + /** + * Returns all possible FQNs in a type + * + * @param Type $type + * @return string[] + */ + public static function getFqnsFromType(Type $type): array + { + $fqns = []; + if ($type instanceof Types\Object_) { + $fqsen = $type->getFqsen(); + if ($fqsen !== null) { + $fqns[] = substr((string)$fqsen, 1); + } + } + if ($type instanceof Types\Compound) { + for ($i = 0; $t = $type->get($i); $i++) { + foreach (self::getFqnsFromType($type) as $fqn) { + $fqns[] = $fqn; + } + } + } + return $fqns; + } + /** * Given any node, returns the FQN of the symbol that is referenced * Returns null if the FQN could not be resolved or the reference node references a variable diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index 3b57c82..36ea265 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -30,13 +30,37 @@ class CompletionTest extends TestCase $this->textDocument = new Server\TextDocument($this->project, $client); } - public function testForPropertiesAndMethods() + public function testPropertyAndMethodWithPrefix() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property_with_prefix.php'); + $this->project->openDocument($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(3, 7) + )->wait(); + $this->assertEquals([ + new CompletionItem( + 'testProperty', + CompletionItemKind::PROPERTY, + '\TestClass', // Type of the property + 'Reprehenderit magna velit mollit ipsum do.' + ), + new CompletionItem( + 'testMethod', + CompletionItemKind::METHOD, + '\TestClass', // Return type of the method + 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' + ) + ], $items); + } + + public function testPropertyAndMethodWithoutPrefix() { $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php'); $this->project->openDocument($completionUri, file_get_contents($completionUri)); $items = $this->textDocument->completion( new TextDocumentIdentifier($completionUri), - new Position(3, 7) + new Position(3, 6) )->wait(); $this->assertEquals([ new CompletionItem(