1
0
Fork 0

Enable LS to operate without accessing the file system (#136)

This PR decouples the LS from direct file system access by implementing the proposals for workspace/files and textDocument/content under workspace/xfiles and textDocument/xcontent. The requests are only used when the client expressed support for them through ClientCapabilities, otherwise direct FS access is used.
This turns document content retrieval and recursive file search into async operations.
In turn, all server handlers can now operate async by returning a promise.
pull/147/head
Felix Becker 2016-11-14 10:25:44 +01:00 committed by GitHub
parent 25f300c157
commit 03bbf5f4ba
31 changed files with 799 additions and 386 deletions

View File

@ -29,7 +29,11 @@
"sabre/event": "^5.0", "sabre/event": "^5.0",
"felixfbecker/advanced-json-rpc": "^2.0", "felixfbecker/advanced-json-rpc": "^2.0",
"squizlabs/php_codesniffer" : "^2.7", "squizlabs/php_codesniffer" : "^2.7",
"symfony/debug": "^3.1" "symfony/debug": "^3.1",
"netresearch/jsonmapper": "^1.0",
"webmozart/path-util": "^2.3",
"webmozart/glob": "^4.1",
"sabre/uri": "^2.0"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true, "prefer-stable": true,

View File

@ -1 +0,0 @@
A

View File

@ -1 +0,0 @@
B

View File

@ -1 +0,0 @@
Peeakboo!

View File

@ -4,8 +4,9 @@ declare(strict_types = 1);
namespace LanguageServer\Client; namespace LanguageServer\Client;
use LanguageServer\ClientHandler; use LanguageServer\ClientHandler;
use LanguageServer\Protocol\Message; use LanguageServer\Protocol\{Message, TextDocumentItem, TextDocumentIdentifier};
use Sabre\Event\Promise; use Sabre\Event\Promise;
use JsonMapper;
/** /**
* Provides method handlers for all textDocument/* methods * Provides method handlers for all textDocument/* methods
@ -17,9 +18,15 @@ class TextDocument
*/ */
private $handler; private $handler;
public function __construct(ClientHandler $handler) /**
* @var JsonMapper
*/
private $mapper;
public function __construct(ClientHandler $handler, JsonMapper $mapper)
{ {
$this->handler = $handler; $this->handler = $handler;
$this->mapper = $mapper;
} }
/** /**
@ -36,4 +43,21 @@ class TextDocument
'diagnostics' => $diagnostics 'diagnostics' => $diagnostics
]); ]);
} }
/**
* The content request is sent from a server to a client
* to request the current content of a text document identified by the URI
*
* @param TextDocumentIdentifier $textDocument The document to get the content for
* @return Promise <TextDocumentItem> The document's current content
*/
public function xcontent(TextDocumentIdentifier $textDocument): Promise
{
return $this->handler->request(
'textDocument/xcontent',
['textDocument' => $textDocument]
)->then(function ($result) {
return $this->mapper->map($result, new TextDocumentItem);
});
}
} }

47
src/Client/Workspace.php Normal file
View File

@ -0,0 +1,47 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Client;
use LanguageServer\ClientHandler;
use LanguageServer\Protocol\TextDocumentIdentifier;
use Sabre\Event\Promise;
use JsonMapper;
/**
* Provides method handlers for all workspace/* methods
*/
class Workspace
{
/**
* @var ClientHandler
*/
private $handler;
/**
* @var JsonMapper
*/
private $mapper;
public function __construct(ClientHandler $handler, JsonMapper $mapper)
{
$this->handler = $handler;
$this->mapper = $mapper;
}
/**
* Returns a list of all files in a directory
*
* @param string $base The base directory (defaults to the workspace)
* @return Promise <TextDocumentIdentifier[]> Array of documents
*/
public function xfiles(string $base = null): Promise
{
return $this->handler->request(
'workspace/xfiles',
['base' => $base]
)->then(function (array $textDocuments) {
return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class);
});
}
}

View File

@ -3,6 +3,8 @@ declare(strict_types = 1);
namespace LanguageServer; namespace LanguageServer;
use JsonMapper;
class LanguageClient class LanguageClient
{ {
/** /**
@ -19,11 +21,20 @@ class LanguageClient
*/ */
public $window; public $window;
/**
* Handles workspace/* methods
*
* @var Client\Workspace
*/
public $workspace;
public function __construct(ProtocolReader $reader, ProtocolWriter $writer) public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
{ {
$handler = new ClientHandler($reader, $writer); $handler = new ClientHandler($reader, $writer);
$mapper = new JsonMapper;
$this->textDocument = new Client\TextDocument($handler); $this->textDocument = new Client\TextDocument($handler, $mapper);
$this->window = new Client\Window($handler); $this->window = new Client\Window($handler);
$this->workspace = new Client\Workspace($handler, $mapper);
} }
} }

View File

