1
0
Fork 0

use tree representation index

pull/451/head
Nicolas MURE 2017-11-13 21:26:43 +01:00
parent e9fd572a49
commit d1f85f15b6
No known key found for this signature in database
GPG Key ID: E5B036F9145C4CAA
4 changed files with 245 additions and 126 deletions

View File

@ -207,7 +207,7 @@ class CompletionProvider
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 @@ class CompletionProvider
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 @@ class CompletionProvider
}
}
// 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`

View File

@ -107,24 +107,11 @@ abstract class AbstractAggregateIndex implements ReadableIndex
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 @@ abstract class AbstractAggregateIndex implements ReadableIndex
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 @@ abstract class AbstractAggregateIndex implements ReadableIndex
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);
}
}
}

View File

@ -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 @@ class Index implements ReadableIndex, \Serializable
*/
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 @@ class Index implements ReadableIndex, \Serializable
*/
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 @@ class Index implements ReadableIndex, \Serializable
*/
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 @@ class Index implements ReadableIndex, \Serializable
*/
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 @@ class Index implements ReadableIndex, \Serializable
*/
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 @@ class Index implements ReadableIndex, \Serializable
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 @@ class Index implements ReadableIndex, \Serializable
public function serialize()
{
return serialize([
'fqnDefinitions' => $this->fqnDefinitions,
'definitions' => $this->definitions,
'references' => $this->references,
'complete' => $this->complete,
'staticComplete' => $this->staticComplete
@ -299,31 +295,167 @@ class Index implements ReadableIndex, \Serializable
}
/**
* 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
* @param string $fqn The symbol FQN
* @return string The namespaced FQN extracted from the given symbol FQN
*/
private function setGlobalDefinition(string $fqn, Definition $definition)
// 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 array &$storage
* @param string $prefix (optional)
* @return \Generator
*/
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);
}
}
}

View File

@ -37,14 +37,6 @@ interface ReadableIndex extends EmitterInterface
*/
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
*