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) { foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) {
// Add the object access operator to only get members of all parents // Add the object access operator to only get members of all parents
$prefix = $parentFqn . '->'; $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); $list->items[] = CompletionItem::fromDefinition($def);
} }
} }
@ -237,7 +237,7 @@ class CompletionProvider
foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) { foreach ($this->index->getDefinitionsForFqn($parentFqn) as $fqn => $def) {
// Append :: operator to only get static members of all parents // Append :: operator to only get static members of all parents
$prefix = strtolower($parentFqn . '::'); $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); $list->items[] = CompletionItem::fromDefinition($def);
} }
} }
@ -301,14 +301,17 @@ 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 the current namespace + prefix, if the Name node is not fully qualified
// - start with just the prefix, if the Name node is fully qualified // - start with just the prefix, if the Name node is fully qualified
foreach ($this->index->getGlobalDefinitions() as $fqn => $def) { foreach ($this->index->getDefinitions() as $fqn => $def) {
$fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix; $fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix;
if ( if (
// Exclude methods, properties etc.
!$def->isMember
&& (
!$prefix !$prefix
|| ( || (
// Either not qualified, but a matching prefix with global fallback // Either not qualified, but a matching prefix with global fallback
@ -322,6 +325,7 @@ class CompletionProvider
&& substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix
) )
) )
)
// Only suggest classes for `new` // Only suggest classes for `new`
&& (!isset($creation) || $def->canBeInstantiated) && (!isset($creation) || $def->canBeInstantiated)
) { ) {

View File

@ -107,24 +107,11 @@ abstract class AbstractAggregateIndex implements ReadableIndex
public function getDefinitions(): \Generator public function getDefinitions(): \Generator
{ {
foreach ($this->getIndexes() as $index) { foreach ($this->getIndexes() as $index) {
foreach ($index->getDefinitions() as $fqn => $definitions) { // foreach ($index->getDefinitions() as $fqn => $definition) {
yield $fqn => $definition; // yield $fqn => $definition;
} // }
}
}
/** yield from $index->getDefinitions();
* 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;
}
} }
} }
@ -137,9 +124,11 @@ abstract class AbstractAggregateIndex implements ReadableIndex
public function getDefinitionsForFqn(string $fqn): \Generator public function getDefinitionsForFqn(string $fqn): \Generator
{ {
foreach ($this->getIndexes() as $index) { foreach ($this->getIndexes() as $index) {
foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) { // foreach ($index->getDefinitionsForFqn($fqn) as $symbolFqn => $definition) {
yield $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 public function getReferenceUris(string $fqn): \Generator
{ {
foreach ($this->getIndexes() as $index) { foreach ($this->getIndexes() as $index) {
foreach ($index->getReferenceUris($fqn) as $uri) { // foreach ($index->getReferenceUris($fqn) as $uri) {
yield $uri; // yield $uri;
} // }
yield from $index->getReferenceUris($fqn);
} }
} }
} }

View File

@ -15,27 +15,21 @@ class Index implements ReadableIndex, \Serializable
use EmitterTrait; use EmitterTrait;
/** /**
* An associative array that maps fully qualified names to * An associative array that maps splitted fully qualified symbol names
* an associative array that maps fully qualified symbol names * to definitions, eg :
* to Definitions, e.g. :
* [ * [
* 'Psr\Log\LoggerInterface' => [ * 'Psr' => [
* 'Psr\Log\LoggerInterface->log()' => $definition, * '\Log' => [
* 'Psr\Log\LoggerInterface->debug()' => $definition, * '\LoggerInterface' => [
* '->log()' => $definition,
* ],
* ],
* ], * ],
* ] * ]
* *
* @var array * @var array
*/ */
private $fqnDefinitions = []; private $definitions = [];
/**
* An associative array that maps fully qualified symbol names
* to global (ie non member) Definitions
*
* @var Definition[]
*/
private $globalDefinitions = [];
/** /**
* An associative array that maps fully qualified symbol names * An associative array that maps fully qualified symbol names
@ -108,24 +102,13 @@ class Index implements ReadableIndex, \Serializable
*/ */
public function getDefinitions(): \Generator public function getDefinitions(): \Generator
{ {
foreach ($this->fqnDefinitions as $fqnDefinition) { // foreach ($this->fqnDefinitions as $fqnDefinition) {
foreach ($fqnDefinition as $fqn => $definition) { // foreach ($fqnDefinition as $fqn => $definition) {
yield $fqn => $definition; // yield $fqn => $definition;
} // }
} // }
}
/** yield from $this->generateDefinitionsRecursively($this->definitions);
* 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;
}
} }
/** /**
@ -136,8 +119,17 @@ class Index implements ReadableIndex, \Serializable
*/ */
public function getDefinitionsForFqn(string $fqn): \Generator public function getDefinitionsForFqn(string $fqn): \Generator
{ {
foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) { // foreach ($this->fqnDefinitions[$fqn] ?? [] as $symbolFqn => $definition) {
yield $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) public function getDefinition(string $fqn, bool $globalFallback = false)
{ {
$namespacedFqn = $this->extractNamespacedFqn($fqn); // $namespacedFqn = $this->extractNamespacedFqn($fqn);
$definitions = $this->fqnDefinitions[$namespacedFqn] ?? []; // $definitions = $this->fqnDefinitions[$namespacedFqn] ?? [];
if (isset($definitions[$fqn])) { // if (isset($definitions[$fqn])) {
return $definitions[$fqn]; // return $definitions[$fqn];
} // }
if ($globalFallback) { // if ($globalFallback) {
$parts = explode('\\', $fqn); // $parts = explode('\\', $fqn);
$fqn = end($parts); // $fqn = end($parts);
return $this->getDefinition($fqn); // 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) public function setDefinition(string $fqn, Definition $definition)
{ {
$namespacedFqn = $this->extractNamespacedFqn($fqn); // $namespacedFqn = $this->extractNamespacedFqn($fqn);
if (!isset($this->fqnDefinitions[$namespacedFqn])) { // if (!isset($this->fqnDefinitions[$namespacedFqn])) {
$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'); $this->emit('definition-added');
} }
@ -192,16 +192,18 @@ class Index implements ReadableIndex, \Serializable
*/ */
public function removeDefinition(string $fqn) public function removeDefinition(string $fqn)
{ {
$namespacedFqn = $this->extractNamespacedFqn($fqn); // $namespacedFqn = $this->extractNamespacedFqn($fqn);
if (isset($this->fqnDefinitions[$namespacedFqn])) { // if (isset($this->fqnDefinitions[$namespacedFqn])) {
unset($this->fqnDefinitions[$namespacedFqn][$fqn]); // unset($this->fqnDefinitions[$namespacedFqn][$fqn]);
if (empty($this->fqnDefinitions[$namespacedFqn])) { // if (empty($this->fqnDefinitions[$namespacedFqn])) {
unset($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]); unset($this->references[$fqn]);
} }
@ -276,12 +278,6 @@ class Index implements ReadableIndex, \Serializable
foreach ($data as $prop => $val) { foreach ($data as $prop => $val) {
$this->$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() public function serialize()
{ {
return serialize([ return serialize([
'fqnDefinitions' => $this->fqnDefinitions, 'definitions' => $this->definitions,
'references' => $this->references, 'references' => $this->references,
'complete' => $this->complete, 'complete' => $this->complete,
'staticComplete' => $this->staticComplete '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 symbol FQN
* * @return string The namespaced FQN extracted from the given symbol FQN
* @param string $fqn The fully qualified name of the symbol
* @param Definition $definition The Definition object
* @return void
*/ */
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) { foreach ($storage as $key => $value) {
$this->globalDefinitions[$fqn] = $definition; $prefix .= $key;
if (!is_array($value)) {
yield $prefix => $value;
} else {
yield from generateDefinitionsRecursively($value, $prefix);
}
} }
} }
/** /**
* @param string $fqn The symbol FQN * Splits the given FQN into an array, eg :
* @return string The namespaced FQN extracted from the given symbol FQN * '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) { foreach (['::', '->'] as $operator) {
if (false !== ($pos = strpos($fqn, $operator))) { $endParts = explode($operator, $lastPart);
return substr($fqn, 0, $pos); 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; 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 * Returns a Generator providing the Definitions that are in the given FQN
* *