1
0
Fork 0

Add textDocument/definition support

pull/49/head
Felix Becker 2016-10-08 14:59:08 +02:00
parent 827ab4c842
commit d4757e0a24
13 changed files with 820 additions and 5 deletions

24
fixtures/references.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace TestNamespace;
$obj = new TestClass();
$obj->testMethod();
echo $obj->testProperty;
TestClass::staticTestMethod();
echo TestClass::$staticTestProperty;
echo TestClass::TEST_CLASS_CONST;
test_function();
$var = 123;
echo $var;
function whatever(TestClass $param): TestClass {
echo $param;
}
$fn = function() use ($var) {
echo $var;
};
echo TEST_CONST;

View File

@ -4,7 +4,7 @@ namespace TestNamespace;
const TEST_CONST = 123;
class TestClass
class TestClass implements TestInterface
{
const TEST_CLASS_CONST = 123;
public static $staticTestProperty;
@ -30,3 +30,24 @@ interface TestInterface
{
}
function test_function()
{
}
new class {
const TEST_CLASS_CONST = 123;
public static $staticTestProperty;
public $testProperty;
public static function staticTestMethod()
{
}
public function testMethod($testParameter)
{
$testVariable = 123;
}
};

6
fixtures/use.php Normal file
View File

@ -0,0 +1,6 @@
<?php
namespace SecondTestNamespace;
use TestNamespace\TestClass;
use TestNamespace\{TestTrait, TestInterface};

View File

@ -103,6 +103,9 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher
$serverCapabilities->workspaceSymbolProvider = true;
// Support "Format Code"
$serverCapabilities->documentFormattingProvider = true;
// Support "Go to definition"
$serverCapabilities->definitionProvider = true;
return new InitializeResult($serverCapabilities);
}

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\NodeVisitors;
use PhpParser\{NodeVisitorAbstract, Node};
/**
* Collects definitions of classes, interfaces, traits, methods, properties and constants
* Depends on ReferencesAdder and NameResolver
*/
class DefinitionCollector extends NodeVisitorAbstract
{
/**
* Map from fully qualified name (FQN) to Node
* Examples of fully qualified names:
* - testFunction()
* - TestNamespace\TestClass
* - TestNamespace\TestClass::TEST_CONSTANT
* - TestNamespace\TestClass::staticTestProperty
* - TestNamespace\TestClass::testProperty
* - TestNamespace\TestClass::staticTestMethod()
* - TestNamespace\TestClass::testMethod()
*
* @var Node[]
*/
public $definitions = [];
public function enterNode(Node $node)
{
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
// Class, interface or trait declaration
$this->definitions[(string)$node->namespacedName] = $node;
} else if ($node instanceof Node\Stmt\Function_) {
// Function: use functioName() as the name
$name = (string)$node->namespacedName . '()';
$this->definitions[$name] = $node;
} else if ($node instanceof Node\Stmt\ClassMethod) {
// Class method: use ClassName::methodName() as name
$class = $node->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return;
}
$name = (string)$class->namespacedName . '::' . (string)$node->name . '()';
$this->definitions[$name] = $node;
} else if ($node instanceof Node\Stmt\PropertyProperty) {
// Property: use ClassName::propertyName as name
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return;
}
$name = (string)$class->namespacedName . '::' . (string)$node->name;
$this->definitions[$name] = $node;
} else if ($node instanceof Node\Const_) {
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\Const_) {
// Basic constant: use CONSTANT_NAME as name
$name = (string)$node->namespacedName;
} else if ($parent instanceof Node\Stmt\ClassConst) {
// Class constant: use ClassName::CONSTANT_NAME as name
$class = $parent->getAttribute('parentNode');
if (!isset($class->name) || $class->name instanceof Node\Expr) {
return;
}
$name = (string)$class->namespacedName . '::' . $node->name;
}
$this->definitions[$name] = $node;
}
}
}

View File

@ -38,6 +38,11 @@ class NodeAtPositionFinder extends NodeVisitorAbstract
new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1),
new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn') - 1)
);
// Workaround for https://github.com/nikic/PHP-Parser/issues/311
$parent = $node->getAttribute('parentNode');
if (isset($parent) && $parent instanceof Node\Stmt\GroupUse && $parent->prefix === $node) {
return;
}
if (!isset($this->node) && $range->includes($this->position)) {
$this->node = $node;
}

