diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 426b552..b479e25 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -132,6 +132,7 @@ class DefinitionResolver implements DefinitionResolverInterface $def->type = $this->getTypeFromNode($node); $def->declarationLine = $this->getDeclarationLineFromNode($node); $def->documentation = $this->getDocumentationFromNode($node); +// var_dump($def); return $def; } @@ -440,6 +441,8 @@ class DefinitionResolver implements DefinitionResolverInterface } // Resolve object $objType = $this->resolveExpressionNodeToType($expr->var); + // var_dump((string)$expr->var->name); +// var_dump($objType); if (!($objType instanceof Types\Compound)) { $objType = new Types\Compound([$objType]); } @@ -453,6 +456,7 @@ class DefinitionResolver implements DefinitionResolverInterface return new Types\Mixed; } else { $classFqn = substr((string)$t->getFqsen(), 1); + // var_dump($classFqn); } $fqn = $classFqn . '->' . $expr->name; if ($expr instanceof Node\Expr\MethodCall) { @@ -771,6 +775,9 @@ class DefinitionResolver implements DefinitionResolverInterface && !empty($varTags = $docBlock->getTagsByName('var')) && ($type = $varTags[0]->getType()) ) { + if ((string)$type === "\\TestNamespace\\TestClass") { + var_dump($type); + } return $type; } // Resolve the expression diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 6c3cf00..23387f5 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -257,7 +257,7 @@ class PhpDocument } else { $offset = $position->toOffset($this->stmts->getFileContents()); $node = $this->stmts->getDescendantNodeAtPosition($offset); - if ($node->getStart() > $offset) { + if ($node !== null && $node->getStart() > $offset) { return null; } return $node; diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 1d4d0d5..632c2e3 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -3,9 +3,11 @@ declare(strict_types = 1); namespace LanguageServer\Server; +use Microsoft\PhpParser as Tolerant; +use phpDocumentor\Reflection\DocBlock\Tags\Param; use PhpParser\{Node, NodeTraverser}; use LanguageServer\{ - DefinitionResolverInterface, FqnUtilities, LanguageClient, PhpDocumentLoader, PhpDocument, DefinitionResolver, CompletionProvider + DefinitionResolverInterface, FqnUtilities, LanguageClient, PhpDocumentLoader, PhpDocument, DefinitionResolver, CompletionProvider, TolerantDefinitionResolver, TolerantTreeAnalyzer }; use LanguageServer\NodeVisitor\VariableReferencesCollector; use LanguageServer\Protocol\{ @@ -197,31 +199,35 @@ class TextDocument // Variables always stay in the boundary of the file and need to be searched inside their function scope // by traversing the AST if ( - $node instanceof Node\Expr\Variable - || $node instanceof Node\Param - || $node instanceof Node\Expr\ClosureUse + + ($node instanceof Tolerant\Node\Expression\Variable && !($node->getParent()->getParent() instanceof Tolerant\Node\PropertyDeclaration)) + || $node instanceof Tolerant\Node\Parameter + || $node instanceof Tolerant\Node\UseVariableName ) { - if ($node->name instanceof Node\Expr) { + if (isset($node->name) && $node->name instanceof Tolerant\Node\Expression) { return null; } // Find function/method/closure scope $n = $node; - while (isset($n) && !($n instanceof Node\FunctionLike)) { - $n = $n->getAttribute('parentNode'); + + $n = $n->getFirstAncestor(Tolerant\Node\Statement\FunctionDeclaration::class, Tolerant\Node\MethodDeclaration::class, Tolerant\Node\Expression\AnonymousFunctionCreationExpression::class, Tolerant\Node\SourceFileNode::class); + + if ($n === null) { + $n = $node->getFirstAncestor(Tolerant\Node\Statement\ExpressionStatement::class)->getParent(); } - if (!isset($n)) { - $n = $node->getAttribute('ownerDocument'); - } - $traverser = new NodeTraverser; - $refCollector = new VariableReferencesCollector($node->name); - $traverser->addVisitor($refCollector); - $traverser->traverse($n->getStmts()); - foreach ($refCollector->nodes as $ref) { - $locations[] = Location::fromNode($ref); + + foreach ($n->getDescendantNodes() as $descendantNode) { + if ($descendantNode instanceof Tolerant\Node\Expression\Variable && + $descendantNode->getName() === $node->getName() + ) { + var_dump($descendantNode->getName()); + $locations[] = Location::fromNode($descendantNode); + } } } else { // Definition with a global FQN $fqn = FqnUtilities::getDefinedFqn($node); + // var_dump($fqn); // Wait until indexing finished if (!$this->index->isComplete()) { yield waitForEvent($this->index, 'complete'); @@ -240,6 +246,7 @@ class TextDocument $refs = $document->getReferenceNodesByFqn($fqn); if ($refs !== null) { foreach ($refs as $ref) { + // var_dump($ref->getNodeKindName()); $locations[] = Location::fromNode($ref); } } diff --git a/src/TolerantDefinitionResolver.php b/src/TolerantDefinitionResolver.php index a6ba05b..e4130e1 100644 --- a/src/TolerantDefinitionResolver.php +++ b/src/TolerantDefinitionResolver.php @@ -229,7 +229,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface if ($node->getName() === 'this' && $fqn = $this->getContainingClassFqn($node)) { return $this->index->getDefinition($fqn, false); } - // TODO running throug thid for class constants or properties + // TODO running through thid for class constants or properties // Resolve the variable to a definition node (assignment, param or closure use) $defNode = $this->resolveVariableToNode($node); @@ -246,7 +246,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface } // If the node is a function or constant, it could be namespaced, but PHP falls back to global // http://php.net/manual/en/language.namespaces.fallback.php - $globalFallback = $this->isConstantFetch($node) || $node->getFirstAncestor(Tolerant\Node\Expression\CallExpression::class) !== null; + $globalFallback = $this->isConstantFetch($node) || $node->parent instanceof Tolerant\Node\Expression\CallExpression; // Return the Definition object from the index index return $this->index->getDefinition($fqn, $globalFallback); } @@ -278,7 +278,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface } } - if ($useClause->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) { + if ($useClause->functionOrConst !== null && $useClause->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) { $name .= '()'; } } @@ -356,7 +356,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface // TODO // $classFqn = $node->getNamespaceDefinition()->name->getNamespacedName() . (string)$varType->getFqsen(); - var_dump($classFqn); +// var_dump($classFqn); } $memberSuffix = '->' . (string)($access->memberName->getText() ?? $access->memberName->getText($node->getFileContents())); if ($node instanceof Tolerant\Node\Expression\CallExpression) { @@ -453,8 +453,9 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface return $name; } - private function isConstantFetch(Tolerant\Node $node) : bool { + public static function isConstantFetch(Tolerant\Node $node) : bool { return + ( $node instanceof Tolerant\Node\QualifiedName && ($node->parent instanceof Tolerant\Node\Statement\ExpressionStatement || $node->parent instanceof Tolerant\Node\Expression || $node->parent instanceof Tolerant\Node\DelimitedList\ExpressionList) && !( @@ -462,7 +463,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface $node->parent instanceof Tolerant\Node\Expression\ObjectCreationExpression || $node->parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression || $node->parent instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression || ($node->parent instanceof Tolerant\Node\Expression\BinaryExpression && $node->parent->operator->kind === Tolerant\TokenKind::InstanceOfKeyword) - ); + )); } /** @@ -547,7 +548,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface ); } - static function isFunctionLike(Tolerant\Node $node) { + public static function isFunctionLike(Tolerant\Node $node) { return $node instanceof Tolerant\Node\Statement\FunctionDeclaration || $node instanceof Tolerant\Node\MethodDeclaration || @@ -638,7 +639,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface } $var = $access->dereferencableExpression; - var_dump("HERE!!!"); +// var_dump("HERE!!!"); // Resolve object $objType = $this->resolveExpressionNodeToType($var); // var_dump($objType); @@ -655,16 +656,16 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface return new Types\Mixed; } else { $classFqn = substr((string)$t->getFqsen(), 1); - var_dump($classFqn); +// var_dump($classFqn); } $fqn = $classFqn . '->' . $access->memberName->getText($expr->getFileContents()); if ($expr instanceof Tolerant\Node\Expression\CallExpression) { $fqn .= '()'; } - var_dump($fqn); +// var_dump($fqn); // var_dump($fqn); $def = $this->index->getDefinition($fqn); - var_dump($def); +// var_dump($def); if ($def !== null) { return $def->type; } diff --git a/src/TolerantTreeAnalyzer.php b/src/TolerantTreeAnalyzer.php index 1d94d09..48e83c3 100644 --- a/src/TolerantTreeAnalyzer.php +++ b/src/TolerantTreeAnalyzer.php @@ -52,6 +52,54 @@ class TolerantTreeAnalyzer implements TreeAnalyzerInterface { $this->definitionNodes[$fqn] = $node; $this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn); } + + foreach ($this->stmts->getDescendantNodes() as $node) { + $parent = $node->parent; + if ( + ($node instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression + && !( + $node->parent instanceof Tolerant\Node\Expression\CallExpression || + $node->memberName instanceof Tolerant\Token + )) + || ($parent instanceof Tolerant\Node\Statement\NamespaceDefinition && $parent->name->getStart() === $node->getStart()) + ) { + continue; + } + $fqn = $definitionResolver->resolveReferenceNodeToFqn($node); + if ($fqn === null) { + continue; + } + $this->addReference($fqn, $node); + + if ( + $node instanceof Tolerant\Node\QualifiedName + && $node->isQualifiedName() + && !($parent instanceof Tolerant\Node\Statement\NamespaceDefinition && $parent->name->getStart() === $node->getStart() + ) + ) { + // Add references for each referenced namespace + $ns = $fqn; + while (($pos = strrpos($ns, '\\')) !== false) { + $ns = substr($ns, 0, $pos); + $this->addReference($ns, $node); + } + } + + // Namespaced constant access and function calls also need to register a reference + // to the global version because PHP falls back to global at runtime + // http://php.net/manual/en/language.namespaces.fallback.php + if ($definitionResolver::isConstantFetch($node) || + ($parent instanceof Tolerant\Node\Expression\CallExpression + && !( + $node instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression + ))) { + $parts = explode('\\', $fqn); + if (count($parts) > 1) { + $globalFqn = end($parts); + $this->addReference($globalFqn, $node); + } + } + } } public function getDiagnostics() { @@ -71,6 +119,14 @@ class TolerantTreeAnalyzer implements TreeAnalyzerInterface { return $diagnostics; } + private function addReference(string $fqn, Tolerant\Node $node) + { + if (!isset($this->referenceNodes[$fqn])) { + $this->referenceNodes[$fqn] = []; + } + $this->referenceNodes[$fqn][] = $node; + } + public function getDefinitions() { return $this->definitions ?? []; } diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 15d2338..2d725e5 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -110,7 +110,7 @@ abstract class ServerTestCase extends TestCase 'TestNamespace' => [ 0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function; 1 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; - 2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 17))) // use TestNamespace\{TestTrait, TestInterface}; + 2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 18))) // use TestNamespace\{TestTrait, TestInterface}; ], 'TestNamespace\\TEST_CONST' => [ 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))) @@ -145,8 +145,8 @@ abstract class ServerTestCase extends TestCase 3 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestNamespace\\TestClass::staticTestProperty' => [ - 0 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 35))), // echo TestClass::$staticTestProperty; - 1 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; + 0 => new Location($referencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty; + 1 => new Location($referencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestNamespace\\TestClass::staticTestMethod()' => [ 0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 27))) @@ -196,7 +196,7 @@ abstract class ServerTestCase extends TestCase ], 'TestClass::staticTestProperty' => [ 0 => new Location($globalReferencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty; - 1 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; + 1 => new Location($globalReferencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestClass::staticTestMethod()' => [ 0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 27))) diff --git a/tests/Server/TextDocument/References/NamespacedTest.php b/tests/Server/TextDocument/References/NamespacedTest.php index 1ee1ab4..32b8c4b 100644 --- a/tests/Server/TextDocument/References/NamespacedTest.php +++ b/tests/Server/TextDocument/References/NamespacedTest.php @@ -28,6 +28,7 @@ class NamespacedTest extends GlobalTest new TextDocumentIdentifier($definition->uri), $definition->range->end )->wait(); + // var_dump($result); $this->assertEquals(parent::getReferenceLocations('TestNamespace'), $result); } }