Much better
parent
1e6bf90424
commit
91323a8e07
|
@ -4,3 +4,4 @@
|
||||||
vendor/
|
vendor/
|
||||||
.phpls/
|
.phpls/
|
||||||
composer.lock
|
composer.lock
|
||||||
|
stubs
|
||||||
|
|
|
@ -22,6 +22,11 @@
|
||||||
"refactor"
|
"refactor"
|
||||||
],
|
],
|
||||||
"bin": ["bin/php-language-server.php"],
|
"bin": ["bin/php-language-server.php"],
|
||||||
|
"scripts": {
|
||||||
|
"parse-stubs": "LanguageServer\\ComposerScripts::parseStubs",
|
||||||
|
"post-install-cmd": "@parse-stubs",
|
||||||
|
"post-root-package-install": "@parse-stubs"
|
||||||
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.0",
|
"php": ">=7.0",
|
||||||
"nikic/php-parser": "dev-master#e52ffc4447e034514339a03b450aab9cd625e37c",
|
"nikic/php-parser": "dev-master#e52ffc4447e034514339a03b450aab9cd625e37c",
|
||||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextEdit,
|
TextEdit,
|
||||||
Range,
|
Range,
|
||||||
|
@ -97,13 +98,18 @@ class CompletionProvider
|
||||||
private $project;
|
private $project;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DefinitionResolver $definitionResolver
|
* @var ReadableIndex
|
||||||
* @param Project $project
|
|
||||||
*/
|
*/
|
||||||
public function __construct(DefinitionResolver $definitionResolver, Project $project)
|
private $index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DefinitionResolver $definitionResolver
|
||||||
|
* @param ReadableIndex $index
|
||||||
|
*/
|
||||||
|
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index)
|
||||||
{
|
{
|
||||||
$this->definitionResolver = $definitionResolver;
|
$this->definitionResolver = $definitionResolver;
|
||||||
$this->project = $project;
|
$this->index = $index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,7 +159,7 @@ class CompletionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->project->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
foreach ($prefixes as $prefix) {
|
foreach ($prefixes as $prefix) {
|
||||||
if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) {
|
if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) {
|
||||||
$list->items[] = CompletionItem::fromDefinition($def);
|
$list->items[] = CompletionItem::fromDefinition($def);
|
||||||
|
@ -185,7 +191,9 @@ class CompletionProvider
|
||||||
// Get the definition for the used namespace, class-like, function or constant
|
// Get the definition for the used namespace, class-like, function or constant
|
||||||
// And save it under the alias
|
// And save it under the alias
|
||||||
$fqn = (string)Node\Name::concat($stmt->prefix ?? null, $use->name);
|
$fqn = (string)Node\Name::concat($stmt->prefix ?? null, $use->name);
|
||||||
$aliasedDefs[$use->alias] = $this->project->getDefinition($fqn);
|
if ($def = $this->index->getDefinition($fqn)) {
|
||||||
|
$aliasedDefs[$use->alias] = $def;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use statements are always the first statements in a namespace
|
// Use statements are always the first statements in a namespace
|
||||||
|
@ -206,7 +214,7 @@ class CompletionProvider
|
||||||
// Additionally, suggest global symbols that either
|
// Additionally, suggest global symbols that either
|
||||||
// - start with the current namespace + prefix, if the Name node is not fully qualified
|
// - start with the current namespace + prefix, if the Name node is not fully qualified
|
||||||
// - start with just the prefix, if the Name node is fully qualified
|
// - start with just the prefix, if the Name node is fully qualified
|
||||||
foreach ($this->project->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
if (
|
if (
|
||||||
$def->isGlobal // exclude methods, properties etc.
|
$def->isGlobal // exclude methods, properties etc.
|
||||||
&& (
|
&& (
|
||||||
|
@ -326,7 +334,7 @@ class CompletionProvider
|
||||||
}
|
}
|
||||||
if ($level instanceof Node\Expr\Closure) {
|
if ($level instanceof Node\Expr\Closure) {
|
||||||
foreach ($level->uses as $use) {
|
foreach ($level->uses as $use) {
|
||||||
if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) {
|
if (!isset($vars[$use->var]) && substr($use->var, 0, strlen($namePrefix)) === $namePrefix) {
|
||||||
$vars[$use->var] = $use;
|
$vars[$use->var] = $use;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
||||||
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\StubsIndex;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use Webmozart\PathUtil\Path;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/sabre/event/lib/coroutine.php';
|
||||||
|
require_once __DIR__ . '/../vendor/sabre/event/lib/Loop/functions.php';
|
||||||
|
require_once __DIR__ . '/../vendor/sabre/event/lib/Promise/functions.php';
|
||||||
|
require_once __DIR__ . '/utils.php';
|
||||||
|
|
||||||
|
class ComposerScripts
|
||||||
|
{
|
||||||
|
public static function parseStubs()
|
||||||
|
{
|
||||||
|
coroutine(function () {
|
||||||
|
|
||||||
|
$index = new StubsIndex;
|
||||||
|
|
||||||
|
$finder = new FileSystemFilesFinder;
|
||||||
|
$contentRetriever = new FileSystemContentRetriever;
|
||||||
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
|
$parser = new Parser;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
|
||||||
|
$uris = yield $finder->find(Path::canonicalize(__DIR__ . '/../vendor/JetBrains/phpstorm-stubs/**/*.php'));
|
||||||
|
|
||||||
|
foreach ($uris as $uri) {
|
||||||
|
echo "Parsing $uri\n";
|
||||||
|
$content = yield $contentRetriever->retrieve($uri);
|
||||||
|
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Saving Index\n";
|
||||||
|
|
||||||
|
$index->save();
|
||||||
|
|
||||||
|
echo "Finished\n";
|
||||||
|
|
||||||
|
})->wait();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,15 +7,16 @@ use PhpParser\Node;
|
||||||
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
||||||
use LanguageServer\Protocol\SymbolInformation;
|
use LanguageServer\Protocol\SymbolInformation;
|
||||||
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
class DefinitionResolver
|
class DefinitionResolver
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \LanguageServer\Index[]
|
* @var \LanguageServer\Index
|
||||||
*/
|
*/
|
||||||
private $indexes;
|
private $index;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \phpDocumentor\Reflection\TypeResolver
|
* @var \phpDocumentor\Reflection\TypeResolver
|
||||||
|
@ -28,24 +29,15 @@ class DefinitionResolver
|
||||||
private $prettyPrinter;
|
private $prettyPrinter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Index[] $indexes
|
* @param ReadableIndex $index
|
||||||
*/
|
*/
|
||||||
public function __construct(array $indexes)
|
public function __construct(ReadableIndex $index)
|
||||||
{
|
{
|
||||||
$this->indexes = $indexes;
|
$this->index = $index;
|
||||||
$this->typeResolver = new TypeResolver;
|
$this->typeResolver = new TypeResolver;
|
||||||
$this->prettyPrinter = new PrettyPrinter;
|
$this->prettyPrinter = new PrettyPrinter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getDefinition(string $fqn, bool $globalFallback = false)
|
|
||||||
{
|
|
||||||
foreach ($this->indexes as $index) {
|
|
||||||
if ($def = $index->getDefinition($fqn, $globalFallback)) {
|
|
||||||
return $def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the declaration line for a given node
|
* Builds the declaration line for a given node
|
||||||
*
|
*
|
||||||
|
@ -160,7 +152,7 @@ class DefinitionResolver
|
||||||
$parent = $node->getAttribute('parentNode');
|
$parent = $node->getAttribute('parentNode');
|
||||||
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
|
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
|
||||||
// Return the Definition object from the index index
|
// Return the Definition object from the index index
|
||||||
return $this->getDefinition($fqn, $globalFallback);
|
return $this->index->getDefinition($fqn, $globalFallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -415,7 +407,7 @@ class DefinitionResolver
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||||
$def = $this->getDefinition($fqn, true);
|
$def = $this->index->getDefinition($fqn, true);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
|
@ -426,7 +418,7 @@ class DefinitionResolver
|
||||||
}
|
}
|
||||||
// Resolve constant
|
// Resolve constant
|
||||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||||
$def = $this->getDefinition($fqn, true);
|
$def = $this->index->getDefinition($fqn, true);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
|
@ -455,7 +447,7 @@ class DefinitionResolver
|
||||||
if ($expr instanceof Node\Expr\MethodCall) {
|
if ($expr instanceof Node\Expr\MethodCall) {
|
||||||
$fqn .= '()';
|
$fqn .= '()';
|
||||||
}
|
}
|
||||||
$def = $this->getDefinition($fqn);
|
$def = $this->index->getDefinition($fqn);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
|
@ -478,7 +470,7 @@ class DefinitionResolver
|
||||||
if ($expr instanceof Node\Expr\StaticCall) {
|
if ($expr instanceof Node\Expr\StaticCall) {
|
||||||
$fqn .= '()';
|
$fqn .= '()';
|
||||||
}
|
}
|
||||||
$def = $this->getDefinition($fqn);
|
$def = $this->index->getDefinition($fqn);
|
||||||
if ($def === null) {
|
if ($def === null) {
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
abstract class AbstractAggregateIndex implements ReadableIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns all indexes managed by the aggregate index
|
||||||
|
*
|
||||||
|
* @return ReadableIndex[]
|
||||||
|
*/
|
||||||
|
protected abstract function getIndexes(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
||||||
|
* to Definitions
|
||||||
|
*
|
||||||
|
* @return Definition[]
|
||||||
|
*/
|
||||||
|
public function getDefinitions(): array
|
||||||
|
{
|
||||||
|
$defs = [];
|
||||||
|
foreach ($this->getIndexes() as $index) {
|
||||||
|
foreach ($index->getDefinitions() as $fqn => $def) {
|
||||||
|
$defs[$fqn] = $def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Definition object by a specific FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
||||||
|
* @return Definition|null
|
||||||
|
*/
|
||||||
|
public function getDefinition(string $fqn, bool $globalFallback = false)
|
||||||
|
{
|
||||||
|
foreach ($this->getIndexes() as $index) {
|
||||||
|
if ($def = $index->getDefinition($fqn, $globalFallback)) {
|
||||||
|
return $def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all URIs in this index that reference a symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getReferenceUris(string $fqn): array
|
||||||
|
{
|
||||||
|
$refs = [];
|
||||||
|
foreach ($this->getIndexes() as $index) {
|
||||||
|
foreach ($index->getReferenceUris($fqn) as $ref) {
|
||||||
|
$refs[] = $ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $refs;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
class DependenciesIndex extends AbstractAggregateIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Map from package name to index
|
||||||
|
*
|
||||||
|
* @var Index[]
|
||||||
|
*/
|
||||||
|
protected $indexes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Index[]
|
||||||
|
*/
|
||||||
|
protected function getIndexes(): array
|
||||||
|
{
|
||||||
|
return $this->indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return Index
|
||||||
|
*/
|
||||||
|
public function getDependencyIndex(string $packageName): Index
|
||||||
|
{
|
||||||
|
if (!isset($this->indexes[$packageName])) {
|
||||||
|
$this->indexes[$packageName] = new Index;
|
||||||
|
}
|
||||||
|
return $this->indexes[$packageName];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeDependencyIndex(string $packageName)
|
||||||
|
{
|
||||||
|
unset($this->indexes[$packageName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasDependencyIndex(string $packageName): bool
|
||||||
|
{
|
||||||
|
return isset($this->indexes[$packageName]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregates definitions of the project and stubs
|
||||||
|
*/
|
||||||
|
class GlobalIndex extends AbstractAggregateIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Index
|
||||||
|
*/
|
||||||
|
private $stubsIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ProjectIndex
|
||||||
|
*/
|
||||||
|
private $projectIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StubsIndex $stubsIndex
|
||||||
|
* @param ProjectIndex $projectIndex
|
||||||
|
*/
|
||||||
|
public function __construct(StubsIndex $stubsIndex, ProjectIndex $projectIndex)
|
||||||
|
{
|
||||||
|
$this->stubsIndex = $stubsIndex;
|
||||||
|
$this->projectIndex = $projectIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ReadableIndex[]
|
||||||
|
*/
|
||||||
|
protected function getIndexes(): array
|
||||||
|
{
|
||||||
|
return [$this->stubsIndex, $this->projectIndex];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
||||||
|
use LanguageServer\Definition;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
|
@ -12,7 +13,7 @@ use function Sabre\Event\coroutine;
|
||||||
* Represents the index of a project or dependency
|
* Represents the index of a project or dependency
|
||||||
* Serializable for caching
|
* Serializable for caching
|
||||||
*/
|
*/
|
||||||
class Index
|
class Index implements ReadableIndex
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* An associative array that maps fully qualified symbol names to Definitions
|
* An associative array that maps fully qualified symbol names to Definitions
|
||||||
|
@ -32,9 +33,9 @@ class Index
|
||||||
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
||||||
* to Definitions
|
* to Definitions
|
||||||
*
|
*
|
||||||
* @return Definitions[]
|
* @return Definition[]
|
||||||
*/
|
*/
|
||||||
public function getDefinitions()
|
public function getDefinitions(): array
|
||||||
{
|
{
|
||||||
return $this->definitions;
|
return $this->definitions;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +47,7 @@ class Index
|
||||||
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
||||||
* @return Definition|null
|
* @return Definition|null
|
||||||
*/
|
*/
|
||||||
public function getDefinition(string $fqn, $globalFallback = false)
|
public function getDefinition(string $fqn, bool $globalFallback = false)
|
||||||
{
|
{
|
||||||
if (isset($this->definitions[$fqn])) {
|
if (isset($this->definitions[$fqn])) {
|
||||||
return $this->definitions[$fqn];
|
return $this->definitions[$fqn];
|
||||||
|
@ -69,17 +70,6 @@ class Index
|
||||||
$this->definitions[$fqn] = $definition;
|
$this->definitions[$fqn] = $definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Definition index
|
|
||||||
*
|
|
||||||
* @param Definition[] $definitions Map from FQN to Definition
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setDefinitions(array $definitions)
|
|
||||||
{
|
|
||||||
$this->definitions = $definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsets the Definition for a specific symbol
|
* Unsets the Definition for a specific symbol
|
||||||
* and removes all references pointing to that symbol
|
* and removes all references pointing to that symbol
|
||||||
|
@ -93,6 +83,17 @@ class Index
|
||||||
unset($this->references[$fqn]);
|
unset($this->references[$fqn]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all URIs in this index that reference a symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getReferenceUris(string $fqn): array
|
||||||
|
{
|
||||||
|
return $this->references[$fqn] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a document URI as a referencee of a specific symbol
|
* Adds a document URI as a referencee of a specific symbol
|
||||||
*
|
*
|
||||||
|
@ -128,26 +129,4 @@ class Index
|
||||||
}
|
}
|
||||||
array_splice($this->references[$fqn], $index, 1);
|
array_splice($this->references[$fqn], $index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an associative array [string => string[]] that maps fully qualified symbol names
|
|
||||||
* to URIs of the document where the symbol is referenced
|
|
||||||
*
|
|
||||||
* @return string[][]
|
|
||||||
*/
|
|
||||||
public function getReferenceUris()
|
|
||||||
{
|
|
||||||
return $this->references;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the reference index
|
|
||||||
*
|
|
||||||
* @param string[][] $references an associative array [string => string[]] from FQN to URIs
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setReferenceUris(array $references)
|
|
||||||
{
|
|
||||||
$this->references = $references;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use LanguageServer\ContentRetriever\ContentRetriever;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A project index manages the source and dependency indexes
|
||||||
|
*/
|
||||||
|
class ProjectIndex extends AbstractAggregateIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The index for dependencies
|
||||||
|
*
|
||||||
|
* @var DependenciesIndex
|
||||||
|
*/
|
||||||
|
private $dependenciesIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Index for the project source
|
||||||
|
*
|
||||||
|
* @var Index
|
||||||
|
*/
|
||||||
|
private $sourceIndex;
|
||||||
|
|
||||||
|
public function __construct(Index $sourceIndex, DependenciesIndex $dependenciesIndex)
|
||||||
|
{
|
||||||
|
$this->sourceIndex = $sourceIndex;
|
||||||
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ReadableIndex[]
|
||||||
|
*/
|
||||||
|
protected function getIndexes(): array
|
||||||
|
{
|
||||||
|
return [$this->sourceIndex, $this->dependenciesIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $uri
|
||||||
|
* @return Index
|
||||||
|
*/
|
||||||
|
public function getIndexForUri(string $uri): Index
|
||||||
|
{
|
||||||
|
if (preg_match('/\/vendor\/(\w+\/\w+)\//', $uri, $matches)) {
|
||||||
|
$packageName = $matches[0];
|
||||||
|
return $this->dependenciesIndex->getDependencyIndex($packageName);
|
||||||
|
}
|
||||||
|
return $this->sourceIndex;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ReadableIndex interface provides methods to lookup definitions and references
|
||||||
|
*/
|
||||||
|
interface ReadableIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
||||||
|
* to Definitions
|
||||||
|
*
|
||||||
|
* @return Definitions[]
|
||||||
|
*/
|
||||||
|
public function getDefinitions(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Definition object by a specific FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
||||||
|
* @return Definition|null
|
||||||
|
*/
|
||||||
|
public function getDefinition(string $fqn, bool $globalFallback = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all URIs in this index that reference a symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getReferenceUris(string $fqn): array;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
class StubsIndex extends Index
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Reads the serialized StubsIndex from disk
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function read()
|
||||||
|
{
|
||||||
|
return unserialize(file_get_contents(__DIR__ . '/../../stubs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes and saves the StubsIndex
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
file_put_contents(__DIR__ . '/../../stubs', serialize($this));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
||||||
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\StubsIndex;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use Webmozart\PathUtil\Path;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for the StubIndex
|
||||||
|
*/
|
||||||
|
class StubsIndexer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
private $filesFinder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Promise <StubsIndex>
|
||||||
|
*/
|
||||||
|
public function index(): Promise
|
||||||
|
{
|
||||||
|
coroutine(function () {
|
||||||
|
|
||||||
|
$index = new StubsIndex;
|
||||||
|
|
||||||
|
$finder = new FileSystemFilesFinder;
|
||||||
|
$contentRetriever = new FileSystemContentRetriever;
|
||||||
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
|
$parser = new Parser;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
|
||||||
|
$uris = yield $finder->find(Path::canonicalize(__DIR__ . '/../vendor/JetBrains/phpstorm-stubs/**/*.php'));
|
||||||
|
|
||||||
|
foreach ($uris as $uri) {
|
||||||
|
echo "Parsing $uri\n";
|
||||||
|
$content = yield $contentRetriever->retrieve($uri);
|
||||||
|
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Saving Index\n";
|
||||||
|
|
||||||
|
file_put_contents(__DIR__ . '/../stubs', serialize($index));
|
||||||
|
|
||||||
|
echo "Finished\n";
|
||||||
|
|
||||||
|
})->wait();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ use LanguageServer\Protocol\{
|
||||||
};
|
};
|
||||||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
||||||
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
||||||
|
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Sabre\Event\{Loop, Promise};
|
use Sabre\Event\{Loop, Promise};
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
|
@ -51,12 +52,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
private $client;
|
private $client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The root project path that was passed to initialize()
|
* @var AggregateIndex
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
private $rootPath;
|
private $index;
|
||||||
private $project;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var FilesFinder
|
* @var FilesFinder
|
||||||
|
@ -127,8 +125,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
||||||
|
|
||||||
$this->rootPath = $rootPath;
|
|
||||||
|
|
||||||
if ($capabilities->xfilesProvider) {
|
if ($capabilities->xfilesProvider) {
|
||||||
$this->filesFinder = new ClientFilesFinder($this->client);
|
$this->filesFinder = new ClientFilesFinder($this->client);
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,28 +137,33 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$this->contentRetriever = new FileSystemContentRetriever;
|
$this->contentRetriever = new FileSystemContentRetriever;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
|
$stubsIndex = StubsIndex::read();
|
||||||
|
$globalIndex = new GlobalIndex($stubsIndex, $projectIndex);
|
||||||
|
|
||||||
|
// The DefinitionResolver should look in stubs, the project source and dependencies
|
||||||
|
$definitionResolver = new DefinitionResolver($globalIndex);
|
||||||
|
|
||||||
|
$this->documentLoader = new PhpDocumentLoader(
|
||||||
|
$this->contentRetriever,
|
||||||
|
$projectIndex,
|
||||||
|
$definitionResolver
|
||||||
|
);
|
||||||
|
|
||||||
if ($rootPath !== null) {
|
if ($rootPath !== null) {
|
||||||
$pattern = Path::makeAbsolute('**/{*.php,composer.lock}', $this->rootPath);
|
$pattern = Path::makeAbsolute('**/*.php', $rootPath);
|
||||||
$composerLockPattern = Path::makeAbsolute('**/composer.lock}', $this->rootPath);
|
|
||||||
$uris = yield $this->filesFinder->find($pattern);
|
$uris = yield $this->filesFinder->find($pattern);
|
||||||
|
$this->index($uris)->otherwise('\\LanguageServer\\crash');
|
||||||
// Find composer.lock files
|
|
||||||
$composerLockFiles = [];
|
|
||||||
$phpFiles = [];
|
|
||||||
foreach ($uris as $uri) {
|
|
||||||
if (Glob::match(Uri\parse($uri)['path'], $composerLockPattern)) {
|
|
||||||
$composerLockFiles[$uri] = json_decode(yield $this->contentRetriever->retrieve($uri));
|
|
||||||
} else {
|
|
||||||
$phpFiles[] = $uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->index($phpFiles)->otherwise('\\LanguageServer\\crash');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->project = new Project($this->client, $capabilities, $rootPath);
|
$this->textDocument = new Server\TextDocument(
|
||||||
$this->textDocument = new Server\TextDocument($this->project, $this->client);
|
$this->documentLoader,
|
||||||
$this->workspace = new Server\Workspace($this->project, $this->client);
|
$definitionResolver,
|
||||||
|
$this->client,
|
||||||
|
$globalIndex
|
||||||
|
);
|
||||||
|
// workspace/symbol should only look inside the project source and dependencies
|
||||||
|
$this->workspace = new Server\Workspace($projectIndex, $this->client);
|
||||||
|
|
||||||
$serverCapabilities = new ServerCapabilities();
|
$serverCapabilities = new ServerCapabilities();
|
||||||
// Ask the client to return always full documents (because we need to rebuild the AST from scratch)
|
// Ask the client to return always full documents (because we need to rebuild the AST from scratch)
|
||||||
|
@ -217,7 +218,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
*/
|
*/
|
||||||
private function index(array $phpFiles): Promise
|
private function index(array $phpFiles): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () {
|
return coroutine(function () use ($phpFiles) {
|
||||||
|
|
||||||
$count = count($phpFiles);
|
$count = count($phpFiles);
|
||||||
|
|
||||||
|
@ -226,6 +227,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
// Parse PHP files
|
// Parse PHP files
|
||||||
foreach ($phpFiles as $i => $uri) {
|
foreach ($phpFiles as $i => $uri) {
|
||||||
|
|
||||||
|
if ($this->documentLoader->isOpen($uri)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Give LS to the chance to handle requests while indexing
|
// Give LS to the chance to handle requests while indexing
|
||||||
yield timeout();
|
yield timeout();
|
||||||
$path = Uri\parse($uri);
|
$path = Uri\parse($uri);
|
||||||
|
@ -234,7 +239,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
"Parsing file $i/$count: {$uri}"
|
"Parsing file $i/$count: {$uri}"
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
yield $this->project->loadDocument($uri);
|
yield $this->documentLoader->load($uri);
|
||||||
} catch (ContentTooLargeException $e) {
|
} catch (ContentTooLargeException $e) {
|
||||||
$this->client->window->logMessage(
|
$this->client->window->logMessage(
|
||||||
MessageType::INFO,
|
MessageType::INFO,
|
||||||
|
|
|
@ -13,12 +13,13 @@ use LanguageServer\NodeVisitor\{
|
||||||
ReferencesCollector,
|
ReferencesCollector,
|
||||||
VariableReferencesCollector
|
VariableReferencesCollector
|
||||||
};
|
};
|
||||||
|
use LanguageServer\Index\Index;
|
||||||
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
|
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
use Sabre\Uri;
|
use Sabre\Uri;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
class PhpDocument
|
class PhpDocument
|
||||||
{
|
{
|
||||||
|
@ -43,6 +44,11 @@ class PhpDocument
|
||||||
*/
|
*/
|
||||||
private $definitionResolver;
|
private $definitionResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Index
|
||||||
|
*/
|
||||||
|
private $index;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URI of the document
|
* The URI of the document
|
||||||
*
|
*
|
||||||
|
@ -95,7 +101,7 @@ class PhpDocument
|
||||||
/**
|
/**
|
||||||
* @param string $uri The URI of the document
|
* @param string $uri The URI of the document
|
||||||
* @param string $content The content of the document
|
* @param string $content The content of the document
|
||||||
* @param Index $index The Index to register definitions etc
|
* @param Index $index The Index to register definitions and references to
|
||||||
* @param Parser $parser The PHPParser instance
|
* @param Parser $parser The PHPParser instance
|
||||||
* @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks
|
* @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks
|
||||||
*/
|
*/
|
||||||
|
@ -108,6 +114,7 @@ class PhpDocument
|
||||||
DefinitionResolver $definitionResolver
|
DefinitionResolver $definitionResolver
|
||||||
) {
|
) {
|
||||||
$this->uri = $uri;
|
$this->uri = $uri;
|
||||||
|
$this->index = $index;
|
||||||
$this->parser = $parser;
|
$this->parser = $parser;
|
||||||
$this->docBlockFactory = $docBlockFactory;
|
$this->docBlockFactory = $docBlockFactory;
|
||||||
$this->definitionResolver = $definitionResolver;
|
$this->definitionResolver = $definitionResolver;
|
||||||
|
@ -250,7 +257,7 @@ class PhpDocument
|
||||||
*
|
*
|
||||||
* @return Diagnostic[]
|
* @return Diagnostic[]
|
||||||
*/
|
*/
|
||||||
public function getContent()
|
public function getDiagnostics()
|
||||||
{
|
{
|
||||||
return $this->diagnostics;
|
return $this->diagnostics;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\ContentRetriever\ContentRetriever;
|
||||||
|
use LanguageServer\Index\ProjectIndex;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of loading documents and managing "open" documents
|
||||||
|
*/
|
||||||
|
class PhpDocumentLoader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A map from URI => PhpDocument of open documents that should be kept in memory
|
||||||
|
*
|
||||||
|
* @var PhpDocument
|
||||||
|
*/
|
||||||
|
private $documents = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ContentRetriever
|
||||||
|
*/
|
||||||
|
private $contentRetriever;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ProjectIndex
|
||||||
|
*/
|
||||||
|
private $projectIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Parser
|
||||||
|
*/
|
||||||
|
private $parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DocBlockFactory
|
||||||
|
*/
|
||||||
|
private $docBlockFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DefinitionResolver
|
||||||
|
*/
|
||||||
|
private $definitionResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ContentRetriever $contentRetriever
|
||||||
|
* @param ProjectIndex $project
|
||||||
|
* @param DefinitionResolver $definitionResolver
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
ContentRetriever $contentRetriever,
|
||||||
|
ProjectIndex $projectIndex,
|
||||||
|
DefinitionResolver $definitionResolver
|
||||||
|
) {
|
||||||
|
$this->contentRetriever = $contentRetriever;
|
||||||
|
$this->projectIndex = $projectIndex;
|
||||||
|
$this->definitionResolver = $definitionResolver;
|
||||||
|
$this->parser = new Parser;
|
||||||
|
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the document indicated by uri.
|
||||||
|
* Returns null if the document if not loaded.
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return PhpDocument|null
|
||||||
|
*/
|
||||||
|
public function get(string $uri)
|
||||||
|
{
|
||||||
|
return $this->documents[$uri] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the document indicated by uri.
|
||||||
|
* If the document is not open, loads it.
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return Promise <PhpDocument>
|
||||||
|
*/
|
||||||
|
public function getOrLoad(string $uri): Promise
|
||||||
|
{
|
||||||
|
return isset($this->documents[$uri]) ? Promise\resolve($this->documents[$uri]) : $this->load($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.
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return Promise <PhpDocument>
|
||||||
|
*/
|
||||||
|
public function load(string $uri): Promise
|
||||||
|
{
|
||||||
|
return coroutine(function () use ($uri) {
|
||||||
|
|
||||||
|
$limit = 150000;
|
||||||
|
$content = yield $this->contentRetriever->retrieve($uri);
|
||||||
|
$size = strlen($content);
|
||||||
|
if ($size > $limit) {
|
||||||
|
throw new ContentTooLargeException($uri, $size, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->documents[$uri])) {
|
||||||
|
$document = $this->documents[$uri];
|
||||||
|
$document->updateContent($content);
|
||||||
|
} else {
|
||||||
|
$document = $this->create($uri, $content);
|
||||||
|
}
|
||||||
|
return $document;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a PhpDocument instance
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @param string $content
|
||||||
|
* @return PhpDocument
|
||||||
|
*/
|
||||||
|
public function create(string $uri, string $content): PhpDocument
|
||||||
|
{
|
||||||
|
return new PhpDocument(
|
||||||
|
$uri,
|
||||||
|
$content,
|
||||||
|
$this->projectIndex->getIndexForUri($uri),
|
||||||
|
$this->parser,
|
||||||
|
$this->docBlockFactory,
|
||||||
|
$this->definitionResolver
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures a document is loaded and added to the list of open documents.
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @param string $content
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function open(string $uri, string $content)
|
||||||
|
{
|
||||||
|
if (isset($this->documents[$uri])) {
|
||||||
|
$document = $this->documents[$uri];
|
||||||
|
$document->updateContent($content);
|
||||||
|
} else {
|
||||||
|
$document = $this->create($uri, $content);
|
||||||
|
$this->documents[$uri] = $document;
|
||||||
|
}
|
||||||
|
return $document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the document with the specified URI from the list of open documents
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function close(string $uri)
|
||||||
|
{
|
||||||
|
unset($this->documents[$uri]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the document is open (and loaded)
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isOpen(string $uri): bool
|
||||||
|
{
|
||||||
|
return isset($this->documents[$uri]);
|
||||||
|
}
|
||||||
|
}
|
419
src/Project.php
419
src/Project.php
|
@ -1,419 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace LanguageServer;
|
|
||||||
|
|
||||||
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
|
||||||
use LanguageServer\ContentRetriever\ContentRetriever;
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
|
|
||||||
class Project
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An associative array [string => PhpDocument]
|
|
||||||
* that maps URIs to loaded PhpDocuments
|
|
||||||
*
|
|
||||||
* @var PhpDocument[]
|
|
||||||
*/
|
|
||||||
private $documents = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associative array from package identifier to index
|
|
||||||
*
|
|
||||||
* @var Index[]
|
|
||||||
*/
|
|
||||||
private $dependencyIndexes = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Index for the project itself
|
|
||||||
*
|
|
||||||
* @var Index
|
|
||||||
*/
|
|
||||||
private $sourceIndex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Index for PHP built-ins
|
|
||||||
*
|
|
||||||
* @var Index
|
|
||||||
*/
|
|
||||||
private $stubIndex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An associative array that maps fully qualified symbol names to Definitions
|
|
||||||
*
|
|
||||||
* @var Definition[]
|
|
||||||
*/
|
|
||||||
private $definitions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An associative array that maps fully qualified symbol names to arrays of document URIs that reference the symbol
|
|
||||||
*
|
|
||||||
* @var PhpDocument[][]
|
|
||||||
*/
|
|
||||||
private $references = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instance of the PHP parser
|
|
||||||
*
|
|
||||||
* @var Parser
|
|
||||||
*/
|
|
||||||
private $parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DocBlockFactory instance to parse docblocks
|
|
||||||
*
|
|
||||||
* @var DocBlockFactory
|
|
||||||
*/
|
|
||||||
private $docBlockFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DefinitionResolver instance to resolve reference nodes to Definitions
|
|
||||||
*
|
|
||||||
* @var DefinitionResolver
|
|
||||||
*/
|
|
||||||
private $definitionResolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to the language server client interface
|
|
||||||
*
|
|
||||||
* @var LanguageClient
|
|
||||||
*/
|
|
||||||
private $client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The content retriever
|
|
||||||
*
|
|
||||||
* @var ContentRetriever
|
|
||||||
*/
|
|
||||||
private $contentRetriever;
|
|
||||||
|
|
||||||
private $rootPath;
|
|
||||||
|
|
||||||
private $composerLockFiles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param LanguageClient $client Used for logging and reporting diagnostics
|
|
||||||
* @param ClientCapabilities $clientCapabilities Used for determining the right content/find strategies
|
|
||||||
* @param string|null $rootPath Used for finding files in the project
|
|
||||||
* @param string $composerLockFiles An array of URI => parsed composer.lock JSON
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
LanguageClient $client,
|
|
||||||
ClientCapabilities $clientCapabilities,
|
|
||||||
array $composerLockFiles,
|
|
||||||
DefinitionResolver $definitionResolver,
|
|
||||||
string $rootPath = null
|
|
||||||
) {
|
|
||||||
$this->client = $client;
|
|
||||||
$this->rootPath = $rootPath;
|
|
||||||
$this->parser = new Parser;
|
|
||||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
|
||||||
$this->definitionResolver = $definitionResolver;
|
|
||||||
$this->contentRetriever = $contentRetriever;
|
|
||||||
$this->composerLockFiles = $composerLockFiles;
|
|
||||||
// The index for the project itself
|
|
||||||
$this->projectIndex = new Index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the document indicated by uri.
|
|
||||||
* Returns null if the document if not loaded.
|
|
||||||
*
|
|
||||||
* @param string $uri
|
|
||||||
* @return PhpDocument|null
|
|
||||||
*/
|
|
||||||
public function getDocument(string $uri)
|
|
||||||
{
|
|
||||||
return $this->documents[$uri] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures a document is loaded and added to the list of open documents.
|
|
||||||
*
|
|
||||||
* @param string $uri
|
|
||||||
* @param string $content
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function openDocument(string $uri, string $content)
|
|
||||||
{
|
|
||||||
if (isset($this->documents[$uri])) {
|
|
||||||
$document = $this->documents[$uri];
|
|
||||||
$document->updateContent($content);
|
|
||||||
} else {
|
|
||||||
$document = new PhpDocument(
|
|
||||||
$uri,
|
|
||||||
$content,
|
|
||||||
$this->parser,
|
|
||||||
$this->docBlockFactory,
|
|
||||||
$this->definitionResolver
|
|
||||||
);
|
|
||||||
$this->documents[$uri] = $document;
|
|
||||||
}
|
|
||||||
return $document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the document with the specified URI from the list of open documents
|
|
||||||
*
|
|
||||||
* @param string $uri
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function closeDocument(string $uri)
|
|
||||||
{
|
|
||||||
unset($this->documents[$uri]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the document is open (and loaded)
|
|
||||||
*
|
|
||||||
* @param string $uri
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isDocumentOpen(string $uri): bool
|
|
||||||
{
|
|
||||||
return isset($this->documents[$uri]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
|
||||||
* to Definitions
|
|
||||||
*
|
|
||||||
* @return Definitions[]
|
|
||||||
*/
|
|
||||||
public function getDefinitions()
|
|
||||||
{
|
|
||||||
$defs = [];
|
|
||||||
foreach ($this->sourceIndex->getDefinitions() as $def) {
|
|
||||||
$defs[] = $def;
|
|
||||||
}
|
|
||||||
foreach ($this->dependenciesIndexes as $dependencyIndex) {
|
|
||||||
foreach ($dependencyIndex->getDefinitions() as $def) {
|
|
||||||
$defs[] = $def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($this->stubIndex->getDefinitions() as $def) {
|
|
||||||
$defs[] = $def;
|
|
||||||
}
|
|
||||||
return $defs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Definition object by a specific FQN
|
|
||||||
*
|
|
||||||
* @param string $fqn
|
|
||||||
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
|
||||||
* @return Definition|null
|
|
||||||
*/
|
|
||||||
public function getDefinition(string $fqn, $globalFallback = false)
|
|
||||||
{
|
|
||||||
foreach (array_merge([$this->sourceIndex, $this->stubsIndex], ...$this->dependencyIndexes) as $index) {
|
|
||||||
if ($index->isDefined($fqn)) {
|
|
||||||
return $index->getDefinition($fqn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($globalFallback) {
|
|
||||||
$parts = explode('\\', $fqn);
|
|
||||||
$fqn = end($parts);
|
|
||||||
return $this->getDefinition($fqn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a definition
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @param string $definition The Definition object
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setDefinition(string $fqn, Definition $definition)
|
|
||||||
{
|
|
||||||
$this->definitions[$fqn] = $definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Definition index
|
|
||||||
*
|
|
||||||
* @param Definition[] $definitions Map from FQN to Definition
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setDefinitions(array $definitions)
|
|
||||||
{
|
|
||||||
$this->definitions = $definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsets the Definition for a specific symbol
|
|
||||||
* and removes all references pointing to that symbol
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function removeDefinition(string $fqn)
|
|
||||||
{
|
|
||||||
unset($this->definitions[$fqn]);
|
|
||||||
unset($this->references[$fqn]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a document URI as a referencee of a specific symbol
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addReferenceUri(string $fqn, string $uri)
|
|
||||||
{
|
|
||||||
if (!isset($this->references[$fqn])) {
|
|
||||||
$this->references[$fqn] = [];
|
|
||||||
}
|
|
||||||
// TODO: use DS\Set instead of searching array
|
|
||||||
if (array_search($uri, $this->references[$fqn], true) === false) {
|
|
||||||
$this->references[$fqn][] = $uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a document URI as the container for a specific symbol
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @param string $uri The URI
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function removeReferenceUri(string $fqn, string $uri)
|
|
||||||
{
|
|
||||||
if (!isset($this->references[$fqn])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$index = array_search($fqn, $this->references[$fqn], true);
|
|
||||||
if ($index === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
array_splice($this->references[$fqn], $index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all documents that reference a symbol
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @return Promise <PhpDocument[]>
|
|
||||||
*/
|
|
||||||
public function getReferenceDocuments(string $fqn): Promise
|
|
||||||
{
|
|
||||||
if (!isset($this->references[$fqn])) {
|
|
||||||
return Promise\resolve([]);
|
|
||||||
}
|
|
||||||
return Promise\all(array_map([$this, 'getOrLoadDocument'], $this->references[$fqn]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an associative array [string => string[]] that maps fully qualified symbol names
|
|
||||||
* to URIs of the document where the symbol is referenced
|
|
||||||
*
|
|
||||||
* @return string[][]
|
|
||||||
*/
|
|
||||||
public function getReferenceUris()
|
|
||||||
{
|
|
||||||
return $this->references;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the reference index
|
|
||||||
*
|
|
||||||
* @param string[][] $references an associative array [string => string[]] from FQN to URIs
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setReferenceUris(array $references)
|
|
||||||
{
|
|
||||||
$this->references = $references;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the document where a symbol is defined
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @return Promise <PhpDocument|null>
|
|
||||||
*/
|
|
||||||
public function getDefinitionDocument(string $fqn): Promise
|
|
||||||
{
|
|
||||||
if (!isset($this->definitions[$fqn])) {
|
|
||||||
return Promise\resolve(null);
|
|
||||||
}
|
|
||||||
return $this->getOrLoadDocument($this->definitions[$fqn]->symbolInformation->location->uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given FQN is defined in the project
|
|
||||||
*
|
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isDefined(string $fqn): bool
|
|
||||||
{
|
|
||||||
return isset($this->definitions[$fqn]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the reference nodes for any node
|
|
||||||
*
|
|
||||||
* @param Node $node
|
|
||||||
* @return Promise <Node[]>
|
|
||||||
*/
|
|
||||||
public function getReferenceNodesByNode(Node $node): Promise
|
|
||||||
{
|
|
||||||
return coroutine(function () use ($node) {
|
|
||||||
// Variables always stay in the boundary of the file and need to be searched inside their function scope
|
|
||||||
// by traversing the AST
|
|
||||||
if (
|
|
||||||
$node instanceof Node\Expr\Variable
|
|
||||||
|| $node instanceof Node\Param
|
|
||||||
|| $node instanceof Node\Expr\ClosureUse
|
|
||||||
) {
|
|
||||||
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->nodes;
|
|
||||||
}
|
|
||||||
// Definition with a global FQN
|
|
||||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
|
||||||
if ($fqn === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$refDocuments = yield $this->getReferenceDocuments($fqn);
|
|
||||||
$nodes = [];
|
|
||||||
foreach ($refDocuments as $document) {
|
|
||||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
|
||||||
if ($refs !== null) {
|
|
||||||
foreach ($refs as $ref) {
|
|
||||||
$nodes[] = $ref;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $nodes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,10 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\Server;
|
namespace LanguageServer\Server;
|
||||||
|
|
||||||
use LanguageServer\{LanguageClient, Project, PhpDocument, DefinitionResolver, CompletionProvider};
|
|
||||||
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||||
use PhpParser\Node;
|
use PhpParser\{Node, NodeTraverser};
|
||||||
|
use LanguageServer\{LanguageClient, PhpDocumentLoader, PhpDocument, DefinitionResolver, CompletionProvider};
|
||||||
|
use LanguageServer\NodeVisitor\VariableReferencesCollector;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextDocumentItem,
|
TextDocumentItem,
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
|
@ -23,8 +24,10 @@ use LanguageServer\Protocol\{
|
||||||
CompletionItem,
|
CompletionItem,
|
||||||
CompletionItemKind
|
CompletionItemKind
|
||||||
};
|
};
|
||||||
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
|
use function LanguageServer\getReferenceNodesByNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all textDocument/* methods
|
* Provides method handlers for all textDocument/* methods
|
||||||
|
@ -59,14 +62,20 @@ class TextDocument
|
||||||
private $completionProvider;
|
private $completionProvider;
|
||||||
|
|
||||||
private $openDocuments = [];
|
private $openDocuments = [];
|
||||||
|
private $index;
|
||||||
|
|
||||||
public function __construct(Project $project, LanguageClient $client)
|
public function __construct(
|
||||||
{
|
PhpDocumentLoader $documentLoader,
|
||||||
$this->project = $project;
|
DefinitionResolver $definitionResolver,
|
||||||
|
LanguageClient $client,
|
||||||
|
ReadableIndex $index
|
||||||
|
) {
|
||||||
|
$this->documentLoader = $documentLoader;
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
$this->prettyPrinter = new PrettyPrinter();
|
$this->prettyPrinter = new PrettyPrinter();
|
||||||
$this->definitionResolver = new DefinitionResolver();
|
$this->definitionResolver = $definitionResolver;
|
||||||
$this->completionProvider = new CompletionProvider($this->definitionResolver, $project);
|
$this->completionProvider = new CompletionProvider($this->definitionResolver, $index);
|
||||||
|
$this->index = $index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,7 +87,7 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
|
public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
|
||||||
{
|
{
|
||||||
return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
|
return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) {
|
||||||
$symbols = [];
|
$symbols = [];
|
||||||
foreach ($document->getDefinitions() as $fqn => $definition) {
|
foreach ($document->getDefinitions() as $fqn => $definition) {
|
||||||
$symbols[] = $definition->symbolInformation;
|
$symbols[] = $definition->symbolInformation;
|
||||||
|
@ -97,9 +106,9 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function didOpen(TextDocumentItem $textDocument)
|
public function didOpen(TextDocumentItem $textDocument)
|
||||||
{
|
{
|
||||||
$document = $this->project->openDocument($textDocument->uri, $textDocument->text);
|
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
|
||||||
if (!$document->isVendored()) {
|
if (!$document->isVendored()) {
|
||||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +121,9 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
||||||
{
|
{
|
||||||
$document = $this->project->getDocument($textDocument->uri);
|
$document = $this->documentLoader->get($textDocument->uri);
|
||||||
$document->updateContent($contentChanges[0]->text);
|
$document->updateContent($contentChanges[0]->text);
|
||||||
$this->client->publishDiagnostics($document->getDiagnostics());
|
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +136,7 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function didClose(TextDocumentIdentifier $textDocument)
|
public function didClose(TextDocumentIdentifier $textDocument)
|
||||||
{
|
{
|
||||||
$this->project->closeDocument($textDocument->uri);
|
$this->documentLoader->close($textDocument->uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,7 +148,7 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
|
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
|
||||||
{
|
{
|
||||||
return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
|
return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) {
|
||||||
return $document->getFormattedText();
|
return $document->getFormattedText();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -157,15 +166,55 @@ class TextDocument
|
||||||
Position $position
|
Position $position
|
||||||
): Promise {
|
): Promise {
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
return coroutine(function () use ($textDocument, $position) {
|
||||||
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$refs = yield $document->getReferenceNodesByNode($node);
|
|
||||||
$locations = [];
|
$locations = [];
|
||||||
foreach ($refs as $ref) {
|
// Variables always stay in the boundary of the file and need to be searched inside their function scope
|
||||||
$locations[] = Location::fromNode($ref);
|
// by traversing the AST
|
||||||
|
if (
|
||||||
|
$node instanceof Node\Expr\Variable
|
||||||
|
|| $node instanceof Node\Param
|
||||||
|
|| $node instanceof Node\Expr\ClosureUse
|
||||||
|
) {
|
||||||
|
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());
|
||||||
|
foreach ($refCollector->nodes as $ref) {
|
||||||
|
$locations[] = Location::fromNode($ref);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Definition with a global FQN
|
||||||
|
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||||
|
if ($fqn === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$refDocuments = yield Promise\all(array_map(
|
||||||
|
[$this->documentLoader, 'getOrLoad'],
|
||||||
|
$this->index->getReferenceUris($fqn)
|
||||||
|
));
|
||||||
|
foreach ($refDocuments as $document) {
|
||||||
|
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||||
|
if ($refs !== null) {
|
||||||
|
foreach ($refs as $ref) {
|
||||||
|
$locations[] = Location::fromNode($ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $locations;
|
return $locations;
|
||||||
});
|
});
|
||||||
|
@ -182,7 +231,7 @@ class TextDocument
|
||||||
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
return coroutine(function () use ($textDocument, $position) {
|
||||||
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -205,7 +254,7 @@ class TextDocument
|
||||||
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
return coroutine(function () use ($textDocument, $position) {
|
||||||
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
// Find the node under the cursor
|
// Find the node under the cursor
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
|
@ -244,7 +293,7 @@ class TextDocument
|
||||||
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
return coroutine(function () use ($textDocument, $position) {
|
||||||
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
return $this->completionProvider->provideCompletion($document, $position);
|
return $this->completionProvider->provideCompletion($document, $position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Server;
|
namespace LanguageServer\Server;
|
||||||
|
|
||||||
use LanguageServer\{LanguageClient, Project};
|
use LanguageServer\{LanguageClient, Project};
|
||||||
|
use LanguageServer\Index\ProjectIndex;
|
||||||
use LanguageServer\Protocol\SymbolInformation;
|
use LanguageServer\Protocol\SymbolInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,15 +20,18 @@ class Workspace
|
||||||
private $client;
|
private $client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current project database
|
* The symbol index for the workspace
|
||||||
*
|
*
|
||||||
* @var Project
|
* @var ProjectIndex
|
||||||
*/
|
*/
|
||||||
private $project;
|
private $index;
|
||||||
|
|
||||||
public function __construct(Project $project, LanguageClient $client)
|
/**
|
||||||
|
* @param ProjectIndex $index Index that is searched on a workspace/symbol request
|
||||||
|
*/
|
||||||
|
public function __construct(ProjectIndex $index, LanguageClient $client)
|
||||||
{
|
{
|
||||||
$this->project = $project;
|
$this->index = $index;
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +44,7 @@ class Workspace
|
||||||
public function symbol(string $query): array
|
public function symbol(string $query): array
|
||||||
{
|
{
|
||||||
$symbols = [];
|
$symbols = [];
|
||||||
foreach ($this->project->getDefinitions() as $fqn => $definition) {
|
foreach ($this->index->getDefinitions() as $fqn => $definition) {
|
||||||
if ($query === '' || stripos($fqn, $query) !== false) {
|
if ($query === '' || stripos($fqn, $query) !== false) {
|
||||||
$symbols[] = $definition->symbolInformation;
|
$symbols[] = $definition->symbolInformation;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace LanguageServer;
|
|
||||||
|
|
||||||
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
|
|
||||||
coroutine(function () {
|
|
||||||
|
|
||||||
$index = new Index;
|
|
||||||
|
|
||||||
$finder = new FileSystemFilesFinder;
|
|
||||||
$contentRetriever = new FileSystemContentRetriever;
|
|
||||||
$docBlockFactory = DocBlockFactory::createInstance();
|
|
||||||
$definitionResolver = new DefinitionResolver([$index]);
|
|
||||||
|
|
||||||
$uris = yield $finder->find(__DIR__ . '/../vendor/JetBrains/phpstorm-stubs');
|
|
||||||
|
|
||||||
foreach ($uris as $uri) {
|
|
||||||
$content = $contentRetriever->retrieve($uri);
|
|
||||||
$document = new PhpDocument($uri, $content, $index, $docBlockFactory, $definitionResolver);
|
|
||||||
}
|
|
||||||
|
|
||||||
})->wait();
|
|
|
@ -6,7 +6,16 @@ namespace LanguageServer\Tests;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\LanguageServer;
|
use LanguageServer\LanguageServer;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
Message, ClientCapabilities, TextDocumentSyncKind, MessageType, TextDocumentItem, TextDocumentIdentifier};
|
Message,
|
||||||
|
ClientCapabilities,
|
||||||
|
TextDocumentSyncKind,
|
||||||
|
MessageType,
|
||||||
|
TextDocumentItem,
|
||||||
|
TextDocumentIdentifier,
|
||||||
|
InitializeResult,
|
||||||
|
ServerCapabilities,
|
||||||
|
CompletionOptions
|
||||||
|
};
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Webmozart\Glob\Glob;
|
use Webmozart\Glob\Glob;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
|
@ -18,41 +27,22 @@ class LanguageServerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testInitialize()
|
public function testInitialize()
|
||||||
{
|
{
|
||||||
$reader = new MockProtocolStream();
|
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
||||||
$writer = new MockProtocolStream();
|
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid())->wait();
|
||||||
$server = new LanguageServer($reader, $writer);
|
|
||||||
$promise = new Promise;
|
$serverCapabilities = new ServerCapabilities();
|
||||||
$writer->once('message', [$promise, 'fulfill']);
|
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
||||||
$reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [
|
$serverCapabilities->documentSymbolProvider = true;
|
||||||
'rootPath' => __DIR__,
|
$serverCapabilities->workspaceSymbolProvider = true;
|
||||||
'processId' => getmypid(),
|
$serverCapabilities->documentFormattingProvider = true;
|
||||||
'capabilities' => new ClientCapabilities()
|
$serverCapabilities->definitionProvider = true;
|
||||||
])));
|
$serverCapabilities->referencesProvider = true;
|
||||||
$msg = $promise->wait();
|
$serverCapabilities->hoverProvider = true;
|
||||||
$this->assertNotNull($msg, 'message event should be emitted');
|
$serverCapabilities->completionProvider = new CompletionOptions;
|
||||||
$this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body);
|
$serverCapabilities->completionProvider->resolveProvider = false;
|
||||||
$this->assertEquals((object)[
|
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
|
||||||
'capabilities' => (object)[
|
|
||||||
'textDocumentSync' => TextDocumentSyncKind::FULL,
|
$this->assertEquals(new InitializeResult($serverCapabilities), $result);
|
||||||
'documentSymbolProvider' => true,
|
|
||||||
'hoverProvider' => true,
|
|
||||||
'completionProvider' => (object)[
|
|
||||||
'resolveProvider' => false,
|
|
||||||
'triggerCharacters' => ['$', '>']
|
|
||||||
],
|
|
||||||
'signatureHelpProvider' => null,
|
|
||||||
'definitionProvider' => true,
|
|
||||||
'referencesProvider' => true,
|
|
||||||
'documentHighlightProvider' => null,
|
|
||||||
'workspaceSymbolProvider' => true,
|
|
||||||
'codeActionProvider' => null,
|
|
||||||
'codeLensProvider' => null,
|
|
||||||
'documentFormattingProvider' => true,
|
|
||||||
'documentRangeFormattingProvider' => null,
|
|
||||||
'documentOnTypeFormattingProvider' => null,
|
|
||||||
'renameProvider' => null
|
|
||||||
]
|
|
||||||
], $msg->body->result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexingWithDirectFileAccess()
|
public function testIndexingWithDirectFileAccess()
|
||||||
|
|
|
@ -6,9 +6,11 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
use PHPUnit\Framework\TestCase;
|
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, DefinitionResolver};
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use LanguageServer\{LanguageClient, PhpDocument, PhpDocumentLoader, Parser, DefinitionResolver};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\ClientCapabilities;
|
use LanguageServer\Protocol\ClientCapabilities;
|
||||||
|
use LanguageServer\Index\{ProjectIndex, Index, DependenciesIndex};
|
||||||
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;
|
||||||
|
@ -17,19 +19,25 @@ class DefinitionCollectorTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testCollectsSymbols()
|
public function testCollectsSymbols()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$path = realpath(__DIR__ . '/../../fixtures/symbols.php');
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$uri = pathToUri($path);
|
||||||
$parser = new Parser;
|
$parser = new Parser;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
$document = $project->loadDocument($uri)->wait();
|
$index = new Index;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
|
$stmts = $parser->parse($content);
|
||||||
|
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver);
|
||||||
$traverser->addVisitor(new ReferencesAdder($document));
|
$traverser->addVisitor(new ReferencesAdder($document));
|
||||||
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
$definitionCollector = new DefinitionCollector($definitionResolver);
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
$stmts = $parser->parse(file_get_contents($uri));
|
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
|
|
||||||
$defNodes = $definitionCollector->nodes;
|
$defNodes = $definitionCollector->nodes;
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'TestNamespace',
|
'TestNamespace',
|
||||||
'TestNamespace\\TEST_CONST',
|
'TestNamespace\\TEST_CONST',
|
||||||
|
@ -57,19 +65,25 @@ class DefinitionCollectorTest extends TestCase
|
||||||
|
|
||||||
public function testDoesNotCollectReferences()
|
public function testDoesNotCollectReferences()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$path = realpath(__DIR__ . '/../../fixtures/references.php');
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$uri = pathToUri($path);
|
||||||
$parser = new Parser;
|
$parser = new Parser;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
$document = $project->loadDocument($uri)->wait();
|
$index = new Index;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
|
$stmts = $parser->parse($content);
|
||||||
|
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver);
|
||||||
$traverser->addVisitor(new ReferencesAdder($document));
|
$traverser->addVisitor(new ReferencesAdder($document));
|
||||||
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
$definitionCollector = new DefinitionCollector($definitionResolver);
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
$stmts = $parser->parse(file_get_contents($uri));
|
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
|
|
||||||
$defNodes = $definitionCollector->nodes;
|
$defNodes = $definitionCollector->nodes;
|
||||||
|
|
||||||
$this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes));
|
$this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes));
|
||||||
$this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']);
|
$this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']);
|
||||||
$this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']);
|
$this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']);
|
||||||
|
|
|
@ -5,8 +5,9 @@ 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, PhpDocumentLoader, DefinitionResolver};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextDocumentItem,
|
TextDocumentItem,
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
|
@ -18,31 +19,35 @@ use LanguageServer\Protocol\{
|
||||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
|
||||||
class ProjectTest extends TestCase
|
class PhpDocumentLoaderTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var Project $project
|
* @var PhpDocumentLoader
|
||||||
*/
|
*/
|
||||||
private $project;
|
private $loader;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$this->project = new Project($client, new FileSystemContentRetriever);
|
$this->loader = new PhpDocumentLoader(
|
||||||
|
new FileSystemContentRetriever,
|
||||||
|
$projectIndex,
|
||||||
|
new DefinitionResolver($projectIndex)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetOrLoadDocumentLoadsDocument()
|
public function testGetOrLoadLoadsDocument()
|
||||||
{
|
{
|
||||||
$document = $this->project->getOrLoadDocument(pathToUri(__FILE__))->wait();
|
$document = $this->loader->getOrLoad(pathToUri(__FILE__))->wait();
|
||||||
|
|
||||||
$this->assertNotNull($document);
|
$this->assertNotNull($document);
|
||||||
$this->assertInstanceOf(PhpDocument::class, $document);
|
$this->assertInstanceOf(PhpDocument::class, $document);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetDocumentReturnsOpenedInstance()
|
public function testGetReturnsOpenedInstance()
|
||||||
{
|
{
|
||||||
$document1 = $this->project->openDocument(pathToUri(__FILE__), file_get_contents(__FILE__));
|
$document1 = $this->loader->open(pathToUri(__FILE__), file_get_contents(__FILE__));
|
||||||
$document2 = $this->project->getDocument(pathToUri(__FILE__));
|
$document2 = $this->loader->get(pathToUri(__FILE__));
|
||||||
|
|
||||||
$this->assertSame($document1, $document2);
|
$this->assertSame($document1, $document2);
|
||||||
}
|
}
|
|
@ -4,36 +4,36 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Tests\Server;
|
namespace LanguageServer\Tests\Server;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{LanguageClient, Project};
|
use LanguageServer\{LanguageClient, PhpDocument, DefinitionResolver, Parser};
|
||||||
use LanguageServer\NodeVisitor\NodeAtPositionFinder;
|
use LanguageServer\NodeVisitor\NodeAtPositionFinder;
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
|
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
|
||||||
class PhpDocumentTest extends TestCase
|
class PhpDocumentTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
public function createDocument(string $uri, string $content)
|
||||||
* @var Project $project
|
|
||||||
*/
|
|
||||||
private $project;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$parser = new Parser;
|
||||||
$this->project = new Project($client, new FileSystemContentRetriever);
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
|
$index = new Index;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
return new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParsesVariableVariables()
|
public function testParsesVariableVariables()
|
||||||
{
|
{
|
||||||
$document = $this->project->openDocument('whatever', "<?php\n$\$a = 'foo';\n\$bar = 'baz';\n");
|
$document = $this->createDocument('whatever', "<?php\n$\$a = 'foo';\n\$bar = 'baz';\n");
|
||||||
|
|
||||||
$this->assertEquals([], $document->getDefinitions());
|
$this->assertEquals([], $document->getDefinitions());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetNodeAtPosition()
|
public function testGetNodeAtPosition()
|
||||||
{
|
{
|
||||||
$document = $this->project->openDocument('whatever', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('whatever', "<?php\n$\$a = new SomeClass;");
|
||||||
$node = $document->getNodeAtPosition(new Position(1, 13));
|
$node = $document->getNodeAtPosition(new Position(1, 13));
|
||||||
$this->assertInstanceOf(Node\Name\FullyQualified::class, $node);
|
$this->assertInstanceOf(Node\Name\FullyQualified::class, $node);
|
||||||
$this->assertEquals('SomeClass', (string)$node);
|
$this->assertEquals('SomeClass', (string)$node);
|
||||||
|
@ -41,19 +41,19 @@ class PhpDocumentTest extends TestCase
|
||||||
|
|
||||||
public function testIsVendored()
|
public function testIsVendored()
|
||||||
{
|
{
|
||||||
$document = $this->project->openDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, $document->isVendored());
|
||||||
|
|
||||||
$document = $this->project->openDocument('file:///c:/dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///c:/dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, $document->isVendored());
|
||||||
|
|
||||||
$document = $this->project->openDocument('file:///vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, $document->isVendored());
|
||||||
|
|
||||||
$document = $this->project->openDocument('file:///dir/vendor.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/vendor.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(false, $document->isVendored());
|
$this->assertEquals(false, $document->isVendored());
|
||||||
|
|
||||||
$document = $this->project->openDocument('file:///dir/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(false, $document->isVendored());
|
$this->assertEquals(false, $document->isVendored());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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, PhpDocumentLoader, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{ProjectIndex, StubsIndex, GlobalIndex, DependenciesIndex, Index};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities};
|
use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
@ -24,9 +25,9 @@ abstract class ServerTestCase extends TestCase
|
||||||
protected $workspace;
|
protected $workspace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Project
|
* @var PhpDocumentLoader
|
||||||
*/
|
*/
|
||||||
protected $project;
|
protected $documentLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map from FQN to Location of definition
|
* Map from FQN to Location of definition
|
||||||
|
@ -44,10 +45,13 @@ abstract class ServerTestCase extends TestCase
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$this->project = new Project($client, new FileSystemContentRetriever);
|
|
||||||
$this->textDocument = new Server\TextDocument($this->project, $client);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$this->workspace = new Server\Workspace($this->project, $client);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
|
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
|
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$this->workspace = new Server\Workspace($projectIndex, $client);
|
||||||
|
|
||||||
$globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php'));
|
$globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php'));
|
||||||
$globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php'));
|
$globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php'));
|
||||||
|
@ -55,11 +59,11 @@ 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)->wait();
|
$this->documentLoader->load($symbolsUri)->wait();
|
||||||
$this->project->loadDocument($referencesUri)->wait();
|
$this->documentLoader->load($referencesUri)->wait();
|
||||||
$this->project->loadDocument($globalSymbolsUri)->wait();
|
$this->documentLoader->load($globalSymbolsUri)->wait();
|
||||||
$this->project->loadDocument($globalReferencesUri)->wait();
|
$this->documentLoader->load($globalReferencesUri)->wait();
|
||||||
$this->project->loadDocument($useUri)->wait();
|
$this->documentLoader->load($useUri)->wait();
|
||||||
|
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
$this->definitionLocations = [
|
$this->definitionLocations = [
|
||||||
|
|
|
@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{Server, LanguageClient, Project, CompletionProvider};
|
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, CompletionProvider, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex, GlobalIndex, StubsIndex};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
|
@ -27,23 +28,26 @@ class CompletionTest extends TestCase
|
||||||
private $textDocument;
|
private $textDocument;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Project
|
* @var PhpDocumentLoader
|
||||||
*/
|
*/
|
||||||
private $project;
|
private $loader;
|
||||||
|
|
||||||
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, new FileSystemContentRetriever);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait();
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/symbols.php'))->wait();
|
$contentRetriever = new FileSystemContentRetriever;
|
||||||
$this->textDocument = new Server\TextDocument($this->project, $client);
|
$this->loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver);
|
||||||
|
$this->loader->load(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait();
|
||||||
|
$this->loader->load(pathToUri(__DIR__ . '/../../../fixtures/symbols.php'))->wait();
|
||||||
|
$this->textDocument = new Server\TextDocument($this->loader, $definitionResolver, $client, $projectIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPropertyAndMethodWithPrefix()
|
public function testPropertyAndMethodWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(3, 7)
|
new Position(3, 7)
|
||||||
|
@ -67,7 +71,7 @@ class CompletionTest extends TestCase
|
||||||
public function testPropertyAndMethodWithoutPrefix()
|
public function testPropertyAndMethodWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(3, 6)
|
new Position(3, 6)
|
||||||
|
@ -91,7 +95,7 @@ class CompletionTest extends TestCase
|
||||||
public function testVariable()
|
public function testVariable()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(8, 5)
|
new Position(8, 5)
|
||||||
|
@ -123,7 +127,7 @@ class CompletionTest extends TestCase
|
||||||
public function testVariableWithPrefix()
|
public function testVariableWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(8, 6)
|
new Position(8, 6)
|
||||||
|
@ -145,7 +149,7 @@ class CompletionTest extends TestCase
|
||||||
public function testNewInNamespace()
|
public function testNewInNamespace()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_new.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_new.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(6, 10)
|
new Position(6, 10)
|
||||||
|
@ -177,7 +181,7 @@ class CompletionTest extends TestCase
|
||||||
public function testUsedClass()
|
public function testUsedClass()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(6, 5)
|
new Position(6, 5)
|
||||||
|
@ -195,7 +199,7 @@ class CompletionTest extends TestCase
|
||||||
public function testStaticPropertyWithPrefix()
|
public function testStaticPropertyWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_property_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_property_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(2, 14)
|
new Position(2, 14)
|
||||||
|
@ -216,7 +220,7 @@ class CompletionTest extends TestCase
|
||||||
public function testStaticWithoutPrefix()
|
public function testStaticWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(2, 11)
|
new Position(2, 11)
|
||||||
|
@ -249,7 +253,7 @@ class CompletionTest extends TestCase
|
||||||
public function testStaticMethodWithPrefix()
|
public function testStaticMethodWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_method_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_method_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(2, 13)
|
new Position(2, 13)
|
||||||
|
@ -282,7 +286,7 @@ class CompletionTest extends TestCase
|
||||||
public function testClassConstWithPrefix()
|
public function testClassConstWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/class_const_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/class_const_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(2, 13)
|
new Position(2, 13)
|
||||||
|
@ -315,7 +319,7 @@ class CompletionTest extends TestCase
|
||||||
public function testFullyQualifiedClass()
|
public function testFullyQualifiedClass()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/fully_qualified_class.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/fully_qualified_class.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(6, 6)
|
new Position(6, 6)
|
||||||
|
@ -336,7 +340,7 @@ class CompletionTest extends TestCase
|
||||||
public function testKeywords()
|
public function testKeywords()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/keywords.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/keywords.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(2, 1)
|
new Position(2, 1)
|
||||||
|
@ -350,7 +354,7 @@ class CompletionTest extends TestCase
|
||||||
public function testHtmlWithoutPrefix()
|
public function testHtmlWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(0, 0)
|
new Position(0, 0)
|
||||||
|
@ -372,7 +376,7 @@ class CompletionTest extends TestCase
|
||||||
public function testHtmlWithPrefix()
|
public function testHtmlWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(0, 1)
|
new Position(0, 1)
|
||||||
|
@ -394,7 +398,7 @@ class CompletionTest extends TestCase
|
||||||
public function testNamespace()
|
public function testNamespace()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/namespace.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/namespace.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
$items = $this->textDocument->completion(
|
$items = $this->textDocument->completion(
|
||||||
new TextDocumentIdentifier($completionUri),
|
new TextDocumentIdentifier($completionUri),
|
||||||
new Position(4, 6)
|
new Position(4, 6)
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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, PhpDocumentLoader, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities};
|
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities};
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
|
@ -14,11 +15,14 @@ class GlobalFallbackTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$this->textDocument = new Server\TextDocument($project, $client);
|
$contentRetriever = new FileSystemContentRetriever;
|
||||||
$project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
$loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver);
|
||||||
$project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
$this->textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$loader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
||||||
|
$loader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClassDoesNotFallback()
|
public function testClassDoesNotFallback()
|
||||||
|
|
|
@ -5,8 +5,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};
|
use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
TextDocumentItem,
|
TextDocumentItem,
|
||||||
|
@ -21,10 +22,12 @@ class DidChangeTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$textDocument = new Server\TextDocument($project, $client);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n");
|
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
||||||
|
|
||||||
$identifier = new VersionedTextDocumentIdentifier('whatever');
|
$identifier = new VersionedTextDocumentIdentifier('whatever');
|
||||||
$changeEvent = new TextDocumentContentChangeEvent();
|
$changeEvent = new TextDocumentContentChangeEvent();
|
||||||
|
|
|
@ -5,8 +5,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};
|
use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, ClientCapabilities};
|
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, ClientCapabilities};
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
|
@ -14,10 +15,12 @@ class DidCloseTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$textDocument = new Server\TextDocument($project, $client);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$phpDocument = $project->openDocument('whatever', 'hello world');
|
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
||||||
|
|
||||||
$textDocumentItem = new TextDocumentItem();
|
$textDocumentItem = new TextDocumentItem();
|
||||||
$textDocumentItem->uri = 'whatever';
|
$textDocumentItem->uri = 'whatever';
|
||||||
|
@ -28,6 +31,6 @@ class DidCloseTest extends TestCase
|
||||||
|
|
||||||
$textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri));
|
$textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri));
|
||||||
|
|
||||||
$this->assertFalse($project->isDocumentOpen($textDocumentItem->uri));
|
$this->assertFalse($loader->isOpen($textDocumentItem->uri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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, PhpDocumentLoader, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
|
@ -20,23 +21,14 @@ use function LanguageServer\{pathToUri, uriToPath};
|
||||||
|
|
||||||
class FormattingTest extends TestCase
|
class FormattingTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var Server\TextDocument
|
|
||||||
*/
|
|
||||||
private $textDocument;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
|
||||||
$this->textDocument = new Server\TextDocument($project, $client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFormatting()
|
public function testFormatting()
|
||||||
{
|
{
|
||||||
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$textDocument = new Server\TextDocument($project, $client);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
|
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
|
||||||
$path = realpath(__DIR__ . '/../../../fixtures/format.php');
|
$path = realpath(__DIR__ . '/../../../fixtures/format.php');
|
||||||
$uri = pathToUri($path);
|
$uri = pathToUri($path);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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, ClientHandler, PhpDocumentLoader, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities};
|
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities};
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
|
@ -36,8 +37,10 @@ class ParseErrorsTest extends TestCase
|
||||||
return Promise\resolve(null);
|
return Promise\resolve(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$this->textDocument = new Server\TextDocument($project, $client);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
|
$this->textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function openFile($file)
|
private function openFile($file)
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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, PhpDocumentLoader, DefinitionResolver};
|
||||||
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities};
|
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities};
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
|
@ -14,11 +15,13 @@ class GlobalFallbackTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$project = new Project($client, new FileSystemContentRetriever);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$this->textDocument = new Server\TextDocument($project, $client);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$this->documentLoader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
||||||
|
$this->documentLoader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClassDoesNotFallback()
|
public function testClassDoesNotFallback()
|
||||||
|
|
Loading…
Reference in New Issue