View File

@ -4,7 +4,7 @@ declare(strict_types = 1);
namespace LanguageServer;
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, SymbolKind, TextEdit};
use LanguageServer\NodeVisitors\{NodeAtPositionFinder, ReferencesAdder, SymbolFinder, ColumnCalculator};
use LanguageServer\NodeVisitors\{NodeAtPositionFinder, ReferencesAdder, DefinitionCollector, SymbolFinder, ColumnCalculator};
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, Parser};
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PhpParser\NodeVisitor\NameResolver;
@ -23,7 +23,9 @@ class PhpDocument
*
* @var Project
*/
private $project;
public $project;
// for whatever reason I get "cannot access private property" error if $project is not public
// https://github.com/felixfbecker/php-language-server/pull/49#issuecomment-252427359
/**
* The PHPParser instance
@ -46,6 +48,28 @@ class PhpDocument
*/
private $content;
/**
* The AST of the document
*
* @var Node[]
*/
private $stmts = [];
/**
* Map from fully qualified name (FQN) to Node
* Examples of fully qualified names:
* - testFunction()
* - TestNamespace\TestClass
* - TestNamespace\TestClass::TEST_CONSTANT
* - TestNamespace\TestClass::staticTestProperty
* - TestNamespace\TestClass::testProperty
* - TestNamespace\TestClass::staticTestMethod()
* - TestNamespace\TestClass::testMethod()
*
* @var Node[]
*/
private $definitions = [];
/**
* @var SymbolInformation[]
*/
@ -149,13 +173,24 @@ class PhpDocument
$traverser->addVisitor(new ColumnCalculator($this->content));
// Collect all symbols
// TODO: use DefinitionCollector for this
$symbolFinder = new SymbolFinder($this->uri);
$traverser->addVisitor($symbolFinder);
// Collect all definitions
$definitionCollector = new DefinitionCollector;
$traverser->addVisitor($definitionCollector);
$traverser->traverse($stmts);
$this->symbols = $symbolFinder->symbols;
$this->definitions = $definitionCollector->definitions;
// Register this document on the project for all the symbols defined in it
foreach ($definitionCollector->definitions as $fqn => $node) {
$this->project->addDefinitionDocument($fqn, $this);
}
$this->stmts = $stmts;
}
}
@ -187,6 +222,16 @@ class PhpDocument
return $this->content;
}
/**
* Returns the URI of the document
*
* @return string
*/
public function getUri(): string
{
return $this->uri;
}
/**
* Returns the node at a specified position
*
@ -204,4 +249,173 @@ class PhpDocument
$traverser->traverse($this->stmts);
return $finder->node;
}
/**
* Returns the definition node for a fully qualified name
*
* @param string $fqn
* @return Node|null
*/
public function getDefinitionByFqn(string $fqn)
{
return $this->definitions[$fqn] ?? null;
}
/**
* Returns the definition node for any node
* The definition node MAY be in another document, check the ownerDocument attribute
*
* @param Node $node
* @return Node|null
*/
public function getDefinitionByNode(Node $node)
{
if ($node instanceof Node\Name) {
$nameNode = $node;
$node = $node->getAttribute('parentNode');
}
// 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) {
return $this->getVariableDefinition($node);
}
if (
($node instanceof Node\Stmt\ClassLike
|| $node instanceof Node\Param
|| $node instanceof Node\Stmt\Function_)
&& isset($nameNode)
) {
// For extends, implements and type hints use the name directly
$name = (string)$nameNode;
} else if ($node instanceof Node\Stmt\UseUse) {
$name = (string)$node->name;
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\GroupUse) {
$name = $parent->prefix . '\\' . $name;
}
} else if ($node instanceof Node\Expr\New_) {
if (!($node->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$node->class;
} else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
if ($node->name instanceof Node\Expr || !($node->var instanceof Node\Expr\Variable)) {
// Cannot get definition of dynamic calls
return null;
}
// Need to resolve variable to a class
$varDef = $this->getVariableDefinition($node->var);
if (!isset($varDef)) {
return null;
}
if ($varDef instanceof Node\Param) {
if (!isset($varDef->type)) {
// Cannot resolve to class without a type hint
// TODO: parse docblock
return null;
}
$name = (string)$varDef->type;
} else if ($varDef instanceof Node\Expr\Assign) {
if ($varDef->expr instanceof Node\Expr\New_) {
if (!($varDef->expr->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$varDef->expr->class;
} else {
return null;
}
} else {
return null;
}
$name .= '::' . (string)$node->name;
} else if ($node instanceof Node\Expr\FuncCall) {
if ($node->name instanceof Node\Expr) {
return null;
}
$name = (string)$node->name;
} else if ($node instanceof Node\Expr\ConstFetch) {
$name = (string)$node->name;
} else if (
$node instanceof Node\Expr\ClassConstFetch
|| $node instanceof Node\Expr\StaticPropertyFetch
|| $node instanceof Node\Expr\StaticCall
) {
if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) {
// Cannot get definition of dynamic names
return null;
}
$name = (string)$node->class . '::' . $node->name;
}
if (
$node instanceof Node\Expr\MethodCall
|| $node instanceof Node\Expr\FuncCall
|| $node instanceof Node\Expr\StaticCall
) {
$name .= '()';
}
if (!isset($name)) {
return null;
}
// Search for the document where the class, interface, trait, function, method or property is defined
$document = $this->project->getDefinitionDocument($name);
if (!$document && $node instanceof Node\Expr\FuncCall) {
// Find and try with namespace
// Namespaces aren't added automatically by NameResolver because PHP falls back to global functions
$n = $node;
while (isset($n)) {
$n = $n->getAttribute('parentNode');
if ($n instanceof Node\Stmt\Namespace_) {
$name = (string)$n->name . '\\' . $name;
$document = $this->project->getDefinitionDocument($name);
break;
}
}
}
if (!isset($document)) {
return null;
}
return $document->getDefinitionByFqn($name);
}
/**
* Returns the assignment or parameter node where a variable was defined
*
* @param Node\Expr\Variable $n The variable access
* @return Node\Expr\Assign|Node\Param|Node\Expr\ClosureUse|null
*/
public function getVariableDefinition(Node\Expr\Variable $var)
{
$n = $var;
// Traverse the AST up
while (isset($n) && $n = $n->getAttribute('parentNode')) {
// If a function is met, check the parameters and use statements
if ($n instanceof Node\FunctionLike) {
foreach ($n->getParams() as $param) {
if ($param->name === $var->name) {
return $param;
}
}
// If it is a closure, also check use statements
if ($n instanceof Node\Expr\Closure) {
foreach ($n->uses as $use) {
if ($use->var === $var->name) {
return $use;
}
}
}
break;
}
// Check each previous sibling node for a variable assignment to that variable
while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) {
if ($n instanceof Node\Expr\Assign && $n->var->name === $var->name) {
return $n;
}
}
}
// Return null if nothing was found
return null;
}
}