@ -10,12 +10,18 @@ use LanguageServer\Protocol\{
Message, Message,
MessageType, MessageType,
InitializeResult, InitializeResult,
SymbolInformation SymbolInformation,
TextDocumentIdentifier
}; };
use AdvancedJsonRpc; use AdvancedJsonRpc;
use Sabre\Event\Loop; use Sabre\Event\{Loop, Promise};
use function Sabre\Event\coroutine;
use Exception; use Exception;
use Throwable; use Throwable;
use Webmozart\Glob\Iterator\GlobIterator;
use Webmozart\Glob\Glob;
use Webmozart\PathUtil\Path;
use Sabre\Uri;
class LanguageServer extends AdvancedJsonRpc\Dispatcher class LanguageServer extends AdvancedJsonRpc\Dispatcher
{ {
@ -38,6 +44,11 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
public $completionItem; public $completionItem;
public $codeLens; public $codeLens;
/**
* ClientCapabilities
*/
private $clientCapabilities;
private $protocolReader; private $protocolReader;
private $protocolWriter; private $protocolWriter;
private $client; private $client;
@ -55,40 +66,42 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
parent::__construct($this, '/'); parent::__construct($this, '/');
$this->protocolReader = $reader; $this->protocolReader = $reader;
$this->protocolReader->on('message', function (Message $msg) { $this->protocolReader->on('message', function (Message $msg) {
// Ignore responses, this is the handler for requests and notifications coroutine(function () use ($msg) {
if (AdvancedJsonRpc\Response::isResponse($msg->body)) { // Ignore responses, this is the handler for requests and notifications
return; if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
} return;
$result = null;
$error = null;
try {
// Invoke the method handler to get a result
$result = $this->dispatch($msg->body);
} catch (AdvancedJsonRpc\Error $e) {
// If a ResponseError is thrown, send it back in the Response
$error = $e;
} catch (Throwable $e) {
// If an unexpected error occured, send back an INTERNAL_ERROR error response
$error = new AdvancedJsonRpc\Error($e->getMessage(), AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, null, $e);
}
// Only send a Response for a Request
// Notifications do not send Responses
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
if ($error !== null) {
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
} else {
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
} }
$this->protocolWriter->write(new Message($responseBody)); $result = null;
} $error = null;
try {
// Invoke the method handler to get a result
$result = yield $this->dispatch($msg->body);
} catch (AdvancedJsonRpc\Error $e) {
// If a ResponseError is thrown, send it back in the Response
$error = $e;
} catch (Throwable $e) {
// If an unexpected error occured, send back an INTERNAL_ERROR error response
$error = new AdvancedJsonRpc\Error(
$e->getMessage(),
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
null,
$e
);
}
// Only send a Response for a Request
// Notifications do not send Responses
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
if ($error !== null) {
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
} else {
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
}
$this->protocolWriter->write(new Message($responseBody));
}
})->otherwise('\\LanguageServer\\crash');
}); });
$this->protocolWriter = $writer; $this->protocolWriter = $writer;
$this->client = new LanguageClient($reader, $writer); $this->client = new LanguageClient($reader, $writer);
$this->project = new Project($this->client);
$this->textDocument = new Server\TextDocument($this->project, $this->client);
$this->workspace = new Server\Workspace($this->project, $this->client);
} }
/** /**
@ -102,10 +115,14 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult
{ {
$this->rootPath = $rootPath; $this->rootPath = $rootPath;
$this->clientCapabilities = $capabilities;
$this->project = new Project($this->client, $capabilities);
$this->textDocument = new Server\TextDocument($this->project, $this->client);
$this->workspace = new Server\Workspace($this->project, $this->client);
// start building project index // start building project index
if ($rootPath !== null) { if ($rootPath !== null) {
$this->indexProject(); $this->indexProject()->otherwise('\\LanguageServer\\crash');
} }
$serverCapabilities = new ServerCapabilities(); $serverCapabilities = new ServerCapabilities();
@ -136,6 +153,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
*/ */
public function shutdown() public function shutdown()
{ {
unset($this->project);
} }
/** /**
@ -151,42 +169,71 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
/** /**
* Parses workspace files, one at a time. * Parses workspace files, one at a time.
* *
* @return void * @return Promise <void>
*/ */
private function indexProject() private function indexProject(): Promise
{ {
$fileList = findFilesRecursive($this->rootPath, '/^.+\.php$/i'); return coroutine(function () {
$numTotalFiles = count($fileList); $textDocuments = yield $this->findPhpFiles();
$count = count($textDocuments);
$startTime = microtime(true); $startTime = microtime(true);
$fileNum = 0;
$processFile = function () use (&$fileList, &$fileNum, &$processFile, $numTotalFiles, $startTime) { yield Promise\all(array_map(function ($textDocument, $i) use ($count) {
if ($fileNum < $numTotalFiles) { return coroutine(function () use ($textDocument, $i, $count) {
$file = $fileList[$fileNum]; // Give LS to the chance to handle requests while indexing
$uri = pathToUri($file); yield timeout();
$fileNum++; $this->client->window->logMessage(
$shortName = substr($file, strlen($this->rootPath) + 1); MessageType::INFO,
"Parsing file $i/$count: {$textDocument->uri}"
if (filesize($file) > 500000) { );
$this->client->window->logMessage(MessageType::INFO, "Not parsing $shortName because it exceeds size limit of 0.5MB");
} else {
$this->client->window->logMessage(MessageType::INFO, "Parsing file $fileNum/$numTotalFiles: $shortName.");
try { try {
$this->project->loadDocument($uri); yield $this->project->loadDocument($textDocument->uri);
} catch (Exception $e) { } catch (Exception $e) {
$this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); $this->client->window->logMessage(
MessageType::ERROR,
"Error parsing file {$textDocument->uri}: " . (string)$e
);
}
});
}, $textDocuments, array_keys($textDocuments)));
$duration = (int)(microtime(true) - $startTime);
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
$this->client->window->logMessage(
MessageType::INFO,
"All $count PHP files parsed in $duration seconds. $mem MiB allocated."
);
});
}
/**
* Returns all PHP files in the workspace.
* If the client does not support workspace/files, it falls back to searching the file system directly.
*
* @return Promise <TextDocumentIdentifier[]>
*/
private function findPhpFiles(): Promise
{
return coroutine(function () {
$textDocuments = [];
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
if ($this->clientCapabilities->xfilesProvider) {
// Use xfiles request
foreach (yield $this->client->workspace->xfiles() as $textDocument) {
$path = Uri\parse($textDocument->uri)['path'];
if (Glob::match($path, $pattern)) {
$textDocuments[] = $textDocument;
} }
} }
Loop\setTimeout($processFile, 0);
} else { } else {
$duration = (int)(microtime(true) - $startTime); // Use the file system
$mem = (int)(memory_get_usage(true) / (1024 * 1024)); foreach (new GlobIterator($pattern) as $path) {
$this->client->window->logMessage(MessageType::INFO, "All $numTotalFiles PHP files parsed in $duration seconds. $mem MiB allocated."); $textDocuments[] = new TextDocumentIdentifier(pathToUri($path));
yield timeout();
}
} }
}; return $textDocuments;
});
Loop\setTimeout($processFile, 0);
} }
} }

View File

@ -17,6 +17,8 @@ use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NameResolver;
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn}; use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn};
use Sabre\Event\Promise;
use function Sabre\Event\coroutine;
class PhpDocument class PhpDocument
{ {
@ -314,34 +316,36 @@ class PhpDocument
* The definition node MAY be in another document, check the ownerDocument attribute * The definition node MAY be in another document, check the ownerDocument attribute
* *
* @param Node $node * @param Node $node
* @return Node|null * @return Promise <Node|null>
*/ */
public function getDefinitionByNode(Node $node) public function getDefinitionByNode(Node $node): Promise
{ {
// Variables always stay in the boundary of the file and need to be searched inside their function scope return coroutine(function () use ($node) {
// by traversing the AST // Variables always stay in the boundary of the file and need to be searched inside their function scope
if ($node instanceof Node\Expr\Variable) { // by traversing the AST
return getVariableDefinition($node); if ($node instanceof Node\Expr\Variable) {
} return getVariableDefinition($node);
$fqn = getReferencedFqn($node);
if (!isset($fqn)) {
return null;
}
$document = $this->project->getDefinitionDocument($fqn);
if (!isset($document)) {
// 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
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) {
$parts = explode('\\', $fqn);
$fqn = end($parts);
$document = $this->project->getDefinitionDocument($fqn);
} }
} $fqn = getReferencedFqn($node);
if (!isset($document)) { if (!isset($fqn)) {
return null; return null;
} }
return $document->getDefinitionByFqn($fqn); $document = yield $this->project->getDefinitionDocument($fqn);
if (!isset($document)) {
// 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
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) {
$parts = explode('\\', $fqn);
$fqn = end($parts);
$document = yield $this->project->getDefinitionDocument($fqn);
}
}
if (!isset($document)) {
return null;
}
return $document->getDefinitionByFqn($fqn);
});
} }
/** /**
@ -349,45 +353,47 @@ class PhpDocument
* The references node MAY be in other documents, check the ownerDocument attribute * The references node MAY be in other documents, check the ownerDocument attribute
* *
* @param Node $node * @param Node $node
* @return Node[] * @return Promise <Node[]>
*/ */
public function getReferencesByNode(Node $node) public function getReferencesByNode(Node $node): Promise
{ {
// Variables always stay in the boundary of the file and need to be searched inside their function scope return coroutine(function () use ($node) {
// by traversing the AST // Variables always stay in the boundary of the file and need to be searched inside their function scope
if ($node instanceof Node\Expr\Variable || $node instanceof Node\Param) { // by traversing the AST
if ($node->name instanceof Node\Expr) { if ($node instanceof Node\Expr\Variable || $node instanceof Node\Param) {
return null; if ($node->name instanceof Node\Expr) {
return null;
}
// Find function/method/closure scope
$n = $node;
while (isset($n) && !($n instanceof Node\FunctionLike)) {
$n = $n->getAttribute('parentNode');
}
if (!isset($n)) {
$n = $node->getAttribute('ownerDocument');
}
$traverser = new NodeTraverser;
$refCollector = new VariableReferencesCollector($node->name);
$traverser->addVisitor($refCollector);
$traverser->traverse($n->getStmts());
return $refCollector->references;
} }
// Find function/method/closure scope // Definition with a global FQN
$n = $node; $fqn = getDefinedFqn($node);
while (isset($n) && !($n instanceof Node\FunctionLike)) { if ($fqn === null) {
$n = $n->getAttribute('parentNode'); return [];
} }
if (!isset($n)) { $refDocuments = yield $this->project->getReferenceDocuments($fqn);
$n = $node->getAttribute('ownerDocument'); $nodes = [];
} foreach ($refDocuments as $document) {
$traverser = new NodeTraverser; $refs = $document->getReferencesByFqn($fqn);
$refCollector = new VariableReferencesCollector($node->name); if ($refs !== null) {
$traverser->addVisitor($refCollector); foreach ($refs as $ref) {
$traverser->traverse($n->getStmts()); $nodes[] = $ref;
return $refCollector->references; }
}
// Definition with a global FQN
$fqn = getDefinedFqn($node);
if ($fqn === null) {
return [];
}
$refDocuments = $this->project->getReferenceDocuments($fqn);
$nodes = [];
foreach ($refDocuments as $document) {
$refs = $document->getReferencesByFqn($fqn);
if ($refs !== null) {
foreach ($refs as $ref) {
$nodes[] = $ref;
} }
} }
} return $nodes;
return $nodes; });
} }
} }

View File

