From 91323a8e0771a817d45238adf2d7296b509ba743 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 12 Dec 2016 22:59:08 +0100 Subject: [PATCH] Much better --- .gitignore | 1 + composer.json | 5 + src/CompletionProvider.php | 24 +- src/ComposerScripts.php | 47 ++ src/DefinitionResolver.php | 30 +- src/Index/AbstractAggregateIndex.php | 64 +++ src/Index/DependenciesIndex.php | 52 +++ src/Index/GlobalIndex.php | 38 ++ src/{ => Index}/Index.php | 55 +-- src/Index/ProjectIndex.php | 57 +++ src/Index/ReadableIndex.php | 35 ++ src/Index/StubsIndex.php | 27 ++ src/Index/StubsIndexer.php | 56 +++ src/LanguageServer.php | 59 +-- src/PhpDocument.php | 13 +- src/PhpDocumentLoader.php | 178 ++++++++ src/Project.php | 419 ------------------ src/Server/TextDocument.php | 91 +++- src/Server/Workspace.php | 16 +- src/parse_stubs.php | 26 -- tests/LanguageServerTest.php | 62 ++- tests/NodeVisitor/DefinitionCollectorTest.php | 40 +- ...jectTest.php => PhpDocumentLoaderTest.php} | 27 +- tests/PhpDocumentTest.php | 32 +- tests/Server/ServerTestCase.php | 28 +- tests/Server/TextDocument/CompletionTest.php | 48 +- .../Definition/GlobalFallbackTest.php | 14 +- tests/Server/TextDocument/DidChangeTest.php | 11 +- tests/Server/TextDocument/DidCloseTest.php | 13 +- tests/Server/TextDocument/FormattingTest.php | 22 +- tests/Server/TextDocument/ParseErrorsTest.php | 9 +- .../References/GlobalFallbackTest.php | 15 +- 32 files changed, 899 insertions(+), 715 deletions(-) create mode 100644 src/ComposerScripts.php create mode 100644 src/Index/AbstractAggregateIndex.php create mode 100644 src/Index/DependenciesIndex.php create mode 100644 src/Index/GlobalIndex.php rename src/{ => Index}/Index.php (76%) create mode 100644 src/Index/ProjectIndex.php create mode 100644 src/Index/ReadableIndex.php create mode 100644 src/Index/StubsIndex.php create mode 100644 src/Index/StubsIndexer.php create mode 100644 src/PhpDocumentLoader.php delete mode 100644 src/Project.php delete mode 100644 src/parse_stubs.php rename tests/{ProjectTest.php => PhpDocumentLoaderTest.php} (50%) diff --git a/.gitignore b/.gitignore index c018fa6..4791ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor/ .phpls/ composer.lock +stubs diff --git a/composer.json b/composer.json index 7d29d0a..0608882 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,11 @@ "refactor" ], "bin": ["bin/php-language-server.php"], + "scripts": { + "parse-stubs": "LanguageServer\\ComposerScripts::parseStubs", + "post-install-cmd": "@parse-stubs", + "post-root-package-install": "@parse-stubs" + }, "require": { "php": ">=7.0", "nikic/php-parser": "dev-master#e52ffc4447e034514339a03b450aab9cd625e37c", diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 5d65ea3..fb9ebb8 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace LanguageServer; use PhpParser\Node; +use LanguageServer\Index\ReadableIndex; use LanguageServer\Protocol\{ TextEdit, Range, @@ -97,13 +98,18 @@ class CompletionProvider private $project; /** - * @param DefinitionResolver $definitionResolver - * @param Project $project + * @var ReadableIndex */ - 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->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) { if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) { $list->items[] = CompletionItem::fromDefinition($def); @@ -185,7 +191,9 @@ class CompletionProvider // Get the definition for the used namespace, class-like, function or constant // And save it under the alias $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 { // Use statements are always the first statements in a namespace @@ -206,7 +214,7 @@ class CompletionProvider // Additionally, suggest global symbols that either // - 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 - foreach ($this->project->getDefinitions() as $fqn => $def) { + foreach ($this->index->getDefinitions() as $fqn => $def) { if ( $def->isGlobal // exclude methods, properties etc. && ( @@ -326,7 +334,7 @@ class CompletionProvider } if ($level instanceof Node\Expr\Closure) { 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; } } diff --git a/src/ComposerScripts.php b/src/ComposerScripts.php new file mode 100644 index 0000000..12572fe --- /dev/null +++ b/src/ComposerScripts.php @@ -0,0 +1,47 @@ +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(); + } +} diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 7de3f6f..6682d81 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -7,15 +7,16 @@ use PhpParser\Node; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver}; use LanguageServer\Protocol\SymbolInformation; +use LanguageServer\Index\ReadableIndex; use Sabre\Event\Promise; use function Sabre\Event\coroutine; class DefinitionResolver { /** - * @var \LanguageServer\Index[] + * @var \LanguageServer\Index */ - private $indexes; + private $index; /** * @var \phpDocumentor\Reflection\TypeResolver @@ -28,24 +29,15 @@ class DefinitionResolver 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->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 * @@ -160,7 +152,7 @@ class DefinitionResolver $parent = $node->getAttribute('parentNode'); $globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall; // 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; } $fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name); - $def = $this->getDefinition($fqn, true); + $def = $this->index->getDefinition($fqn, true); if ($def !== null) { return $def->type; } @@ -426,7 +418,7 @@ class DefinitionResolver } // Resolve constant $fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name); - $def = $this->getDefinition($fqn, true); + $def = $this->index->getDefinition($fqn, true); if ($def !== null) { return $def->type; } @@ -455,7 +447,7 @@ class DefinitionResolver if ($expr instanceof Node\Expr\MethodCall) { $fqn .= '()'; } - $def = $this->getDefinition($fqn); + $def = $this->index->getDefinition($fqn); if ($def !== null) { return $def->type; } @@ -478,7 +470,7 @@ class DefinitionResolver if ($expr instanceof Node\Expr\StaticCall) { $fqn .= '()'; } - $def = $this->getDefinition($fqn); + $def = $this->index->getDefinition($fqn); if ($def === null) { return new Types\Mixed; } diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php new file mode 100644 index 0000000..590bbd5 --- /dev/null +++ b/src/Index/AbstractAggregateIndex.php @@ -0,0 +1,64 @@ + 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; + } +} diff --git a/src/Index/DependenciesIndex.php b/src/Index/DependenciesIndex.php new file mode 100644 index 0000000..a355821 --- /dev/null +++ b/src/Index/DependenciesIndex.php @@ -0,0 +1,52 @@ +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]); + } +} diff --git a/src/Index/GlobalIndex.php b/src/Index/GlobalIndex.php new file mode 100644 index 0000000..e1e6d48 --- /dev/null +++ b/src/Index/GlobalIndex.php @@ -0,0 +1,38 @@ +stubsIndex = $stubsIndex; + $this->projectIndex = $projectIndex; + } + + /** + * @return ReadableIndex[] + */ + protected function getIndexes(): array + { + return [$this->stubsIndex, $this->projectIndex]; + } +} diff --git a/src/Index.php b/src/Index/Index.php similarity index 76% rename from src/Index.php rename to src/Index/Index.php index 1bd4515..2a4ea4d 100644 --- a/src/Index.php +++ b/src/Index/Index.php @@ -1,9 +1,10 @@ Definition] that maps fully qualified symbol names * to Definitions * - * @return Definitions[] + * @return Definition[] */ - public function getDefinitions() + public function getDefinitions(): array { return $this->definitions; } @@ -46,7 +47,7 @@ class Index * @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) + public function getDefinition(string $fqn, bool $globalFallback = false) { if (isset($this->definitions[$fqn])) { return $this->definitions[$fqn]; @@ -69,17 +70,6 @@ class Index $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 @@ -93,6 +83,17 @@ class Index 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 * @@ -128,26 +129,4 @@ class Index } 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; - } } diff --git a/src/Index/ProjectIndex.php b/src/Index/ProjectIndex.php new file mode 100644 index 0000000..51fed85 --- /dev/null +++ b/src/Index/ProjectIndex.php @@ -0,0 +1,57 @@ +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; + } +} diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php new file mode 100644 index 0000000..2a22ee3 --- /dev/null +++ b/src/Index/ReadableIndex.php @@ -0,0 +1,35 @@ + 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; +} diff --git a/src/Index/StubsIndex.php b/src/Index/StubsIndex.php new file mode 100644 index 0000000..3828f31 --- /dev/null +++ b/src/Index/StubsIndex.php @@ -0,0 +1,27 @@ + + */ + 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(); + } +} diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 660ec14..a37003c 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -16,6 +16,7 @@ use LanguageServer\Protocol\{ }; use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder}; use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever}; +use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex}; use AdvancedJsonRpc; use Sabre\Event\{Loop, Promise}; use function Sabre\Event\coroutine; @@ -51,12 +52,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher private $client; /** - * The root project path that was passed to initialize() - * - * @var string + * @var AggregateIndex */ - private $rootPath; - private $project; + private $index; /** * @var FilesFinder @@ -127,8 +125,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher { return coroutine(function () use ($capabilities, $rootPath, $processId) { - $this->rootPath = $rootPath; - if ($capabilities->xfilesProvider) { $this->filesFinder = new ClientFilesFinder($this->client); } else { @@ -141,28 +137,33 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $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) { - $pattern = Path::makeAbsolute('**/{*.php,composer.lock}', $this->rootPath); - $composerLockPattern = Path::makeAbsolute('**/composer.lock}', $this->rootPath); + $pattern = Path::makeAbsolute('**/*.php', $rootPath); $uris = yield $this->filesFinder->find($pattern); - - // 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->index($uris)->otherwise('\\LanguageServer\\crash'); } - $this->project = new Project($this->client, $capabilities, $rootPath); - $this->textDocument = new Server\TextDocument($this->project, $this->client); - $this->workspace = new Server\Workspace($this->project, $this->client); + $this->textDocument = new Server\TextDocument( + $this->documentLoader, + $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(); // 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 { - return coroutine(function () { + return coroutine(function () use ($phpFiles) { $count = count($phpFiles); @@ -226,6 +227,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher // Parse PHP files foreach ($phpFiles as $i => $uri) { + if ($this->documentLoader->isOpen($uri)) { + continue; + } + // Give LS to the chance to handle requests while indexing yield timeout(); $path = Uri\parse($uri); @@ -234,7 +239,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher "Parsing file $i/$count: {$uri}" ); try { - yield $this->project->loadDocument($uri); + yield $this->documentLoader->load($uri); } catch (ContentTooLargeException $e) { $this->client->window->logMessage( MessageType::INFO, diff --git a/src/PhpDocument.php b/src/PhpDocument.php index f5901d0..8b3f4fa 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -13,12 +13,13 @@ use LanguageServer\NodeVisitor\{ ReferencesCollector, VariableReferencesCollector }; +use LanguageServer\Index\Index; use PhpParser\{Error, ErrorHandler, Node, NodeTraverser}; use PhpParser\NodeVisitor\NameResolver; use phpDocumentor\Reflection\DocBlockFactory; use Sabre\Event\Promise; -use function Sabre\Event\coroutine; use Sabre\Uri; +use function Sabre\Event\coroutine; class PhpDocument { @@ -43,6 +44,11 @@ class PhpDocument */ private $definitionResolver; + /** + * @var Index + */ + private $index; + /** * The URI of the document * @@ -95,7 +101,7 @@ class PhpDocument /** * @param string $uri The URI 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 DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks */ @@ -108,6 +114,7 @@ class PhpDocument DefinitionResolver $definitionResolver ) { $this->uri = $uri; + $this->index = $index; $this->parser = $parser; $this->docBlockFactory = $docBlockFactory; $this->definitionResolver = $definitionResolver; @@ -250,7 +257,7 @@ class PhpDocument * * @return Diagnostic[] */ - public function getContent() + public function getDiagnostics() { return $this->diagnostics; } diff --git a/src/PhpDocumentLoader.php b/src/PhpDocumentLoader.php new file mode 100644 index 0000000..b6c04cb --- /dev/null +++ b/src/PhpDocumentLoader.php @@ -0,0 +1,178 @@ + 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 + */ + 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 + */ + 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]); + } +} diff --git a/src/Project.php b/src/Project.php deleted file mode 100644 index 54bf47f..0000000 --- a/src/Project.php +++ /dev/null @@ -1,419 +0,0 @@ - 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 - */ - 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 - */ - 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 - */ - 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 - */ - 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; - }); - } -} diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 6d424f8..32f4449 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -3,9 +3,10 @@ declare(strict_types = 1); namespace LanguageServer\Server; -use LanguageServer\{LanguageClient, Project, PhpDocument, DefinitionResolver, CompletionProvider}; 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\{ TextDocumentItem, TextDocumentIdentifier, @@ -23,8 +24,10 @@ use LanguageServer\Protocol\{ CompletionItem, CompletionItemKind }; +use LanguageServer\Index\ReadableIndex; use Sabre\Event\Promise; use function Sabre\Event\coroutine; +use function LanguageServer\getReferenceNodesByNode; /** * Provides method handlers for all textDocument/* methods @@ -59,14 +62,20 @@ class TextDocument private $completionProvider; private $openDocuments = []; + private $index; - public function __construct(Project $project, LanguageClient $client) - { - $this->project = $project; + public function __construct( + PhpDocumentLoader $documentLoader, + DefinitionResolver $definitionResolver, + LanguageClient $client, + ReadableIndex $index + ) { + $this->documentLoader = $documentLoader; $this->client = $client; $this->prettyPrinter = new PrettyPrinter(); - $this->definitionResolver = new DefinitionResolver(); - $this->completionProvider = new CompletionProvider($this->definitionResolver, $project); + $this->definitionResolver = $definitionResolver; + $this->completionProvider = new CompletionProvider($this->definitionResolver, $index); + $this->index = $index; } /** @@ -78,7 +87,7 @@ class TextDocument */ 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 = []; foreach ($document->getDefinitions() as $fqn => $definition) { $symbols[] = $definition->symbolInformation; @@ -97,9 +106,9 @@ class 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()) { - $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) { - $document = $this->project->getDocument($textDocument->uri); + $document = $this->documentLoader->get($textDocument->uri); $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) { - $this->project->closeDocument($textDocument->uri); + $this->documentLoader->close($textDocument->uri); } /** @@ -139,7 +148,7 @@ class TextDocument */ 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(); }); } @@ -157,15 +166,55 @@ class TextDocument Position $position ): Promise { return coroutine(function () use ($textDocument, $position) { - $document = yield $this->project->getOrLoadDocument($textDocument->uri); + $document = yield $this->documentLoader->getOrLoad($textDocument->uri); $node = $document->getNodeAtPosition($position); if ($node === null) { return []; } - $refs = yield $document->getReferenceNodesByNode($node); $locations = []; - foreach ($refs as $ref) { - $locations[] = Location::fromNode($ref); + // 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()); + 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; }); @@ -182,7 +231,7 @@ class TextDocument public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise { return coroutine(function () use ($textDocument, $position) { - $document = yield $this->project->getOrLoadDocument($textDocument->uri); + $document = yield $this->documentLoader->getOrLoad($textDocument->uri); $node = $document->getNodeAtPosition($position); if ($node === null) { return []; @@ -205,7 +254,7 @@ class TextDocument public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise { 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 $node = $document->getNodeAtPosition($position); if ($node === null) { @@ -244,7 +293,7 @@ class TextDocument public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise { 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); }); } diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index 26feb72..66f8606 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace LanguageServer\Server; use LanguageServer\{LanguageClient, Project}; +use LanguageServer\Index\ProjectIndex; use LanguageServer\Protocol\SymbolInformation; /** @@ -19,15 +20,18 @@ class Workspace 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; } @@ -40,7 +44,7 @@ class Workspace public function symbol(string $query): array { $symbols = []; - foreach ($this->project->getDefinitions() as $fqn => $definition) { + foreach ($this->index->getDefinitions() as $fqn => $definition) { if ($query === '' || stripos($fqn, $query) !== false) { $symbols[] = $definition->symbolInformation; } diff --git a/src/parse_stubs.php b/src/parse_stubs.php deleted file mode 100644 index 2e8955a..0000000 --- a/src/parse_stubs.php +++ /dev/null @@ -1,26 +0,0 @@ -find(__DIR__ . '/../vendor/JetBrains/phpstorm-stubs'); - - foreach ($uris as $uri) { - $content = $contentRetriever->retrieve($uri); - $document = new PhpDocument($uri, $content, $index, $docBlockFactory, $definitionResolver); - } - -})->wait(); diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 8bd7b4f..d9f0d81 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -6,7 +6,16 @@ namespace LanguageServer\Tests; use PHPUnit\Framework\TestCase; use LanguageServer\LanguageServer; use LanguageServer\Protocol\{ - Message, ClientCapabilities, TextDocumentSyncKind, MessageType, TextDocumentItem, TextDocumentIdentifier}; + Message, + ClientCapabilities, + TextDocumentSyncKind, + MessageType, + TextDocumentItem, + TextDocumentIdentifier, + InitializeResult, + ServerCapabilities, + CompletionOptions +}; use AdvancedJsonRpc; use Webmozart\Glob\Glob; use Webmozart\PathUtil\Path; @@ -18,41 +27,22 @@ class LanguageServerTest extends TestCase { public function testInitialize() { - $reader = new MockProtocolStream(); - $writer = new MockProtocolStream(); - $server = new LanguageServer($reader, $writer); - $promise = new Promise; - $writer->once('message', [$promise, 'fulfill']); - $reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [ - 'rootPath' => __DIR__, - 'processId' => getmypid(), - 'capabilities' => new ClientCapabilities() - ]))); - $msg = $promise->wait(); - $this->assertNotNull($msg, 'message event should be emitted'); - $this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body); - $this->assertEquals((object)[ - 'capabilities' => (object)[ - 'textDocumentSync' => TextDocumentSyncKind::FULL, - '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); + $server = new LanguageServer(new MockProtocolStream, new MockProtocolStream); + $result = $server->initialize(new ClientCapabilities, __DIR__, getmypid())->wait(); + + $serverCapabilities = new ServerCapabilities(); + $serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL; + $serverCapabilities->documentSymbolProvider = true; + $serverCapabilities->workspaceSymbolProvider = true; + $serverCapabilities->documentFormattingProvider = true; + $serverCapabilities->definitionProvider = true; + $serverCapabilities->referencesProvider = true; + $serverCapabilities->hoverProvider = true; + $serverCapabilities->completionProvider = new CompletionOptions; + $serverCapabilities->completionProvider->resolveProvider = false; + $serverCapabilities->completionProvider->triggerCharacters = ['$', '>']; + + $this->assertEquals(new InitializeResult($serverCapabilities), $result); } public function testIndexingWithDirectFileAccess() diff --git a/tests/NodeVisitor/DefinitionCollectorTest.php b/tests/NodeVisitor/DefinitionCollectorTest.php index 800373d..9b60814 100644 --- a/tests/NodeVisitor/DefinitionCollectorTest.php +++ b/tests/NodeVisitor/DefinitionCollectorTest.php @@ -6,9 +6,11 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; use PhpParser\{NodeTraverser, Node}; 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\Protocol\ClientCapabilities; +use LanguageServer\Index\{ProjectIndex, Index, DependenciesIndex}; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector}; use function LanguageServer\pathToUri; @@ -17,19 +19,25 @@ class DefinitionCollectorTest extends TestCase { public function testCollectsSymbols() { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); + $path = realpath(__DIR__ . '/../../fixtures/symbols.php'); + $uri = pathToUri($path); $parser = new Parser; - $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php')); - $document = $project->loadDocument($uri)->wait(); + $docBlockFactory = DocBlockFactory::createInstance(); + $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->addVisitor(new NameResolver); $traverser->addVisitor(new ReferencesAdder($document)); - $definitionCollector = new DefinitionCollector(new DefinitionResolver($project)); + $definitionCollector = new DefinitionCollector($definitionResolver); $traverser->addVisitor($definitionCollector); - $stmts = $parser->parse(file_get_contents($uri)); $traverser->traverse($stmts); + $defNodes = $definitionCollector->nodes; + $this->assertEquals([ 'TestNamespace', 'TestNamespace\\TEST_CONST', @@ -57,19 +65,25 @@ class DefinitionCollectorTest extends TestCase public function testDoesNotCollectReferences() { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); + $path = realpath(__DIR__ . '/../../fixtures/references.php'); + $uri = pathToUri($path); $parser = new Parser; - $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); - $document = $project->loadDocument($uri)->wait(); + $docBlockFactory = DocBlockFactory::createInstance(); + $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->addVisitor(new NameResolver); $traverser->addVisitor(new ReferencesAdder($document)); - $definitionCollector = new DefinitionCollector(new DefinitionResolver($project)); + $definitionCollector = new DefinitionCollector($definitionResolver); $traverser->addVisitor($definitionCollector); - $stmts = $parser->parse(file_get_contents($uri)); $traverser->traverse($stmts); + $defNodes = $definitionCollector->nodes; + $this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes)); $this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']); $this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']); diff --git a/tests/ProjectTest.php b/tests/PhpDocumentLoaderTest.php similarity index 50% rename from tests/ProjectTest.php rename to tests/PhpDocumentLoaderTest.php index 6fef176..7be062d 100644 --- a/tests/ProjectTest.php +++ b/tests/PhpDocumentLoaderTest.php @@ -5,8 +5,9 @@ namespace LanguageServer\Tests\Server; use PHPUnit\Framework\TestCase; 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\Index\{Index, ProjectIndex, DependenciesIndex}; use LanguageServer\Protocol\{ TextDocumentItem, TextDocumentIdentifier, @@ -18,31 +19,35 @@ use LanguageServer\Protocol\{ use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody}; use function LanguageServer\pathToUri; -class ProjectTest extends TestCase +class PhpDocumentLoaderTest extends TestCase { /** - * @var Project $project + * @var PhpDocumentLoader */ - private $project; + private $loader; public function setUp() { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $this->project = new Project($client, new FileSystemContentRetriever); + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); + $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->assertInstanceOf(PhpDocument::class, $document); } - public function testGetDocumentReturnsOpenedInstance() + public function testGetReturnsOpenedInstance() { - $document1 = $this->project->openDocument(pathToUri(__FILE__), file_get_contents(__FILE__)); - $document2 = $this->project->getDocument(pathToUri(__FILE__)); + $document1 = $this->loader->open(pathToUri(__FILE__), file_get_contents(__FILE__)); + $document2 = $this->loader->get(pathToUri(__FILE__)); $this->assertSame($document1, $document2); } diff --git a/tests/PhpDocumentTest.php b/tests/PhpDocumentTest.php index a4e8ffa..b9b3704 100644 --- a/tests/PhpDocumentTest.php +++ b/tests/PhpDocumentTest.php @@ -4,36 +4,36 @@ declare(strict_types = 1); namespace LanguageServer\Tests\Server; use PHPUnit\Framework\TestCase; +use phpDocumentor\Reflection\DocBlockFactory; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{LanguageClient, Project}; +use LanguageServer\{LanguageClient, PhpDocument, DefinitionResolver, Parser}; use LanguageServer\NodeVisitor\NodeAtPositionFinder; use LanguageServer\ContentRetriever\FileSystemContentRetriever; use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities}; +use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; use PhpParser\Node; class PhpDocumentTest extends TestCase { - /** - * @var Project $project - */ - private $project; - - public function setUp() + public function createDocument(string $uri, string $content) { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $this->project = new Project($client, new FileSystemContentRetriever); + $parser = new Parser; + $docBlockFactory = DocBlockFactory::createInstance(); + $index = new Index; + $definitionResolver = new DefinitionResolver($index); + return new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver); } public function testParsesVariableVariables() { - $document = $this->project->openDocument('whatever', "createDocument('whatever', "assertEquals([], $document->getDefinitions()); } public function testGetNodeAtPosition() { - $document = $this->project->openDocument('whatever', "createDocument('whatever', "getNodeAtPosition(new Position(1, 13)); $this->assertInstanceOf(Node\Name\FullyQualified::class, $node); $this->assertEquals('SomeClass', (string)$node); @@ -41,19 +41,19 @@ class PhpDocumentTest extends TestCase public function testIsVendored() { - $document = $this->project->openDocument('file:///dir/vendor/x.php', "createDocument('file:///dir/vendor/x.php', "assertEquals(true, $document->isVendored()); - $document = $this->project->openDocument('file:///c:/dir/vendor/x.php', "createDocument('file:///c:/dir/vendor/x.php', "assertEquals(true, $document->isVendored()); - $document = $this->project->openDocument('file:///vendor/x.php', "createDocument('file:///vendor/x.php', "assertEquals(true, $document->isVendored()); - $document = $this->project->openDocument('file:///dir/vendor.php', "createDocument('file:///dir/vendor.php', "assertEquals(false, $document->isVendored()); - $document = $this->project->openDocument('file:///dir/x.php', "createDocument('file:///dir/x.php', "assertEquals(false, $document->isVendored()); } } diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 38e2c8a..602bda0 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server; use PHPUnit\Framework\TestCase; 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\Protocol\{Position, Location, Range, ClientCapabilities}; use function LanguageServer\pathToUri; @@ -24,9 +25,9 @@ abstract class ServerTestCase extends TestCase protected $workspace; /** - * @var Project + * @var PhpDocumentLoader */ - protected $project; + protected $documentLoader; /** * Map from FQN to Location of definition @@ -44,10 +45,13 @@ abstract class ServerTestCase extends TestCase public function setUp() { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $this->project = new Project($client, new FileSystemContentRetriever); - $this->textDocument = new Server\TextDocument($this->project, $client); - $this->workspace = new Server\Workspace($this->project, $client); + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); + + $definitionResolver = new DefinitionResolver($projectIndex); + $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')); $globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php')); @@ -55,11 +59,11 @@ abstract class ServerTestCase extends TestCase $referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); $useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php')); - $this->project->loadDocument($symbolsUri)->wait(); - $this->project->loadDocument($referencesUri)->wait(); - $this->project->loadDocument($globalSymbolsUri)->wait(); - $this->project->loadDocument($globalReferencesUri)->wait(); - $this->project->loadDocument($useUri)->wait(); + $this->documentLoader->load($symbolsUri)->wait(); + $this->documentLoader->load($referencesUri)->wait(); + $this->documentLoader->load($globalSymbolsUri)->wait(); + $this->documentLoader->load($globalReferencesUri)->wait(); + $this->documentLoader->load($useUri)->wait(); // @codingStandardsIgnoreStart $this->definitionLocations = [ diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index 587f171..1e48d7b 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; 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\Protocol\{ TextDocumentIdentifier, @@ -27,23 +28,26 @@ class CompletionTest extends TestCase private $textDocument; /** - * @var Project + * @var PhpDocumentLoader */ - private $project; + private $loader; public function setUp() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $this->project = new Project($client, new FileSystemContentRetriever); - $this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait(); - $this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/symbols.php'))->wait(); - $this->textDocument = new Server\TextDocument($this->project, $client); + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); + $definitionResolver = new DefinitionResolver($projectIndex); + $contentRetriever = new FileSystemContentRetriever; + $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() { $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( new TextDocumentIdentifier($completionUri), new Position(3, 7) @@ -67,7 +71,7 @@ class CompletionTest extends TestCase public function testPropertyAndMethodWithoutPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(3, 6) @@ -91,7 +95,7 @@ class CompletionTest extends TestCase public function testVariable() { $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( new TextDocumentIdentifier($completionUri), new Position(8, 5) @@ -123,7 +127,7 @@ class CompletionTest extends TestCase public function testVariableWithPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(8, 6) @@ -145,7 +149,7 @@ class CompletionTest extends TestCase public function testNewInNamespace() { $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( new TextDocumentIdentifier($completionUri), new Position(6, 10) @@ -177,7 +181,7 @@ class CompletionTest extends TestCase public function testUsedClass() { $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( new TextDocumentIdentifier($completionUri), new Position(6, 5) @@ -195,7 +199,7 @@ class CompletionTest extends TestCase public function testStaticPropertyWithPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(2, 14) @@ -216,7 +220,7 @@ class CompletionTest extends TestCase public function testStaticWithoutPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(2, 11) @@ -249,7 +253,7 @@ class CompletionTest extends TestCase public function testStaticMethodWithPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(2, 13) @@ -282,7 +286,7 @@ class CompletionTest extends TestCase public function testClassConstWithPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(2, 13) @@ -315,7 +319,7 @@ class CompletionTest extends TestCase public function testFullyQualifiedClass() { $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( new TextDocumentIdentifier($completionUri), new Position(6, 6) @@ -336,7 +340,7 @@ class CompletionTest extends TestCase public function testKeywords() { $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( new TextDocumentIdentifier($completionUri), new Position(2, 1) @@ -350,7 +354,7 @@ class CompletionTest extends TestCase public function testHtmlWithoutPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(0, 0) @@ -372,7 +376,7 @@ class CompletionTest extends TestCase public function testHtmlWithPrefix() { $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( new TextDocumentIdentifier($completionUri), new Position(0, 1) @@ -394,7 +398,7 @@ class CompletionTest extends TestCase public function testNamespace() { $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( new TextDocumentIdentifier($completionUri), new Position(4, 6) diff --git a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php index 20ea70b..4c09e18 100644 --- a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument\Definition; use LanguageServer\Tests\MockProtocolStream; 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\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities}; use Sabre\Event\Promise; @@ -14,11 +15,14 @@ class GlobalFallbackTest extends ServerTestCase { public function setUp() { + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); - $this->textDocument = new Server\TextDocument($project, $client); - $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); - $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); + $definitionResolver = new DefinitionResolver($projectIndex); + $contentRetriever = new FileSystemContentRetriever; + $loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver); + $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() diff --git a/tests/Server/TextDocument/DidChangeTest.php b/tests/Server/TextDocument/DidChangeTest.php index 9df301a..bdd3b22 100644 --- a/tests/Server/TextDocument/DidChangeTest.php +++ b/tests/Server/TextDocument/DidChangeTest.php @@ -5,8 +5,9 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, Client, LanguageClient, Project}; +use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; +use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; use LanguageServer\Protocol\{ TextDocumentIdentifier, TextDocumentItem, @@ -21,10 +22,12 @@ class DidChangeTest extends TestCase { public function test() { + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); - $textDocument = new Server\TextDocument($project, $client); - $phpDocument = $project->openDocument('whatever', "open('whatever', "openDocument('whatever', 'hello world'); + $definitionResolver = new DefinitionResolver($projectIndex); + $loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); + $textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex); + $phpDocument = $loader->open('whatever', "uri = 'whatever'; @@ -28,6 +31,6 @@ class DidCloseTest extends TestCase $textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri)); - $this->assertFalse($project->isDocumentOpen($textDocumentItem->uri)); + $this->assertFalse($loader->isOpen($textDocumentItem->uri)); } } diff --git a/tests/Server/TextDocument/FormattingTest.php b/tests/Server/TextDocument/FormattingTest.php index 32aee22..abb3d6d 100644 --- a/tests/Server/TextDocument/FormattingTest.php +++ b/tests/Server/TextDocument/FormattingTest.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; 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\Protocol\{ TextDocumentIdentifier, @@ -20,23 +21,14 @@ use function LanguageServer\{pathToUri, uriToPath}; 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() { + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); - $textDocument = new Server\TextDocument($project, $client); + $definitionResolver = new DefinitionResolver($projectIndex); + $loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); + $textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex); + $path = realpath(__DIR__ . '/../../../fixtures/format.php'); $uri = pathToUri($path); diff --git a/tests/Server/TextDocument/ParseErrorsTest.php b/tests/Server/TextDocument/ParseErrorsTest.php index af2bdd8..6c927f8 100644 --- a/tests/Server/TextDocument/ParseErrorsTest.php +++ b/tests/Server/TextDocument/ParseErrorsTest.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; 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\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities}; use Sabre\Event\Promise; @@ -36,8 +37,10 @@ class ParseErrorsTest extends TestCase return Promise\resolve(null); } }; - $project = new Project($client, new FileSystemContentRetriever); - $this->textDocument = new Server\TextDocument($project, $client); + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); + $definitionResolver = new DefinitionResolver($projectIndex); + $loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); + $this->textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex); } private function openFile($file) diff --git a/tests/Server/TextDocument/References/GlobalFallbackTest.php b/tests/Server/TextDocument/References/GlobalFallbackTest.php index 4e6d07a..2679d15 100644 --- a/tests/Server/TextDocument/References/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/References/GlobalFallbackTest.php @@ -5,7 +5,8 @@ namespace LanguageServer\Tests\Server\TextDocument\References; use PHPUnit\Framework\TestCase; 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\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities}; use LanguageServer\Tests\Server\ServerTestCase; @@ -14,11 +15,13 @@ class GlobalFallbackTest extends ServerTestCase { public function setUp() { - $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client, new FileSystemContentRetriever); - $this->textDocument = new Server\TextDocument($project, $client); - $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); - $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); + $projectIndex = new ProjectIndex(new Index, new DependenciesIndex); + $definitionResolver = new DefinitionResolver($projectIndex); + $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->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()