View File

@ -17,6 +17,14 @@ class Project
*/
private $documents;
/**
* An associative array [string => PhpDocument]
* that maps fully qualified symbol names to loaded PhpDocuments
*
* @var PhpDocument[]
*/
private $definitions;
/**
* Instance of the PHP parser
*
@ -54,6 +62,28 @@ class Project
return $this->documents[$uri];
}
/**
* Adds a document as the container for a specific symbol
*
* @param string $fqn The fully qualified name of the symbol
* @return void
*/
public function addDefinitionDocument(string $fqn, PhpDocument $document)
{
$this->definitions[$fqn] = $document;
}
/**
* Returns the document where a symbol is defined
*
* @param string $fqn The fully qualified name of the symbol
* @return PhpDocument|null
*/
public function getDefinitionDocument(string $fqn)
{
return $this->definitions[$fqn] ?? null;
}
/**
* Finds symbols in all documents, filtered by query parameter.
*

View File

@ -13,7 +13,8 @@ use LanguageServer\Protocol\{
Range,
Position,
FormattingOptions,
TextEdit
TextEdit,
Location
};
/**
@ -88,4 +89,32 @@ class TextDocument
{
return $this->project->getDocument($textDocument->uri)->getFormattedText();
}
/**
* The goto definition request is sent from the client to the server to resolve the definition location of a symbol
* at a given text document position.
*
* @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document
* @return Location|Location[]|null
*/
public function definition(TextDocumentIdentifier $textDocument, Position $position)
{
$document = $this->project->getDocument($textDocument->uri);
$node = $document->getNodeAtPosition($position);
if ($node === null) {
return null;
}
$def = $document->getDefinitionByNode($node);
if ($def === null) {
return null;
}
return new Location(
$def->getAttribute('ownerDocument')->getUri(),
new Range(
new Position($def->getAttribute('startLine') - 1, $def->getAttribute('startColumn') - 1),
new Position($def->getAttribute('endLine') - 1, $def->getAttribute('endColumn') - 1)
)
);
}
}