@ -3,8 +3,10 @@ declare(strict_types = 1);
namespace LanguageServer; namespace LanguageServer;
use LanguageServer\Protocol\SymbolInformation; use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use Sabre\Event\Promise;
use function Sabre\Event\coroutine;
class Project class Project
{ {
@ -51,47 +53,69 @@ class Project
*/ */
private $client; private $client;
public function __construct(LanguageClient $client) /**
* The client's capabilities
*
* @var ClientCapabilities
*/
private $clientCapabilities;
public function __construct(LanguageClient $client, ClientCapabilities $clientCapabilities)
{ {
$this->client = $client; $this->client = $client;
$this->clientCapabilities = $clientCapabilities;
$this->parser = new Parser; $this->parser = new Parser;
$this->docBlockFactory = DocBlockFactory::createInstance(); $this->docBlockFactory = DocBlockFactory::createInstance();
} }
/** /**
* Returns the document indicated by uri. * 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. * Returns null if the document if not loaded.
* *
* @param string $uri * @param string $uri
* @return LanguageServer\PhpDocument * @return PhpDocument|null
*/ */
public function getDocument(string $uri) public function getDocument(string $uri)
{ {
if (!isset($this->documents[$uri])) { return $this->documents[$uri] ?? null;
return $this->loadDocument($uri);
} else {
return $this->documents[$uri];
}
} }
/** /**
* Reads a document from disk. * Returns the document indicated by uri.
* If the document is not open, loads it.
*
* @param string $uri
* @return Promise <PhpDocument>
*/
public function getOrLoadDocument(string $uri)
{
return isset($this->documents[$uri]) ? Promise\resolve($this->documents[$uri]) : $this->loadDocument($uri);
}
/**
* Loads the document by doing a textDocument/xcontent request to the client.
* If the client does not support textDocument/xcontent, tries to read the file from the file system.
* The document is NOT added to the list of open documents, but definitions are registered. * The document is NOT added to the list of open documents, but definitions are registered.
* *
* @param string $uri * @param string $uri
* @return LanguageServer\PhpDocument * @return Promise <PhpDocument>
*/ */
public function loadDocument(string $uri) public function loadDocument(string $uri): Promise
{ {
$content = file_get_contents(uriToPath($uri)); return coroutine(function () use ($uri) {
if (isset($this->documents[$uri])) { if ($this->clientCapabilities->xcontentProvider) {
$document = $this->documents[$uri]; $content = (yield $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri)))->text;
$document->updateContent($content); } else {
} else { $content = file_get_contents(uriToPath($uri));
$document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory); }
} if (isset($this->documents[$uri])) {
return $document; $document = $this->documents[$uri];
$document->updateContent($content);
} else {
$document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory);
}
return $document;
});
} }
/** /**
@ -222,14 +246,14 @@ class Project
* Returns all documents that reference a symbol * Returns all documents that reference a symbol
* *
* @param string $fqn The fully qualified name of the symbol * @param string $fqn The fully qualified name of the symbol
* @return PhpDocument[] * @return Promise <PhpDocument[]>
*/ */
public function getReferenceDocuments(string $fqn) public function getReferenceDocuments(string $fqn): Promise
{ {
if (!isset($this->references[$fqn])) { if (!isset($this->references[$fqn])) {
return []; return Promise\resolve([]);
} }
return array_map([$this, 'getDocument'], $this->references[$fqn]); return Promise\all(array_map([$this, 'getOrLoadDocument'], $this->references[$fqn]));
} }
/** /**
@ -258,11 +282,14 @@ class Project
* Returns the document where a symbol is defined * Returns the document where a symbol is defined
* *
* @param string $fqn The fully qualified name of the symbol * @param string $fqn The fully qualified name of the symbol
* @return PhpDocument|null * @return Promise <PhpDocument|null>
*/ */
public function getDefinitionDocument(string $fqn) public function getDefinitionDocument(string $fqn): Promise
{ {
return isset($this->symbols[$fqn]) ? $this->getDocument($this->symbols[$fqn]->location->uri) : null; if (!isset($this->symbols[$fqn])) {
return Promise\resolve(null);
}
return $this->getOrLoadDocument($this->symbols[$fqn]->location->uri);
} }
/** /**

View File

@ -4,5 +4,17 @@ namespace LanguageServer\Protocol;
class ClientCapabilities class ClientCapabilities
{ {
/**
* The client supports workspace/xfiles requests
*
* @var bool|null
*/
public $xfilesProvider;
/**
* The client supports textDocument/xcontent requests
*
* @var bool|null
*/
public $xcontentProvider;
} }

View File

@ -3,7 +3,7 @@ declare(strict_types = 1);
namespace LanguageServer\Server; namespace LanguageServer\Server;
use LanguageServer\{LanguageClient, Project}; use LanguageServer\{LanguageClient, Project, PhpDocument};
use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PhpParser\Node; use PhpParser\Node;
use LanguageServer\Protocol\{ use LanguageServer\Protocol\{
@ -20,6 +20,8 @@ use LanguageServer\Protocol\{
Hover, Hover,
MarkedString MarkedString
}; };
use Sabre\Event\Promise;
use function Sabre\Event\coroutine;
/** /**
* Provides method handlers for all textDocument/* methods * Provides method handlers for all textDocument/* methods
@ -55,11 +57,13 @@ class TextDocument
* document. * document.
* *
* @param \LanguageServer\Protocol\TextDocumentIdentifier $textDocument * @param \LanguageServer\Protocol\TextDocumentIdentifier $textDocument
* @return SymbolInformation[] * @return Promise <SymbolInformation[]>
*/ */
public function documentSymbol(TextDocumentIdentifier $textDocument): array public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
{ {
return array_values($this->project->getDocument($textDocument->uri)->getSymbols()); return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
return array_values($document->getSymbols());
});
} }
/** /**
@ -105,11 +109,13 @@ class TextDocument
* *
* @param TextDocumentIdentifier $textDocument The document to format * @param TextDocumentIdentifier $textDocument The document to format
* @param FormattingOptions $options The format options * @param FormattingOptions $options The format options
* @return TextEdit[] * @return Promise <TextEdit[]>
*/ */
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options) public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
{ {
return $this->project->getDocument($textDocument->uri)->getFormattedText(); return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
return $document->getFormattedText();
});
} }
/** /**
@ -117,21 +123,26 @@ class TextDocument
* denoted by the given text document position. * denoted by the given text document position.
* *
* @param ReferenceContext $context * @param ReferenceContext $context
* @return Location[] * @return Promise <Location[]>
*/ */
public function references(ReferenceContext $context, TextDocumentIdentifier $textDocument, Position $position): array public function references(
{ ReferenceContext $context,
$document = $this->project->getDocument($textDocument->uri); TextDocumentIdentifier $textDocument,
$node = $document->getNodeAtPosition($position); Position $position
if ($node === null) { ): Promise {
return []; return coroutine(function () use ($textDocument, $position) {
} $document = yield $this->project->getOrLoadDocument($textDocument->uri);
$refs = $document->getReferencesByNode($node); $node = $document->getNodeAtPosition($position);
$locations = []; if ($node === null) {
foreach ($refs as $ref) { return [];
$locations[] = Location::fromNode($ref); }
} $refs = yield $document->getReferencesByNode($node);
return $locations; $locations = [];
foreach ($refs as $ref) {
$locations[] = Location::fromNode($ref);
}
return $locations;
});
} }
/** /**
@ -140,20 +151,22 @@ class TextDocument
* *
* @param TextDocumentIdentifier $textDocument The text document * @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document * @param Position $position The position inside the text document
* @return Location|Location[] * @return Promise <Location|Location[]>
*/ */
public function definition(TextDocumentIdentifier $textDocument, Position $position) public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
{ {
$document = $this->project->getDocument($textDocument->uri); return coroutine(function () use ($textDocument, $position) {
$node = $document->getNodeAtPosition($position); $document = yield $this->project->getOrLoadDocument($textDocument->uri);
if ($node === null) { $node = $document->getNodeAtPosition($position);
return []; if ($node === null) {
} return [];
$def = $document->getDefinitionByNode($node); }
if ($def === null) { $def = yield $document->getDefinitionByNode($node);
return []; if ($def === null) {
} return [];
return Location::fromNode($def); }
return Location::fromNode($def);
});
} }
/** /**
@ -161,66 +174,68 @@ class TextDocument
* *
* @param TextDocumentIdentifier $textDocument The text document * @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document * @param Position $position The position inside the text document
* @return Hover * @return Promise <Hover>
*/ */
public function hover(TextDocumentIdentifier $textDocument, Position $position): Hover public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
{ {
$document = $this->project->getDocument($textDocument->uri); return coroutine(function () use ($textDocument, $position) {
// Find the node under the cursor $document = yield $this->project->getOrLoadDocument($textDocument->uri);
$node = $document->getNodeAtPosition($position); // Find the node under the cursor
if ($node === null) { $node = $document->getNodeAtPosition($position);
return new Hover([]); if ($node === null) {
} return new Hover([]);
$range = Range::fromNode($node); }
// Get the definition node for whatever node is under the cursor $range = Range::fromNode($node);
$def = $document->getDefinitionByNode($node); // Get the definition node for whatever node is under the cursor
if ($def === null) { $def = yield $document->getDefinitionByNode($node);
return new Hover([], $range); if ($def === null) {
} return new Hover([], $range);
$contents = []; }
$contents = [];
// Build a declaration string // Build a declaration string
if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) { if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) {
// Properties and constants can have multiple declarations // Properties and constants can have multiple declarations
// Use the parent node (that includes the modifiers), but only render the requested declaration // Use the parent node (that includes the modifiers), but only render the requested declaration
$child = $def; $child = $def;
$def = $def->getAttribute('parentNode'); $def = $def->getAttribute('parentNode');
$defLine = clone $def; $defLine = clone $def;
$defLine->props = [$child]; $defLine->props = [$child];
} else { } else {
$defLine = clone $def; $defLine = clone $def;
} }
// Don't include the docblock in the declaration string // Don't include the docblock in the declaration string
$defLine->setAttribute('comments', []); $defLine->setAttribute('comments', []);
if (isset($defLine->stmts)) { if (isset($defLine->stmts)) {
$defLine->stmts = []; $defLine->stmts = [];
} }
$defText = $this->prettyPrinter->prettyPrint([$defLine]); $defText = $this->prettyPrinter->prettyPrint([$defLine]);
$lines = explode("\n", $defText); $lines = explode("\n", $defText);
if (isset($lines[0])) { if (isset($lines[0])) {
$contents[] = new MarkedString('php', "<?php\n" . $lines[0]); $contents[] = new MarkedString('php', "<?php\n" . $lines[0]);
} }
// Get the documentation string // Get the documentation string
if ($def instanceof Node\Param) { if ($def instanceof Node\Param) {
$fn = $def->getAttribute('parentNode'); $fn = $def->getAttribute('parentNode');
$docBlock = $fn->getAttribute('docBlock'); $docBlock = $fn->getAttribute('docBlock');
if ($docBlock !== null) { if ($docBlock !== null) {
$tags = $docBlock->getTagsByName('param'); $tags = $docBlock->getTagsByName('param');
foreach ($tags as $tag) { foreach ($tags as $tag) {
if ($tag->getVariableName() === $def->name) { if ($tag->getVariableName() === $def->name) {
$contents[] = $tag->getDescription()->render(); $contents[] = $tag->getDescription()->render();
break; break;
}
} }
} }
} else {
$docBlock = $def->getAttribute('docBlock');
if ($docBlock !== null) {
$contents[] = $docBlock->getSummary();
}
} }
} else {
$docBlock = $def->getAttribute('docBlock');
if ($docBlock !== null) {
$contents[] = $docBlock->getSummary();
}
}
return new Hover($contents, $range); return new Hover($contents, $range);
});
} }
} }

