Only hold AST for open files in memory (#63)
* Only hold content for open files in memory * Add test for didClose * Remove invalid URI formatting test * Don't keep AST in memory * Fix symbol search crash * Change Project map to FQN => URI Removes PhpDocument::load(), isLoaded(), unload() * Add docblocks * Rename some functions * Extend documentation * Correct docblockpull/58/head
parent
d41cde2039
commit
15e004fb9b
|
@ -153,7 +153,7 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher
|
|||
$shortName = substr($file, strlen($rootPath) + 1);
|
||||
$this->client->window->logMessage(MessageType::INFO, "Parsing file $fileNum/$numTotalFiles: $shortName.");
|
||||
|
||||
$this->project->getDocument($uri)->updateContent(file_get_contents($file));
|
||||
$this->project->loadDocument($uri);
|
||||
|
||||
Loop\setTimeout($processFile, 0);
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,8 @@ use LanguageServer\NodeVisitor\{NodeAtPositionFinder, ReferencesAdder, Definitio
|
|||
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, Parser};
|
||||
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use Exception;
|
||||
use function LanguageServer\uriToPath;
|
||||
|
||||
class PhpDocument
|
||||
{
|
||||
|
@ -53,14 +55,14 @@ class PhpDocument
|
|||
*
|
||||
* @var Node[]
|
||||
*/
|
||||
private $stmts = [];
|
||||
private $statements;
|
||||
|
||||
/**
|
||||
* Map from fully qualified name (FQN) to Node
|
||||
*
|
||||
* @var Node[]
|
||||
*/
|
||||
private $definitions = [];
|
||||
private $definitions;
|
||||
|
||||
/**
|
||||
* Map from fully qualified name (FQN) to array of nodes that reference the symbol
|
||||
|
@ -71,78 +73,23 @@ class PhpDocument
|
|||
|
||||
/**
|
||||
* @param string $uri The URI of the document
|
||||
* @param string $content The content of the document
|
||||
* @param Project $project The Project this document belongs to (to register definitions etc)
|
||||
* @param LanguageClient $client The LanguageClient instance (to report errors etc)
|
||||
* @param Parser $parser The PHPParser instance
|
||||
*/
|
||||
public function __construct(string $uri, Project $project, LanguageClient $client, Parser $parser)
|
||||
public function __construct(string $uri, string $content, Project $project, LanguageClient $client, Parser $parser)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->project = $project;
|
||||
$this->client = $client;
|
||||
$this->parser = $parser;
|
||||
$this->updateContent($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all symbols in this document.
|
||||
*
|
||||
* @return SymbolInformation[]|null
|
||||
*/
|
||||
public function getSymbols()
|
||||
{
|
||||
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) {
|
||||
$class = get_class($node);
|
||||
if (!isset($nodeSymbolKindMap[$class])) {
|
||||
continue;
|
||||
}
|
||||
$symbol = new SymbolInformation();
|
||||
$symbol->kind = $nodeSymbolKindMap[$class];
|
||||
$symbol->name = (string)$node->name;
|
||||
$symbol->location = Location::fromNode($node);
|
||||
$parts = preg_split('/(::|\\\\)/', $fqn);
|
||||
array_pop($parts);
|
||||
$symbol->containerName = implode('\\', $parts);
|
||||
$symbols[] = $symbol;
|
||||
}
|
||||
return $symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns symbols in this document filtered by query string.
|
||||
*
|
||||
* @param string $query The search query
|
||||
* @return SymbolInformation[]|null
|
||||
*/
|
||||
public function findSymbols(string $query)
|
||||
{
|
||||
$symbols = $this->getSymbols();
|
||||
if ($symbols === null) {
|
||||
return null;
|
||||
}
|
||||
if ($query === '') {
|
||||
return $symbols;
|
||||
}
|
||||
return array_filter($symbols, function($symbol) use ($query) {
|
||||
return stripos($symbol->name, $query) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content on this document.
|
||||
* Re-parses a source file, updates symbols and reports parsing errors
|
||||
* that may have occured as diagnostics.
|
||||
*
|
||||
* @param string $content
|
||||
* @return void
|
||||
|
@ -150,21 +97,10 @@ class PhpDocument
|
|||
public function updateContent(string $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-parses a source file, updates symbols, reports parsing errors
|
||||
* that may have occured as diagnostics and returns parsed nodes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parse()
|
||||
{
|
||||
$stmts = null;
|
||||
$errors = [];
|
||||
try {
|
||||
$stmts = $this->parser->parse($this->content);
|
||||
$stmts = $this->parser->parse($content);
|
||||
} catch (\PhpParser\Error $e) {
|
||||
// Lexer can throw errors. e.g for unterminated comments
|
||||
// unfortunately we don't get a location back
|
||||
|
@ -200,7 +136,7 @@ class PhpDocument
|
|||
$traverser->addVisitor(new ReferencesAdder($this));
|
||||
|
||||
// Add column attributes to nodes
|
||||
$traverser->addVisitor(new ColumnCalculator($this->content));
|
||||
$traverser->addVisitor(new ColumnCalculator($content));
|
||||
|
||||
// Collect all definitions
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
|
@ -208,13 +144,13 @@ class PhpDocument
|
|||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
$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->project->setDefinitionUri($fqn, $this->uri);
|
||||
}
|
||||
|
||||
$this->stmts = $stmts;
|
||||
$this->statements = $stmts;
|
||||
$this->definitions = $definitionCollector->definitions;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +161,7 @@ class PhpDocument
|
|||
*/
|
||||
public function getFormattedText()
|
||||
{
|
||||
if (empty($this->getContent())) {
|
||||
if (empty($this->content)) {
|
||||
return [];
|
||||
}
|
||||
return Formatter::format($this->content, $this->uri);
|
||||
|
@ -259,13 +195,10 @@ class PhpDocument
|
|||
*/
|
||||
public function getNodeAtPosition(Position $position)
|
||||
{
|
||||
if ($this->stmts === null) {
|
||||
return null;
|
||||
}
|
||||
$traverser = new NodeTraverser;
|
||||
$finder = new NodeAtPositionFinder($position);
|
||||
$traverser->addVisitor($finder);
|
||||
$traverser->traverse($this->stmts);
|
||||
$traverser->traverse($this->statements);
|
||||
return $finder->node;
|
||||
}
|
||||
|
||||
|
@ -280,6 +213,16 @@ class PhpDocument
|
|||
return $this->definitions[$fqn] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map from fully qualified name (FQN) to Nodes defined in this document
|
||||
*
|
||||
* @return Node[]
|
||||
*/
|
||||
public function getDefinitions()
|
||||
{
|
||||
return $this->definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given FQN is defined in this document
|
||||
*
|
||||
|
@ -307,16 +250,11 @@ class PhpDocument
|
|||
*/
|
||||
public function getDefinedFqn(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Name) {
|
||||
$nameNode = $node;
|
||||
$node = $node->getAttribute('parentNode');
|
||||
}
|
||||
// Only the class node should count as the definition, not the name node
|
||||
// Anonymous classes don't count as a definition
|
||||
if ($node instanceof Node\Stmt\ClassLike && !isset($nameNode) && isset($node->name)) {
|
||||
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
|
||||
// Class, interface or trait declaration
|
||||
return (string)$node->namespacedName;
|
||||
} else if ($node instanceof Node\Stmt\Function_ && !isset($nameNode)) {
|
||||
} else if ($node instanceof Node\Stmt\Function_) {
|
||||
// Function: use functionName() as the name
|
||||
return (string)$node->namespacedName . '()';
|
||||
} else if ($node instanceof Node\Stmt\ClassMethod) {
|
||||
|
|
113
src/Project.php
113
src/Project.php
|
@ -18,10 +18,9 @@ class Project
|
|||
private $documents = [];
|
||||
|
||||
/**
|
||||
* An associative array [string => PhpDocument]
|
||||
* that maps fully qualified symbol names to loaded PhpDocuments
|
||||
* An associative array that maps fully qualified symbol names to document URIs
|
||||
*
|
||||
* @var PhpDocument[]
|
||||
* @var string[]
|
||||
*/
|
||||
private $definitions = [];
|
||||
|
||||
|
@ -48,7 +47,8 @@ class Project
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the document indicated by uri. Instantiates a new document if none exists.
|
||||
* Returns the document indicated by uri.
|
||||
* If the document is not open, tries to read it from disk, but the document is not added the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return LanguageServer\PhpDocument
|
||||
|
@ -56,20 +56,93 @@ class Project
|
|||
public function getDocument(string $uri)
|
||||
{
|
||||
if (!isset($this->documents[$uri])) {
|
||||
$this->documents[$uri] = new PhpDocument($uri, $this, $this->client, $this->parser);
|
||||
}
|
||||
return $this->loadDocument($uri);
|
||||
} else {
|
||||
return $this->documents[$uri];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a document as the container for a specific symbol
|
||||
* Reads a document from disk.
|
||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||
*
|
||||
* @param string $fqn The fully qualified name of the symbol
|
||||
* @param string $uri
|
||||
* @return LanguageServer\PhpDocument
|
||||
*/
|
||||
public function loadDocument(string $uri)
|
||||
{
|
||||
$content = file_get_contents(uriToPath($uri));
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument($uri, $content, $this, $this->client, $this->parser);
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a document is loaded and added to the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $content
|
||||
* @return void
|
||||
*/
|
||||
public function addDefinitionDocument(string $fqn, PhpDocument $document)
|
||||
public function openDocument(string $uri, string $content)
|
||||
{
|
||||
$this->definitions[$fqn] = $document;
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument($uri, $content, $this, $this->client, $this->parser);
|
||||
$this->documents[$uri] = $document;
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the document with the specified URI from the list of open documents
|
||||
*
|
||||
* @param string $uri
|
||||
* @return void
|
||||
*/
|
||||
public function closeDocument(string $uri)
|
||||
{
|
||||
unset($this->documents[$uri]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the document is open (and loaded)
|
||||
*
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function isDocumentOpen(string $uri): bool
|
||||
{
|
||||
return isset($this->documents[$uri]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array [string => string] that maps fully qualified symbol names
|
||||
* to URIs of the document where the symbol is defined
|
||||
*
|
||||
* @return PhpDocument[]
|
||||
*/
|
||||
public function getDefinitionUris()
|
||||
{
|
||||
return $this->definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a document URI as the container for a specific symbol
|
||||
*
|
||||
* @param string $fqn The fully qualified name of the symbol
|
||||
* @param string $uri The URI
|
||||
* @return void
|
||||
*/
|
||||
public function setDefinitionUri(string $fqn, string $uri)
|
||||
{
|
||||
$this->definitions[$fqn] = $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +153,7 @@ class Project
|
|||
*/
|
||||
public function getDefinitionDocument(string $fqn)
|
||||
{
|
||||
return $this->definitions[$fqn] ?? null;
|
||||
return isset($this->definitions[$fqn]) ? $this->getDocument($this->definitions[$fqn]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,22 +166,4 @@ class Project
|
|||
{
|
||||
return isset($this->definitions[$fqn]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds symbols in all documents, filtered by query parameter.
|
||||
*
|
||||
* @param string $query
|
||||
* @return SymbolInformation[]
|
||||
*/
|
||||
public function findSymbols(string $query)
|
||||
{
|
||||
$queryResult = [];
|
||||
foreach ($this->documents as $uri => $document) {
|
||||
$documentQueryResult = $document->findSymbols($query);
|
||||
if ($documentQueryResult !== null) {
|
||||
$queryResult = array_merge($queryResult, $documentQueryResult);
|
||||
}
|
||||
}
|
||||
return $queryResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace LanguageServer\Protocol;
|
||||
|
||||
use PhpParser\Node;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Represents information about programming constructs like variables, classes,
|
||||
|
@ -37,4 +38,39 @@ class SymbolInformation
|
|||
* @var string|null
|
||||
*/
|
||||
public $containerName;
|
||||
|
||||
/**
|
||||
* Converts a Node to a SymbolInformation
|
||||
*
|
||||
* @param Node $node
|
||||
* @param string $fqn If given, $containerName will be extracted from it
|
||||
* @return self
|
||||
*/
|
||||
public static function fromNode(Node $node, string $fqn = 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
|
||||
];
|
||||
$class = get_class($node);
|
||||
if (!isset($nodeSymbolKindMap[$class])) {
|
||||
throw new Exception("Not a declaration node: $class");
|
||||
}
|
||||
$symbol = new self;
|
||||
$symbol->kind = $nodeSymbolKindMap[$class];
|
||||
$symbol->name = (string)$node->name;
|
||||
$symbol->location = Location::fromNode($node);
|
||||
if ($fqn !== null) {
|
||||
$parts = preg_split('/(::|\\\\)/', $fqn);
|
||||
array_pop($parts);
|
||||
$symbol->containerName = implode('\\', $parts);
|
||||
}
|
||||
return $symbol;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ use LanguageServer\Protocol\{
|
|||
Position,
|
||||
FormattingOptions,
|
||||
TextEdit,
|
||||
Location
|
||||
Location,
|
||||
SymbolInformation
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -49,7 +50,11 @@ class TextDocument
|
|||
*/
|
||||
public function documentSymbol(TextDocumentIdentifier $textDocument): array
|
||||
{
|
||||
return $this->project->getDocument($textDocument->uri)->getSymbols();
|
||||
$symbols = [];
|
||||
foreach ($this->project->getDocument($textDocument->uri)->getDefinitions() as $fqn => $node) {
|
||||
$symbols[] = SymbolInformation::fromNode($node, $fqn);
|
||||
}
|
||||
return $symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +67,7 @@ class TextDocument
|
|||
*/
|
||||
public function didOpen(TextDocumentItem $textDocument)
|
||||
{
|
||||
$this->project->getDocument($textDocument->uri)->updateContent($textDocument->text);
|
||||
$this->project->openDocument($textDocument->uri, $textDocument->text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,6 +82,18 @@ class TextDocument
|
|||
$this->project->getDocument($textDocument->uri)->updateContent($contentChanges[0]->text);
|
||||
}
|
||||
|
||||
/**
|
||||
* The document close notification is sent from the client to the server when the document got closed in the client.
|
||||
* The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the
|
||||
* truth now exists on disk).
|
||||
*
|
||||
* @param \LanguageServer\Protocol\TextDocumentItem $textDocument The document that was closed
|
||||
* @return void
|
||||
*/
|
||||
public function didClose(TextDocumentIdentifier $textDocument)
|
||||
{
|
||||
$this->project->closeDocument($textDocument->uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* The document formatting request is sent from the server to the client to format a whole document.
|
||||
|
|
|
@ -53,6 +53,12 @@ class Workspace
|
|||
*/
|
||||
public function symbol(string $query): array
|
||||
{
|
||||
return $this->project->findSymbols($query);
|
||||
$symbols = [];
|
||||
foreach ($this->project->getDefinitionUris() as $fqn => $uri) {
|
||||
if ($query === '' || stripos($fqn, $query) !== false) {
|
||||
$symbols[] = SymbolInformation::fromNode($this->project->getDocument($uri)->getDefinitionByFqn($fqn), $fqn);
|
||||
}
|
||||
}
|
||||
return $symbols;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use PhpParser\NodeVisitor\NameResolver;
|
|||
use LanguageServer\{LanguageClient, Project, PhpDocument};
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
||||
use function LanguageServer\pathToUri;
|
||||
|
||||
class DefinitionCollectorTest extends TestCase
|
||||
{
|
||||
|
@ -17,13 +18,14 @@ class DefinitionCollectorTest extends TestCase
|
|||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$document = new PhpDocument('symbols', $project, $client, $parser);
|
||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
||||
$document = $project->loadDocument($uri);
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$stmts = $parser->parse(file_get_contents(__DIR__ . '/../../fixtures/symbols.php'));
|
||||
$stmts = $parser->parse(file_get_contents($uri));
|
||||
$traverser->traverse($stmts);
|
||||
$defs = $definitionCollector->definitions;
|
||||
$this->assertEquals([
|
||||
|
@ -55,13 +57,14 @@ class DefinitionCollectorTest extends TestCase
|
|||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$document = new PhpDocument('references', $project, $client, $parser);
|
||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
||||
$document = $project->loadDocument($uri);
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$stmts = $parser->parse(file_get_contents(__DIR__ . '/../../fixtures/references.php'));
|
||||
$stmts = $parser->parse(file_get_contents($uri));
|
||||
$traverser->traverse($stmts);
|
||||
$defs = $definitionCollector->definitions;
|
||||
$this->assertEquals(['TestNamespace\\whatever()'], array_keys($defs));
|
||||
|
|
|
@ -24,19 +24,14 @@ class PhpDocumentTest extends TestCase
|
|||
|
||||
public function testParsesVariableVariables()
|
||||
{
|
||||
$document = $this->project->getDocument('whatever');
|
||||
$document = $this->project->openDocument('whatever', "<?php\n$\$a = 'foo';\n\$bar = 'baz';\n");
|
||||
|
||||
$document->updateContent("<?php\n$\$a = 'foo';\n\$bar = 'baz';\n");
|
||||
|
||||
$symbols = $document->getSymbols();
|
||||
|
||||
$this->assertEquals([], json_decode(json_encode($symbols), true));
|
||||
$this->assertEquals([], $document->getDefinitions());
|
||||
}
|
||||
|
||||
public function testGetNodeAtPosition()
|
||||
{
|
||||
$document = $this->project->getDocument('whatever');
|
||||
$document->updateContent("<?php\n$\$a = new SomeClass;");
|
||||
$document = $this->project->openDocument('whatever', "<?php\n$\$a = new SomeClass;");
|
||||
$node = $document->getNodeAtPosition(new Position(1, 13));
|
||||
$this->assertInstanceOf(Node\Name\FullyQualified::class, $node);
|
||||
$this->assertEquals('SomeClass', (string)$node);
|
||||
|
|
|
@ -8,6 +8,7 @@ use LanguageServer\Tests\MockProtocolStream;
|
|||
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
|
||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, SymbolKind, DiagnosticSeverity, FormattingOptions};
|
||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
||||
use function LanguageServer\pathToUri;
|
||||
|
||||
class ProjectTest extends TestCase
|
||||
{
|
||||
|
@ -21,67 +22,19 @@ class ProjectTest extends TestCase
|
|||
$this->project = new Project(new LanguageClient(new MockProtocolStream()));
|
||||
}
|
||||
|
||||
public function testGetDocumentCreatesNewDocument()
|
||||
public function testGetDocumentLoadsDocument()
|
||||
{
|
||||
$document = $this->project->getDocument('file:///document1.php');
|
||||
$document = $this->project->getDocument(pathToUri(__FILE__));
|
||||
|
||||
$this->assertNotNull($document);
|
||||
$this->assertInstanceOf(PhpDocument::class, $document);
|
||||
}
|
||||
|
||||
public function testGetDocumentCreatesDocumentOnce()
|
||||
public function testGetDocumentReturnsOpenedInstance()
|
||||
{
|
||||
$document1 = $this->project->getDocument('file:///document1.php');
|
||||
$document2 = $this->project->getDocument('file:///document1.php');
|
||||
$document1 = $this->project->openDocument(pathToUri(__FILE__), file_get_contents(__FILE__));
|
||||
$document2 = $this->project->getDocument(pathToUri(__FILE__));
|
||||
|
||||
$this->assertSame($document1, $document2);
|
||||
}
|
||||
|
||||
public function testFindSymbols()
|
||||
{
|
||||
$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('invalid_file')->updateContent(file_get_contents(__DIR__ . '/../fixtures/invalid_file.php'));
|
||||
|
||||
$symbols = $this->project->findSymbols('ba');
|
||||
|
||||
$this->assertEquals([
|
||||
[
|
||||
'name' => 'bar',
|
||||
'kind' => SymbolKind::FUNCTION,
|
||||
'location' => [
|
||||
'uri' => 'file:///document1.php',
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 2,
|
||||
'character' => 0
|
||||
],
|
||||
'end' => [
|
||||
'line' => 2,
|
||||
'character' => 17
|
||||
]
|
||||
]
|
||||
],
|
||||
'containerName' => null
|
||||
],
|
||||
[
|
||||
'name' => 'baz',
|
||||
'kind' => SymbolKind::FUNCTION,
|
||||
'location' => [
|
||||
'uri' => 'file:///document2.php',
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 1,
|
||||
'character' => 0
|
||||
],
|
||||
'end' => [
|
||||
'line' => 1,
|
||||
'character' => 17
|
||||
]
|
||||
]
|
||||
],
|
||||
'containerName' => null
|
||||
]
|
||||
], json_decode(json_encode($symbols), true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ class DefinitionTest extends TestCase
|
|||
$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'));
|
||||
$project->openDocument('references', file_get_contents(__DIR__ . '/../../../fixtures/references.php'));
|
||||
$project->openDocument('symbols', file_get_contents(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||
$project->openDocument('use', file_get_contents(__DIR__ . '/../../../fixtures/use.php'));
|
||||
}
|
||||
|
||||
public function testDefinitionFileBeginning() {
|
||||
|
|
|
@ -22,8 +22,7 @@ class DidChangeTest extends TestCase
|
|||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$textDocument = new Server\TextDocument($project, $client);
|
||||
$phpDocument = $project->getDocument('whatever');
|
||||
$phpDocument->updateContent("<?php\necho 'Hello, World'\n");
|
||||
$phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n");
|
||||
|
||||
$identifier = new VersionedTextDocumentIdentifier('whatever');
|
||||
$changeEvent = new TextDocumentContentChangeEvent();
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\Tests\Server\TextDocument;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, Project};
|
||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier};
|
||||
use Exception;
|
||||
|
||||
class DidCloseTest extends TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$textDocument = new Server\TextDocument($project, $client);
|
||||
$phpDocument = $project->openDocument('whatever', 'hello world');
|
||||
|
||||
$textDocumentItem = new TextDocumentItem();
|
||||
$textDocumentItem->uri = 'whatever';
|
||||
$textDocumentItem->languageId = 'php';
|
||||
$textDocumentItem->version = 1;
|
||||
$textDocumentItem->text = 'hello world';
|
||||
$textDocument->didOpen($textDocumentItem);
|
||||
|
||||
$textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri));
|
||||
|
||||
$this->assertFalse($project->isDocumentOpen($textDocumentItem->uri));
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class DocumentSymbolTest extends TestCase
|
|||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$this->textDocument = new Server\TextDocument($project, $client);
|
||||
$project->getDocument('symbols')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||
$project->openDocument('symbols', file_get_contents(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||
}
|
||||
|
||||
public function test()
|
||||
|
|
|
@ -57,14 +57,4 @@ class FormattingTest extends TestCase
|
|||
'newText' => $expected
|
||||
]], json_decode(json_encode($result), true));
|
||||
}
|
||||
|
||||
public function testFormattingInvalidUri()
|
||||
{
|
||||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$textDocument = new Server\TextDocument($project, $client);
|
||||
|
||||
$result = $textDocument->formatting(new TextDocumentIdentifier('whatever'), new FormattingOptions());
|
||||
$this->assertSame([], $result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\Tests\Server;
|
||||
namespace LanguageServer\Tests\Server\Workspace;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
|
||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, SymbolKind, DiagnosticSeverity, FormattingOptions};
|
||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
||||
use function LanguageServer\pathToUri;
|
||||
|
||||
class SymbolTest extends TestCase
|
||||
{
|
||||
|
@ -16,13 +17,25 @@ class SymbolTest extends TestCase
|
|||
*/
|
||||
private $workspace;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $symbolsUri;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $referencesUri;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$client = new LanguageClient(new MockProtocolStream());
|
||||
$project = new Project($client);
|
||||
$this->workspace = new Server\Workspace($project, $client);
|
||||
$project->getDocument('symbols')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||
$project->getDocument('references')->updateContent(file_get_contents(__DIR__ . '/../../../fixtures/references.php'));
|
||||
$this->symbolsUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||
$this->referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||
$project->loadDocument($this->symbolsUri);
|
||||
$project->loadDocument($this->referencesUri);
|
||||
}
|
||||
|
||||
public function testEmptyQueryReturnsAllSymbols()
|
||||
|
@ -34,7 +47,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'TEST_CONST',
|
||||
'kind' => SymbolKind::CONSTANT,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 4,
|
||||
|
@ -52,7 +65,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'TestClass',
|
||||
'kind' => SymbolKind::CLASS_,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 6,
|
||||
|
@ -70,7 +83,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'TEST_CLASS_CONST',
|
||||
'kind' => SymbolKind::CONSTANT,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 8,
|
||||
|
@ -88,7 +101,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'staticTestProperty',
|
||||
'kind' => SymbolKind::PROPERTY,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 9,
|
||||
|
@ -106,7 +119,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'testProperty',
|
||||
'kind' => SymbolKind::PROPERTY,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 10,
|
||||
|
@ -124,7 +137,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'staticTestMethod',
|
||||
'kind' => SymbolKind::METHOD,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 12,
|
||||
|
@ -142,7 +155,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'testMethod',
|
||||
'kind' => SymbolKind::METHOD,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 17,
|
||||
|
@ -160,7 +173,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'TestTrait',
|
||||
'kind' => SymbolKind::CLASS_,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 23,
|
||||
|
@ -178,7 +191,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'TestInterface',
|
||||
'kind' => SymbolKind::INTERFACE,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 28,
|
||||
|
@ -196,7 +209,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'test_function',
|
||||
'kind' => SymbolKind::FUNCTION,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 33,
|
||||
|
@ -214,7 +227,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'whatever',
|
||||
'kind' => SymbolKind::FUNCTION,
|
||||
'location' => [
|
||||
'uri' => 'references',
|
||||
'uri' => $this->referencesUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 15,
|
||||
|
@ -240,7 +253,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'staticTestMethod',
|
||||
'kind' => SymbolKind::METHOD,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 12,
|
||||
|
@ -258,7 +271,7 @@ class SymbolTest extends TestCase
|
|||
'name' => 'testMethod',
|
||||
'kind' => SymbolKind::METHOD,
|
||||
'location' => [
|
||||
'uri' => 'symbols',
|
||||
'uri' => $this->symbolsUri,
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 17,
|
||||
|
|
Loading…
Reference in New Issue