View File

@ -34,7 +34,7 @@ class LanguageServerTest extends TestCase
'hoverProvider' => null,
'completionProvider' => null,
'signatureHelpProvider' => null,
'definitionProvider' => null,
'definitionProvider' => true,
'referencesProvider' => null,
'documentHighlightProvider' => null,
'workspaceSymbolProvider' => true,

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase;
use PhpParser\{ParserFactory, NodeTraverser, Node};
use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\NodeVisitors\{ReferencesAdder, DefinitionCollector};
class DefinitionCollectorTest extends TestCase
{
public function test()
{
$traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ReferencesAdder);
$definitionCollector = new DefinitionCollector;
$traverser->addVisitor($definitionCollector);
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$stmts = $parser->parse(file_get_contents(__DIR__ . '/../../fixtures/symbols.php'));
$traverser->traverse($stmts);
$defs = $definitionCollector->definitions;
$this->assertEquals([
'TestNamespace\\TEST_CONST',
'TestNamespace\\TestClass',
'TestNamespace\\TestClass::TEST_CLASS_CONST',
'TestNamespace\\TestClass::staticTestProperty',
'TestNamespace\\TestClass::testProperty',
'TestNamespace\\TestClass::staticTestMethod()',
'TestNamespace\\TestClass::testMethod()',
'TestNamespace\\TestTrait',
'TestNamespace\\TestInterface',
'TestNamespace\\test_function()'
], array_keys($defs));
$this->assertInstanceOf(Node\Const_::class, $defs['TestNamespace\\TEST_CONST']);
$this->assertInstanceOf(Node\Stmt\Class_::class, $defs['TestNamespace\\TestClass']);
$this->assertInstanceOf(Node\Const_::class, $defs['TestNamespace\\TestClass::TEST_CLASS_CONST']);
$this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defs['TestNamespace\\TestClass::staticTestProperty']);
$this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defs['TestNamespace\\TestClass::testProperty']);
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defs['TestNamespace\\TestClass::staticTestMethod()']);
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defs['TestNamespace\\TestClass::testMethod()']);
$this->assertInstanceOf(Node\Stmt\Trait_::class, $defs['TestNamespace\\TestTrait']);
$this->assertInstanceOf(Node\Stmt\Interface_::class, $defs['TestNamespace\\TestInterface']);
$this->assertInstanceOf(Node\Stmt\Function_::class, $defs['TestNamespace\\test_function()']);
}
}

View File