View File

@ -3,26 +3,9 @@ declare(strict_types = 1);
namespace LanguageServer; namespace LanguageServer;
use Throwable;
use InvalidArgumentException; use InvalidArgumentException;
use Sabre\Event\{Loop, Promise};
/**
* Recursively Searches files with matching filename, starting at $path.
*
* @param string $path
* @param string $pattern
* @return array
*/
function findFilesRecursive(string $path, string $pattern): array
{
$dir = new \RecursiveDirectoryIterator($path);
$ite = new \RecursiveIteratorIterator($dir);
$files = new \RegexIterator($ite, $pattern, \RegexIterator::GET_MATCH);
$fileList = [];
foreach ($files as $file) {
$fileList = array_merge($fileList, $file);
}
return $fileList;
}
/** /**
* Transforms an absolute file path into a URI as used by the language server protocol. * Transforms an absolute file path into a URI as used by the language server protocol.
@ -66,3 +49,31 @@ function uriToPath(string $uri)
} }
return $filepath; return $filepath;
} }
/**
* Throws an exception on the next tick.
* Useful for letting a promise crash the process on rejection.
*
* @param Throwable $err
* @return void
*/
function crash(Throwable $err)
{
Loop\nextTick(function () use ($err) {
throw $err;
});
}
/**
* Returns a promise that is resolved after x seconds.
* Useful for giving back control to the event loop inside a coroutine.
*
* @param int $seconds
* @return Promise <void>
*/
function timeout($seconds = 0): Promise
{
$promise = new Promise;
Loop\setTimeout([$promise, 'fulfill'], $seconds);
return $promise;
}

View File

@ -5,9 +5,13 @@ namespace LanguageServer\Tests;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\LanguageServer; use LanguageServer\LanguageServer;
use LanguageServer\Protocol\{Message, ClientCapabilities, TextDocumentSyncKind, MessageType}; use LanguageServer\Protocol\{
Message, ClientCapabilities, TextDocumentSyncKind, MessageType, TextDocumentItem, TextDocumentIdentifier};
use AdvancedJsonRpc; use AdvancedJsonRpc;
use Webmozart\Glob\Glob;
use Webmozart\PathUtil\Path;
use Sabre\Event\Promise; use Sabre\Event\Promise;
use Exception;
use function LanguageServer\pathToUri; use function LanguageServer\pathToUri;
class LanguageServerTest extends TestCase class LanguageServerTest extends TestCase
@ -17,15 +21,14 @@ class LanguageServerTest extends TestCase
$reader = new MockProtocolStream(); $reader = new MockProtocolStream();
$writer = new MockProtocolStream(); $writer = new MockProtocolStream();
$server = new LanguageServer($reader, $writer); $server = new LanguageServer($reader, $writer);
$msg = null; $promise = new Promise;
$writer->on('message', function (Message $message) use (&$msg) { $writer->once('message', [$promise, 'fulfill']);
$msg = $message;
});
$reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [ $reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [
'rootPath' => __DIR__, 'rootPath' => __DIR__,
'processId' => getmypid(), 'processId' => getmypid(),
'capabilities' => new ClientCapabilities() 'capabilities' => new ClientCapabilities()
]))); ])));
$msg = $promise->wait();
$this->assertNotNull($msg, 'message event should be emitted'); $this->assertNotNull($msg, 'message event should be emitted');
$this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body); $this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body);
$this->assertEquals((object)[ $this->assertEquals((object)[
@ -49,7 +52,7 @@ class LanguageServerTest extends TestCase
], $msg->body->result); ], $msg->body->result);
} }
public function testIndexing() public function testIndexingWithDirectFileAccess()
{ {
$promise = new Promise; $promise = new Promise;
$input = new MockProtocolStream; $input = new MockProtocolStream;
@ -68,4 +71,54 @@ class LanguageServerTest extends TestCase
$server->initialize(getmypid(), $capabilities, realpath(__DIR__ . '/../fixtures')); $server->initialize(getmypid(), $capabilities, realpath(__DIR__ . '/../fixtures'));
$promise->wait(); $promise->wait();
} }
public function testIndexingWithFilesAndContentRequests()
{
$promise = new Promise;
$filesCalled = false;
$contentCalled = false;
$rootPath = realpath(__DIR__ . '/../fixtures');
$input = new MockProtocolStream;
$output = new MockProtocolStream;
$output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$filesCalled, &$contentCalled) {
if ($msg->body->method === 'textDocument/xcontent') {
// Document content requested
$contentCalled = true;
$textDocumentItem = new TextDocumentItem;
$textDocumentItem->uri = $msg->body->params->textDocument->uri;
$textDocumentItem->version = 1;
$textDocumentItem->languageId = 'php';
$textDocumentItem->text = file_get_contents($msg->body->params->textDocument->uri);
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $textDocumentItem)));
} else if ($msg->body->method === 'workspace/xfiles') {
// Files requested
$filesCalled = true;
$pattern = Path::makeAbsolute('**/*.php', $msg->body->params->base ?? $rootPath);
$files = [];
foreach (Glob::glob($pattern) as $path) {
$files[] = new TextDocumentIdentifier(pathToUri($path));
}
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $files)));
} else if ($msg->body->method === 'window/logMessage') {
// Message logged
if ($msg->body->params->type === MessageType::ERROR) {
// Error happened during indexing, fail test
if ($promise->state === Promise::PENDING) {
$promise->reject(new Exception($msg->body->params->message));
}
} else if (strpos($msg->body->params->message, 'All 10 PHP files parsed') !== false) {
// Indexing finished
$promise->fulfill();
}
}
});
$server = new LanguageServer($input, $output);
$capabilities = new ClientCapabilities;
$capabilities->xfilesProvider = true;
$capabilities->xcontentProvider = true;
$server->initialize(getmypid(), $capabilities, $rootPath);
$promise->wait();
$this->assertTrue($filesCalled);
$this->assertTrue($contentCalled);
}
} }

