2016-12-13 00:51:02 +00:00
|
|
|
<?php
|
|
|
|
declare(strict_types = 1);
|
|
|
|
|
|
|
|
namespace LanguageServer\Index;
|
|
|
|
|
|
|
|
use LanguageServer\Definition;
|
2017-01-25 00:38:11 +00:00
|
|
|
use Sabre\Event\EmitterTrait;
|
2016-12-13 00:51:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents the index of a project or dependency
|
|
|
|
* Serializable for caching
|
|
|
|
*/
|
2017-02-03 23:20:38 +00:00
|
|
|
class Index implements ReadableIndex, \Serializable
|
2016-12-13 00:51:02 +00:00
|
|
|
{
|
2017-01-25 00:38:11 +00:00
|
|
|
use EmitterTrait;
|
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
2017-08-09 20:10:13 +00:00
|
|
|
* An associative array that maps namespaces to
|
2017-08-10 08:01:18 +00:00
|
|
|
* 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,
|
|
|
|
* ],
|
|
|
|
* ]
|
2016-12-13 00:51:02 +00:00
|
|
|
*
|
2017-08-09 20:10:13 +00:00
|
|
|
* @var array
|
2016-12-13 00:51:02 +00:00
|
|
|
*/
|
2017-08-09 20:10:13 +00:00
|
|
|
private $namespaceDefinitions = [];
|
2016-12-13 00:51:02 +00:00
|
|
|
|
2017-08-06 21:05:32 +00:00
|
|
|
/**
|
2017-08-10 08:01:18 +00:00
|
|
|
* An associative array that maps fully qualified symbol names
|
|
|
|
* to global Definitions
|
2017-08-06 21:05:32 +00:00
|
|
|
*
|
|
|
|
* @var Definition[]
|
|
|
|
*/
|
|
|
|
private $globalDefinitions = [];
|
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
2017-08-10 08:01:18 +00:00
|
|
|
* An associative array that maps fully qualified symbol names
|
|
|
|
* to arrays of document URIs that reference the symbol
|
2016-12-13 00:51:02 +00:00
|
|
|
*
|
|
|
|
* @var string[][]
|
|
|
|
*/
|
|
|
|
private $references = [];
|
|
|
|
|
2017-01-25 00:38:11 +00:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $complete = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $staticComplete = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Marks this index as complete
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setComplete()
|
|
|
|
{
|
|
|
|
if (!$this->isStaticComplete()) {
|
|
|
|
$this->setStaticComplete();
|
|
|
|
}
|
|
|
|
$this->complete = true;
|
|
|
|
$this->emit('complete');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Marks this index as complete for static definitions and references
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setStaticComplete()
|
|
|
|
{
|
|
|
|
$this->staticComplete = true;
|
|
|
|
$this->emit('static-complete');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if this index is complete
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isComplete(): bool
|
|
|
|
{
|
|
|
|
return $this->complete;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if this index is complete
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isStaticComplete(): bool
|
|
|
|
{
|
|
|
|
return $this->staticComplete;
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
2017-08-09 20:10:13 +00:00
|
|
|
* Returns a Generator providing an associative array [string => Definition]
|
|
|
|
* that maps fully qualified symbol names to Definitions (global or not)
|
2016-12-13 00:51:02 +00:00
|
|
|
*
|
2017-08-09 20:10:13 +00:00
|
|
|
* @return \Generator providing Definition[]
|
2016-12-13 00:51:02 +00:00
|
|
|
*/
|
2017-08-09 20:10:13 +00:00
|
|
|
public function getDefinitions(): \Generator
|
2016-12-13 00:51:02 +00:00
|
|
|
{
|
2017-08-09 20:10:13 +00:00
|
|
|
foreach ($this->namespaceDefinitions as $namespaceDefinition) {
|
|
|
|
foreach ($namespaceDefinition as $fqn => $definition) {
|
|
|
|
yield $fqn => $definition;
|
|
|
|
}
|
|
|
|
}
|
2016-12-13 00:51:02 +00:00
|
|
|
}
|
|
|
|
|
2017-08-06 21:05:32 +00:00
|
|
|
/**
|
2017-08-09 20:51:42 +00:00
|
|
|
* Returns a Generator providing an associative array [string => Definition]
|
|
|
|
* that maps fully qualified symbol names to global Definitions
|
2017-08-06 21:05:32 +00:00
|
|
|
*
|
2017-08-09 20:51:42 +00:00
|
|
|
* @return \Generator providing Definitions[]
|
2017-08-06 21:05:32 +00:00
|
|
|
*/
|
2017-08-09 20:51:42 +00:00
|
|
|
public function getGlobalDefinitions(): \Generator
|
2017-08-06 21:05:32 +00:00
|
|
|
{
|
2017-08-09 20:51:42 +00:00
|
|
|
foreach ($this->globalDefinitions as $fqn => $definition) {
|
|
|
|
yield $fqn => $definition;
|
|
|
|
}
|
2017-08-06 21:05:32 +00:00
|
|
|
}
|
|
|
|
|
2017-08-06 14:48:41 +00:00
|
|
|
/**
|
2017-08-09 20:51:42 +00:00
|
|
|
* Returns a Generator providing the Definitions that are in the given namespace
|
2017-08-06 14:48:41 +00:00
|
|
|
*
|
|
|
|
* @param string $namespace
|
2017-08-09 20:51:42 +00:00
|
|
|
* @return \Generator providing Definitions[]
|
2017-08-06 14:48:41 +00:00
|
|
|
*/
|
2017-08-09 20:51:42 +00:00
|
|
|
public function getDefinitionsForNamespace(string $namespace): \Generator
|
2017-08-06 14:48:41 +00:00
|
|
|
{
|
2017-08-09 20:51:42 +00:00
|
|
|
foreach ($this->doGetDefinitionsForNamespace($namespace) as $fqn => $definition) {
|
|
|
|
yield $fqn => $definition;
|
|
|
|
}
|
2017-08-06 14:48:41 +00:00
|
|
|
}
|
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
|
|
|
* Returns the Definition object by a specific FQN
|
|
|
|
*
|
|
|
|
* @param string $fqn
|
|
|
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
|
|
|
* @return Definition|null
|
|
|
|
*/
|
|
|
|
public function getDefinition(string $fqn, bool $globalFallback = false)
|
|
|
|
{
|
2017-08-09 20:10:13 +00:00
|
|
|
$namespace = $this->extractNamespace($fqn);
|
2017-08-09 20:51:42 +00:00
|
|
|
$definitions = $this->doGetDefinitionsForNamespace($namespace);
|
2017-08-09 20:10:13 +00:00
|
|
|
|
|
|
|
if (isset($definitions[$fqn])) {
|
|
|
|
return $definitions[$fqn];
|
2016-12-13 00:51:02 +00:00
|
|
|
}
|
2017-08-09 20:10:13 +00:00
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
if ($globalFallback) {
|
|
|
|
$parts = explode('\\', $fqn);
|
|
|
|
$fqn = end($parts);
|
|
|
|
return $this->getDefinition($fqn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a definition
|
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
2017-04-17 15:03:08 +00:00
|
|
|
* @param Definition $definition The Definition object
|
2016-12-13 00:51:02 +00:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setDefinition(string $fqn, Definition $definition)
|
|
|
|
{
|
2017-08-09 20:10:13 +00:00
|
|
|
$namespace = $this->extractNamespace($fqn);
|
|
|
|
if (!isset($this->namespaceDefinitions[$namespace])) {
|
|
|
|
$this->namespaceDefinitions[$namespace] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->namespaceDefinitions[$namespace][$fqn] = $definition;
|
2017-08-06 21:05:32 +00:00
|
|
|
$this->setGlobalDefinition($fqn, $definition);
|
2017-01-25 00:38:11 +00:00
|
|
|
$this->emit('definition-added');
|
2016-12-13 00:51:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unsets the Definition for a specific symbol
|
|
|
|
* and removes all references pointing to that symbol
|
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeDefinition(string $fqn)
|
|
|
|
{
|
2017-08-09 20:10:13 +00:00
|
|
|
$namespace = $this->extractNamespace($fqn);
|
|
|
|
if (isset($this->namespaceDefinitions[$namespace])) {
|
|
|
|
unset($this->namespaceDefinitions[$namespace][$fqn]);
|
|
|
|
|
|
|
|
if (empty($this->namespaceDefinitions[$namespace])) {
|
|
|
|
unset($this->namespaceDefinitions[$namespace]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-06 21:05:32 +00:00
|
|
|
unset($this->globalDefinitions[$fqn]);
|
2016-12-13 00:51:02 +00:00
|
|
|
unset($this->references[$fqn]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-08-09 22:06:53 +00:00
|
|
|
* Returns a Generator providing all URIs in this index that reference a symbol
|
2016-12-13 00:51:02 +00:00
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
2017-08-09 22:06:53 +00:00
|
|
|
* @return \Generator providing string[]
|
2016-12-13 00:51:02 +00:00
|
|
|
*/
|
2017-08-09 22:06:53 +00:00
|
|
|
public function getReferenceUris(string $fqn): \Generator
|
2016-12-13 00:51:02 +00:00
|
|
|
{
|
2017-08-09 22:06:53 +00:00
|
|
|
$uris = isset($this->references[$fqn])
|
|
|
|
? $this->references[$fqn]
|
|
|
|
: []
|
|
|
|
;
|
|
|
|
|
|
|
|
foreach ($uris as $uri) {
|
|
|
|
yield $uri;
|
|
|
|
}
|
2016-12-13 00:51:02 +00:00
|
|
|
}
|
|
|
|
|
2017-06-09 18:25:30 +00:00
|
|
|
/**
|
|
|
|
* For test use.
|
|
|
|
* Returns all references, keyed by fqn.
|
|
|
|
*
|
|
|
|
* @return string[][]
|
|
|
|
*/
|
|
|
|
public function getReferences(): array
|
|
|
|
{
|
|
|
|
return $this->references;
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
|
|
|
* Adds a document URI as a referencee of a specific symbol
|
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addReferenceUri(string $fqn, string $uri)
|
|
|
|
{
|
|
|
|
if (!isset($this->references[$fqn])) {
|
|
|
|
$this->references[$fqn] = [];
|
|
|
|
}
|
|
|
|
// TODO: use DS\Set instead of searching array
|
|
|
|
if (array_search($uri, $this->references[$fqn], true) === false) {
|
|
|
|
$this->references[$fqn][] = $uri;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a document URI as the container for a specific symbol
|
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
|
|
|
* @param string $uri The URI
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeReferenceUri(string $fqn, string $uri)
|
|
|
|
{
|
|
|
|
if (!isset($this->references[$fqn])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$index = array_search($fqn, $this->references[$fqn], true);
|
|
|
|
if ($index === false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
array_splice($this->references[$fqn], $index, 1);
|
|
|
|
}
|
2017-02-03 23:20:38 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $serialized
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function unserialize($serialized)
|
|
|
|
{
|
|
|
|
$data = unserialize($serialized);
|
2017-08-06 21:05:32 +00:00
|
|
|
|
2017-02-03 23:20:38 +00:00
|
|
|
foreach ($data as $prop => $val) {
|
|
|
|
$this->$prop = $val;
|
|
|
|
}
|
2017-08-06 21:05:32 +00:00
|
|
|
|
2017-08-09 20:10:13 +00:00
|
|
|
foreach ($this->namespaceDefinitions as $namespaceDefinition) {
|
|
|
|
foreach ($namespaceDefinition as $fqn => $definition) {
|
|
|
|
$this->setGlobalDefinition($fqn, $definition);
|
|
|
|
}
|
2017-08-06 21:05:32 +00:00
|
|
|
}
|
2017-02-03 23:20:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $serialized
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function serialize()
|
|
|
|
{
|
|
|
|
return serialize([
|
2017-08-09 20:10:13 +00:00
|
|
|
'namespaceDefinitions' => $this->namespaceDefinitions,
|
2017-02-03 23:20:38 +00:00
|
|
|
'references' => $this->references,
|
|
|
|
'complete' => $this->complete,
|
|
|
|
'staticComplete' => $this->staticComplete
|
|
|
|
]);
|
|
|
|
}
|
2017-08-06 14:48:41 +00:00
|
|
|
|
|
|
|
/**
|
2017-08-06 21:05:32 +00:00
|
|
|
* 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
|
2017-08-06 14:48:41 +00:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-08-06 21:05:32 +00:00
|
|
|
private function setGlobalDefinition(string $fqn, Definition $definition)
|
2017-08-06 14:48:41 +00:00
|
|
|
{
|
2017-08-06 21:05:32 +00:00
|
|
|
if ($definition->isGlobal) {
|
|
|
|
$this->globalDefinitions[$fqn] = $definition;
|
2017-08-06 14:48:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
2017-08-09 20:51:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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]
|
|
|
|
: []
|
|
|
|
;
|
|
|
|
}
|
2016-12-13 00:51:02 +00:00
|
|
|
}
|