From 2d91ee8eff807bd4c48168cc0c6eb9f1bda13415 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 6 Aug 2017 16:48:41 +0200 Subject: [PATCH 01/25] speedup autocomplete with ReadableIndex::getDefinitionsForNamespace --- src/CompletionProvider.php | 35 +++++++++--- src/Index/AbstractAggregateIndex.php | 17 ++++++ src/Index/Index.php | 79 ++++++++++++++++++++++++++++ src/Index/ReadableIndex.php | 8 +++ 4 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 68c5a0cc..ae4f958b 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -201,21 +201,44 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi $this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression) ); + $checksCount = 0; + $start = microtime(true); // Add the object access operator to only get members of all parents - $prefixes = []; - foreach ($this->expandParentFqns($fqns) as $prefix) { - $prefixes[] = $prefix . '->'; + $namespaces = []; + foreach ($this->expandParentFqns($fqns) as $namespace) { + $namespaces[] = $namespace; } // Collect all definitions that match any of the prefixes - foreach ($this->index->getDefinitions() as $fqn => $def) { - foreach ($prefixes as $prefix) { - if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) { + foreach ($namespaces as $namespace) { + foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { + ++$checksCount; + $prefix = $namespace . '->'; + if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } } } + // $prefixes = []; + // foreach ($this->expandParentFqns($fqns) as $prefix) { + // $prefixes[] = $prefix . '->'; + // } + // foreach ($this->index->getDefinitions() as $fqn => $def) { + // foreach ($prefixes as $prefix) { + // ++$checksCount; + // if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) { + // $list->items[] = CompletionItem::fromDefinition($def); + // } + // } + // } + $duration = microtime(true) - $start; + file_put_contents( + '/home/nicolas/tmp/php_language-server.log', + sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), + FILE_APPEND + ); + } elseif ( ($scoped = $node->parent) instanceof Node\Expression\ScopedPropertyAccessExpression || ($scoped = $node) instanceof Node\Expression\ScopedPropertyAccessExpression diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 5377c3a4..fa13d148 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -115,6 +115,23 @@ public function getDefinitions(): array return $defs; } + /** + * Returns the Definitions that are in the given namespace + * + * @param string $namespace + * @return Definitions[] + */ + public function getDefinitionsForNamespace(string $namespace): array + { + $defs = []; + foreach ($this->getIndexes() as $index) { + foreach ($index->getDefinitionsForNamespace($namespace) as $fqn => $def) { + $defs[$fqn] = $def; + } + } + return $defs; + } + /** * Returns the Definition object by a specific FQN * diff --git a/src/Index/Index.php b/src/Index/Index.php index 9cb975e5..8af0688a 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -21,6 +21,13 @@ class Index implements ReadableIndex, \Serializable */ private $definitions = []; + /** + * An associative array that maps namespaces to an associative array of FQN to Definitions + * + * @var Definition[] + */ + private $namespaceDefinitions = []; + /** * An associative array that maps fully qualified symbol names to arrays of document URIs that reference the symbol * @@ -94,6 +101,20 @@ public function getDefinitions(): array return $this->definitions; } + /** + * Returns the Definitions that are in the given namespace + * + * @param string $namespace + * @return Definitions[] + */ + public function getDefinitionsForNamespace(string $namespace): array + { + return isset($this->namespaceDefinitions[$namespace]) + ? $this->namespaceDefinitions[$namespace] + : [] + ; + } + /** * Returns the Definition object by a specific FQN * @@ -123,6 +144,7 @@ public function getDefinition(string $fqn, bool $globalFallback = false) public function setDefinition(string $fqn, Definition $definition) { $this->definitions[$fqn] = $definition; + $this->setNamespaceDefinition($fqn, $definition); $this->emit('definition-added'); } @@ -137,6 +159,7 @@ public function removeDefinition(string $fqn) { unset($this->definitions[$fqn]); unset($this->references[$fqn]); + $this->removeNamespaceDefinition($fqn); } /** @@ -207,6 +230,7 @@ public function unserialize($serialized) foreach ($data as $prop => $val) { $this->$prop = $val; } + $this->buildNamespaceDefinitionsIndex(); } /** @@ -222,4 +246,59 @@ public function serialize() 'staticComplete' => $this->staticComplete ]); } + + /** + * @return void + */ + private function buildNamespaceDefinitionsIndex() + { + foreach ($this->definitions as $fqn => $definition) { + $this->setNamespaceDefinition($fqn, $definition); + } + } + + /** + * Registers a definition to a namespace + * + * @param string $fqn The fully qualified name of the symbol + * @param Definition $definition The Definition object + * @return void + */ + private function setNamespaceDefinition(string $fqn, Definition $definition) + { + $namespace = $this->extractNamespace($fqn); + if (!isset($this->namespaceDefinitions[$namespace])) { + $this->namespaceDefinitions[$namespace] = []; + } + $this->namespaceDefinitions[$namespace][$fqn] = $definition; + } + + /** + * Removes a definition from a namespace + * + * @param string $fqn The fully qualified name of the symbol + * @return void + */ + private function removeNamespaceDefinition(string $fqn) + { + $namespace = $this->extractNamespace($fqn); + if (isset($this->namespaceDefinitions[$namespace])) { + unset($this->namespaceDefinitions[$namespace][$fqn]); + } + } + + /** + * @param string $fqn + * @return string The namespace extracted from the given FQN + */ + private function extractNamespace(string $fqn): string + { + foreach (['::', '->'] as $operator) { + if (false !== ($pos = strpos($fqn, $operator))) { + return substr($fqn, 0, $pos); + } + } + + return $fqn; + } } diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 67b20b63..c7d57321 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -37,6 +37,14 @@ public function isStaticComplete(): bool; */ public function getDefinitions(): array; + /** + * Returns the Definitions that are in the given namespace + * + * @param string $namespace + * @return Definitions[] + */ + public function getDefinitionsForNamespace(string $namespace): array; + /** * Returns the Definition object by a specific FQN * From b7c712842f514213137af25c8f8db6307c9e2373 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 6 Aug 2017 17:25:26 +0200 Subject: [PATCH 02/25] update tests --- tests/Server/TextDocument/CompletionTest.php | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index 0d68ec3e..08b52a55 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -605,7 +605,7 @@ public function testThisWithoutPrefix() ) ], true), $items); } - + public function testThisWithPrefix() { $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_with_prefix.php'); @@ -615,18 +615,6 @@ public function testThisWithPrefix() new Position(12, 16) )->wait(); $this->assertEquals(new CompletionList([ - new CompletionItem( - 'testProperty', - CompletionItemKind::PROPERTY, - '\TestClass', // Type of the property - 'Reprehenderit magna velit mollit ipsum do.' - ), - new CompletionItem( - 'testMethod', - CompletionItemKind::METHOD, - '\TestClass', // Return type of the method - 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' - ), new CompletionItem( 'foo', CompletionItemKind::PROPERTY, @@ -650,7 +638,19 @@ public function testThisWithPrefix() CompletionItemKind::METHOD, 'mixed', // Return type of the method null - ) + ), + new CompletionItem( + 'testProperty', + CompletionItemKind::PROPERTY, + '\TestClass', // Type of the property + 'Reprehenderit magna velit mollit ipsum do.' + ), + new CompletionItem( + 'testMethod', + CompletionItemKind::METHOD, + '\TestClass', // Return type of the method + 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' + ), ], true), $items); } @@ -663,11 +663,6 @@ public function testThisReturnValue() new Position(17, 23) )->wait(); $this->assertEquals(new CompletionList([ - new CompletionItem( - 'foo', - CompletionItemKind::METHOD, - '$this' // Return type of the method - ), new CompletionItem( 'bar', CompletionItemKind::METHOD, @@ -677,7 +672,12 @@ public function testThisReturnValue() 'qux', CompletionItemKind::METHOD, 'mixed' // Return type of the method - ) + ), + new CompletionItem( + 'foo', + CompletionItemKind::METHOD, + '$this' // Return type of the method + ), ], true), $items); } } From 6d725a234c9f1c07edb862ef601335a32988b862 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 6 Aug 2017 17:48:06 +0200 Subject: [PATCH 03/25] speedup static access autocomplete --- src/CompletionProvider.php | 46 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index ae4f958b..07602e40 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -233,11 +233,11 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi // } // } $duration = microtime(true) - $start; - file_put_contents( - '/home/nicolas/tmp/php_language-server.log', - sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), - FILE_APPEND - ); + // file_put_contents( + // '/home/nicolas/tmp/php_language-server.log', + // sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), + // FILE_APPEND + // ); } elseif ( ($scoped = $node->parent) instanceof Node\Expression\ScopedPropertyAccessExpression || @@ -252,26 +252,50 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi // // TODO: $a::| + $checksCount = 0; + $start = microtime(true); // Resolve all possible types to FQNs $fqns = FqnUtilities\getFqnsFromType( $classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier) ); // Append :: operator to only get static members of all parents - $prefixes = []; - foreach ($this->expandParentFqns($fqns) as $prefix) { - $prefixes[] = $prefix . '::'; + $namespaces = []; + foreach ($this->expandParentFqns($fqns) as $namespace) { + $namespaces[] = $namespace; } // Collect all definitions that match any of the prefixes - foreach ($this->index->getDefinitions() as $fqn => $def) { - foreach ($prefixes as $prefix) { - if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && $def->isMember) { + foreach ($namespaces as $namespace) { + foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { + ++$checksCount; + $prefix = strtolower($namespace . '::'); + if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } } } + // $prefixes = []; + // foreach ($this->expandParentFqns($fqns) as $prefix) { + // $prefixes[] = $prefix . '::'; + // } + + // foreach ($this->index->getDefinitions() as $fqn => $def) { + // foreach ($prefixes as $prefix) { + // ++$checksCount; + // if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && !$def->isGlobal) { + // $list->items[] = CompletionItem::fromDefinition($def); + // } + // } + // } + $duration = microtime(true) - $start; + file_put_contents( + '/home/nicolas/tmp/php_language-server_static.log', + sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), + FILE_APPEND + ); + } elseif ( ParserHelpers\isConstantFetch($node) // Creation gets set in case of an instantiation (`new` expression) From 18f2f4c85c02edb9d2d5fd89642c53ef8e442959 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 6 Aug 2017 18:12:32 +0200 Subject: [PATCH 04/25] cleanup --- src/CompletionProvider.php | 55 +++++--------------------------------- 1 file changed, 6 insertions(+), 49 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 07602e40..4e313ad4 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -201,18 +201,16 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi $this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression) ); - $checksCount = 0; - $start = microtime(true); - // Add the object access operator to only get members of all parents + // The namespaces of the symbol and its parents (eg the implemented interfaces) $namespaces = []; foreach ($this->expandParentFqns($fqns) as $namespace) { $namespaces[] = $namespace; } - // Collect all definitions that match any of the prefixes + // Collect namespaces definitions foreach ($namespaces as $namespace) { foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { - ++$checksCount; + // Add the object access operator to only get members of all parents $prefix = $namespace . '->'; if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); @@ -220,25 +218,6 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi } } - // $prefixes = []; - // foreach ($this->expandParentFqns($fqns) as $prefix) { - // $prefixes[] = $prefix . '->'; - // } - // foreach ($this->index->getDefinitions() as $fqn => $def) { - // foreach ($prefixes as $prefix) { - // ++$checksCount; - // if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) { - // $list->items[] = CompletionItem::fromDefinition($def); - // } - // } - // } - $duration = microtime(true) - $start; - // file_put_contents( - // '/home/nicolas/tmp/php_language-server.log', - // sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), - // FILE_APPEND - // ); - } elseif ( ($scoped = $node->parent) instanceof Node\Expression\ScopedPropertyAccessExpression || ($scoped = $node) instanceof Node\Expression\ScopedPropertyAccessExpression @@ -252,23 +231,21 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi // // TODO: $a::| - $checksCount = 0; - $start = microtime(true); // Resolve all possible types to FQNs $fqns = FqnUtilities\getFqnsFromType( $classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier) ); - // Append :: operator to only get static members of all parents + // The namespaces of the symbol and its parents (eg the implemented interfaces) $namespaces = []; foreach ($this->expandParentFqns($fqns) as $namespace) { $namespaces[] = $namespace; } - // Collect all definitions that match any of the prefixes + // Collect namespaces definitions foreach ($namespaces as $namespace) { foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { - ++$checksCount; + // Append :: operator to only get static members of all parents $prefix = strtolower($namespace . '::'); if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); @@ -276,26 +253,6 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi } } - // $prefixes = []; - // foreach ($this->expandParentFqns($fqns) as $prefix) { - // $prefixes[] = $prefix . '::'; - // } - - // foreach ($this->index->getDefinitions() as $fqn => $def) { - // foreach ($prefixes as $prefix) { - // ++$checksCount; - // if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && !$def->isGlobal) { - // $list->items[] = CompletionItem::fromDefinition($def); - // } - // } - // } - $duration = microtime(true) - $start; - file_put_contents( - '/home/nicolas/tmp/php_language-server_static.log', - sprintf("%d items found, %d checks, memory : %d bytes, %ss\n", sizeof($list->items), $checksCount, memory_get_usage(true), $duration), - FILE_APPEND - ); - } elseif ( ParserHelpers\isConstantFetch($node) // Creation gets set in case of an instantiation (`new` expression) From 17602aa7686a893547aa39641135e935bf52b0bc Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 6 Aug 2017 23:05:32 +0200 Subject: [PATCH 05/25] globalDefinitions cache to speedup autocomplete --- src/CompletionProvider.php | 26 ++++++++---------- src/Index/AbstractAggregateIndex.php | 19 ++++++++++++- src/Index/Index.php | 41 ++++++++++++++++++++++++---- src/Index/ReadableIndex.php | 10 ++++++- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 4e313ad4..59348138 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -314,26 +314,22 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi // 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->index->getDefinitions() as $fqn => $def) { + foreach ($this->index->getGlobalDefinitions() as $fqn => $def) { $fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix; if ( - // Exclude methods, properties etc. - !$def->isMember - && ( - !$prefix + !$prefix + || ( + // Either not qualified, but a matching prefix with global fallback + ($def->roamed && !$isQualified && $fqnStartsWithPrefix) + // Or not in a namespace or a fully qualified name or AND matching the prefix + || ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix) + // Or in a namespace, not fully qualified and matching the prefix + current namespace || ( - // Either not qualified, but a matching prefix with global fallback - ($def->roamed && !$isQualified && $fqnStartsWithPrefix) - // Or not in a namespace or a fully qualified name or AND matching the prefix - || ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix) - // Or in a namespace, not fully qualified and matching the prefix + current namespace - || ( - $namespaceNode - && !$isFullyQualified - && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix - ) + $namespaceNode + && !$isFullyQualified + && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix ) ) // Only suggest classes for `new` diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index fa13d148..038117a9 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -100,7 +100,7 @@ public function isStaticComplete(): bool /** * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions + * to Definitions (global or not) * * @return Definition[] */ @@ -115,6 +115,23 @@ public function getDefinitions(): array return $defs; } + /** + * Returns an associative array [string => Definition] that maps fully qualified symbol names + * to global Definitions + * + * @return Definition[] + */ + public function getGlobalDefinitions(): array + { + $defs = []; + foreach ($this->getIndexes() as $index) { + foreach ($index->getGlobalDefinitions() as $fqn => $def) { + $defs[$fqn] = $def; + } + } + return $defs; + } + /** * Returns the Definitions that are in the given namespace * diff --git a/src/Index/Index.php b/src/Index/Index.php index 8af0688a..e33bc3c2 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -15,12 +15,19 @@ class Index implements ReadableIndex, \Serializable use EmitterTrait; /** - * An associative array that maps fully qualified symbol names to Definitions + * An associative array that maps fully qualified symbol names to Definitions (global or not) * * @var Definition[] */ private $definitions = []; + /** + * An associative array that maps fully qualified symbol names to global Definitions + * + * @var Definition[] + */ + private $globalDefinitions = []; + /** * An associative array that maps namespaces to an associative array of FQN to Definitions * @@ -92,7 +99,7 @@ public function isStaticComplete(): bool /** * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions + * to Definitions (global or not) * * @return Definition[] */ @@ -101,6 +108,17 @@ public function getDefinitions(): array return $this->definitions; } + /** + * Returns an associative array [string => Definition] that maps fully qualified symbol names + * to global Definitions + * + * @return Definition[] + */ + public function getGlobalDefinitions(): array + { + return $this->globalDefinitions; + } + /** * Returns the Definitions that are in the given namespace * @@ -144,6 +162,7 @@ public function getDefinition(string $fqn, bool $globalFallback = false) public function setDefinition(string $fqn, Definition $definition) { $this->definitions[$fqn] = $definition; + $this->setGlobalDefinition($fqn, $definition); $this->setNamespaceDefinition($fqn, $definition); $this->emit('definition-added'); } @@ -158,6 +177,7 @@ public function setDefinition(string $fqn, Definition $definition) public function removeDefinition(string $fqn) { unset($this->definitions[$fqn]); + unset($this->globalDefinitions[$fqn]); unset($this->references[$fqn]); $this->removeNamespaceDefinition($fqn); } @@ -227,10 +247,15 @@ public function removeReferenceUri(string $fqn, string $uri) public function unserialize($serialized) { $data = unserialize($serialized); + foreach ($data as $prop => $val) { $this->$prop = $val; } - $this->buildNamespaceDefinitionsIndex(); + + foreach ($this->definitions as $fqn => $definition) { + $this->setGlobalDefinition($fqn, $definition); + $this->setNamespaceDefinition($fqn, $definition); + } } /** @@ -248,12 +273,16 @@ public function serialize() } /** + * Registers a definition to the global definitions index if it is global + * + * @param string $fqn The fully qualified name of the symbol + * @param Definition $definition The Definition object * @return void */ - private function buildNamespaceDefinitionsIndex() + private function setGlobalDefinition(string $fqn, Definition $definition) { - foreach ($this->definitions as $fqn => $definition) { - $this->setNamespaceDefinition($fqn, $definition); + if ($definition->isGlobal) { + $this->globalDefinitions[$fqn] = $definition; } } diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index c7d57321..62b9c066 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -31,12 +31,20 @@ public function isStaticComplete(): bool; /** * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions + * to Definitions (global or not) * * @return Definitions[] */ public function getDefinitions(): array; + /** + * Returns an associative array [string => Definition] that maps fully qualified symbol names + * to global Definitions + * + * @return Definitions[] + */ + public function getGlobalDefinitions(): array; + /** * Returns the Definitions that are in the given namespace * From 6d30035c783c88b8e08306414fbcf78168fccc0d Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Tue, 8 Aug 2017 23:33:40 +0200 Subject: [PATCH 06/25] also remove empty namespace index --- src/Index/Index.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Index/Index.php b/src/Index/Index.php index e33bc3c2..bf036223 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -313,6 +313,10 @@ private function removeNamespaceDefinition(string $fqn) $namespace = $this->extractNamespace($fqn); if (isset($this->namespaceDefinitions[$namespace])) { unset($this->namespaceDefinitions[$namespace][$fqn]); + + if (0 === sizeof($this->namespaceDefinitions[$namespace])) { + unset($this->namespaceDefinitions[$namespace]); + } } } From ca0caf16785689c738fca8443f10f06223ccc4d1 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Wed, 9 Aug 2017 22:10:13 +0200 Subject: [PATCH 07/25] store definitions under the namespaceDefinitions cache key directly --- src/Index/AbstractAggregateIndex.php | 14 ++-- src/Index/Index.php | 98 +++++++++++----------------- src/Index/ReadableIndex.php | 8 +-- 3 files changed, 48 insertions(+), 72 deletions(-) diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 038117a9..ca4b7d49 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -99,20 +99,16 @@ public function isStaticComplete(): bool } /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions (global or not) + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to Definitions (global or not) * - * @return Definition[] + * @return \Generator providing Definition[] */ - public function getDefinitions(): array + public function getDefinitions(): \Generator { - $defs = []; foreach ($this->getIndexes() as $index) { - foreach ($index->getDefinitions() as $fqn => $def) { - $defs[$fqn] = $def; - } + yield $index->getDefinitions(); } - return $defs; } /** diff --git a/src/Index/Index.php b/src/Index/Index.php index bf036223..ffd59419 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -15,11 +15,12 @@ class Index implements ReadableIndex, \Serializable use EmitterTrait; /** - * An associative array that maps fully qualified symbol names to Definitions (global or not) + * An associative array that maps namespaces to + * an associative array that maps fully qualified symbol names to global Definitions * - * @var Definition[] + * @var array */ - private $definitions = []; + private $namespaceDefinitions = []; /** * An associative array that maps fully qualified symbol names to global Definitions @@ -28,13 +29,6 @@ class Index implements ReadableIndex, \Serializable */ private $globalDefinitions = []; - /** - * An associative array that maps namespaces to an associative array of FQN to Definitions - * - * @var Definition[] - */ - private $namespaceDefinitions = []; - /** * An associative array that maps fully qualified symbol names to arrays of document URIs that reference the symbol * @@ -98,14 +92,18 @@ public function isStaticComplete(): bool } /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions (global or not) + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to Definitions (global or not) * - * @return Definition[] + * @return \Generator providing Definition[] */ - public function getDefinitions(): array + public function getDefinitions(): \Generator { - return $this->definitions; + foreach ($this->namespaceDefinitions as $namespaceDefinition) { + foreach ($namespaceDefinition as $fqn => $definition) { + yield $fqn => $definition; + } + } } /** @@ -142,9 +140,13 @@ public function getDefinitionsForNamespace(string $namespace): array */ public function getDefinition(string $fqn, bool $globalFallback = false) { - if (isset($this->definitions[$fqn])) { - return $this->definitions[$fqn]; + $namespace = $this->extractNamespace($fqn); + $definitions = $this->getDefinitionsForNamespace($namespace); + + if (isset($definitions[$fqn])) { + return $definitions[$fqn]; } + if ($globalFallback) { $parts = explode('\\', $fqn); $fqn = end($parts); @@ -161,9 +163,13 @@ public function getDefinition(string $fqn, bool $globalFallback = false) */ public function setDefinition(string $fqn, Definition $definition) { - $this->definitions[$fqn] = $definition; + $namespace = $this->extractNamespace($fqn); + if (!isset($this->namespaceDefinitions[$namespace])) { + $this->namespaceDefinitions[$namespace] = []; + } + + $this->namespaceDefinitions[$namespace][$fqn] = $definition; $this->setGlobalDefinition($fqn, $definition); - $this->setNamespaceDefinition($fqn, $definition); $this->emit('definition-added'); } @@ -176,10 +182,17 @@ public function setDefinition(string $fqn, Definition $definition) */ public function removeDefinition(string $fqn) { - unset($this->definitions[$fqn]); + $namespace = $this->extractNamespace($fqn); + if (isset($this->namespaceDefinitions[$namespace])) { + unset($this->namespaceDefinitions[$namespace][$fqn]); + + if (empty($this->namespaceDefinitions[$namespace])) { + unset($this->namespaceDefinitions[$namespace]); + } + } + unset($this->globalDefinitions[$fqn]); unset($this->references[$fqn]); - $this->removeNamespaceDefinition($fqn); } /** @@ -252,9 +265,10 @@ public function unserialize($serialized) $this->$prop = $val; } - foreach ($this->definitions as $fqn => $definition) { - $this->setGlobalDefinition($fqn, $definition); - $this->setNamespaceDefinition($fqn, $definition); + foreach ($this->namespaceDefinitions as $namespaceDefinition) { + foreach ($namespaceDefinition as $fqn => $definition) { + $this->setGlobalDefinition($fqn, $definition); + } } } @@ -265,7 +279,7 @@ public function unserialize($serialized) public function serialize() { return serialize([ - 'definitions' => $this->definitions, + 'namespaceDefinitions' => $this->namespaceDefinitions, 'references' => $this->references, 'complete' => $this->complete, 'staticComplete' => $this->staticComplete @@ -286,40 +300,6 @@ private function setGlobalDefinition(string $fqn, Definition $definition) } } - /** - * Registers a definition to a namespace - * - * @param string $fqn The fully qualified name of the symbol - * @param Definition $definition The Definition object - * @return void - */ - private function setNamespaceDefinition(string $fqn, Definition $definition) - { - $namespace = $this->extractNamespace($fqn); - if (!isset($this->namespaceDefinitions[$namespace])) { - $this->namespaceDefinitions[$namespace] = []; - } - $this->namespaceDefinitions[$namespace][$fqn] = $definition; - } - - /** - * Removes a definition from a namespace - * - * @param string $fqn The fully qualified name of the symbol - * @return void - */ - private function removeNamespaceDefinition(string $fqn) - { - $namespace = $this->extractNamespace($fqn); - if (isset($this->namespaceDefinitions[$namespace])) { - unset($this->namespaceDefinitions[$namespace][$fqn]); - - if (0 === sizeof($this->namespaceDefinitions[$namespace])) { - unset($this->namespaceDefinitions[$namespace]); - } - } - } - /** * @param string $fqn * @return string The namespace extracted from the given FQN diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 62b9c066..0c5efca5 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -30,12 +30,12 @@ public function isComplete(): bool; public function isStaticComplete(): bool; /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to Definitions (global or not) + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to Definitions (global or not) * - * @return Definitions[] + * @return \Generator providing Definition[] */ - public function getDefinitions(): array; + public function getDefinitions(): \Generator; /** * Returns an associative array [string => Definition] that maps fully qualified symbol names From 8768b698d5ce011f67b930355b31c5c75a69ecbb Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Wed, 9 Aug 2017 22:51:42 +0200 Subject: [PATCH 08/25] use Generators to get definitions without wasting memory --- src/Index/AbstractAggregateIndex.php | 30 ++++++++++---------- src/Index/Index.php | 41 +++++++++++++++++++--------- src/Index/ReadableIndex.php | 14 +++++----- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index ca4b7d49..a5826abb 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -107,42 +107,40 @@ public function isStaticComplete(): bool public function getDefinitions(): \Generator { foreach ($this->getIndexes() as $index) { - yield $index->getDefinitions(); + foreach ($index->getDefinitions() as $fqn => $definitions) { + yield $fqn => $definition; + } } } /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to global Definitions + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to global Definitions * - * @return Definition[] + * @return \Generator providing Definitions[] */ - public function getGlobalDefinitions(): array + public function getGlobalDefinitions(): \Generator { - $defs = []; foreach ($this->getIndexes() as $index) { - foreach ($index->getGlobalDefinitions() as $fqn => $def) { - $defs[$fqn] = $def; + foreach ($index->getGlobalDefinitions() as $fqn => $definition) { + yield $fqn => $definition; } } - return $defs; } /** - * Returns the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given namespace * * @param string $namespace - * @return Definitions[] + * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): array + public function getDefinitionsForNamespace(string $namespace): \Generator { - $defs = []; foreach ($this->getIndexes() as $index) { - foreach ($index->getDefinitionsForNamespace($namespace) as $fqn => $def) { - $defs[$fqn] = $def; + foreach ($index->getDefinitionsForNamespace($namespace) as $fqn => $definition) { + yield $fqn => $definition; } } - return $defs; } /** diff --git a/src/Index/Index.php b/src/Index/Index.php index ffd59419..91302467 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -107,28 +107,29 @@ public function getDefinitions(): \Generator } /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to global Definitions + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to global Definitions * - * @return Definition[] + * @return \Generator providing Definitions[] */ - public function getGlobalDefinitions(): array + public function getGlobalDefinitions(): \Generator { - return $this->globalDefinitions; + foreach ($this->globalDefinitions as $fqn => $definition) { + yield $fqn => $definition; + } } /** - * Returns the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given namespace * * @param string $namespace - * @return Definitions[] + * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): array + public function getDefinitionsForNamespace(string $namespace): \Generator { - return isset($this->namespaceDefinitions[$namespace]) - ? $this->namespaceDefinitions[$namespace] - : [] - ; + foreach ($this->doGetDefinitionsForNamespace($namespace) as $fqn => $definition) { + yield $fqn => $definition; + } } /** @@ -141,7 +142,7 @@ public function getDefinitionsForNamespace(string $namespace): array public function getDefinition(string $fqn, bool $globalFallback = false) { $namespace = $this->extractNamespace($fqn); - $definitions = $this->getDefinitionsForNamespace($namespace); + $definitions = $this->doGetDefinitionsForNamespace($namespace); if (isset($definitions[$fqn])) { return $definitions[$fqn]; @@ -314,4 +315,18 @@ private function extractNamespace(string $fqn): string return $fqn; } + + /** + * Returns the Definitions that are in the given namespace + * + * @param string $namespace + * @return Definition[] + */ + private function doGetDefinitionsForNamespace(string $namespace): array + { + return isset($this->namespaceDefinitions[$namespace]) + ? $this->namespaceDefinitions[$namespace] + : [] + ; + } } diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 0c5efca5..8d4e1cad 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -38,20 +38,20 @@ public function isStaticComplete(): bool; public function getDefinitions(): \Generator; /** - * Returns an associative array [string => Definition] that maps fully qualified symbol names - * to global Definitions + * Returns a Generator providing an associative array [string => Definition] + * that maps fully qualified symbol names to global Definitions * - * @return Definitions[] + * @return \Generator providing Definitions[] */ - public function getGlobalDefinitions(): array; + public function getGlobalDefinitions(): \Generator; /** - * Returns the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given namespace * * @param string $namespace - * @return Definitions[] + * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): array; + public function getDefinitionsForNamespace(string $namespace): \Generator; /** * Returns the Definition object by a specific FQN From 8801edb7a2e4f3bf9bcd9c6444ddbead43b0fbeb Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 10 Aug 2017 00:06:53 +0200 Subject: [PATCH 09/25] also yield URIs to save memory --- src/Index/AbstractAggregateIndex.php | 12 +++++------- src/Index/Index.php | 15 +++++++++++---- src/Index/ReadableIndex.php | 6 +++--- src/Server/TextDocument.php | 20 ++++++++++++++++---- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index a5826abb..7ba38701 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -160,19 +160,17 @@ public function getDefinition(string $fqn, bool $globalFallback = false) } /** - * Returns all URIs in this index that reference a symbol + * Returns a Generator providing all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return string[] + * @return \Generator providing string[] */ - public function getReferenceUris(string $fqn): array + public function getReferenceUris(string $fqn): \Generator { - $refs = []; foreach ($this->getIndexes() as $index) { - foreach ($index->getReferenceUris($fqn) as $ref) { - $refs[] = $ref; + foreach ($index->getReferenceUris($fqn) as $uri) { + yield $uri; } } - return $refs; } } diff --git a/src/Index/Index.php b/src/Index/Index.php index 91302467..8d8675d3 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -197,14 +197,21 @@ public function removeDefinition(string $fqn) } /** - * Returns all URIs in this index that reference a symbol + * Returns a Generator providing all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return string[] + * @return \Generator providing string[] */ - public function getReferenceUris(string $fqn): array + public function getReferenceUris(string $fqn): \Generator { - return $this->references[$fqn] ?? []; + $uris = isset($this->references[$fqn]) + ? $this->references[$fqn] + : [] + ; + + foreach ($uris as $uri) { + yield $uri; + } } /** diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 8d4e1cad..6e2b8c61 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -63,10 +63,10 @@ public function getDefinitionsForNamespace(string $namespace): \Generator; public function getDefinition(string $fqn, bool $globalFallback = false); /** - * Returns all URIs in this index that reference a symbol + * Returns a Generator providing all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return string[] + * @return \Generator providing string[] */ - public function getReferenceUris(string $fqn): array; + public function getReferenceUris(string $fqn): \Generator; } diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 58e7074a..46e822ac 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -219,10 +219,9 @@ public function references( return []; } } - $refDocuments = yield Promise\all(array_map( - [$this->documentLoader, 'getOrLoad'], - $this->index->getReferenceUris($fqn) - )); + $refDocuments = yield Promise\all(iterator_to_array( + $this->getOrLoadReferences($fqn)) + ); foreach ($refDocuments as $document) { $refs = $document->getReferenceNodesByFqn($fqn); if ($refs !== null) { @@ -397,4 +396,17 @@ public function xdefinition(TextDocumentIdentifier $textDocument, Position $posi return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)]; }); } + + /** + * Gets or loads the documents referencing the given FQN. + * + * @param string $fqn + * @return \Generator providing Promise + */ + private function getOrLoadReferences(string $fqn): \Generator + { + foreach ($this->index->getReferenceUris($fqn) as $ref) { + yield $this->documentLoader->getOrLoad($ref); + } + } } From 6a41a7f0dc95abf428989f31125c578e98968088 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 10 Aug 2017 10:01:18 +0200 Subject: [PATCH 10/25] add example of indexed definitions --- src/Index/Index.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index 8d8675d3..a33d9adb 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -16,21 +16,30 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps namespaces to - * an associative array that maps fully qualified symbol names to global Definitions + * an associative array that maps fully qualified symbol names + * to global Definitions, e.g. : + * [ + * 'Psr\Log\LoggerInterface' => [ + * 'Psr\Log\LoggerInterface->log()' => $definition, + * 'Psr\Log\LoggerInterface->debug()' => $definition, + * ], + * ] * * @var array */ private $namespaceDefinitions = []; /** - * An associative array that maps fully qualified symbol names to global Definitions + * An associative array that maps fully qualified symbol names + * to global Definitions * * @var Definition[] */ private $globalDefinitions = []; /** - * An associative array that maps fully qualified symbol names to arrays of document URIs that reference the symbol + * An associative array that maps fully qualified symbol names + * to arrays of document URIs that reference the symbol * * @var string[][] */ From 6a368280691d0ba24a36ada8992398dc2fcf9f36 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 10 Aug 2017 20:42:08 +0200 Subject: [PATCH 11/25] avoid useless array --- src/CompletionProvider.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 59348138..0548e498 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -202,13 +202,8 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi ); // The namespaces of the symbol and its parents (eg the implemented interfaces) - $namespaces = []; foreach ($this->expandParentFqns($fqns) as $namespace) { - $namespaces[] = $namespace; - } - - // Collect namespaces definitions - foreach ($namespaces as $namespace) { + // Collect namespaces definitions foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { // Add the object access operator to only get members of all parents $prefix = $namespace . '->'; @@ -237,13 +232,8 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi ); // The namespaces of the symbol and its parents (eg the implemented interfaces) - $namespaces = []; foreach ($this->expandParentFqns($fqns) as $namespace) { - $namespaces[] = $namespace; - } - - // Collect namespaces definitions - foreach ($namespaces as $namespace) { + // Collect namespaces definitions foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { // Append :: operator to only get static members of all parents $prefix = strtolower($namespace . '::'); From e34d8e15dd780d96c85b74e5cb8c9c1c03d95b20 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 10 Aug 2017 22:03:16 +0200 Subject: [PATCH 12/25] use of null coalescing operator --- src/Index/Index.php | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index a33d9adb..649ba297 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -136,7 +136,7 @@ public function getGlobalDefinitions(): \Generator */ public function getDefinitionsForNamespace(string $namespace): \Generator { - foreach ($this->doGetDefinitionsForNamespace($namespace) as $fqn => $definition) { + foreach ($this->namespaceDefinitions[$namespace] ?? [] as $fqn => $definition) { yield $fqn => $definition; } } @@ -151,7 +151,7 @@ public function getDefinitionsForNamespace(string $namespace): \Generator public function getDefinition(string $fqn, bool $globalFallback = false) { $namespace = $this->extractNamespace($fqn); - $definitions = $this->doGetDefinitionsForNamespace($namespace); + $definitions = $this->namespaceDefinitions[$namespace] ?? []; if (isset($definitions[$fqn])) { return $definitions[$fqn]; @@ -213,12 +213,7 @@ public function removeDefinition(string $fqn) */ public function getReferenceUris(string $fqn): \Generator { - $uris = isset($this->references[$fqn]) - ? $this->references[$fqn] - : [] - ; - - foreach ($uris as $uri) { + foreach ($this->references[$fqn] ?? [] as $uri) { yield $uri; } } @@ -331,18 +326,4 @@ private function extractNamespace(string $fqn): string return $fqn; } - - /** - * Returns the Definitions that are in the given namespace - * - * @param string $namespace - * @return Definition[] - */ - private function doGetDefinitionsForNamespace(string $namespace): array - { - return isset($this->namespaceDefinitions[$namespace]) - ? $this->namespaceDefinitions[$namespace] - : [] - ; - } } From 14f840bd2f5d9accd81b949d9926e18e880569bf Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 5 Oct 2017 20:45:57 +0200 Subject: [PATCH 13/25] use correct terminology --- src/CompletionProvider.php | 20 +++++------ src/Index/AbstractAggregateIndex.php | 10 +++--- src/Index/Index.php | 52 ++++++++++++++-------------- src/Index/ReadableIndex.php | 6 ++-- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 0548e498..f058baca 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -201,12 +201,12 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi $this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression) ); - // The namespaces of the symbol and its parents (eg the implemented interfaces) - foreach ($this->expandParentFqns($fqns) as $namespace) { - // Collect namespaces definitions - foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { + // The FQNs of the symbol and its parents (eg the implemented interfaces) + foreach ($this->expandParentFqns($fqns) as $parentFqn) { + // Collect fqn definitions + foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { // Add the object access operator to only get members of all parents - $prefix = $namespace . '->'; + $prefix = $parentFqn . '->'; if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } @@ -231,12 +231,12 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi $classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier) ); - // The namespaces of the symbol and its parents (eg the implemented interfaces) - foreach ($this->expandParentFqns($fqns) as $namespace) { - // Collect namespaces definitions - foreach ($this->index->getDefinitionsForNamespace($namespace) as $fqn => $def) { + // The FQNs of the symbol and its parents (eg the implemented interfaces) + foreach ($this->expandParentFqns($fqns) as $parentFqn) { + // Collect fqn definitions + foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { // Append :: operator to only get static members of all parents - $prefix = strtolower($namespace . '::'); + $prefix = strtolower($parentFqn . '::'); if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && !$def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 7ba38701..e4e33b38 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -129,16 +129,16 @@ public function getGlobalDefinitions(): \Generator } /** - * Returns a Generator providing the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given FQN * - * @param string $namespace + * @param string $fqn * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): \Generator + public function getDefinitionsForFqn(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - foreach ($index->getDefinitionsForNamespace($namespace) as $fqn => $definition) { - yield $fqn => $definition; + foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) { + yield $symbolFqn => $definition; } } } diff --git a/src/Index/Index.php b/src/Index/Index.php index 649ba297..bb1e6b40 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -15,7 +15,7 @@ class Index implements ReadableIndex, \Serializable use EmitterTrait; /** - * An associative array that maps namespaces to + * An associative array that maps fully qualified names to * an associative array that maps fully qualified symbol names * to global Definitions, e.g. : * [ @@ -27,7 +27,7 @@ class Index implements ReadableIndex, \Serializable * * @var array */ - private $namespaceDefinitions = []; + private $fqnDefinitions = []; /** * An associative array that maps fully qualified symbol names @@ -108,8 +108,8 @@ public function isStaticComplete(): bool */ public function getDefinitions(): \Generator { - foreach ($this->namespaceDefinitions as $namespaceDefinition) { - foreach ($namespaceDefinition as $fqn => $definition) { + foreach ($this->fqnDefinitions as $fqnDefinition) { + foreach ($fqnDefinition as $fqn => $definition) { yield $fqn => $definition; } } @@ -129,15 +129,15 @@ public function getGlobalDefinitions(): \Generator } /** - * Returns a Generator providing the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given FQN * - * @param string $namespace + * @param string $fqn * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): \Generator + public function getDefinitionsForFqn(string $fqn): \Generator { - foreach ($this->namespaceDefinitions[$namespace] ?? [] as $fqn => $definition) { - yield $fqn => $definition; + foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) { + yield $symbolFqn => $definition; } } @@ -150,8 +150,8 @@ public function getDefinitionsForNamespace(string $namespace): \Generator */ public function getDefinition(string $fqn, bool $globalFallback = false) { - $namespace = $this->extractNamespace($fqn); - $definitions = $this->namespaceDefinitions[$namespace] ?? []; + $namespacedFqn = $this->extractNamespacedFqn($fqn); + $definitions = $this->fqnDefinitions[$namespacedFqn] ?? []; if (isset($definitions[$fqn])) { return $definitions[$fqn]; @@ -173,12 +173,12 @@ public function getDefinition(string $fqn, bool $globalFallback = false) */ public function setDefinition(string $fqn, Definition $definition) { - $namespace = $this->extractNamespace($fqn); - if (!isset($this->namespaceDefinitions[$namespace])) { - $this->namespaceDefinitions[$namespace] = []; + $namespacedFqn = $this->extractNamespacedFqn($fqn); + if (!isset($this->fqnDefinitions[$namespacedFqn])) { + $this->fqnDefinitions[$namespacedFqn] = []; } - $this->namespaceDefinitions[$namespace][$fqn] = $definition; + $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; $this->setGlobalDefinition($fqn, $definition); $this->emit('definition-added'); } @@ -192,12 +192,12 @@ public function setDefinition(string $fqn, Definition $definition) */ public function removeDefinition(string $fqn) { - $namespace = $this->extractNamespace($fqn); - if (isset($this->namespaceDefinitions[$namespace])) { - unset($this->namespaceDefinitions[$namespace][$fqn]); + $namespacedFqn = $this->extractNamespacedFqn($fqn); + if (isset($this->fqnDefinitions[$namespacedFqn])) { + unset($this->fqnDefinitions[$namespacedFqn][$fqn]); - if (empty($this->namespaceDefinitions[$namespace])) { - unset($this->namespaceDefinitions[$namespace]); + if (empty($this->fqnDefinitions[$namespacedFqn])) { + unset($this->fqnDefinitions[$namespacedFqn]); } } @@ -277,8 +277,8 @@ public function unserialize($serialized) $this->$prop = $val; } - foreach ($this->namespaceDefinitions as $namespaceDefinition) { - foreach ($namespaceDefinition as $fqn => $definition) { + foreach ($this->fqnDefinitions as $fqnDefinition) { + foreach ($fqnDefinition as $fqn => $definition) { $this->setGlobalDefinition($fqn, $definition); } } @@ -291,7 +291,7 @@ public function unserialize($serialized) public function serialize() { return serialize([ - 'namespaceDefinitions' => $this->namespaceDefinitions, + 'fqnDefinitions' => $this->fqnDefinitions, 'references' => $this->references, 'complete' => $this->complete, 'staticComplete' => $this->staticComplete @@ -313,10 +313,10 @@ private function setGlobalDefinition(string $fqn, Definition $definition) } /** - * @param string $fqn - * @return string The namespace extracted from the given FQN + * @param string $fqn The symbol FQN + * @return string The namespaced FQN extracted from the given symbol FQN */ - private function extractNamespace(string $fqn): string + private function extractNamespacedFqn(string $fqn): string { foreach (['::', '->'] as $operator) { if (false !== ($pos = strpos($fqn, $operator))) { diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 6e2b8c61..7d3750ef 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -46,12 +46,12 @@ public function getDefinitions(): \Generator; public function getGlobalDefinitions(): \Generator; /** - * Returns a Generator providing the Definitions that are in the given namespace + * Returns a Generator providing the Definitions that are in the given FQN * - * @param string $namespace + * @param string $fqn * @return \Generator providing Definitions[] */ - public function getDefinitionsForNamespace(string $namespace): \Generator; + public function getDefinitionsForFqn(string $fqn): \Generator; /** * Returns the Definition object by a specific FQN From e9fd572a4978acb13e111d00c319361f505264a6 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Mon, 13 Nov 2017 20:57:22 +0100 Subject: [PATCH 14/25] consider the merge of #511 --- src/Index/Index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index bb1e6b40..a5532b7e 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -17,7 +17,7 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps fully qualified names to * an associative array that maps fully qualified symbol names - * to global Definitions, e.g. : + * to Definitions, e.g. : * [ * 'Psr\Log\LoggerInterface' => [ * 'Psr\Log\LoggerInterface->log()' => $definition, @@ -31,7 +31,7 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps fully qualified symbol names - * to global Definitions + * to global (ie non member) Definitions * * @var Definition[] */ @@ -307,7 +307,7 @@ public function serialize() */ private function setGlobalDefinition(string $fqn, Definition $definition) { - if ($definition->isGlobal) { + if ($definition->isMember) { $this->globalDefinitions[$fqn] = $definition; } } From d1f85f15b6181b08ea37ea0ca3c9463865198a7b Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Mon, 13 Nov 2017 21:26:43 +0100 Subject: [PATCH 15/25] use tree representation index --- src/CompletionProvider.php | 32 +-- src/Index/AbstractAggregateIndex.php | 37 ++-- src/Index/Index.php | 292 +++++++++++++++++++-------- src/Index/ReadableIndex.php | 8 - 4 files changed, 244 insertions(+), 125 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index f058baca..bf107d5d 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -207,7 +207,7 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { // Add the object access operator to only get members of all parents $prefix = $parentFqn . '->'; - if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isMember) { + if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } } @@ -237,7 +237,7 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { // Append :: operator to only get static members of all parents $prefix = strtolower($parentFqn . '::'); - if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && !$def->isMember) { + if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && $def->isMember) { $list->items[] = CompletionItem::fromDefinition($def); } } @@ -301,25 +301,29 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi } } - // Suggest global symbols that either + // Suggest global (ie non member) 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->index->getGlobalDefinitions() as $fqn => $def) { + foreach ($this->index->getDefinitions() as $fqn => $def) { $fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix; if ( - !$prefix - || ( - // Either not qualified, but a matching prefix with global fallback - ($def->roamed && !$isQualified && $fqnStartsWithPrefix) - // Or not in a namespace or a fully qualified name or AND matching the prefix - || ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix) - // Or in a namespace, not fully qualified and matching the prefix + current namespace + // Exclude methods, properties etc. + !$def->isMember + && ( + !$prefix || ( - $namespaceNode - && !$isFullyQualified - && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix + // Either not qualified, but a matching prefix with global fallback + ($def->roamed && !$isQualified && $fqnStartsWithPrefix) + // Or not in a namespace or a fully qualified name or AND matching the prefix + || ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix) + // Or in a namespace, not fully qualified and matching the prefix + current namespace + || ( + $namespaceNode + && !$isFullyQualified + && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix + ) ) ) // Only suggest classes for `new` diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index e4e33b38..8c88659e 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -107,24 +107,11 @@ public function isStaticComplete(): bool public function getDefinitions(): \Generator { foreach ($this->getIndexes() as $index) { - foreach ($index->getDefinitions() as $fqn => $definitions) { - yield $fqn => $definition; - } - } - } + // foreach ($index->getDefinitions() as $fqn => $definition) { + // yield $fqn => $definition; + // } - /** - * Returns a Generator providing an associative array [string => Definition] - * that maps fully qualified symbol names to global Definitions - * - * @return \Generator providing Definitions[] - */ - public function getGlobalDefinitions(): \Generator - { - foreach ($this->getIndexes() as $index) { - foreach ($index->getGlobalDefinitions() as $fqn => $definition) { - yield $fqn => $definition; - } + yield from $index->getDefinitions(); } } @@ -137,9 +124,11 @@ public function getGlobalDefinitions(): \Generator public function getDefinitionsForFqn(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) { - yield $symbolFqn => $definition; - } + // foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) { + // yield $symbolFqn => $definition; + // } + + yield from $index->getDefinitionsForFqn($fqn); } } @@ -168,9 +157,11 @@ public function getDefinition(string $fqn, bool $globalFallback = false) public function getReferenceUris(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - foreach ($index->getReferenceUris($fqn) as $uri) { - yield $uri; - } + // foreach ($index->getReferenceUris($fqn) as $uri) { + // yield $uri; + // } + + yield from $index->getReferenceUris($fqn); } } } diff --git a/src/Index/Index.php b/src/Index/Index.php index a5532b7e..af8eae03 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -15,27 +15,21 @@ class Index implements ReadableIndex, \Serializable use EmitterTrait; /** - * An associative array that maps fully qualified names to - * an associative array that maps fully qualified symbol names - * to Definitions, e.g. : - * [ - * 'Psr\Log\LoggerInterface' => [ - * 'Psr\Log\LoggerInterface->log()' => $definition, - * 'Psr\Log\LoggerInterface->debug()' => $definition, + * An associative array that maps splitted fully qualified symbol names + * to definitions, eg : + * [ + * 'Psr' => [ + * '\Log' => [ + * '\LoggerInterface' => [ + * '->log()' => $definition, + * ], * ], - * ] + * ], + * ] * * @var array */ - private $fqnDefinitions = []; - - /** - * An associative array that maps fully qualified symbol names - * to global (ie non member) Definitions - * - * @var Definition[] - */ - private $globalDefinitions = []; + private $definitions = []; /** * An associative array that maps fully qualified symbol names @@ -108,24 +102,13 @@ public function isStaticComplete(): bool */ public function getDefinitions(): \Generator { - foreach ($this->fqnDefinitions as $fqnDefinition) { - foreach ($fqnDefinition as $fqn => $definition) { - yield $fqn => $definition; - } - } - } + // foreach ($this->fqnDefinitions as $fqnDefinition) { + // foreach ($fqnDefinition as $fqn => $definition) { + // yield $fqn => $definition; + // } + // } - /** - * Returns a Generator providing an associative array [string => Definition] - * that maps fully qualified symbol names to global Definitions - * - * @return \Generator providing Definitions[] - */ - public function getGlobalDefinitions(): \Generator - { - foreach ($this->globalDefinitions as $fqn => $definition) { - yield $fqn => $definition; - } + yield from $this->generateDefinitionsRecursively($this->definitions); } /** @@ -136,8 +119,17 @@ public function getGlobalDefinitions(): \Generator */ public function getDefinitionsForFqn(string $fqn): \Generator { - foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) { - yield $symbolFqn => $definition; + // foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) { + // yield $symbolFqn => $definition; + // } + + $parts = $this->splitFqn($fqn); + $result = $this->getIndexValue($parts, $this->definitions); + + if ($result instanceof Definition) { + yield $fqn => $definition; + } elseif (is_array($result)) { + yield from $this->generateDefinitionsRecursively($result, $fqn); } } @@ -150,18 +142,23 @@ public function getDefinitionsForFqn(string $fqn): \Generator */ public function getDefinition(string $fqn, bool $globalFallback = false) { - $namespacedFqn = $this->extractNamespacedFqn($fqn); - $definitions = $this->fqnDefinitions[$namespacedFqn] ?? []; + // $namespacedFqn = $this->extractNamespacedFqn($fqn); + // $definitions = $this->fqnDefinitions[$namespacedFqn] ?? []; - if (isset($definitions[$fqn])) { - return $definitions[$fqn]; - } + // if (isset($definitions[$fqn])) { + // return $definitions[$fqn]; + // } - if ($globalFallback) { - $parts = explode('\\', $fqn); - $fqn = end($parts); - return $this->getDefinition($fqn); - } + // if ($globalFallback) { + // $parts = explode('\\', $fqn); + // $fqn = end($parts); + // return $this->getDefinition($fqn); + // } + + $parts = $this->splitFqn($fqn); + $result = $this->getIndexValue($parts, $this->definitions); + + return $result instanceof Definition ?? null; } /** @@ -173,13 +170,16 @@ public function getDefinition(string $fqn, bool $globalFallback = false) */ public function setDefinition(string $fqn, Definition $definition) { - $namespacedFqn = $this->extractNamespacedFqn($fqn); - if (!isset($this->fqnDefinitions[$namespacedFqn])) { - $this->fqnDefinitions[$namespacedFqn] = []; - } + // $namespacedFqn = $this->extractNamespacedFqn($fqn); + // if (!isset($this->fqnDefinitions[$namespacedFqn])) { + // $this->fqnDefinitions[$namespacedFqn] = []; + // } + + // $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; + + $parts = $this->splitFqn($fqn); + $this->indexDefinition(0, $parts, $this->definitions, $definition); - $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; - $this->setGlobalDefinition($fqn, $definition); $this->emit('definition-added'); } @@ -192,16 +192,18 @@ public function setDefinition(string $fqn, Definition $definition) */ public function removeDefinition(string $fqn) { - $namespacedFqn = $this->extractNamespacedFqn($fqn); - if (isset($this->fqnDefinitions[$namespacedFqn])) { - unset($this->fqnDefinitions[$namespacedFqn][$fqn]); + // $namespacedFqn = $this->extractNamespacedFqn($fqn); + // if (isset($this->fqnDefinitions[$namespacedFqn])) { + // unset($this->fqnDefinitions[$namespacedFqn][$fqn]); - if (empty($this->fqnDefinitions[$namespacedFqn])) { - unset($this->fqnDefinitions[$namespacedFqn]); - } - } + // if (empty($this->fqnDefinitions[$namespacedFqn])) { + // unset($this->fqnDefinitions[$namespacedFqn]); + // } + // } + + $parts = $this->splitFqn($fqn); + $this->removeIndexedDefinition(0, $parts, $this->definitions); - unset($this->globalDefinitions[$fqn]); unset($this->references[$fqn]); } @@ -276,12 +278,6 @@ public function unserialize($serialized) foreach ($data as $prop => $val) { $this->$prop = $val; } - - foreach ($this->fqnDefinitions as $fqnDefinition) { - foreach ($fqnDefinition as $fqn => $definition) { - $this->setGlobalDefinition($fqn, $definition); - } - } } /** @@ -291,7 +287,7 @@ public function unserialize($serialized) public function serialize() { return serialize([ - 'fqnDefinitions' => $this->fqnDefinitions, + 'definitions' => $this->definitions, 'references' => $this->references, 'complete' => $this->complete, 'staticComplete' => $this->staticComplete @@ -299,31 +295,167 @@ public function serialize() } /** - * Registers a definition to the global definitions index if it is global + * @param string $fqn The symbol FQN + * @return string The namespaced FQN extracted from the given symbol FQN + */ + // private function extractNamespacedFqn(string $fqn): string + // { + // foreach (['::', '->'] as $operator) { + // if (false !== ($pos = strpos($fqn, $operator))) { + // return substr($fqn, 0, $pos); + // } + // } + + // return $fqn; + // } + + /** + * Returns a Genrerator containing all the into the given $storage recursively. + * The generator yields key => value pairs, eg + * 'Psr\Log\LoggerInterface->log()' => $definition * - * @param string $fqn The fully qualified name of the symbol - * @param Definition $definition The Definition object - * @return void + * @param array &$storage + * @param string $prefix (optional) + * @return \Generator */ - private function setGlobalDefinition(string $fqn, Definition $definition) + private function generateDefinitionsRecursively(array &$storage, string $prefix = ''): \Generator { - if ($definition->isMember) { - $this->globalDefinitions[$fqn] = $definition; + foreach ($storage as $key => $value) { + $prefix .= $key; + if (!is_array($value)) { + yield $prefix => $value; + } else { + yield from generateDefinitionsRecursively($value, $prefix); + } } } /** - * @param string $fqn The symbol FQN - * @return string The namespaced FQN extracted from the given symbol FQN + * Splits the given FQN into an array, eg : + * 'Psr\Log\LoggerInterface->log' will be ['Psr', '\Log', '\LoggerInterface', '->log()'] + * '\Exception->getMessage()' will be ['\Exception', '->getMessage()'] + * 'PHP_VERSION' will be ['PHP_VERSION'] + * + * @param string $fqn + * @return array */ - private function extractNamespacedFqn(string $fqn): string + private function splitFqn(string $fqn): array { + // split fqn at backslashes + $parts = explode('\\', $fqn); + + // write back the backslach prefix to the first part if it was present + if ('' === $parts[0]) { + $parts = array_slice($parts, 1); + $parts[0] = sprintf('\\%s', $parts[0]); + } + + // write back the backslashes prefixes for the other parts + for ($i = 1; $i < count($parts); $i++) { + $parts[$i] = sprintf('\\%s', $parts[$i]); + } + + // split the last part in 2 parts at the operator + $lastPart = end($parts); foreach (['::', '->'] as $operator) { - if (false !== ($pos = strpos($fqn, $operator))) { - return substr($fqn, 0, $pos); + $endParts = explode($operator, $lastPart); + if (count($endParts) > 1) { + // replace the last part by its pieces + array_pop($parts); + $parts[] = $endParts[0]; + $parts[] = sprintf('%s%s', $operator, $endParts[1]); + break; } } - return $fqn; + return $parts; + } + + /** + * Return the values stored in this index under the given $parts array. + * It can be an index node or a Definition if the $parts are precise + * enough. Returns null when nothing is found. + * + * @param array $parts The splitted FQN + * @param array &$storage The array in which to store the $definition + * @return array|Definition|null + */ + private function getIndexValue(array $parts, array &$storage) + { + $part = $parts[0]; + + if (!isset($storage[$part])) { + return null; + } + + $parts = array_slice($parts, 1); + // we've reached the last provided part + if (0 === count($parts)) { + return $storage[$part]; + } + + return getIndexValue($parts, $storage[$part]); + } + + /** + * Recusrive function which store the given definition in the given $storage + * array represented as a tree matching the given $parts. + * + * @param int $level The current level of FQN part + * @param array $parts The splitted FQN + * @param array &$storage The array in which to store the $definition + * @param Definition $definition The Definition to store + */ + private function indexDefinition(int $level, array $parts, array &$storage, Definition $definition) + { + $part = $parts[$level]; + + if ($level + 1 === count($parts)) { + $storage[$part] = $definition; + + return; + } + + if (!isset($storage[$part])) { + $storage[$part] = []; + } + + $this->indexDefinition($level + 1, $parts, $storage[$part], $definition); + } + + /** + * Recusrive function which remove the definition matching the given $parts + * from the given $storage array. + * The function also looks up recursively to remove the parents of the + * definition which no longer has children to avoid to let empty arrays + * in the index. + * + * @param int $level The current level of FQN part + * @param array $parts The splitted FQN + * @param array &$storage The current array in which to remove data + * @param array &$rootStorage The root storage array + */ + private function removeIndexedDefinition(int $level, array $parts, array &$storage, &$rootStorage) + { + $part = $parts[$level]; + + if ($level + 1 === count($parts)) { + if (isset($storage[$part]) && count($storage[$part]) < 2) { + unset($storage[$part]); + + if (0 === $level) { + // we're at root level, no need to check for parents + // w/o children + return; + } + + array_pop($parts); + // parse again the definition tree to see if the parent + // can be removed too if it has no more children + removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); + } + } else { + removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); + } } } diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 7d3750ef..5c54557d 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -37,14 +37,6 @@ public function isStaticComplete(): bool; */ public function getDefinitions(): \Generator; - /** - * Returns a Generator providing an associative array [string => Definition] - * that maps fully qualified symbol names to global Definitions - * - * @return \Generator providing Definitions[] - */ - public function getGlobalDefinitions(): \Generator; - /** * Returns a Generator providing the Definitions that are in the given FQN * From 188a5df382fcbc63956cd63c5564b0a785ba4077 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Tue, 14 Nov 2017 04:41:10 +0100 Subject: [PATCH 16/25] fix definitions storage collision (member / non member) --- src/Index/Index.php | 98 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index af8eae03..28e0c85a 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -16,7 +16,7 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps splitted fully qualified symbol names - * to definitions, eg : + * to member definitions, eg : * [ * 'Psr' => [ * '\Log' => [ @@ -29,7 +29,23 @@ class Index implements ReadableIndex, \Serializable * * @var array */ - private $definitions = []; + private $memberDefinitions = []; + + /** + * An associative array that maps splitted fully qualified symbol names + * to non member definitions, eg : + * [ + * 'Psr' => [ + * '\Log' => [ + * '\LoggerInterface' => $definition, + * ], + * ], + * ] + * + * @var array + */ + private $nonMemberDefinitions = []; + /** * An associative array that maps fully qualified symbol names @@ -108,7 +124,8 @@ public function getDefinitions(): \Generator // } // } - yield from $this->generateDefinitionsRecursively($this->definitions); + yield from $this->yieldDefinitionsRecursively($this->memberDefinitions); + yield from $this->yieldDefinitionsRecursively($this->nonMemberDefinitions); } /** @@ -124,12 +141,20 @@ public function getDefinitionsForFqn(string $fqn): \Generator // } $parts = $this->splitFqn($fqn); - $result = $this->getIndexValue($parts, $this->definitions); + $result = $this->getIndexValue($parts, $this->memberDefinitions); if ($result instanceof Definition) { - yield $fqn => $definition; + yield $fqn => $result; } elseif (is_array($result)) { - yield from $this->generateDefinitionsRecursively($result, $fqn); + yield from $this->yieldDefinitionsRecursively($result, $fqn); + } else { + $result = $this->getIndexValue($parts, $this->nonMemberDefinitions); + + if ($result instanceof Definition) { + yield $fqn => $result; + } elseif (is_array($result)) { + yield from $this->yieldDefinitionsRecursively($result, $fqn); + } } } @@ -156,9 +181,18 @@ public function getDefinition(string $fqn, bool $globalFallback = false) // } $parts = $this->splitFqn($fqn); - $result = $this->getIndexValue($parts, $this->definitions); + $result = $this->getIndexValue($parts, $this->memberDefinitions); - return $result instanceof Definition ?? null; + if ($result instanceof Definition) { + return $result; + } + + $result = $this->getIndexValue($parts, $this->nonMemberDefinitions); + + return $result instanceof Definition + ? $result + : null + ; } /** @@ -178,7 +212,12 @@ public function setDefinition(string $fqn, Definition $definition) // $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; $parts = $this->splitFqn($fqn); - $this->indexDefinition(0, $parts, $this->definitions, $definition); + + if ($definition->isMember) { + $this->indexDefinition(0, $parts, $this->memberDefinitions, $definition); + } else { + $this->indexDefinition(0, $parts, $this->nonMemberDefinitions, $definition); + } $this->emit('definition-added'); } @@ -202,7 +241,10 @@ public function removeDefinition(string $fqn) // } $parts = $this->splitFqn($fqn); - $this->removeIndexedDefinition(0, $parts, $this->definitions); + + if (true !== $this->removeIndexedDefinition(0, $parts, $this->memberDefinitions)) { + $this->removeIndexedDefinition(0, $parts, $this->nonMemberDefinitions); + } unset($this->references[$fqn]); } @@ -287,7 +329,8 @@ public function unserialize($serialized) public function serialize() { return serialize([ - 'definitions' => $this->definitions, + 'memberDefinitions' => $this->memberDefinitions, + 'nonMemberDefinitions' => $this->nonMemberDefinitions, 'references' => $this->references, 'complete' => $this->complete, 'staticComplete' => $this->staticComplete @@ -318,14 +361,13 @@ public function serialize() * @param string $prefix (optional) * @return \Generator */ - private function generateDefinitionsRecursively(array &$storage, string $prefix = ''): \Generator + private function yieldDefinitionsRecursively(array &$storage, string $prefix = ''): \Generator { foreach ($storage as $key => $value) { - $prefix .= $key; if (!is_array($value)) { - yield $prefix => $value; + yield sprintf('%s%s', $prefix, $key) => $value; } else { - yield from generateDefinitionsRecursively($value, $prefix); + yield from $this->yieldDefinitionsRecursively($value, sprintf('%s%s', $prefix, $key)); } } } @@ -346,7 +388,10 @@ private function splitFqn(string $fqn): array // write back the backslach prefix to the first part if it was present if ('' === $parts[0]) { - $parts = array_slice($parts, 1); + if (count($parts) > 1) { + $parts = array_slice($parts, 1); + } + $parts[0] = sprintf('\\%s', $parts[0]); } @@ -394,7 +439,13 @@ private function getIndexValue(array $parts, array &$storage) return $storage[$part]; } - return getIndexValue($parts, $storage[$part]); + if (!is_array($storage[$part]) && count($parts) > 0) { + // we're looking for a member definition in the non member index, + // no matches can be found. + return null; + } + + return $this->getIndexValue($parts, $storage[$part]); } /** @@ -420,6 +471,12 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi $storage[$part] = []; } + if (!is_array($storage[$part])) { + // it's a non member definition, we can't add it to the member + // definitions index + return; + } + $this->indexDefinition($level + 1, $parts, $storage[$part], $definition); } @@ -434,6 +491,7 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi * @param array $parts The splitted FQN * @param array &$storage The current array in which to remove data * @param array &$rootStorage The root storage array + * @return boolean|null True when the definition has been found and removed, null otherwise. */ private function removeIndexedDefinition(int $level, array $parts, array &$storage, &$rootStorage) { @@ -446,16 +504,16 @@ private function removeIndexedDefinition(int $level, array $parts, array &$stora if (0 === $level) { // we're at root level, no need to check for parents // w/o children - return; + return true; } array_pop($parts); // parse again the definition tree to see if the parent // can be removed too if it has no more children - removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); + return $this->removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); } } else { - removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); + return $this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); } } } From cacde1ea72e9d8550c9d1dfbbc2c05f330d613d7 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Tue, 14 Nov 2017 13:40:35 +0100 Subject: [PATCH 17/25] use a single array to store definitions --- src/Index/Index.php | 112 +++++++++++++++++++------------------------- src/Indexer.php | 2 +- 2 files changed, 50 insertions(+), 64 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index 28e0c85a..45663836 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -16,12 +16,13 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps splitted fully qualified symbol names - * to member definitions, eg : + * to definitions, eg : * [ * 'Psr' => [ * '\Log' => [ * '\LoggerInterface' => [ - * '->log()' => $definition, + * '' => $def1, // definition for 'Psr\Log\LoggerInterface' which is non-member + * '->log()' => $def2, // definition for 'Psr\Log\LoggerInterface->log()' which is a member definition * ], * ], * ], @@ -29,23 +30,7 @@ class Index implements ReadableIndex, \Serializable * * @var array */ - private $memberDefinitions = []; - - /** - * An associative array that maps splitted fully qualified symbol names - * to non member definitions, eg : - * [ - * 'Psr' => [ - * '\Log' => [ - * '\LoggerInterface' => $definition, - * ], - * ], - * ] - * - * @var array - */ - private $nonMemberDefinitions = []; - + private $definitions = []; /** * An associative array that maps fully qualified symbol names @@ -124,8 +109,7 @@ public function getDefinitions(): \Generator // } // } - yield from $this->yieldDefinitionsRecursively($this->memberDefinitions); - yield from $this->yieldDefinitionsRecursively($this->nonMemberDefinitions); + yield from $this->yieldDefinitionsRecursively($this->definitions); } /** @@ -141,20 +125,18 @@ public function getDefinitionsForFqn(string $fqn): \Generator // } $parts = $this->splitFqn($fqn); - $result = $this->getIndexValue($parts, $this->memberDefinitions); + if ('' === end($parts)) { + // we want to return all the definitions in the given FQN, not only + // the one matching exactly the FQN. + array_pop($parts); + } + + $result = $this->getIndexValue($parts, $this->definitions); if ($result instanceof Definition) { yield $fqn => $result; } elseif (is_array($result)) { yield from $this->yieldDefinitionsRecursively($result, $fqn); - } else { - $result = $this->getIndexValue($parts, $this->nonMemberDefinitions); - - if ($result instanceof Definition) { - yield $fqn => $result; - } elseif (is_array($result)) { - yield from $this->yieldDefinitionsRecursively($result, $fqn); - } } } @@ -181,18 +163,23 @@ public function getDefinition(string $fqn, bool $globalFallback = false) // } $parts = $this->splitFqn($fqn); - $result = $this->getIndexValue($parts, $this->memberDefinitions); + $result = $this->getIndexValue($parts, $this->definitions); if ($result instanceof Definition) { return $result; } - $result = $this->getIndexValue($parts, $this->nonMemberDefinitions); + if ($globalFallback) { + $parts = explode('\\', $fqn); + $fqn = end($parts); - return $result instanceof Definition - ? $result - : null - ; + return $this->getDefinition($fqn); + } + + // return $result instanceof Definition + // ? $result + // : null + // ; } /** @@ -212,12 +199,7 @@ public function setDefinition(string $fqn, Definition $definition) // $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; $parts = $this->splitFqn($fqn); - - if ($definition->isMember) { - $this->indexDefinition(0, $parts, $this->memberDefinitions, $definition); - } else { - $this->indexDefinition(0, $parts, $this->nonMemberDefinitions, $definition); - } + $this->indexDefinition(0, $parts, $this->definitions, $definition); $this->emit('definition-added'); } @@ -242,9 +224,7 @@ public function removeDefinition(string $fqn) $parts = $this->splitFqn($fqn); - if (true !== $this->removeIndexedDefinition(0, $parts, $this->memberDefinitions)) { - $this->removeIndexedDefinition(0, $parts, $this->nonMemberDefinitions); - } + $this->removeIndexedDefinition(0, $parts, $this->definitions); unset($this->references[$fqn]); } @@ -317,6 +297,14 @@ public function unserialize($serialized) { $data = unserialize($serialized); + if (isset($data['definitions'])) { + foreach ($data['definitions'] as $fqn => $definition) { + $this->setDefinition($fqn, $definition); + } + + unset($data['definitions']); + } + foreach ($data as $prop => $val) { $this->$prop = $val; } @@ -329,8 +317,7 @@ public function unserialize($serialized) public function serialize() { return serialize([ - 'memberDefinitions' => $this->memberDefinitions, - 'nonMemberDefinitions' => $this->nonMemberDefinitions, + 'definitions' => iterator_to_array($this->getDefinitions(), true), 'references' => $this->references, 'complete' => $this->complete, 'staticComplete' => $this->staticComplete @@ -401,10 +388,12 @@ private function splitFqn(string $fqn): array } // split the last part in 2 parts at the operator + $hasOperator = false; $lastPart = end($parts); foreach (['::', '->'] as $operator) { $endParts = explode($operator, $lastPart); if (count($endParts) > 1) { + $hasOperator = true; // replace the last part by its pieces array_pop($parts); $parts[] = $endParts[0]; @@ -413,6 +402,16 @@ private function splitFqn(string $fqn): array } } + if (!$hasOperator) { + // add an empty part to store the non-member definition to avoid + // definition collisions in the index array, eg + // 'Psr\Log\LoggerInterface' will be stored at + // ['Psr']['\Log']['\LoggerInterface'][''] to be able to also store + // member definitions, ie 'Psr\Log\LoggerInterface->log()' will be + // stored at ['Psr']['\Log']['\LoggerInterface']['->log()'] + $parts[] = ''; + } + return $parts; } @@ -439,12 +438,6 @@ private function getIndexValue(array $parts, array &$storage) return $storage[$part]; } - if (!is_array($storage[$part]) && count($parts) > 0) { - // we're looking for a member definition in the non member index, - // no matches can be found. - return null; - } - return $this->getIndexValue($parts, $storage[$part]); } @@ -471,12 +464,6 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi $storage[$part] = []; } - if (!is_array($storage[$part])) { - // it's a non member definition, we can't add it to the member - // definitions index - return; - } - $this->indexDefinition($level + 1, $parts, $storage[$part], $definition); } @@ -491,7 +478,6 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi * @param array $parts The splitted FQN * @param array &$storage The current array in which to remove data * @param array &$rootStorage The root storage array - * @return boolean|null True when the definition has been found and removed, null otherwise. */ private function removeIndexedDefinition(int $level, array $parts, array &$storage, &$rootStorage) { @@ -504,16 +490,16 @@ private function removeIndexedDefinition(int $level, array $parts, array &$stora if (0 === $level) { // we're at root level, no need to check for parents // w/o children - return true; + return; } array_pop($parts); // parse again the definition tree to see if the parent // can be removed too if it has no more children - return $this->removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); + $this->removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); } } else { - return $this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); + $this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); } } } diff --git a/src/Indexer.php b/src/Indexer.php index 2618d43a..f7d7c80e 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -18,7 +18,7 @@ class Indexer /** * @var int The prefix for every cache item */ - const CACHE_VERSION = 2; + const CACHE_VERSION = 1; /** * @var FilesFinder From e162d94e14114601db77d5ae8943b43c1cbf88ac Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Tue, 14 Nov 2017 13:44:23 +0100 Subject: [PATCH 18/25] cleanup --- src/Index/AbstractAggregateIndex.php | 12 ------ src/Index/Index.php | 62 +--------------------------- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 8c88659e..19988894 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -107,10 +107,6 @@ public function isStaticComplete(): bool public function getDefinitions(): \Generator { foreach ($this->getIndexes() as $index) { - // foreach ($index->getDefinitions() as $fqn => $definition) { - // yield $fqn => $definition; - // } - yield from $index->getDefinitions(); } } @@ -124,10 +120,6 @@ public function getDefinitions(): \Generator public function getDefinitionsForFqn(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - // foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) { - // yield $symbolFqn => $definition; - // } - yield from $index->getDefinitionsForFqn($fqn); } } @@ -157,10 +149,6 @@ public function getDefinition(string $fqn, bool $globalFallback = false) public function getReferenceUris(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - // foreach ($index->getReferenceUris($fqn) as $uri) { - // yield $uri; - // } - yield from $index->getReferenceUris($fqn); } } diff --git a/src/Index/Index.php b/src/Index/Index.php index 45663836..a2927bde 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -103,12 +103,6 @@ public function isStaticComplete(): bool */ public function getDefinitions(): \Generator { - // foreach ($this->fqnDefinitions as $fqnDefinition) { - // foreach ($fqnDefinition as $fqn => $definition) { - // yield $fqn => $definition; - // } - // } - yield from $this->yieldDefinitionsRecursively($this->definitions); } @@ -120,14 +114,10 @@ public function getDefinitions(): \Generator */ public function getDefinitionsForFqn(string $fqn): \Generator { - // foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) { - // yield $symbolFqn => $definition; - // } - $parts = $this->splitFqn($fqn); if ('' === end($parts)) { // we want to return all the definitions in the given FQN, not only - // the one matching exactly the FQN. + // the one (non member) matching exactly the FQN. array_pop($parts); } @@ -149,19 +139,6 @@ public function getDefinitionsForFqn(string $fqn): \Generator */ public function getDefinition(string $fqn, bool $globalFallback = false) { - // $namespacedFqn = $this->extractNamespacedFqn($fqn); - // $definitions = $this->fqnDefinitions[$namespacedFqn] ?? []; - - // if (isset($definitions[$fqn])) { - // return $definitions[$fqn]; - // } - - // if ($globalFallback) { - // $parts = explode('\\', $fqn); - // $fqn = end($parts); - // return $this->getDefinition($fqn); - // } - $parts = $this->splitFqn($fqn); $result = $this->getIndexValue($parts, $this->definitions); @@ -175,11 +152,6 @@ public function getDefinition(string $fqn, bool $globalFallback = false) return $this->getDefinition($fqn); } - - // return $result instanceof Definition - // ? $result - // : null - // ; } /** @@ -191,13 +163,6 @@ public function getDefinition(string $fqn, bool $globalFallback = false) */ public function setDefinition(string $fqn, Definition $definition) { - // $namespacedFqn = $this->extractNamespacedFqn($fqn); - // if (!isset($this->fqnDefinitions[$namespacedFqn])) { - // $this->fqnDefinitions[$namespacedFqn] = []; - // } - - // $this->fqnDefinitions[$namespacedFqn][$fqn] = $definition; - $parts = $this->splitFqn($fqn); $this->indexDefinition(0, $parts, $this->definitions, $definition); @@ -213,17 +178,7 @@ public function setDefinition(string $fqn, Definition $definition) */ public function removeDefinition(string $fqn) { - // $namespacedFqn = $this->extractNamespacedFqn($fqn); - // if (isset($this->fqnDefinitions[$namespacedFqn])) { - // unset($this->fqnDefinitions[$namespacedFqn][$fqn]); - - // if (empty($this->fqnDefinitions[$namespacedFqn])) { - // unset($this->fqnDefinitions[$namespacedFqn]); - // } - // } - $parts = $this->splitFqn($fqn); - $this->removeIndexedDefinition(0, $parts, $this->definitions); unset($this->references[$fqn]); @@ -324,21 +279,6 @@ public function serialize() ]); } - /** - * @param string $fqn The symbol FQN - * @return string The namespaced FQN extracted from the given symbol FQN - */ - // private function extractNamespacedFqn(string $fqn): string - // { - // foreach (['::', '->'] as $operator) { - // if (false !== ($pos = strpos($fqn, $operator))) { - // return substr($fqn, 0, $pos); - // } - // } - - // return $fqn; - // } - /** * Returns a Genrerator containing all the into the given $storage recursively. * The generator yields key => value pairs, eg From 7437d30d884897050db466c538b81c03a4f4be2e Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 14 Nov 2017 22:25:54 -0800 Subject: [PATCH 19/25] Fix formatting --- src/Server/TextDocument.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 46e822ac..1c7d2923 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -220,8 +220,8 @@ public function references( } } $refDocuments = yield Promise\all(iterator_to_array( - $this->getOrLoadReferences($fqn)) - ); + $this->getOrLoadReferences($fqn) + )); foreach ($refDocuments as $document) { $refs = $document->getReferenceNodesByFqn($fqn); if ($refs !== null) { From b118c776991e2f259bb2bb313bf6de14b8cf23fa Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 14 Nov 2017 22:36:07 -0800 Subject: [PATCH 20/25] Correct some docblocks --- src/Index/Index.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index a2927bde..203b8818 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -301,12 +301,12 @@ private function yieldDefinitionsRecursively(array &$storage, string $prefix = ' /** * Splits the given FQN into an array, eg : - * 'Psr\Log\LoggerInterface->log' will be ['Psr', '\Log', '\LoggerInterface', '->log()'] - * '\Exception->getMessage()' will be ['\Exception', '->getMessage()'] - * 'PHP_VERSION' will be ['PHP_VERSION'] + * - `'Psr\Log\LoggerInterface->log'` will be `['Psr', '\Log', '\LoggerInterface', '->log()']` + * - `'\Exception->getMessage()'` will be `['\Exception', '->getMessage()']` + * - `'PHP_VERSION'` will be `['PHP_VERSION']` * * @param string $fqn - * @return array + * @return string[] */ private function splitFqn(string $fqn): array { @@ -360,7 +360,7 @@ private function splitFqn(string $fqn): array * It can be an index node or a Definition if the $parts are precise * enough. Returns null when nothing is found. * - * @param array $parts The splitted FQN + * @param string[] $parts The splitted FQN * @param array &$storage The array in which to store the $definition * @return array|Definition|null */ @@ -386,7 +386,7 @@ private function getIndexValue(array $parts, array &$storage) * array represented as a tree matching the given $parts. * * @param int $level The current level of FQN part - * @param array $parts The splitted FQN + * @param string[] $parts The splitted FQN * @param array &$storage The array in which to store the $definition * @param Definition $definition The Definition to store */ @@ -415,7 +415,7 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi * in the index. * * @param int $level The current level of FQN part - * @param array $parts The splitted FQN + * @param string[] $parts The splitted FQN * @param array &$storage The current array in which to remove data * @param array &$rootStorage The root storage array */ From b3f30f31f1c7979a6ada016ea536acfc7f7a9939 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Wed, 15 Nov 2017 09:51:18 +0100 Subject: [PATCH 21/25] cache is backward compatible --- src/Indexer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Indexer.php b/src/Indexer.php index f7d7c80e..2618d43a 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -18,7 +18,7 @@ class Indexer /** * @var int The prefix for every cache item */ - const CACHE_VERSION = 1; + const CACHE_VERSION = 2; /** * @var FilesFinder From fcdf791c2c00afabb47fb25e665c08623885dbef Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 16 Nov 2017 17:44:04 +0100 Subject: [PATCH 22/25] use string concatenation instead of sprintf --- src/Index/Index.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index 203b8818..7088e03c 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -292,9 +292,9 @@ private function yieldDefinitionsRecursively(array &$storage, string $prefix = ' { foreach ($storage as $key => $value) { if (!is_array($value)) { - yield sprintf('%s%s', $prefix, $key) => $value; + yield $prefix.$key => $value; } else { - yield from $this->yieldDefinitionsRecursively($value, sprintf('%s%s', $prefix, $key)); + yield from $this->yieldDefinitionsRecursively($value, $prefix.$key); } } } @@ -319,12 +319,12 @@ private function splitFqn(string $fqn): array $parts = array_slice($parts, 1); } - $parts[0] = sprintf('\\%s', $parts[0]); + $parts[0] = '\\'.$parts[0]; } // write back the backslashes prefixes for the other parts for ($i = 1; $i < count($parts); $i++) { - $parts[$i] = sprintf('\\%s', $parts[$i]); + $parts[$i] = '\\'.$parts[$i]; } // split the last part in 2 parts at the operator @@ -337,7 +337,7 @@ private function splitFqn(string $fqn): array // replace the last part by its pieces array_pop($parts); $parts[] = $endParts[0]; - $parts[] = sprintf('%s%s', $operator, $endParts[1]); + $parts[] = $operator.$endParts[1]; break; } } From b03950cb53cf492459ec3494d6316d106d324b24 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sat, 18 Nov 2017 14:53:18 -0800 Subject: [PATCH 23/25] Cleanup --- src/CompletionProvider.php | 4 ++-- src/Index/AbstractAggregateIndex.php | 12 +++++----- src/Index/Index.php | 34 +++++++++++++--------------- src/Index/ReadableIndex.php | 12 +++++----- src/Server/TextDocument.php | 21 ++++------------- 5 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index fe78ef77..2f223334 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -221,7 +221,7 @@ public function provideCompletion(PhpDocument $doc, Position $pos, CompletionCon // The FQNs of the symbol and its parents (eg the implemented interfaces) foreach ($this->expandParentFqns($fqns) as $parentFqn) { // Collect fqn definitions - foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { + foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn) as $fqn => $def) { // Add the object access operator to only get members of all parents $prefix = $parentFqn . '->'; if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) { @@ -251,7 +251,7 @@ public function provideCompletion(PhpDocument $doc, Position $pos, CompletionCon // The FQNs of the symbol and its parents (eg the implemented interfaces) foreach ($this->expandParentFqns($fqns) as $parentFqn) { // Collect fqn definitions - foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { + foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn) as $fqn => $def) { // Append :: operator to only get static members of all parents $prefix = strtolower($parentFqn . '::'); if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && $def->isMember) { diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 19988894..90490ab2 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -102,7 +102,7 @@ public function isStaticComplete(): bool * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * - * @return \Generator providing Definition[] + * @return \Generator yields Definition */ public function getDefinitions(): \Generator { @@ -112,15 +112,15 @@ public function getDefinitions(): \Generator } /** - * Returns a Generator providing the Definitions that are in the given FQN + * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn - * @return \Generator providing Definitions[] + * @return \Generator yields Definition */ - public function getDefinitionsForFqn(string $fqn): \Generator + public function getDescendantDefinitionsForFqn(string $fqn): \Generator { foreach ($this->getIndexes() as $index) { - yield from $index->getDefinitionsForFqn($fqn); + yield from $index->getDescendantDefinitionsForFqn($fqn); } } @@ -144,7 +144,7 @@ public function getDefinition(string $fqn, bool $globalFallback = false) * Returns a Generator providing all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return \Generator providing string[] + * @return \Generator yields string */ public function getReferenceUris(string $fqn): \Generator { diff --git a/src/Index/Index.php b/src/Index/Index.php index 7088e03c..40002173 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -99,7 +99,7 @@ public function isStaticComplete(): bool * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * - * @return \Generator providing Definition[] + * @return \Generator yields Definition */ public function getDefinitions(): \Generator { @@ -107,12 +107,12 @@ public function getDefinitions(): \Generator } /** - * Returns a Generator providing the Definitions that are in the given FQN + * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn - * @return \Generator providing Definitions[] + * @return \Generator yields Definition */ - public function getDefinitionsForFqn(string $fqn): \Generator + public function getDescendantDefinitionsForFqn(string $fqn): \Generator { $parts = $this->splitFqn($fqn); if ('' === end($parts)) { @@ -188,7 +188,7 @@ public function removeDefinition(string $fqn) * Returns a Generator providing all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return \Generator providing string[] + * @return \Generator yields string */ public function getReferenceUris(string $fqn): \Generator { @@ -280,9 +280,9 @@ public function serialize() } /** - * Returns a Genrerator containing all the into the given $storage recursively. - * The generator yields key => value pairs, eg - * 'Psr\Log\LoggerInterface->log()' => $definition + * Returns a Generator that yields all the Definitions in the given $storage recursively. + * The generator yields key => value pairs, e.g. + * `'Psr\Log\LoggerInterface->log()' => $definition` * * @param array &$storage * @param string $prefix (optional) @@ -319,12 +319,12 @@ private function splitFqn(string $fqn): array $parts = array_slice($parts, 1); } - $parts[0] = '\\'.$parts[0]; + $parts[0] = '\\' . $parts[0]; } // write back the backslashes prefixes for the other parts for ($i = 1; $i < count($parts); $i++) { - $parts[$i] = '\\'.$parts[$i]; + $parts[$i] = '\\' . $parts[$i]; } // split the last part in 2 parts at the operator @@ -337,7 +337,7 @@ private function splitFqn(string $fqn): array // replace the last part by its pieces array_pop($parts); $parts[] = $endParts[0]; - $parts[] = $operator.$endParts[1]; + $parts[] = $operator . $endParts[1]; break; } } @@ -382,8 +382,8 @@ private function getIndexValue(array $parts, array &$storage) } /** - * Recusrive function which store the given definition in the given $storage - * array represented as a tree matching the given $parts. + * Recursive function that stores the given Definition in the given $storage array represented + * as a tree matching the given $parts. * * @param int $level The current level of FQN part * @param string[] $parts The splitted FQN @@ -408,11 +408,9 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi } /** - * Recusrive function which remove the definition matching the given $parts - * from the given $storage array. - * The function also looks up recursively to remove the parents of the - * definition which no longer has children to avoid to let empty arrays - * in the index. + * Recursive function that removes the definition matching the given $parts from the given + * $storage array. The function also looks up recursively to remove the parents of the + * definition which no longer has children to avoid to let empty arrays in the index. * * @param int $level The current level of FQN part * @param string[] $parts The splitted FQN diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 5c54557d..90ddcc45 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -33,17 +33,17 @@ public function isStaticComplete(): bool; * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * - * @return \Generator providing Definition[] + * @return \Generator yields Definition */ public function getDefinitions(): \Generator; /** - * Returns a Generator providing the Definitions that are in the given FQN + * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn - * @return \Generator providing Definitions[] + * @return \Generator yields Definition */ - public function getDefinitionsForFqn(string $fqn): \Generator; + public function getDescendantDefinitionsForFqn(string $fqn): \Generator; /** * Returns the Definition object by a specific FQN @@ -55,10 +55,10 @@ public function getDefinitionsForFqn(string $fqn): \Generator; public function getDefinition(string $fqn, bool $globalFallback = false); /** - * Returns a Generator providing all URIs in this index that reference a symbol + * Returns a Generator that yields all URIs in this index that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return \Generator providing string[] + * @return \Generator yields string */ public function getReferenceUris(string $fqn): \Generator; } diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 53d804f6..0ad2d815 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -220,9 +220,11 @@ public function references( return []; } } - $refDocuments = yield Promise\all(iterator_to_array( - $this->getOrLoadReferences($fqn) - )); + $refDocumentPromises = []; + foreach ($this->index->getReferenceUris($fqn) as $uri) { + $refDocumentPromises[] = $this->documentLoader->getOrLoad($uri); + } + $refDocuments = yield Promise\all($refDocumentPromises); foreach ($refDocuments as $document) { $refs = $document->getReferenceNodesByFqn($fqn); if ($refs !== null) { @@ -398,17 +400,4 @@ public function xdefinition(TextDocumentIdentifier $textDocument, Position $posi return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)]; }); } - - /** - * Gets or loads the documents referencing the given FQN. - * - * @param string $fqn - * @return \Generator providing Promise - */ - private function getOrLoadReferences(string $fqn): \Generator - { - foreach ($this->index->getReferenceUris($fqn) as $ref) { - yield $this->documentLoader->getOrLoad($ref); - } - } } From 97ec127312069ca9a4a1b78aed6128022361129b Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 19 Nov 2017 16:34:51 +0100 Subject: [PATCH 24/25] fix definition removal --- src/Index/Index.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index 40002173..dcb1b8f7 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -179,7 +179,7 @@ public function setDefinition(string $fqn, Definition $definition) public function removeDefinition(string $fqn) { $parts = $this->splitFqn($fqn); - $this->removeIndexedDefinition(0, $parts, $this->definitions); + $this->removeIndexedDefinition(0, $parts, $this->definitions, $this->definitions); unset($this->references[$fqn]); } @@ -417,24 +417,19 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi * @param array &$storage The current array in which to remove data * @param array &$rootStorage The root storage array */ - private function removeIndexedDefinition(int $level, array $parts, array &$storage, &$rootStorage) + private function removeIndexedDefinition(int $level, array $parts, array &$storage, array &$rootStorage) { $part = $parts[$level]; if ($level + 1 === count($parts)) { - if (isset($storage[$part]) && count($storage[$part]) < 2) { + if (isset($storage[$part])) { unset($storage[$part]); - if (0 === $level) { - // we're at root level, no need to check for parents - // w/o children - return; + if (0 === count($storage)) { + // parse again the definition tree to remove the parent + // when it has no more children + $this->removeIndexedDefinition(0, array_slice($parts, 0, $level), $rootStorage, $rootStorage); } - - array_pop($parts); - // parse again the definition tree to see if the parent - // can be removed too if it has no more children - $this->removeIndexedDefinition(0, $parts, $rootStorage, $rootStorage); } } else { $this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); From 48bbbb5d14a3d1fc01fce7e48383f0ba1690c2bb Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 19 Nov 2017 18:29:23 +0100 Subject: [PATCH 25/25] differenciate member and non member definitions --- src/CompletionProvider.php | 14 ++--- src/Index/AbstractAggregateIndex.php | 10 +-- src/Index/Index.php | 91 ++++++++++++++++++++++----- src/Index/ReadableIndex.php | 6 +- tests/Server/Workspace/SymbolTest.php | 31 +++++---- 5 files changed, 105 insertions(+), 47 deletions(-) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 2f223334..a5f91194 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -221,10 +221,10 @@ public function provideCompletion(PhpDocument $doc, Position $pos, CompletionCon // The FQNs of the symbol and its parents (eg the implemented interfaces) foreach ($this->expandParentFqns($fqns) as $parentFqn) { // Collect fqn definitions - foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn) as $fqn => $def) { + foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn, true) as $fqn => $def) { // Add the object access operator to only get members of all parents $prefix = $parentFqn . '->'; - if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) { + if (substr($fqn, 0, strlen($prefix)) === $prefix) { $list->items[] = CompletionItem::fromDefinition($def); } } @@ -251,10 +251,10 @@ public function provideCompletion(PhpDocument $doc, Position $pos, CompletionCon // The FQNs of the symbol and its parents (eg the implemented interfaces) foreach ($this->expandParentFqns($fqns) as $parentFqn) { // Collect fqn definitions - foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn) as $fqn => $def) { + foreach ($this->index->getDescendantDefinitionsForFqn($parentFqn, true) as $fqn => $def) { // Append :: operator to only get static members of all parents $prefix = strtolower($parentFqn . '::'); - if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix && $def->isMember) { + if (substr(strtolower($fqn), 0, strlen($prefix)) === $prefix) { $list->items[] = CompletionItem::fromDefinition($def); } } @@ -321,14 +321,12 @@ public function provideCompletion(PhpDocument $doc, Position $pos, CompletionCon // Suggest global (ie non member) 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->index->getDefinitions() as $fqn => $def) { + foreach ($this->index->getDefinitions(false) as $fqn => $def) { $fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix; if ( - // Exclude methods, properties etc. - !$def->isMember - && ( + ( !$prefix || ( // Either not qualified, but a matching prefix with global fallback diff --git a/src/Index/AbstractAggregateIndex.php b/src/Index/AbstractAggregateIndex.php index 90490ab2..3458d552 100644 --- a/src/Index/AbstractAggregateIndex.php +++ b/src/Index/AbstractAggregateIndex.php @@ -102,12 +102,13 @@ public function isStaticComplete(): bool * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDefinitions(): \Generator + public function getDefinitions(bool $member = null): \Generator { foreach ($this->getIndexes() as $index) { - yield from $index->getDefinitions(); + yield from $index->getDefinitions($member); } } @@ -115,12 +116,13 @@ public function getDefinitions(): \Generator * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDescendantDefinitionsForFqn(string $fqn): \Generator + public function getDescendantDefinitionsForFqn(string $fqn, bool $member = null): \Generator { foreach ($this->getIndexes() as $index) { - yield from $index->getDescendantDefinitionsForFqn($fqn); + yield from $index->getDescendantDefinitionsForFqn($fqn, $member); } } diff --git a/src/Index/Index.php b/src/Index/Index.php index dcb1b8f7..1099fab7 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -16,13 +16,12 @@ class Index implements ReadableIndex, \Serializable /** * An associative array that maps splitted fully qualified symbol names - * to definitions, eg : + * to non-member definitions, eg : * [ * 'Psr' => [ * '\Log' => [ * '\LoggerInterface' => [ - * '' => $def1, // definition for 'Psr\Log\LoggerInterface' which is non-member - * '->log()' => $def2, // definition for 'Psr\Log\LoggerInterface->log()' which is a member definition + * '' => $definition, * ], * ], * ], @@ -30,7 +29,24 @@ class Index implements ReadableIndex, \Serializable * * @var array */ - private $definitions = []; + private $nonMemberDefinitions = []; + + /** + * An associative array that maps splitted fully qualified symbol names + * to member definitions, eg : + * [ + * 'Psr' => [ + * '\Log' => [ + * '\LoggerInterface' => [ + * '->log()' => $definition, + * ], + * ], + * ], + * ] + * + * @var array + */ + private $memberDefinitions = []; /** * An associative array that maps fully qualified symbol names @@ -99,20 +115,29 @@ public function isStaticComplete(): bool * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDefinitions(): \Generator + public function getDefinitions(bool $member = null): \Generator { - yield from $this->yieldDefinitionsRecursively($this->definitions); + if (true === $member) { + yield from $this->yieldDefinitionsRecursively($this->memberDefinitions); + } elseif (false === $member) { + yield from $this->yieldDefinitionsRecursively($this->nonMemberDefinitions); + } else { + yield from $this->yieldDefinitionsRecursively($this->memberDefinitions); + yield from $this->yieldDefinitionsRecursively($this->nonMemberDefinitions); + } } /** * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDescendantDefinitionsForFqn(string $fqn): \Generator + public function getDescendantDefinitionsForFqn(string $fqn, bool $member = null): \Generator { $parts = $this->splitFqn($fqn); if ('' === end($parts)) { @@ -121,12 +146,13 @@ public function getDescendantDefinitionsForFqn(string $fqn): \Generator array_pop($parts); } - $result = $this->getIndexValue($parts, $this->definitions); - - if ($result instanceof Definition) { - yield $fqn => $result; - } elseif (is_array($result)) { - yield from $this->yieldDefinitionsRecursively($result, $fqn); + if (true === $member) { + yield from $this->doGetDescendantDefinitionsForFqn($fqn, $parts, $this->memberDefinitions); + } elseif (false === $member) { + yield from $this->doGetDescendantDefinitionsForFqn($fqn, $parts, $this->nonMemberDefinitions); + } else { + yield from $this->doGetDescendantDefinitionsForFqn($fqn, $parts, $this->memberDefinitions); + yield from $this->doGetDescendantDefinitionsForFqn($fqn, $parts, $this->nonMemberDefinitions); } } @@ -140,8 +166,13 @@ public function getDescendantDefinitionsForFqn(string $fqn): \Generator public function getDefinition(string $fqn, bool $globalFallback = false) { $parts = $this->splitFqn($fqn); - $result = $this->getIndexValue($parts, $this->definitions); + $result = $this->getIndexValue($parts, $this->memberDefinitions); + if ($result instanceof Definition) { + return $result; + } + + $result = $this->getIndexValue($parts, $this->nonMemberDefinitions); if ($result instanceof Definition) { return $result; } @@ -164,7 +195,12 @@ public function getDefinition(string $fqn, bool $globalFallback = false) public function setDefinition(string $fqn, Definition $definition) { $parts = $this->splitFqn($fqn); - $this->indexDefinition(0, $parts, $this->definitions, $definition); + + if ($definition->isMember) { + $this->indexDefinition(0, $parts, $this->memberDefinitions, $definition); + } else { + $this->indexDefinition(0, $parts, $this->nonMemberDefinitions, $definition); + } $this->emit('definition-added'); } @@ -179,7 +215,8 @@ public function setDefinition(string $fqn, Definition $definition) public function removeDefinition(string $fqn) { $parts = $this->splitFqn($fqn); - $this->removeIndexedDefinition(0, $parts, $this->definitions, $this->definitions); + $this->removeIndexedDefinition(0, $parts, $this->memberDefinitions, $this->memberDefinitions); + $this->removeIndexedDefinition(0, $parts, $this->nonMemberDefinitions, $this->nonMemberDefinitions); unset($this->references[$fqn]); } @@ -279,6 +316,26 @@ public function serialize() ]); } + /** + * Returns a Generator that yields all the descendant Definitions of a given FQN + * in the given definition index. + * + * @param string $fqn + * @param string[] $parts The splitted FQN + * @param array &$storage The definitions index to look into + * @return \Generator yields Definition + */ + private function doGetDescendantDefinitionsForFqn(string $fqn, array $parts, array &$storage): \Generator + { + $result = $this->getIndexValue($parts, $storage); + + if ($result instanceof Definition) { + yield $fqn => $result; + } elseif (is_array($result)) { + yield from $this->yieldDefinitionsRecursively($result, $fqn); + } + } + /** * Returns a Generator that yields all the Definitions in the given $storage recursively. * The generator yields key => value pairs, e.g. @@ -431,7 +488,7 @@ private function removeIndexedDefinition(int $level, array $parts, array &$stora $this->removeIndexedDefinition(0, array_slice($parts, 0, $level), $rootStorage, $rootStorage); } } - } else { + } elseif (isset($storage[$part])) { $this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage); } } diff --git a/src/Index/ReadableIndex.php b/src/Index/ReadableIndex.php index 90ddcc45..d02f22b0 100644 --- a/src/Index/ReadableIndex.php +++ b/src/Index/ReadableIndex.php @@ -33,17 +33,19 @@ public function isStaticComplete(): bool; * Returns a Generator providing an associative array [string => Definition] * that maps fully qualified symbol names to Definitions (global or not) * + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDefinitions(): \Generator; + public function getDefinitions(bool $member = null): \Generator; /** * Returns a Generator that yields all the descendant Definitions of a given FQN * * @param string $fqn + * @param boolean|null $member Indicates if we want member or non-member definitions (null for both, default null) * @return \Generator yields Definition */ - public function getDescendantDefinitionsForFqn(string $fqn): \Generator; + public function getDescendantDefinitionsForFqn(string $fqn, bool $member = null): \Generator; /** * Returns the Definition object by a specific FQN diff --git a/tests/Server/Workspace/SymbolTest.php b/tests/Server/Workspace/SymbolTest.php index 765841bb..02de5abe 100644 --- a/tests/Server/Workspace/SymbolTest.php +++ b/tests/Server/Workspace/SymbolTest.php @@ -30,41 +30,40 @@ public function testEmptyQueryReturnsAllSymbols() // @codingStandardsIgnoreStart $this->assertEquals([ - new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''), - // Namespaced - new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), - new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), + // member new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'), new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'), new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'), new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'), new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'), + new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'), + new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'), + new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'), + new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'), + new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'), + new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'), + new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'), + // non member + new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''), + new SymbolInformation('unusedProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('UnusedClass::unusedProperty'), 'UnusedClass'), + new SymbolInformation('unusedMethod', SymbolKind::METHOD, $this->getDefinitionLocation('UnusedClass::unusedMethod'), 'UnusedClass'), + new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''), + new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), + new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'), new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'), new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'), new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'), new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'), - new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'), - new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'), new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'), - // Global new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''), new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''), - new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'), - new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'), - new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'), - new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'), - new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'), new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''), new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''), new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''), new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''), - new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''), new SymbolInformation('UnusedClass', SymbolKind::CLASS_, $this->getDefinitionLocation('UnusedClass'), ''), - new SymbolInformation('unusedProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('UnusedClass::unusedProperty'), 'UnusedClass'), - new SymbolInformation('unusedMethod', SymbolKind::METHOD, $this->getDefinitionLocation('UnusedClass::unusedMethod'), 'UnusedClass'), new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''), - new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), ''), ], $result); // @codingStandardsIgnoreEnd