View File

@ -5,7 +5,7 @@ namespace LanguageServer\Tests;
use LanguageServer\{ProtocolReader, ProtocolWriter}; use LanguageServer\{ProtocolReader, ProtocolWriter};
use LanguageServer\Protocol\Message; use LanguageServer\Protocol\Message;
use Sabre\Event\{Emitter, Promise}; use Sabre\Event\{Loop, Emitter, Promise};
/** /**
* A fake duplex protocol stream * A fake duplex protocol stream
@ -20,7 +20,9 @@ class MockProtocolStream extends Emitter implements ProtocolReader, ProtocolWrit
*/ */
public function write(Message $msg): Promise public function write(Message $msg): Promise
{ {
$this->emit('message', [Message::parse((string)$msg)]); Loop\nextTick(function () use ($msg) {
$this->emit('message', [Message::parse((string)$msg)]);
});
return Promise\resolve(null); return Promise\resolve(null);
} }
} }

View File

@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase;
use PhpParser\{NodeTraverser, Node}; use PhpParser\{NodeTraverser, Node};
use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser}; use LanguageServer\{LanguageClient, Project, PhpDocument, Parser};
use LanguageServer\Protocol\ClientCapabilities;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector}; use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
use function LanguageServer\pathToUri; use function LanguageServer\pathToUri;
@ -16,10 +17,10 @@ class DefinitionCollectorTest extends TestCase
public function testCollectsSymbols() public function testCollectsSymbols()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$parser = new Parser; $parser = new Parser;
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php')); $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
$document = $project->loadDocument($uri); $document = $project->loadDocument($uri)->wait();
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver); $traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ReferencesAdder($document)); $traverser->addVisitor(new ReferencesAdder($document));
@ -55,10 +56,10 @@ class DefinitionCollectorTest extends TestCase
public function testDoesNotCollectReferences() public function testDoesNotCollectReferences()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$parser = new Parser; $parser = new Parser;
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
$document = $project->loadDocument($uri); $document = $project->loadDocument($uri)->wait();
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver); $traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ReferencesAdder($document)); $traverser->addVisitor(new ReferencesAdder($document));

View File

@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{LanguageClient, Project}; use LanguageServer\{LanguageClient, Project};
use LanguageServer\NodeVisitor\NodeAtPositionFinder; use LanguageServer\NodeVisitor\NodeAtPositionFinder;
use LanguageServer\Protocol\{SymbolKind, Position}; use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
use PhpParser\Node; use PhpParser\Node;
class PhpDocumentTest extends TestCase class PhpDocumentTest extends TestCase
@ -19,7 +19,8 @@ class PhpDocumentTest extends TestCase
public function setUp() public function setUp()
{ {
$this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream)); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$this->project = new Project($client, new ClientCapabilities);
} }
public function testParsesVariableVariables() public function testParsesVariableVariables()

View File

@ -6,7 +6,14 @@ namespace LanguageServer\Tests\Server;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument}; use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, SymbolKind, DiagnosticSeverity, FormattingOptions}; use LanguageServer\Protocol\{
TextDocumentItem,
TextDocumentIdentifier,
SymbolKind,
DiagnosticSeverity,
FormattingOptions,
ClientCapabilities
};
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody}; use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
use function LanguageServer\pathToUri; use function LanguageServer\pathToUri;
@ -19,12 +26,13 @@ class ProjectTest extends TestCase
public function setUp() public function setUp()
{ {
$this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream)); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$this->project = new Project($client, new ClientCapabilities);
} }
public function testGetDocumentLoadsDocument() public function testGetOrLoadDocumentLoadsDocument()
{ {
$document = $this->project->getDocument(pathToUri(__FILE__)); $document = $this->project->getOrLoadDocument(pathToUri(__FILE__))->wait();
$this->assertNotNull($document); $this->assertNotNull($document);
$this->assertInstanceOf(PhpDocument::class, $document); $this->assertInstanceOf(PhpDocument::class, $document);

View File

@ -6,8 +6,9 @@ namespace LanguageServer\Tests\Server;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, LanguageClient, Project}; use LanguageServer\{Server, LanguageClient, Project};
use LanguageServer\Protocol\{Position, Location, Range}; use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities};
use function LanguageServer\pathToUri; use function LanguageServer\pathToUri;
use Sabre\Event\Promise;
abstract class ServerTestCase extends TestCase abstract class ServerTestCase extends TestCase
{ {
@ -43,7 +44,7 @@ abstract class ServerTestCase extends TestCase
public function setUp() public function setUp()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$this->project = new Project($client); $this->project = new Project($client, new ClientCapabilities);
$this->textDocument = new Server\TextDocument($this->project, $client); $this->textDocument = new Server\TextDocument($this->project, $client);
$this->workspace = new Server\Workspace($this->project, $client); $this->workspace = new Server\Workspace($this->project, $client);
@ -53,11 +54,13 @@ abstract class ServerTestCase extends TestCase
$referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); $referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
$useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php')); $useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php'));
$this->project->loadDocument($symbolsUri); Promise\all([
$this->project->loadDocument($referencesUri); $this->project->loadDocument($symbolsUri),
$this->project->loadDocument($globalSymbolsUri); $this->project->loadDocument($referencesUri),
$this->project->loadDocument($globalReferencesUri); $this->project->loadDocument($globalSymbolsUri),
$this->project->loadDocument($useUri); $this->project->loadDocument($globalReferencesUri),
$this->project->loadDocument($useUri)
])->wait();
// @codingStandardsIgnoreStart // @codingStandardsIgnoreStart
$this->definitionLocations = [ $this->definitionLocations = [

View File

@ -6,14 +6,15 @@ namespace LanguageServer\Tests\Server\TextDocument\Definition;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\Tests\Server\ServerTestCase; use LanguageServer\Tests\Server\ServerTestCase;
use LanguageServer\{Server, LanguageClient, Project}; use LanguageServer\{Server, LanguageClient, Project};
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location}; use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities};
use Sabre\Event\Promise;
class GlobalFallbackTest extends ServerTestCase class GlobalFallbackTest extends ServerTestCase
{ {
public function setUp() public function setUp()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$this->textDocument = new Server\TextDocument($project, $client); $this->textDocument = new Server\TextDocument($project, $client);
$project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
$project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
@ -23,7 +24,10 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// $obj = new TestClass(); // $obj = new TestClass();
// Get definition for TestClass should not fall back to global // Get definition for TestClass should not fall back to global
$result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(9, 16)); $result = $this->textDocument->definition(
new TextDocumentIdentifier('global_fallback'),
new Position(9, 16)
)->wait();
$this->assertEquals([], $result); $this->assertEquals([], $result);
} }
@ -31,7 +35,10 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// echo TEST_CONST; // echo TEST_CONST;
// Get definition for TEST_CONST // Get definition for TEST_CONST
$result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(6, 10)); $result = $this->textDocument->definition(
new TextDocumentIdentifier('global_fallback'),
new Position(6, 10)
)->wait();
$this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result); $this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result);
} }
@ -39,7 +46,10 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// test_function(); // test_function();
// Get definition for test_function // Get definition for test_function
$result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(5, 6)); $result = $this->textDocument->definition(
new TextDocumentIdentifier('global_fallback'),
new Position(5, 6)
)->wait();
$this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result); $this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result);
} }
} }

