1
0
Fork 0

Use DefinitionCollector for symbol requests

pull/49/head
Felix Becker 2016-10-08 19:08:44 +02:00
parent d4757e0a24
commit 6be53ad658
8 changed files with 49 additions and 199 deletions

View File

@ -1,121 +0,0 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\NodeVisitors;
use PhpParser\{NodeVisitorAbstract, Node};
use LanguageServer\Protocol\{SymbolInformation, SymbolKind, Range, Position, Location};
class SymbolFinder extends NodeVisitorAbstract
{
const NODE_SYMBOL_KIND_MAP = [
Node\Stmt\Class_::class => SymbolKind::CLASS_,
Node\Stmt\Trait_::class => SymbolKind::CLASS_,
Node\Stmt\Interface_::class => SymbolKind::INTERFACE,
Node\Stmt\Namespace_::class => SymbolKind::NAMESPACE,
Node\Stmt\Function_::class => SymbolKind::FUNCTION,
Node\Stmt\ClassMethod::class => SymbolKind::METHOD,
Node\Stmt\PropertyProperty::class => SymbolKind::PROPERTY,
Node\Const_::class => SymbolKind::CONSTANT,
Node\Expr\Variable::class => SymbolKind::VARIABLE
];
/**
* @var LanguageServer\Protocol\SymbolInformation[]
*/
public $symbols = [];
/**
* @var string
*/
private $uri;
/**
* @var string
*/
private $containerName;
/**
* @var array
*/
private $nameStack = [];
/**
* @var array
*/
private $nodeStack = [];
/**
* @var int
*/
private $functionCount = 0;
public function __construct(string $uri)
{
$this->uri = $uri;
}
public function enterNode(Node $node)
{
$this->nodeStack[] = $node;
$containerName = empty($this->nameStack) ? null : end($this->nameStack);
// If we enter a named node, push its name onto name stack.
// Else push the current name onto stack.
if (!empty($node->name) && (is_string($node->name) || method_exists($node->name, '__toString')) && !empty((string)$node->name)) {
if (empty($containerName)) {
$this->nameStack[] = (string)$node->name;
} else if ($node instanceof Node\Stmt\ClassMethod) {
$this->nameStack[] = $containerName . '::' . (string)$node->name;
} else {
$this->nameStack[] = $containerName . '\\' . (string)$node->name;
}
} else {
$this->nameStack[] = $containerName;
// We are not interested in unnamed nodes, return
return;
}
$class = get_class($node);
if (!isset(self::NODE_SYMBOL_KIND_MAP[$class])) {
return;
}
// if we enter a method or function, increase the function counter
if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) {
$this->functionCount++;
}
$kind = self::NODE_SYMBOL_KIND_MAP[$class];
// exclude non-global variable symbols.
if ($kind === SymbolKind::VARIABLE && $this->functionCount > 0) {
return;
}
$symbol = new SymbolInformation();
$symbol->kind = $kind;
$symbol->name = (string)$node->name;
$symbol->location = new Location(
$this->uri,
new Range(
new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1),
new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn'))
)
);
$symbol->containerName = $containerName;
$this->symbols[] = $symbol;
}
public function leaveNode(Node $node)
{
array_pop($this->nodeStack);
array_pop($this->nameStack);
// if we leave a method or function, decrease the function counter
if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) {
$this->functionCount--;
}
}
}

View File