@ -0,0 +1,346 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, LanguageClient, Project};
use LanguageServer\Protocol\{TextDocumentIdentifier, Position};
class DefinitionTest extends TestCase
{
/**
* @var Server\TextDocument
*/
private $textDocument;
public function setUp()
{
$client = new LanguageClient(new MockProtocolStream());
$project = new Project($client);
$this->textDocument = new Server\TextDocument($project, $client);
$project->getDocument('references')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/references.php'));
$project->getDocument('symbols')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/symbols.php'));
$project->getDocument('use')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/use.php'));
}
public function testDefinitionForClassLike()
{
// $obj = new TestClass();
// Get definition for TestClass
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(4, 16));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 6,
'character' => 0
],
'end' => [
'line' => 21,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForClassLikeUseStatement()
{
// use TestNamespace\TestClass;
// Get definition for TestClass
$result = $this->textDocument->definition(new TextDocumentIdentifier('use'), new Position(4, 22));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 6,
'character' => 0
],
'end' => [
'line' => 21,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForClassLikeGroupUseStatement()
{
// use TestNamespace\{TestTrait, TestInterface};
// Get definition for TestInterface
$result = $this->textDocument->definition(new TextDocumentIdentifier('use'), new Position(5, 37));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 28,
'character' => 0
],
'end' => [
'line' => 31,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForImplements()
{
// class TestClass implements TestInterface
// Get definition for TestInterface
$result = $this->textDocument->definition(new TextDocumentIdentifier('symbols'), new Position(6, 33));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 28,
'character' => 0
],
'end' => [
'line' => 31,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForClassConstants()
{
// echo TestClass::TEST_CLASS_CONST;
// Get definition for TEST_CLASS_CONST
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(9, 21));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 8,
'character' => 10
],
'end' => [
'line' => 8,
'character' => 31
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForConstants()
{
// echo TEST_CONST;
// Get definition for TEST_CONST
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(23, 9));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 4,
'character' => 6
],
'end' => [
'line' => 4,
'character' => 21
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForStaticMethods()
{
// TestClass::staticTestMethod();
// Get definition for staticTestMethod
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(7, 20));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 12,
'character' => 4
],
'end' => [
'line' => 15,
'character' => 4
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForStaticProperties()
{
// echo TestClass::$staticTestProperty;
// Get definition for staticTestProperty
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(8, 25));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 9,
'character' => 18
],
'end' => [
'line' => 9,
'character' => 36
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForMethods()
{
// $obj->testMethod();
// Get definition for testMethod
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(5, 11));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 17,
'character' => 4
],
'end' => [
'line' => 20,
'character' => 4
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForProperties()
{
// echo $obj->testProperty;
// Get definition for testProperty
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(6, 18));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 10,
'character' => 11
],
'end' => [
'line' => 10,
'character' => 23
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForVariables()
{
// echo $var;
// Get definition for $var
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(13, 7));
$this->assertEquals([
'uri' => 'references',
'range' => [
'start' => [
'line' => 12,
'character' => 0
],
'end' => [
'line' => 12,
'character' => 9
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForParamTypeHints()
{
// function whatever(TestClass $param) {
// Get definition for TestClass
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(15, 23));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 6,
'character' => 0
],
'end' => [
'line' => 21,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForReturnTypeHints()
{
// function whatever(TestClass $param) {
// Get definition for TestClass
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(15, 42));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 6,
'character' => 0
],
'end' => [
'line' => 21,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForParams()
{
// echo $param;
// Get definition for $param
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(16, 13));
$this->assertEquals([
'uri' => 'references',
'range' => [
'start' => [
'line' => 15,
'character' => 18
],
'end' => [
'line' => 15,
'character' => 33
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForUsedVariables()
{
// echo $var;
// Get definition for $var
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(20, 11));
$this->assertEquals([
'uri' => 'references',
'range' => [
'start' => [
'line' => 19,
'character' => 22
],
'end' => [
'line' => 19,
'character' => 25
]
]
], json_decode(json_encode($result), true));
}
public function testDefinitionForFunctions()
{
// test_function();
// Get definition for test_function
$result = $this->textDocument->definition(new TextDocumentIdentifier('references'), new Position(10, 4));
$this->assertEquals([
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 33,
'character' => 0
],
'end' => [
'line' => 36,
'character' => 0
]
]
], json_decode(json_encode($result), true));
}
}

View File

@ -207,6 +207,24 @@ class DocumentSymbolTest extends TestCase
]
],
'containerName' => 'TestNamespace'
],
[
'name' => 'test_function',
'kind' => SymbolKind::FUNCTION,
'location' => [
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 33,
'character' => 0
],
'end' => [
'line' => 36,
'character' => 1
]
]
],
'containerName' => 'TestNamespace'
]
], json_decode(json_encode($result), true));
}