View File

@ -12,14 +12,20 @@ class GlobalTest extends ServerTestCase
public function testDefinitionFileBeginning() public function testDefinitionFileBeginning()
{ {
// |<?php // |<?php
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(0, 0)); $result = $this->textDocument->definition(
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
new Position(0, 0)
)->wait();
$this->assertEquals([], $result); $this->assertEquals([], $result);
} }
public function testDefinitionEmptyResult() public function testDefinitionEmptyResult()
{ {
// namespace keyword // namespace keyword
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(2, 4)); $result = $this->textDocument->definition(
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
new Position(2, 4)
)->wait();
$this->assertEquals([], $result); $this->assertEquals([], $result);
} }
@ -28,7 +34,10 @@ class GlobalTest extends ServerTestCase
// $obj = new TestClass(); // $obj = new TestClass();
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[0]; $reference = $this->getReferenceLocations('TestClass')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -37,7 +46,10 @@ class GlobalTest extends ServerTestCase
// TestClass::staticTestMethod(); // TestClass::staticTestMethod();
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[1]; $reference = $this->getReferenceLocations('TestClass')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -46,7 +58,10 @@ class GlobalTest extends ServerTestCase
// echo TestClass::$staticTestProperty; // echo TestClass::$staticTestProperty;
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[2]; $reference = $this->getReferenceLocations('TestClass')[2];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -55,7 +70,10 @@ class GlobalTest extends ServerTestCase
// TestClass::TEST_CLASS_CONST; // TestClass::TEST_CLASS_CONST;
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[3]; $reference = $this->getReferenceLocations('TestClass')[3];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -64,7 +82,10 @@ class GlobalTest extends ServerTestCase
// class TestClass implements TestInterface // class TestClass implements TestInterface
// Get definition for TestInterface // Get definition for TestInterface
$reference = $this->getReferenceLocations('TestInterface')[0]; $reference = $this->getReferenceLocations('TestInterface')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
} }
@ -73,7 +94,10 @@ class GlobalTest extends ServerTestCase
// echo TestClass::TEST_CLASS_CONST; // echo TestClass::TEST_CLASS_CONST;
// Get definition for TEST_CLASS_CONST // Get definition for TEST_CLASS_CONST
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[1]; $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
} }
@ -82,7 +106,10 @@ class GlobalTest extends ServerTestCase
// echo self::TEST_CLASS_CONST; // echo self::TEST_CLASS_CONST;
// Get definition for TEST_CLASS_CONST // Get definition for TEST_CLASS_CONST
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0]; $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
} }
@ -91,7 +118,10 @@ class GlobalTest extends ServerTestCase
// echo TEST_CONST; // echo TEST_CONST;
// Get definition for TEST_CONST // Get definition for TEST_CONST
$reference = $this->getReferenceLocations('TEST_CONST')[1]; $reference = $this->getReferenceLocations('TEST_CONST')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result); $this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
} }
@ -100,7 +130,10 @@ class GlobalTest extends ServerTestCase
// TestClass::staticTestMethod(); // TestClass::staticTestMethod();
// Get definition for staticTestMethod // Get definition for staticTestMethod
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0]; $reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestMethod()'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::staticTestMethod()'), $result);
} }
@ -109,7 +142,10 @@ class GlobalTest extends ServerTestCase
// echo TestClass::$staticTestProperty; // echo TestClass::$staticTestProperty;
// Get definition for staticTestProperty // Get definition for staticTestProperty
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0]; $reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestProperty'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::staticTestProperty'), $result);
} }
@ -118,7 +154,10 @@ class GlobalTest extends ServerTestCase
// $obj->testMethod(); // $obj->testMethod();
// Get definition for testMethod // Get definition for testMethod
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0]; $reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
} }
@ -127,7 +166,10 @@ class GlobalTest extends ServerTestCase
// echo $obj->testProperty; // echo $obj->testProperty;
// Get definition for testProperty // Get definition for testProperty
$reference = $this->getReferenceLocations('TestClass::testProperty')[1]; $reference = $this->getReferenceLocations('TestClass::testProperty')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
} }
@ -136,7 +178,10 @@ class GlobalTest extends ServerTestCase
// $this->testProperty = $testParameter; // $this->testProperty = $testParameter;
// Get definition for testProperty // Get definition for testProperty
$reference = $this->getReferenceLocations('TestClass::testProperty')[0]; $reference = $this->getReferenceLocations('TestClass::testProperty')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
} }
@ -145,7 +190,10 @@ class GlobalTest extends ServerTestCase
// echo $var; // echo $var;
// Get definition for $var // Get definition for $var
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(13, 7)); $result = $this->textDocument->definition(
new TextDocumentIdentifier($uri),
new Position(13, 7)
)->wait();
$this->assertEquals(new Location($uri, new Range(new Position(12, 0), new Position(12, 10))), $result); $this->assertEquals(new Location($uri, new Range(new Position(12, 0), new Position(12, 10))), $result);
} }
@ -154,7 +202,10 @@ class GlobalTest extends ServerTestCase
// function whatever(TestClass $param) { // function whatever(TestClass $param) {
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[4]; $reference = $this->getReferenceLocations('TestClass')[4];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -163,7 +214,10 @@ class GlobalTest extends ServerTestCase
// function whatever(TestClass $param): TestClass { // function whatever(TestClass $param): TestClass {
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[5]; $reference = $this->getReferenceLocations('TestClass')[5];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -172,7 +226,10 @@ class GlobalTest extends ServerTestCase
// public function testMethod($testParameter): TestInterface // public function testMethod($testParameter): TestInterface
// Get definition for TestInterface // Get definition for TestInterface
$reference = $this->getReferenceLocations('TestInterface')[1]; $reference = $this->getReferenceLocations('TestInterface')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
} }
@ -181,7 +238,10 @@ class GlobalTest extends ServerTestCase
// echo $param; // echo $param;
// Get definition for $param // Get definition for $param
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(22, 13)); $result = $this->textDocument->definition(
new TextDocumentIdentifier($uri),
new Position(22, 13)
)->wait();
$this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result); $this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result);
} }
@ -190,7 +250,10 @@ class GlobalTest extends ServerTestCase
// echo $var; // echo $var;
// Get definition for $var // Get definition for $var
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(26, 11)); $result = $this->textDocument->definition(
new TextDocumentIdentifier($uri),
new Position(26, 11)
)->wait();
$this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result); $this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result);
} }
@ -199,7 +262,10 @@ class GlobalTest extends ServerTestCase
// test_function(); // test_function();
// Get definition for test_function // Get definition for test_function
$reference = $this->getReferenceLocations('test_function()')[0]; $reference = $this->getReferenceLocations('test_function()')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result); $this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
} }
@ -208,7 +274,10 @@ class GlobalTest extends ServerTestCase
// use function test_function; // use function test_function;
// Get definition for test_function // Get definition for test_function
$reference = $this->getReferenceLocations('test_function()')[1]; $reference = $this->getReferenceLocations('test_function()')[1];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result); $this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
} }
@ -217,7 +286,10 @@ class GlobalTest extends ServerTestCase
// if ($abc instanceof TestInterface) { // if ($abc instanceof TestInterface) {
// Get definition for TestInterface // Get definition for TestInterface
$reference = $this->getReferenceLocations('TestInterface')[2]; $reference = $this->getReferenceLocations('TestInterface')[2];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
} }
} }

View File

@ -23,7 +23,10 @@ class NamespacedTest extends GlobalTest
// echo TEST_CONST; // echo TEST_CONST;
// Get definition for TEST_CONST // Get definition for TEST_CONST
$reference = $this->getReferenceLocations('TEST_CONST')[0]; $reference = $this->getReferenceLocations('TEST_CONST')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result); $this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
} }
@ -32,7 +35,10 @@ class NamespacedTest extends GlobalTest
// use TestNamespace\TestClass; // use TestNamespace\TestClass;
// Get definition for TestClass // Get definition for TestClass
$reference = $this->getReferenceLocations('TestClass')[6]; $reference = $this->getReferenceLocations('TestClass')[6];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
@ -41,7 +47,10 @@ class NamespacedTest extends GlobalTest
// use TestNamespace\{TestTrait, TestInterface}; // use TestNamespace\{TestTrait, TestInterface};
// Get definition for TestInterface // Get definition for TestInterface
$reference = $this->getReferenceLocations('TestClass')[0]; $reference = $this->getReferenceLocations('TestClass')[0];
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->definition(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
} }
} }