@ -3,8 +3,8 @@ declare(strict_types = 1);
namespace LanguageServer; namespace LanguageServer;
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, SymbolKind, TextEdit}; use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, SymbolInformation, SymbolKind, TextEdit, Location};
use LanguageServer\NodeVisitors\{NodeAtPositionFinder, ReferencesAdder, DefinitionCollector, SymbolFinder, ColumnCalculator}; use LanguageServer\NodeVisitors\{NodeAtPositionFinder, ReferencesAdder, DefinitionCollector, ColumnCalculator};
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, Parser}; use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, Parser};
use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NameResolver;
@ -70,11 +70,6 @@ class PhpDocument
*/ */
private $definitions = []; private $definitions = [];
/**
* @var SymbolInformation[]
*/
private $symbols = [];
/** /**
* @param string $uri The URI of the document * @param string $uri The URI of the document
* @param Project $project The Project this document belongs to (to register definitions etc) * @param Project $project The Project this document belongs to (to register definitions etc)
@ -92,22 +87,56 @@ class PhpDocument
/** /**
* Returns all symbols in this document. * Returns all symbols in this document.
* *
* @return SymbolInformation[] * @return SymbolInformation[]|null
*/ */
public function getSymbols() public function getSymbols()
{ {
return $this->symbols; if (!isset($this->definitions)) {
return null;
}
$nodeSymbolKindMap = [
Node\Stmt\Class_::class => SymbolKind::CLASS_,
Node\Stmt\Trait_::class => SymbolKind::CLASS_,
Node\Stmt\Interface_::class => SymbolKind::INTERFACE,
Node\Stmt\Namespace_::class => SymbolKind::NAMESPACE,
Node\Stmt\Function_::class => SymbolKind::FUNCTION,
Node\Stmt\ClassMethod::class => SymbolKind::METHOD,
Node\Stmt\PropertyProperty::class => SymbolKind::PROPERTY,
Node\Const_::class => SymbolKind::CONSTANT
];
$symbols = [];
foreach ($this->definitions as $fqn => $node) {
$symbol = new SymbolInformation();
$symbol->kind = $nodeSymbolKindMap[get_class($node)];
$symbol->name = (string)$node->name;
$symbol->location = new Location(
$this->getUri(),
new Range(
new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1),
new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn'))
)
);
$parts = preg_split('/(::|\\\\)/', $fqn);
array_pop($parts);
$symbol->containerName = implode('\\', $parts);
$symbols[] = $symbol;
}
return $symbols;
} }
/** /**
* Returns symbols in this document filtered by query string. * Returns symbols in this document filtered by query string.
* *
* @param string $query The search query * @param string $query The search query
* @return SymbolInformation[] * @return SymbolInformation[]|null
*/ */
public function findSymbols(string $query) public function findSymbols(string $query)
{ {
return array_filter($this->symbols, function($symbol) use(&$query) { $symbols = $this->getSymbols();
if ($symbols === null) {
return null;
}
return array_filter($symbols, function($symbol) use ($query) {
return stripos($symbol->name, $query) !== false; return stripos($symbol->name, $query) !== false;
}); });
} }
@ -172,19 +201,12 @@ class PhpDocument
// Add column attributes to nodes // Add column attributes to nodes
$traverser->addVisitor(new ColumnCalculator($this->content)); $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 // Collect all definitions
$definitionCollector = new DefinitionCollector; $definitionCollector = new DefinitionCollector;
$traverser->addVisitor($definitionCollector); $traverser->addVisitor($definitionCollector);
$traverser->traverse($stmts); $traverser->traverse($stmts);
$this->symbols = $symbolFinder->symbols;
$this->definitions = $definitionCollector->definitions; $this->definitions = $definitionCollector->definitions;
// Register this document on the project for all the symbols defined in it // Register this document on the project for all the symbols defined in it
foreach ($definitionCollector->definitions as $fqn => $node) { foreach ($definitionCollector->definitions as $fqn => $node) {

View File

@ -94,7 +94,10 @@ class Project
{ {
$queryResult = []; $queryResult = [];
foreach ($this->documents as $uri => $document) { foreach ($this->documents as $uri => $document) {
$queryResult = array_merge($queryResult, $document->findSymbols($query)); $documentQueryResult = $document->findSymbols($query);
if ($documentQueryResult !== null) {
$queryResult = array_merge($queryResult, $documentQueryResult);
}
} }
return $queryResult; return $queryResult;
} }

View File

@ -3,7 +3,7 @@ declare(strict_types = 1);
namespace LanguageServer\Server; namespace LanguageServer\Server;
use LanguageServer\{LanguageClient, ColumnCalculator, SymbolFinder, Project}; use LanguageServer\{LanguageClient, ColumnCalculator, Project};
use LanguageServer\Protocol\{ use LanguageServer\Protocol\{
TextDocumentItem, TextDocumentItem,
TextDocumentIdentifier, TextDocumentIdentifier,

View File

@ -6,7 +6,7 @@ namespace LanguageServer\Server;
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer}; use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer};
use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\{LanguageClient, ColumnCalculator, SymbolFinder, Project}; use LanguageServer\{LanguageClient, ColumnCalculator, Project};
use LanguageServer\Protocol\{ use LanguageServer\Protocol\{
TextDocumentItem, TextDocumentItem,
TextDocumentIdentifier, TextDocumentIdentifier,

View File

@ -30,44 +30,7 @@ class PhpDocumentTest extends TestCase
$symbols = $document->getSymbols(); $symbols = $document->getSymbols();
$this->assertEquals([ $this->assertEquals([], json_decode(json_encode($symbols), true));
[
'name' => 'a',
'kind' => SymbolKind::VARIABLE,
'location' => [
'uri' => 'whatever',
'range' => [
'start' => [
'line' => 1,
'character' => 0
],
'end' => [
'line' => 1,
'character' => 3
]
]
],
'containerName' => null
],
[
'name' => 'bar',
'kind' => SymbolKind::VARIABLE,
'location' => [
'uri' => 'whatever',
'range' => [
'start' => [
'line' => 2,
'character' => 0
],
'end' => [
'line' => 2,
'character' => 4
]
]
],
'containerName' => null
]
], json_decode(json_encode($symbols), true));
} }
public function testGetNodeAtPosition() public function testGetNodeAtPosition()

View File

@ -41,6 +41,7 @@ class ProjectTest extends TestCase
{ {
$this->project->getDocument('file:///document1.php')->updateContent("<?php\nfunction foo() {}\nfunction bar() {}\n"); $this->project->getDocument('file:///document1.php')->updateContent("<?php\nfunction foo() {}\nfunction bar() {}\n");
$this->project->getDocument('file:///document2.php')->updateContent("<?php\nfunction baz() {}\nfunction frob() {}\n"); $this->project->getDocument('file:///document2.php')->updateContent("<?php\nfunction baz() {}\nfunction frob() {}\n");
$this->project->getDocument('invalid_file')->updateContent(file_get_contents(__DIR__ . '/../fixtures/invalid_file.php'));
$symbols = $this->project->findSymbols('ba'); $symbols = $this->project->findSymbols('ba');

View File

@ -28,24 +28,6 @@ class DocumentSymbolTest extends TestCase
// Request symbols // Request symbols
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier('symbols')); $result = $this->textDocument->documentSymbol(new TextDocumentIdentifier('symbols'));
$this->assertEquals([ $this->assertEquals([
[
'name' => 'TestNamespace',
'kind' => SymbolKind::NAMESPACE,
'location' => [
'uri' => 'symbols',
'range' => [
'start' => [
'line' => 2,
'character' => 0
],
'end' => [
'line' => 2,
'character' => 24
]
]
],
'containerName' => null
],
[ [
'name' => 'TEST_CONST', 'name' => 'TEST_CONST',
'kind' => SymbolKind::CONSTANT, 'kind' => SymbolKind::CONSTANT,