View File

@ -12,7 +12,8 @@ use LanguageServer\Protocol\{
VersionedTextDocumentIdentifier, VersionedTextDocumentIdentifier,
TextDocumentContentChangeEvent, TextDocumentContentChangeEvent,
Range, Range,
Position Position,
ClientCapabilities
}; };
class DidChangeTest extends TestCase class DidChangeTest extends TestCase
@ -20,7 +21,7 @@ class DidChangeTest extends TestCase
public function test() public function test()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$textDocument = new Server\TextDocument($project, $client); $textDocument = new Server\TextDocument($project, $client);
$phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n"); $phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n");

View File

@ -6,7 +6,7 @@ namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, Client, LanguageClient, Project}; use LanguageServer\{Server, Client, LanguageClient, Project};
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier}; use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, ClientCapabilities};
use Exception; use Exception;
class DidCloseTest extends TestCase class DidCloseTest extends TestCase
@ -14,7 +14,7 @@ class DidCloseTest extends TestCase
public function test() public function test()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$textDocument = new Server\TextDocument($project, $client); $textDocument = new Server\TextDocument($project, $client);
$phpDocument = $project->openDocument('whatever', 'hello world'); $phpDocument = $project->openDocument('whatever', 'hello world');

View File

@ -15,7 +15,7 @@ class DocumentSymbolTest extends ServerTestCase
{ {
// Request symbols // Request symbols
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri)); $result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait();
// @codingStandardsIgnoreStart // @codingStandardsIgnoreStart
$this->assertEquals([ $this->assertEquals([
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),

View File

@ -6,7 +6,15 @@ namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, Client, LanguageClient, Project}; use LanguageServer\{Server, Client, LanguageClient, Project};
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, FormattingOptions}; use LanguageServer\Protocol\{
TextDocumentIdentifier,
TextDocumentItem,
FormattingOptions,
ClientCapabilities,
TextEdit,
Range,
Position
};
use function LanguageServer\{pathToUri, uriToPath}; use function LanguageServer\{pathToUri, uriToPath};
class FormattingTest extends TestCase class FormattingTest extends TestCase
@ -19,14 +27,14 @@ class FormattingTest extends TestCase
public function setUp() public function setUp()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$this->textDocument = new Server\TextDocument($project, $client); $this->textDocument = new Server\TextDocument($project, $client);
} }
public function testFormatting() public function testFormatting()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$textDocument = new Server\TextDocument($project, $client); $textDocument = new Server\TextDocument($project, $client);
$path = realpath(__DIR__ . '/../../../fixtures/format.php'); $path = realpath(__DIR__ . '/../../../fixtures/format.php');
$uri = pathToUri($path); $uri = pathToUri($path);
@ -42,19 +50,7 @@ class FormattingTest extends TestCase
// how code should look after formatting // how code should look after formatting
$expected = file_get_contents(__DIR__ . '/../../../fixtures/format_expected.php'); $expected = file_get_contents(__DIR__ . '/../../../fixtures/format_expected.php');
// Request formatting // Request formatting
$result = $textDocument->formatting(new TextDocumentIdentifier($uri), new FormattingOptions()); $result = $textDocument->formatting(new TextDocumentIdentifier($uri), new FormattingOptions())->wait();
$this->assertEquals([0 => [ $this->assertEquals([new TextEdit(new Range(new Position(0, 0), new Position(20, 0)), $expected)], $result);
'range' => [
'start' => [
'line' => 0,
'character' => 0
],
'end' => [
'line' => 20,
'character' => 0
]
],
'newText' => $expected
]], json_decode(json_encode($result), true));
} }
} }

View File

@ -16,7 +16,10 @@ class HoverTest extends ServerTestCase
// $obj = new TestClass(); // $obj = new TestClass();
// Get hover for TestClass // Get hover for TestClass
$reference = $this->getReferenceLocations('TestClass')[0]; $reference = $this->getReferenceLocations('TestClass')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->start); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->start
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"), new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"),
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' 'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
@ -28,7 +31,10 @@ class HoverTest extends ServerTestCase
// $obj->testMethod(); // $obj->testMethod();
// Get hover for testMethod // Get hover for testMethod
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0]; $reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter) : \TestInterface"), new MarkedString('php', "<?php\npublic function testMethod(\$testParameter) : \TestInterface"),
'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.'
@ -40,7 +46,10 @@ class HoverTest extends ServerTestCase
// echo $obj->testProperty; // echo $obj->testProperty;
// Get hover for testProperty // Get hover for testProperty
$reference = $this->getReferenceLocations('TestClass::testProperty')[0]; $reference = $this->getReferenceLocations('TestClass::testProperty')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\npublic \$testProperty;"), new MarkedString('php', "<?php\npublic \$testProperty;"),
'Reprehenderit magna velit mollit ipsum do.' 'Reprehenderit magna velit mollit ipsum do.'
@ -52,7 +61,10 @@ class HoverTest extends ServerTestCase
// TestClass::staticTestMethod(); // TestClass::staticTestMethod();
// Get hover for staticTestMethod // Get hover for staticTestMethod
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0]; $reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\npublic static function staticTestMethod()"), new MarkedString('php', "<?php\npublic static function staticTestMethod()"),
'Do magna consequat veniam minim proident eiusmod incididunt aute proident.' 'Do magna consequat veniam minim proident eiusmod incididunt aute proident.'
@ -64,7 +76,10 @@ class HoverTest extends ServerTestCase
// echo TestClass::staticTestProperty; // echo TestClass::staticTestProperty;
// Get hover for staticTestProperty // Get hover for staticTestProperty
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0]; $reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\npublic static \$staticTestProperty;"), new MarkedString('php', "<?php\npublic static \$staticTestProperty;"),
'Lorem excepteur officia sit anim velit veniam enim.' 'Lorem excepteur officia sit anim velit veniam enim.'
@ -76,7 +91,10 @@ class HoverTest extends ServerTestCase
// echo TestClass::TEST_CLASS_CONST; // echo TestClass::TEST_CLASS_CONST;
// Get hover for TEST_CLASS_CONST // Get hover for TEST_CLASS_CONST
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0]; $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nconst TEST_CLASS_CONST = 123;"), new MarkedString('php', "<?php\nconst TEST_CLASS_CONST = 123;"),
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.' 'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
@ -88,7 +106,10 @@ class HoverTest extends ServerTestCase
// test_function(); // test_function();
// Get hover for test_function // Get hover for test_function
$reference = $this->getReferenceLocations('test_function()')[0]; $reference = $this->getReferenceLocations('test_function()')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nfunction test_function()"), new MarkedString('php', "<?php\nfunction test_function()"),
'Officia aliquip adipisicing et nulla et laboris dolore labore.' 'Officia aliquip adipisicing et nulla et laboris dolore labore.'
@ -100,7 +121,10 @@ class HoverTest extends ServerTestCase
// echo TEST_CONST; // echo TEST_CONST;
// Get hover for TEST_CONST // Get hover for TEST_CONST
$reference = $this->getReferenceLocations('TEST_CONST')[0]; $reference = $this->getReferenceLocations('TEST_CONST')[0];
$result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); $result = $this->textDocument->hover(
new TextDocumentIdentifier($reference->uri),
$reference->range->end
)->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nconst TEST_CONST = 123;"), new MarkedString('php', "<?php\nconst TEST_CONST = 123;"),
'Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit.' 'Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit.'
@ -112,7 +136,7 @@ class HoverTest extends ServerTestCase
// echo $var; // echo $var;
// Get hover for $var // Get hover for $var
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7)); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait();
$this->assertEquals(new Hover( $this->assertEquals(new Hover(
[new MarkedString('php', "<?php\n\$var = 123;")], [new MarkedString('php', "<?php\n\$var = 123;")],
new Range(new Position(13, 5), new Position(13, 9)) new Range(new Position(13, 5), new Position(13, 9))
@ -124,7 +148,7 @@ class HoverTest extends ServerTestCase
// echo $param; // echo $param;
// Get hover for $param // Get hover for $param
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11)); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait();
$this->assertEquals(new Hover( $this->assertEquals(new Hover(
[ [
new MarkedString('php', "<?php\n\TestNamespace\TestClass \$param"), new MarkedString('php', "<?php\n\TestNamespace\TestClass \$param"),

View File

@ -6,8 +6,9 @@ namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, Client, LanguageClient, Project, ClientHandler}; use LanguageServer\{Server, Client, LanguageClient, Project, ClientHandler};
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity}; use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities};
use Sabre\Event\Promise; use Sabre\Event\Promise;
use JsonMapper;
class ParseErrorsTest extends TestCase class ParseErrorsTest extends TestCase
{ {
@ -25,7 +26,7 @@ class ParseErrorsTest extends TestCase
private $args; private $args;
public function __construct(&$args) public function __construct(&$args)
{ {
parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream)); parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream), new JsonMapper);
$this->args = &$args; $this->args = &$args;
} }
public function publishDiagnostics(string $uri, array $diagnostics): Promise public function publishDiagnostics(string $uri, array $diagnostics): Promise
@ -34,7 +35,7 @@ class ParseErrorsTest extends TestCase
return Promise\resolve(null); return Promise\resolve(null);
} }
}; };
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$this->textDocument = new Server\TextDocument($project, $client); $this->textDocument = new Server\TextDocument($project, $client);
} }

View File

@ -6,7 +6,7 @@ namespace LanguageServer\Tests\Server\TextDocument\References;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, LanguageClient, Project}; use LanguageServer\{Server, LanguageClient, Project};
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range}; use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities};
use LanguageServer\Tests\Server\ServerTestCase; use LanguageServer\Tests\Server\ServerTestCase;
class GlobalFallbackTest extends ServerTestCase class GlobalFallbackTest extends ServerTestCase
@ -14,7 +14,7 @@ class GlobalFallbackTest extends ServerTestCase
public function setUp() public function setUp()
{ {
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$project = new Project($client); $project = new Project($client, new ClientCapabilities);
$this->textDocument = new Server\TextDocument($project, $client); $this->textDocument = new Server\TextDocument($project, $client);
$project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
$project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
@ -24,7 +24,11 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// class TestClass implements TestInterface // class TestClass implements TestInterface
// Get references for TestClass // Get references for TestClass
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(6, 9)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier('global_symbols'),
new Position(6, 9)
)->wait();
$this->assertEquals([], $result); $this->assertEquals([], $result);
} }
@ -32,7 +36,11 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// const TEST_CONST = 123; // const TEST_CONST = 123;
// Get references for TEST_CONST // Get references for TEST_CONST
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(9, 13)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier('global_symbols'),
new Position(9, 13)
)->wait();
$this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result); $this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result);
} }
@ -40,7 +48,11 @@ class GlobalFallbackTest extends ServerTestCase
{ {
// function test_function() // function test_function()
// Get references for test_function // Get references for test_function
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(78, 16)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier('global_symbols'),
new Position(78, 16)
)->wait();
$this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result); $this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result);
} }
} }

View File

@ -14,7 +14,11 @@ class GlobalTest extends ServerTestCase
// class TestClass implements TestInterface // class TestClass implements TestInterface
// Get references for TestClass // Get references for TestClass
$definition = $this->getDefinitionLocation('TestClass'); $definition = $this->getDefinitionLocation('TestClass');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass'), $result); $this->assertEquals($this->getReferenceLocations('TestClass'), $result);
} }
@ -23,7 +27,11 @@ class GlobalTest extends ServerTestCase
// const TEST_CLASS_CONST = 123; // const TEST_CLASS_CONST = 123;
// Get references for TEST_CLASS_CONST // Get references for TEST_CLASS_CONST
$definition = $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'); $definition = $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass::TEST_CLASS_CONST'), $result); $this->assertEquals($this->getReferenceLocations('TestClass::TEST_CLASS_CONST'), $result);
} }
@ -32,7 +40,11 @@ class GlobalTest extends ServerTestCase
// const TEST_CONST = 123; // const TEST_CONST = 123;
// Get references for TEST_CONST // Get references for TEST_CONST
$definition = $this->getDefinitionLocation('TEST_CONST'); $definition = $this->getDefinitionLocation('TEST_CONST');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TEST_CONST'), $result); $this->assertEquals($this->getReferenceLocations('TEST_CONST'), $result);
} }
@ -41,7 +53,11 @@ class GlobalTest extends ServerTestCase
// public static function staticTestMethod() // public static function staticTestMethod()
// Get references for staticTestMethod // Get references for staticTestMethod
$definition = $this->getDefinitionLocation('TestClass::staticTestMethod()'); $definition = $this->getDefinitionLocation('TestClass::staticTestMethod()');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestMethod()'), $result); $this->assertEquals($this->getReferenceLocations('TestClass::staticTestMethod()'), $result);
} }
@ -50,7 +66,11 @@ class GlobalTest extends ServerTestCase
// public static $staticTestProperty; // public static $staticTestProperty;
// Get references for $staticTestProperty // Get references for $staticTestProperty
$definition = $this->getDefinitionLocation('TestClass::staticTestProperty'); $definition = $this->getDefinitionLocation('TestClass::staticTestProperty');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestProperty'), $result); $this->assertEquals($this->getReferenceLocations('TestClass::staticTestProperty'), $result);
} }
@ -59,7 +79,11 @@ class GlobalTest extends ServerTestCase
// public function testMethod($testParameter) // public function testMethod($testParameter)
// Get references for testMethod // Get references for testMethod
$definition = $this->getDefinitionLocation('TestClass::testMethod()'); $definition = $this->getDefinitionLocation('TestClass::testMethod()');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass::testMethod()'), $result); $this->assertEquals($this->getReferenceLocations('TestClass::testMethod()'), $result);
} }
@ -68,7 +92,11 @@ class GlobalTest extends ServerTestCase
// public $testProperty; // public $testProperty;
// Get references for testProperty // Get references for testProperty
$definition = $this->getDefinitionLocation('TestClass::testProperty'); $definition = $this->getDefinitionLocation('TestClass::testProperty');
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($definition->uri),
$definition->range->start
)->wait();
$this->assertEquals($this->getReferenceLocations('TestClass::testProperty'), $result); $this->assertEquals($this->getReferenceLocations('TestClass::testProperty'), $result);
} }
@ -77,7 +105,11 @@ class GlobalTest extends ServerTestCase
// $var = 123; // $var = 123;
// Get definition for $var // Get definition for $var
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(12, 3)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($uri),
new Position(12, 3)
)->wait();
$this->assertEquals([ $this->assertEquals([
new Location($uri, new Range(new Position(12, 0), new Position(12, 4))), new Location($uri, new Range(new Position(12, 0), new Position(12, 4))),
new Location($uri, new Range(new Position(13, 5), new Position(13, 9))), new Location($uri, new Range(new Position(13, 5), new Position(13, 9))),
@ -90,7 +122,11 @@ class GlobalTest extends ServerTestCase
// function whatever(TestClass $param): TestClass // function whatever(TestClass $param): TestClass
// Get references for $param // Get references for $param
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(21, 32)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($uri),
new Position(21, 32)
)->wait();
$this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result); $this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result);
} }
@ -100,7 +136,11 @@ class GlobalTest extends ServerTestCase
// Get references for test_function // Get references for test_function
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php')); $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php'));
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($symbolsUri), new Position(78, 16)); $result = $this->textDocument->references(
new ReferenceContext,
new TextDocumentIdentifier($symbolsUri),
new Position(78, 16)
)->wait();
$this->assertEquals([ $this->assertEquals([
new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))), new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))) new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))

View File

@ -1,21 +0,0 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Utils;
use PHPUnit\Framework\TestCase;
class RecursiveFileSearchTest extends TestCase
{
public function testFilesAreFound()
{
$path = realpath(__DIR__ . '/../../fixtures/recursive');
$files = \LanguageServer\findFilesRecursive($path, '/.+\.txt/');
sort($files);
$this->assertEquals([
$path . DIRECTORY_SEPARATOR . 'a.txt',
$path . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . 'b.txt',
$path . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . 'here' . DIRECTORY_SEPARATOR . 'c.txt',
], $files);
}
}