Merge 10a4592d9a
into fc6b069425
commit
901aae8e70
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LanguageServer\Tests;
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use LanguageServer\Index\Index;
|
||||||
|
use LanguageServer\PhpDocument;
|
||||||
|
use LanguageServer\DefinitionResolver;
|
||||||
|
use LanguageServer\Protocol\Position;
|
||||||
|
use LanguageServer\CompletionProvider;
|
||||||
|
use Microsoft\PhpParser;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
$totalSize = 0;
|
||||||
|
|
||||||
|
$framework = "symfony";
|
||||||
|
|
||||||
|
$iterator = new RecursiveDirectoryIterator(__DIR__ . "/../validation/frameworks/$framework");
|
||||||
|
$testProviderArray = array();
|
||||||
|
|
||||||
|
foreach (new RecursiveIteratorIterator($iterator) as $file) {
|
||||||
|
if (strpos((string)$file, ".php") !== false) {
|
||||||
|
$totalSize += $file->getSize();
|
||||||
|
$testProviderArray[] = $file->getRealPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($testProviderArray) === 0) {
|
||||||
|
throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = new Index;
|
||||||
|
$definitionResolver = new DefinitionResolver($index);
|
||||||
|
$completionProvider = new CompletionProvider($definitionResolver, $index);
|
||||||
|
$docBlockFactory = DocBlockFactory::createInstance();
|
||||||
|
$completionFile = realpath(__DIR__ . '/../validation/frameworks/symfony/src/Symfony/Component/HttpFoundation/Request.php');
|
||||||
|
$parser = new PhpParser\Parser();
|
||||||
|
$completionDocument = null;
|
||||||
|
|
||||||
|
echo "Indexing $framework" . PHP_EOL;
|
||||||
|
|
||||||
|
foreach ($testProviderArray as $idx => $testCaseFile) {
|
||||||
|
if (filesize($testCaseFile) > 100000) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($idx % 100 === 0) {
|
||||||
|
echo $idx . '/' . count($testProviderArray) . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileContents = file_get_contents($testCaseFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$d = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
|
if ($testCaseFile === $completionFile) {
|
||||||
|
$completionDocument = $d;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo $e->getMessage() . PHP_EOL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Getting completion". PHP_EOL;
|
||||||
|
|
||||||
|
// Completion in $this->|request = new ParameterBag($request);
|
||||||
|
$start = microtime(true);
|
||||||
|
$list = $completionProvider->provideCompletion($completionDocument, new Position(274, 15));
|
||||||
|
$end = microtime(true);
|
||||||
|
echo 'Time ($this->|): ' . ($end - $start) . 's' . PHP_EOL;
|
||||||
|
echo count($list->items) . ' completion items' . PHP_EOL;
|
||||||
|
|
||||||
|
// Completion in $this->request = new| ParameterBag($request);
|
||||||
|
// (this only finds ParameterBag though.)
|
||||||
|
$start = microtime(true);
|
||||||
|
$list = $completionProvider->provideCompletion($completionDocument, new Position(274, 28));
|
||||||
|
$end = microtime(true);
|
||||||
|
echo 'Time (new|): ' . ($end - $start) . 's' . PHP_EOL;
|
||||||
|
echo count($list->items) . ' completion items' . PHP_EOL;
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
namespace LanguageServer\Tests;
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use LanguageServer\Index\Index;
|
use LanguageServer\Index\Index;
|
||||||
|
@ -17,7 +17,7 @@ $totalSize = 0;
|
||||||
$frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "codeigniter", "cakephp"];
|
$frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "codeigniter", "cakephp"];
|
||||||
|
|
||||||
foreach($frameworks as $framework) {
|
foreach($frameworks as $framework) {
|
||||||
$iterator = new RecursiveDirectoryIterator(__DIR__ . "/validation/frameworks/$framework");
|
$iterator = new RecursiveDirectoryIterator(__DIR__ . "/../validation/frameworks/$framework");
|
||||||
$testProviderArray = array();
|
$testProviderArray = array();
|
||||||
|
|
||||||
foreach (new RecursiveIteratorIterator($iterator) as $file) {
|
foreach (new RecursiveIteratorIterator($iterator) as $file) {
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
namespace Whatever;
|
namespace Whatever;
|
||||||
|
|
||||||
use TestNamespace\{TestClass, TestInterface};
|
use TestNamespace\{TestClass, TestInterface, IDontExist};
|
||||||
|
|
||||||
TestC
|
TestC;
|
||||||
|
|
||||||
|
IDont;
|
||||||
|
|
||||||
class OtherClass {}
|
class OtherClass {}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use const TestNamespace\InnerNamespace\INNER_CONST;
|
||||||
|
use const TestNamespace\InnerNamespace\INNER_CONST as ALIASED_CONST;
|
||||||
|
use const TestNamespace\InnerNamespace\NON_EXISTENT_CONST;
|
||||||
|
|
||||||
|
INNER_C;
|
||||||
|
|
||||||
|
ALIASED_C;
|
||||||
|
|
||||||
|
NON_EXISTENT_C;
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TestNs;
|
||||||
|
|
||||||
|
use function TestNamespace\InnerNamespace\inner_function;
|
||||||
|
use function TestNamespace\InnerNamespace\inner_function2 as second_function;
|
||||||
|
use function TestNamespace\InnerNamespace\i_dont_exist;
|
||||||
|
|
||||||
|
inner_f;
|
||||||
|
second_f;
|
||||||
|
i_dont_ex;
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use TestNamespace\InnerNamespace as AliasNamespace;
|
||||||
|
|
||||||
|
class IDontShowUpInCompletion {}
|
||||||
|
|
||||||
|
AliasNamespace\I;
|
||||||
|
AliasNamespace\;
|
|
@ -103,3 +103,13 @@ class Example {
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
public function __destruct() {}
|
public function __destruct() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace TestNamespace\InnerNamespace;
|
||||||
|
|
||||||
|
class InnerClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
const INNER_CONST = 333;
|
||||||
|
|
||||||
|
function inner_function(InnerClass $i) {}
|
||||||
|
function inner_function2(InnerClass $i) {}
|
||||||
|
|
|
@ -16,7 +16,15 @@ use LanguageServer\Protocol\{
|
||||||
};
|
};
|
||||||
use Microsoft\PhpParser;
|
use Microsoft\PhpParser;
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
|
use Microsoft\PhpParser\ResolvedName;
|
||||||
use Generator;
|
use Generator;
|
||||||
|
use function LanguageServer\FqnUtilities\{
|
||||||
|
nameConcat,
|
||||||
|
nameGetFirstPart,
|
||||||
|
nameGetParent,
|
||||||
|
nameStartsWith,
|
||||||
|
nameWithoutFirstPart
|
||||||
|
};
|
||||||
|
|
||||||
class CompletionProvider
|
class CompletionProvider
|
||||||
{
|
{
|
||||||
|
@ -127,8 +135,11 @@ class CompletionProvider
|
||||||
* @param CompletionContext $context The completion context
|
* @param CompletionContext $context The completion context
|
||||||
* @return CompletionList
|
* @return CompletionList
|
||||||
*/
|
*/
|
||||||
public function provideCompletion(PhpDocument $doc, Position $pos, CompletionContext $context = null): CompletionList
|
public function provideCompletion(
|
||||||
{
|
PhpDocument $doc,
|
||||||
|
Position $pos,
|
||||||
|
CompletionContext $context = null
|
||||||
|
): CompletionList {
|
||||||
// This can be made much more performant if the tree follows specific invariants.
|
// This can be made much more performant if the tree follows specific invariants.
|
||||||
$node = $doc->getNodeAtPosition($pos);
|
$node = $doc->getNodeAtPosition($pos);
|
||||||
|
|
||||||
|
@ -220,16 +231,14 @@ class CompletionProvider
|
||||||
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add the object access operator to only get members of all parents
|
// The FQNs of the symbol and its parents (eg the implemented interfaces)
|
||||||
$prefixes = [];
|
foreach ($this->expandParentFqns($fqns) as $parentFqn) {
|
||||||
foreach ($this->expandParentFqns($fqns) as $prefix) {
|
// Add the object access operator to only get members of all parents
|
||||||
$prefixes[] = $prefix . '->';
|
$prefix = $parentFqn . '->';
|
||||||
}
|
$prefixLen = strlen($prefix);
|
||||||
|
// Collect fqn definitions
|
||||||
// Collect all definitions that match any of the prefixes
|
foreach ($this->index->getChildDefinitionsForFqn($parentFqn) as $fqn => $def) {
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
if (substr($fqn, 0, $prefixLen) === $prefix && $def->isMember) {
|
||||||
foreach ($prefixes as $prefix) {
|
|
||||||
if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) {
|
|
||||||
$list->items[] = CompletionItem::fromDefinition($def);
|
$list->items[] = CompletionItem::fromDefinition($def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,16 +262,14 @@ class CompletionProvider
|
||||||
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Append :: operator to only get static members of all parents
|
// The FQNs of the symbol and its parents (eg the implemented interfaces)
|
||||||
$prefixes = [];
|
foreach ($this->expandParentFqns($fqns) as $parentFqn) {
|
||||||
foreach ($this->expandParentFqns($fqns) as $prefix) {
|
// Append :: operator to only get static members of all parents
|
||||||
$prefixes[] = $prefix . '::';
|
$prefix = strtolower($parentFqn . '::');
|
||||||
}
|
$prefixLen = strlen($prefix);
|
||||||
|
// Collect fqn definitions
|
||||||
// Collect all definitions that match any of the prefixes
|
foreach ($this->index->getChildDefinitionsForFqn($parentFqn) as $fqn => $def) {
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
if (substr(strtolower($fqn), 0, $prefixLen) === $prefix && $def->isMember) {
|
||||||
foreach ($prefixes as $prefix) {
|
|
||||||
if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && $def->isMember) {
|
|
||||||
$list->items[] = CompletionItem::fromDefinition($def);
|
$list->items[] = CompletionItem::fromDefinition($def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,114 +287,294 @@ class CompletionProvider
|
||||||
// my_func|
|
// my_func|
|
||||||
// MY_CONS|
|
// MY_CONS|
|
||||||
// MyCla|
|
// MyCla|
|
||||||
|
// \MyCla|
|
||||||
|
|
||||||
// The name Node under the cursor
|
// The name Node under the cursor
|
||||||
$nameNode = isset($creation) ? $creation->classTypeDesignator : $node;
|
$nameNode = isset($creation) ? $creation->classTypeDesignator : $node;
|
||||||
|
|
||||||
/** The typed name */
|
if ($nameNode instanceof Node\QualifiedName) {
|
||||||
$prefix = $nameNode instanceof Node\QualifiedName
|
/** @var string The typed name. */
|
||||||
? (string)PhpParser\ResolvedName::buildName($nameNode->nameParts, $nameNode->getFileContents())
|
$prefix = (string)PhpParser\ResolvedName::buildName($nameNode->nameParts, $nameNode->getFileContents());
|
||||||
: $nameNode->getText($node->getFileContents());
|
} else {
|
||||||
$prefixLen = strlen($prefix);
|
$prefix = $nameNode->getText($node->getFileContents());
|
||||||
|
}
|
||||||
|
|
||||||
/** Whether the prefix is qualified (contains at least one backslash) */
|
|
||||||
$isQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isQualifiedName();
|
|
||||||
|
|
||||||
/** Whether the prefix is fully qualified (begins with a backslash) */
|
|
||||||
$isFullyQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isFullyQualifiedName();
|
|
||||||
|
|
||||||
/** The closest NamespaceDefinition Node */
|
|
||||||
$namespaceNode = $node->getNamespaceDefinition();
|
$namespaceNode = $node->getNamespaceDefinition();
|
||||||
|
/** @var string The current namespace without a leading backslash. */
|
||||||
|
$currentNamespace = $namespaceNode === null ? '' : $namespaceNode->name->getText();
|
||||||
|
|
||||||
/** @var string The name of the namespace */
|
/** @var bool Whether the prefix is qualified (contains at least one backslash) */
|
||||||
$namespacedPrefix = null;
|
$isFullyQualified = false;
|
||||||
if ($namespaceNode) {
|
|
||||||
$namespacedPrefix = (string)PhpParser\ResolvedName::buildName($namespaceNode->name->nameParts, $node->getFileContents()) . '\\' . $prefix;
|
/** @var bool Whether the prefix is qualified (contains at least one backslash) */
|
||||||
$namespacedPrefixLen = strlen($namespacedPrefix);
|
$isQualified = false;
|
||||||
|
|
||||||
|
if ($nameNode instanceof Node\QualifiedName) {
|
||||||
|
$isFullyQualified = $nameNode->isFullyQualifiedName();
|
||||||
|
$isQualified = $nameNode->isQualifiedName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the namespace use statements
|
/** @var bool Whether we are in a new expression */
|
||||||
// TODO: use function statements, use const statements
|
$isCreation = isset($creation);
|
||||||
|
|
||||||
/** @var string[] $aliases A map from local alias to fully qualified name */
|
/** @var array Import (use) tables */
|
||||||
list($aliases,,) = $node->getImportTablesForCurrentScope();
|
$importTables = $node->getImportTablesForCurrentScope();
|
||||||
|
|
||||||
foreach ($aliases as $alias => $name) {
|
if ($isFullyQualified) {
|
||||||
$aliases[$alias] = (string)$name;
|
// \Prefix\Goes\Here| - Only return completions from the root namespace.
|
||||||
|
/** @var $items \Generator|CompletionItem[] Generator yielding CompletionItems indexed by their FQN */
|
||||||
|
$items = $this->getCompletionsForFqnPrefix($prefix, $isCreation, false);
|
||||||
|
} else if ($isQualified) {
|
||||||
|
// Prefix\Goes\Here|
|
||||||
|
$items = $this->getPartiallyQualifiedCompletions(
|
||||||
|
$prefix,
|
||||||
|
$currentNamespace,
|
||||||
|
$importTables,
|
||||||
|
$isCreation
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// PrefixGoesHere|
|
||||||
|
$items = $this->getUnqualifiedCompletions($prefix, $currentNamespace, $importTables, $isCreation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a prefix that does not start with a slash, suggest `use`d symbols
|
$list->items = array_values(iterator_to_array($items));
|
||||||
if ($prefix && !$isFullyQualified) {
|
foreach ($list->items as $item) {
|
||||||
foreach ($aliases as $alias => $fqn) {
|
// Remove ()
|
||||||
// Suggest symbols that have been `use`d and match the prefix
|
if (is_string($item->insertText) && substr($item->insertText, strlen($item->insertText) - 2) === '()') {
|
||||||
if (substr($alias, 0, $prefixLen) === $prefix && ($def = $this->index->getDefinition($fqn))) {
|
$item->insertText = substr($item->insertText, 0, strlen($item->insertText) - 2);
|
||||||
$list->items[] = CompletionItem::fromDefinition($def);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suggest global symbols that either
|
}
|
||||||
// - start with the current namespace + prefix, if the Name node is not fully qualified
|
return $list;
|
||||||
// - start with just the prefix, if the Name node is fully qualified
|
}
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
|
||||||
|
|
||||||
$fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix;
|
private function getPartiallyQualifiedCompletions(
|
||||||
|
string $prefix,
|
||||||
if (
|
string $currentNamespace,
|
||||||
// Exclude methods, properties etc.
|
array $importTables,
|
||||||
!$def->isMember
|
bool $requireCanBeInstantiated
|
||||||
&& (
|
): \Generator {
|
||||||
!$prefix
|
// If the first part of the partially qualified name matches a namespace alias,
|
||||||
|| (
|
// only definitions below that alias can be completed.
|
||||||
// Either not qualified, but a matching prefix with global fallback
|
list($namespaceAliases,,) = $importTables;
|
||||||
($def->roamed && !$isQualified && $fqnStartsWithPrefix)
|
$prefixFirstPart = nameGetFirstPart($prefix);
|
||||||
// Or not in a namespace or a fully qualified name or AND matching the prefix
|
$foundAlias = $foundAliasFqn = null;
|
||||||
|| ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix)
|
foreach ($namespaceAliases as $alias => $aliasFqn) {
|
||||||
// Or in a namespace, not fully qualified and matching the prefix + current namespace
|
if (strcasecmp($prefixFirstPart, $alias) === 0) {
|
||||||
|| (
|
$foundAlias = $alias;
|
||||||
$namespaceNode
|
$foundAliasFqn = (string)$aliasFqn;
|
||||||
&& !$isFullyQualified
|
break;
|
||||||
&& substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// Only suggest classes for `new`
|
|
||||||
&& (!isset($creation) || $def->canBeInstantiated)
|
|
||||||
) {
|
|
||||||
$item = CompletionItem::fromDefinition($def);
|
|
||||||
// Find the shortest name to reference the symbol
|
|
||||||
if ($namespaceNode && ($alias = array_search($fqn, $aliases, true)) !== false) {
|
|
||||||
// $alias is the name under which this definition is aliased in the current namespace
|
|
||||||
$item->insertText = $alias;
|
|
||||||
} else if ($namespaceNode && !($prefix && $isFullyQualified)) {
|
|
||||||
// Insert the global FQN with leading backslash
|
|
||||||
$item->insertText = '\\' . $fqn;
|
|
||||||
} else {
|
|
||||||
// Insert the FQN without leading backlash
|
|
||||||
$item->insertText = $fqn;
|
|
||||||
}
|
|
||||||
// Don't insert the parenthesis for functions
|
|
||||||
// TODO return a snippet and put the cursor inside
|
|
||||||
if (substr($item->insertText, -2) === '()') {
|
|
||||||
$item->insertText = substr($item->insertText, 0, -2);
|
|
||||||
}
|
|
||||||
$list->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a class instantiation, also suggest keywords
|
|
||||||
if (!isset($creation)) {
|
|
||||||
foreach (self::KEYWORDS as $keyword) {
|
|
||||||
if (substr($keyword, 0, $prefixLen) === $prefix) {
|
|
||||||
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
|
||||||
$item->insertText = $keyword;
|
|
||||||
$list->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $list;
|
if ($foundAlias !== null) {
|
||||||
|
yield from $this->getCompletionsFromAliasedNamespace(
|
||||||
|
$prefix,
|
||||||
|
$foundAlias,
|
||||||
|
$foundAliasFqn,
|
||||||
|
$requireCanBeInstantiated
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
yield from $this->getCompletionsForFqnPrefix(
|
||||||
|
nameConcat($currentNamespace, $prefix),
|
||||||
|
$requireCanBeInstantiated,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields completions for non-qualified global names.
|
||||||
|
*
|
||||||
|
* Yields
|
||||||
|
* - Aliased classes
|
||||||
|
* - Aliased functions (when not creating)
|
||||||
|
* - Aliased constants (when not creating)
|
||||||
|
* - Completions from current namespace
|
||||||
|
* - Roamed completions from the global namespace (when not creating and not already in root NS)
|
||||||
|
* - PHP keywords (when not creating)
|
||||||
|
*
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems
|
||||||
|
*/
|
||||||
|
private function getUnqualifiedCompletions(
|
||||||
|
string $prefix,
|
||||||
|
string $currentNamespace,
|
||||||
|
array $importTables,
|
||||||
|
bool $requireCanBeInstantiated
|
||||||
|
): \Generator {
|
||||||
|
// Aliases
|
||||||
|
list($namespaceAliases, $functionAliases, $constAliases) = $importTables;
|
||||||
|
// use Foo\Bar
|
||||||
|
yield from $this->getCompletionsForAliases(
|
||||||
|
$prefix,
|
||||||
|
$namespaceAliases,
|
||||||
|
$requireCanBeInstantiated,
|
||||||
|
CompletionItemKind::CLASS_
|
||||||
|
);
|
||||||
|
if (!$requireCanBeInstantiated) {
|
||||||
|
// use function Foo\createBar
|
||||||
|
yield from $this->getCompletionsForAliases($prefix, $functionAliases, false, CompletionItemKind::FUNCTION);
|
||||||
|
// use const Foo\BAR_TYPE_COCKTAIL
|
||||||
|
yield from $this->getCompletionsForAliases($prefix, $constAliases, false, CompletionItemKind::VARIABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completions from the current namespace
|
||||||
|
yield from $this->getCompletionsForFqnPrefix(
|
||||||
|
nameConcat($currentNamespace, $prefix),
|
||||||
|
$requireCanBeInstantiated,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($currentNamespace !== '' && $prefix === '') {
|
||||||
|
// Get additional suggestions from the global namespace.
|
||||||
|
// When completing e.g. for new |, suggest \DateTime
|
||||||
|
yield from $this->getCompletionsForFqnPrefix('', $requireCanBeInstantiated, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$requireCanBeInstantiated) {
|
||||||
|
if ($currentNamespace !== '' && $prefix !== '') {
|
||||||
|
// Roamed definitions (i.e. global constants and functions). The prefix is checked against '', since
|
||||||
|
// in that case global completions have already been provided (including non-roamed definitions.)
|
||||||
|
yield from $this->getRoamedCompletions($prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly and least importantly, suggest keywords.
|
||||||
|
yield from $this->getCompletionsForKeywords($prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets completions for prefixes of fully qualified names in their parent namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix Prefix to complete for. Fully qualified.
|
||||||
|
* @param bool $requireCanBeInstantiated If set, only return classes.
|
||||||
|
* @param bool $insertFullyQualified If set, return completion with the leading \ inserted.
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems.
|
||||||
|
*/
|
||||||
|
private function getCompletionsForFqnPrefix(
|
||||||
|
string $prefix,
|
||||||
|
bool $requireCanBeInstantiated,
|
||||||
|
bool $insertFullyQualified
|
||||||
|
): \Generator {
|
||||||
|
$namespace = nameGetParent($prefix);
|
||||||
|
foreach ($this->index->getChildDefinitionsForFqn($namespace) as $fqn => $def) {
|
||||||
|
if ($requireCanBeInstantiated && !$def->canBeInstantiated) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!nameStartsWith($fqn, $prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$completion = CompletionItem::fromDefinition($def);
|
||||||
|
if ($insertFullyQualified) {
|
||||||
|
$completion->insertText = '\\' . $fqn;
|
||||||
|
}
|
||||||
|
yield $fqn => $completion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets completions for non-qualified names matching the start of an used class, function, or constant.
|
||||||
|
*
|
||||||
|
* @param string $prefix Non-qualified name being completed for
|
||||||
|
* @param QualifiedName[] $aliases Array of alias FQNs indexed by the alias.
|
||||||
|
* @param int $defaultSymbolKind The SymbolKind:: constant to use when the definition for the alias is not found.
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems.
|
||||||
|
*/
|
||||||
|
private function getCompletionsForAliases(
|
||||||
|
string $prefix,
|
||||||
|
array $aliases,
|
||||||
|
bool $requireCanBeInstantiated,
|
||||||
|
int $defaultCompletionItemKind
|
||||||
|
): \Generator {
|
||||||
|
foreach ($aliases as $alias => $aliasFqn) {
|
||||||
|
if (!nameStartsWith($alias, $prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$definition = $this->index->getDefinition((string)$aliasFqn);
|
||||||
|
if ($definition) {
|
||||||
|
if ($requireCanBeInstantiated && !$definition->canBeInstantiated) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$completionItem = CompletionItem::fromDefinition($definition);
|
||||||
|
$completionItem->insertText = $alias;
|
||||||
|
yield (string)$aliasFqn => $completionItem;
|
||||||
|
} else {
|
||||||
|
// Use clause referred to a symbol which was not indexed.
|
||||||
|
$completionItem = new CompletionItem($alias, $defaultCompletionItemKind);
|
||||||
|
$completionItem->detail = nameGetParent((string)$aliasFqn);
|
||||||
|
yield (string)$aliasFqn => $completionItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets completions for partially qualified names, where the first part is matched by an alias.
|
||||||
|
*
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems.
|
||||||
|
*/
|
||||||
|
private function getCompletionsFromAliasedNamespace(
|
||||||
|
string $prefix,
|
||||||
|
string $alias,
|
||||||
|
string $aliasFqn,
|
||||||
|
bool $requireCanBeInstantiated
|
||||||
|
): \Generator {
|
||||||
|
$prefixFirstPart = nameGetFirstPart($prefix);
|
||||||
|
// Matched alias.
|
||||||
|
$resolvedPrefix = nameConcat($aliasFqn, nameWithoutFirstPart($prefix));
|
||||||
|
$completionItems = $this->getCompletionsForFqnPrefix(
|
||||||
|
$resolvedPrefix,
|
||||||
|
$requireCanBeInstantiated,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
// Convert FQNs in the CompletionItems so they are expressed in terms of the alias.
|
||||||
|
foreach ($completionItems as $fqn => $completionItem) {
|
||||||
|
/** @var string $fqn with the leading parts determined by the alias removed. Has the leading backslash. */
|
||||||
|
$nameWithoutAliasedPart = substr($fqn, strlen($aliasFqn));
|
||||||
|
$completionItem->insertText = $alias . $nameWithoutAliasedPart;
|
||||||
|
yield $fqn => $completionItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets completions for globally defined functions and constants (i.e. symbols which may be used anywhere)
|
||||||
|
*
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems.
|
||||||
|
*/
|
||||||
|
private function getRoamedCompletions(string $prefix): \Generator
|
||||||
|
{
|
||||||
|
foreach ($this->index->getChildDefinitionsForFqn('') as $fqn => $def) {
|
||||||
|
if (!$def->roamed || !nameStartsWith($fqn, $prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$completionItem = CompletionItem::fromDefinition($def);
|
||||||
|
// Second-guessing the user here - do not trust roaming to work. If the same symbol is
|
||||||
|
// inserted in the current namespace, the code will stop working.
|
||||||
|
$completionItem->insertText = '\\' . $fqn;
|
||||||
|
yield $fqn => $completionItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes PHP keywords.
|
||||||
|
*
|
||||||
|
* @return \Generator|CompletionItem[]
|
||||||
|
* Yields CompletionItems.
|
||||||
|
*/
|
||||||
|
private function getCompletionsForKeywords(string $prefix): \Generator
|
||||||
|
{
|
||||||
|
foreach (self::KEYWORDS as $keyword) {
|
||||||
|
if (nameStartsWith($keyword, $prefix)) {
|
||||||
|
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||||
|
$item->insertText = $keyword;
|
||||||
|
yield $keyword => $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -456,8 +643,9 @@ class CompletionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($level instanceof Node\Expression\AnonymousFunctionCreationExpression && $level->anonymousFunctionUseClause !== null &&
|
if ($level instanceof Node\Expression\AnonymousFunctionCreationExpression
|
||||||
$level->anonymousFunctionUseClause->useVariableNameList !== null) {
|
&& $level->anonymousFunctionUseClause !== null
|
||||||
|
&& $level->anonymousFunctionUseClause->useVariableNameList !== null) {
|
||||||
foreach ($level->anonymousFunctionUseClause->useVariableNameList->getValues() as $use) {
|
foreach ($level->anonymousFunctionUseClause->useVariableNameList->getValues() as $use) {
|
||||||
$useName = $use->getName();
|
$useName = $use->getName();
|
||||||
if (empty($namePrefix) || strpos($useName, $namePrefix) !== false) {
|
if (empty($namePrefix) || strpos($useName, $namePrefix) !== false) {
|
||||||
|
|
|
@ -29,3 +29,91 @@ function getFqnsFromType($type): array
|
||||||
}
|
}
|
||||||
return $fqns;
|
return $fqns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns parent of an FQN.
|
||||||
|
*
|
||||||
|
* getFqnParent('') === ''
|
||||||
|
* getFqnParent('\\') === ''
|
||||||
|
* getFqnParent('\A') === ''
|
||||||
|
* getFqnParent('A') === ''
|
||||||
|
* getFqnParent('\A\') === '\A' // Empty trailing name is considered a name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function nameGetParent(string $name): string
|
||||||
|
{
|
||||||
|
if ($name === '') { // Special-case handling for the root namespace.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$parts = explode('\\', $name);
|
||||||
|
array_pop($parts);
|
||||||
|
return implode('\\', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates two names.
|
||||||
|
*
|
||||||
|
* nameConcat('\Foo\Bar', 'Baz') === '\Foo\Bar\Baz'
|
||||||
|
* nameConcat('\Foo\Bar\\', '\Baz') === '\Foo\Bar\Baz'
|
||||||
|
* nameConcat('\\', 'Baz') === '\Baz'
|
||||||
|
* nameConcat('', 'Baz') === 'Baz'
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function nameConcat(string $a, string $b): string
|
||||||
|
{
|
||||||
|
if ($a === '') {
|
||||||
|
return $b;
|
||||||
|
}
|
||||||
|
$a = rtrim($a, '\\');
|
||||||
|
$b = ltrim($b, '\\');
|
||||||
|
return "$a\\$b";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first component of $name.
|
||||||
|
*
|
||||||
|
* nameGetFirstPart('Foo\Bar') === 'Foo'
|
||||||
|
* nameGetFirstPart('\Foo\Bar') === 'Foo'
|
||||||
|
* nameGetFirstPart('') === ''
|
||||||
|
* nameGetFirstPart('\') === ''
|
||||||
|
*/
|
||||||
|
function nameGetFirstPart(string $name): string
|
||||||
|
{
|
||||||
|
$parts = explode('\\', $name, 3);
|
||||||
|
if ($parts[0] === '' && count($parts) > 1) {
|
||||||
|
return $parts[1];
|
||||||
|
} else {
|
||||||
|
return $parts[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the first component of $name.
|
||||||
|
*
|
||||||
|
* nameWithoutFirstPart('Foo\Bar') === 'Bar'
|
||||||
|
* nameWithoutFirstPart('\Foo\Bar') === 'Bar'
|
||||||
|
* nameWithoutFirstPart('') === ''
|
||||||
|
* nameWithoutFirstPart('\') === ''
|
||||||
|
*/
|
||||||
|
function nameWithoutFirstPart(string $name): string
|
||||||
|
{
|
||||||
|
$parts = explode('\\', $name, 3);
|
||||||
|
if ($parts[0] === '') {
|
||||||
|
array_shift($parts);
|
||||||
|
}
|
||||||
|
array_shift($parts);
|
||||||
|
return implode('\\', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name Name to match against
|
||||||
|
* @param string $prefix Prefix $name has to starts with
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function nameStartsWith(string $name, string $prefix): bool
|
||||||
|
{
|
||||||
|
return strlen($name) >= strlen($prefix)
|
||||||
|
&& strncmp($name, $prefix, strlen($prefix)) === 0;
|
||||||
|
}
|
||||||
|
|
|
@ -99,20 +99,29 @@ abstract class AbstractAggregateIndex implements ReadableIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
* Returns a Generator providing an associative array [string => Definition]
|
||||||
* to Definitions
|
* that maps fully qualified symbol names to Definitions (global or not)
|
||||||
*
|
*
|
||||||
* @return Definition[]
|
* @return \Generator yields Definition
|
||||||
*/
|
*/
|
||||||
public function getDefinitions(): array
|
public function getDefinitions(): \Generator
|
||||||
{
|
{
|
||||||
$defs = [];
|
|
||||||
foreach ($this->getIndexes() as $index) {
|
foreach ($this->getIndexes() as $index) {
|
||||||
foreach ($index->getDefinitions() as $fqn => $def) {
|
yield from $index->getDefinitions();
|
||||||
$defs[$fqn] = $def;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Generator that yields all the direct child Definitions of a given FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @return \Generator yields Definition
|
||||||
|
*/
|
||||||
|
public function getChildDefinitionsForFqn(string $fqn): \Generator
|
||||||
|
{
|
||||||
|
foreach ($this->getIndexes() as $index) {
|
||||||
|
yield from $index->getChildDefinitionsForFqn($fqn);
|
||||||
}
|
}
|
||||||
return $defs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,19 +141,15 @@ abstract class AbstractAggregateIndex implements ReadableIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
* @return string[]
|
* @return \Generator yields string
|
||||||
*/
|
*/
|
||||||
public function getReferenceUris(string $fqn): array
|
public function getReferenceUris(string $fqn): \Generator
|
||||||
{
|
{
|
||||||
$refs = [];
|
|
||||||
foreach ($this->getIndexes() as $index) {
|
foreach ($this->getIndexes() as $index) {
|
||||||
foreach ($index->getReferenceUris($fqn) as $ref) {
|
yield from $index->getReferenceUris($fqn);
|
||||||
$refs[] = $ref;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $refs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,26 @@ class Index implements ReadableIndex, \Serializable
|
||||||
use EmitterTrait;
|
use EmitterTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An associative array that maps fully qualified symbol names to Definitions
|
* An associative array that maps splitted fully qualified symbol names
|
||||||
|
* to 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
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
* ]
|
||||||
*
|
*
|
||||||
* @var Definition[]
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $definitions = [];
|
private $definitions = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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[][]
|
* @var string[][]
|
||||||
*/
|
*/
|
||||||
|
@ -84,14 +96,46 @@ class Index implements ReadableIndex, \Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
* Returns a Generator providing an associative array [string => Definition]
|
||||||
* to Definitions
|
* that maps fully qualified symbol names to Definitions (global or not)
|
||||||
*
|
*
|
||||||
* @return Definition[]
|
* @return \Generator yields Definition
|
||||||
*/
|
*/
|
||||||
public function getDefinitions(): array
|
public function getDefinitions(): \Generator
|
||||||
{
|
{
|
||||||
return $this->definitions;
|
yield from $this->yieldDefinitionsRecursively($this->definitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Generator that yields all the direct child Definitions of a given FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @return \Generator yields Definition
|
||||||
|
*/
|
||||||
|
public function getChildDefinitionsForFqn(string $fqn): \Generator
|
||||||
|
{
|
||||||
|
$parts = $this->splitFqn($fqn);
|
||||||
|
if ('' === end($parts)) {
|
||||||
|
// we want to return all the definitions in the given FQN, not only
|
||||||
|
// the one (non member) matching exactly the FQN.
|
||||||
|
array_pop($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->getIndexValue($parts, $this->definitions);
|
||||||
|
if (!$result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($result as $name => $item) {
|
||||||
|
// Don't yield the parent
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item instanceof Definition) {
|
||||||
|
yield $fqn.$name => $item;
|
||||||
|
} elseif (is_array($item) && isset($item[''])) {
|
||||||
|
yield $fqn.$name => $item[''];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,12 +147,17 @@ class Index implements ReadableIndex, \Serializable
|
||||||
*/
|
*/
|
||||||
public function getDefinition(string $fqn, bool $globalFallback = false)
|
public function getDefinition(string $fqn, bool $globalFallback = false)
|
||||||
{
|
{
|
||||||
if (isset($this->definitions[$fqn])) {
|
$parts = $this->splitFqn($fqn);
|
||||||
return $this->definitions[$fqn];
|
$result = $this->getIndexValue($parts, $this->definitions);
|
||||||
|
|
||||||
|
if ($result instanceof Definition) {
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($globalFallback) {
|
if ($globalFallback) {
|
||||||
$parts = explode('\\', $fqn);
|
$parts = explode('\\', $fqn);
|
||||||
$fqn = end($parts);
|
$fqn = end($parts);
|
||||||
|
|
||||||
return $this->getDefinition($fqn);
|
return $this->getDefinition($fqn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +171,9 @@ class Index implements ReadableIndex, \Serializable
|
||||||
*/
|
*/
|
||||||
public function setDefinition(string $fqn, Definition $definition)
|
public function setDefinition(string $fqn, Definition $definition)
|
||||||
{
|
{
|
||||||
$this->definitions[$fqn] = $definition;
|
$parts = $this->splitFqn($fqn);
|
||||||
|
$this->indexDefinition(0, $parts, $this->definitions, $definition);
|
||||||
|
|
||||||
$this->emit('definition-added');
|
$this->emit('definition-added');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,19 +186,23 @@ class Index implements ReadableIndex, \Serializable
|
||||||
*/
|
*/
|
||||||
public function removeDefinition(string $fqn)
|
public function removeDefinition(string $fqn)
|
||||||
{
|
{
|
||||||
unset($this->definitions[$fqn]);
|
$parts = $this->splitFqn($fqn);
|
||||||
|
$this->removeIndexedDefinition(0, $parts, $this->definitions, $this->definitions);
|
||||||
|
|
||||||
unset($this->references[$fqn]);
|
unset($this->references[$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
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
* @return string[]
|
* @return \Generator yields string
|
||||||
*/
|
*/
|
||||||
public function getReferenceUris(string $fqn): array
|
public function getReferenceUris(string $fqn): \Generator
|
||||||
{
|
{
|
||||||
return $this->references[$fqn] ?? [];
|
foreach ($this->references[$fqn] ?? [] as $uri) {
|
||||||
|
yield $uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,6 +259,15 @@ class Index implements ReadableIndex, \Serializable
|
||||||
public function unserialize($serialized)
|
public function unserialize($serialized)
|
||||||
{
|
{
|
||||||
$data = 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) {
|
foreach ($data as $prop => $val) {
|
||||||
$this->$prop = $val;
|
$this->$prop = $val;
|
||||||
}
|
}
|
||||||
|
@ -216,10 +280,164 @@ class Index implements ReadableIndex, \Serializable
|
||||||
public function serialize()
|
public function serialize()
|
||||||
{
|
{
|
||||||
return serialize([
|
return serialize([
|
||||||
'definitions' => $this->definitions,
|
'definitions' => iterator_to_array($this->getDefinitions(), true),
|
||||||
'references' => $this->references,
|
'references' => $this->references,
|
||||||
'complete' => $this->complete,
|
'complete' => $this->complete,
|
||||||
'staticComplete' => $this->staticComplete
|
'staticComplete' => $this->staticComplete
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* @return \Generator
|
||||||
|
*/
|
||||||
|
private function yieldDefinitionsRecursively(array &$storage, string $prefix = ''): \Generator
|
||||||
|
{
|
||||||
|
foreach ($storage as $key => $value) {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
yield $prefix.$key => $value;
|
||||||
|
} else {
|
||||||
|
yield from $this->yieldDefinitionsRecursively($value, $prefix.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 string[]
|
||||||
|
*/
|
||||||
|
private function splitFqn(string $fqn): array
|
||||||
|
{
|
||||||
|
// split fqn at backslashes
|
||||||
|
$parts = explode('\\', $fqn);
|
||||||
|
|
||||||
|
// write back the backslash prefix to the first part if it was present
|
||||||
|
if ('' === $parts[0] && count($parts) > 1) {
|
||||||
|
$parts = array_slice($parts, 1);
|
||||||
|
$parts[0] = '\\' . $parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// write back the backslashes prefixes for the other parts
|
||||||
|
for ($i = 1; $i < count($parts); $i++) {
|
||||||
|
$parts[$i] = '\\' . $parts[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
$parts[] = $operator . $endParts[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end($parts) === '' holds for the root namespace.
|
||||||
|
if (!$hasOperator && end($parts) !== '') {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 string[] $path The splitted FQN
|
||||||
|
* @param array|Definition &$storage The current level to look for $path.
|
||||||
|
* @return array|Definition|null
|
||||||
|
*/
|
||||||
|
private function getIndexValue(array $path, &$storage)
|
||||||
|
{
|
||||||
|
// Empty path returns the object itself.
|
||||||
|
if (empty($path)) {
|
||||||
|
return $storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = array_shift($path);
|
||||||
|
|
||||||
|
if (!isset($storage[$part])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getIndexValue($path, $storage[$part]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @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, array &$rootStorage)
|
||||||
|
{
|
||||||
|
$part = $parts[$level];
|
||||||
|
|
||||||
|
if ($level + 1 === count($parts)) {
|
||||||
|
if (isset($storage[$part])) {
|
||||||
|
unset($storage[$part]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->removeIndexedDefinition($level + 1, $parts, $storage[$part], $rootStorage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,20 @@ interface ReadableIndex extends EmitterInterface
|
||||||
public function isStaticComplete(): bool;
|
public function isStaticComplete(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
* Returns a Generator providing an associative array [string => Definition]
|
||||||
* to Definitions
|
* that maps fully qualified symbol names to Definitions (global or not)
|
||||||
*
|
*
|
||||||
* @return Definitions[]
|
* @return \Generator yields Definition
|
||||||
*/
|
*/
|
||||||
public function getDefinitions(): array;
|
public function getDefinitions(): \Generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Generator that yields all the direct child Definitions of a given FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @return \Generator yields Definition
|
||||||
|
*/
|
||||||
|
public function getChildDefinitionsForFqn(string $fqn): \Generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Definition object by a specific FQN
|
* Returns the Definition object by a specific FQN
|
||||||
|
@ -47,10 +55,10 @@ interface ReadableIndex extends EmitterInterface
|
||||||
public function getDefinition(string $fqn, bool $globalFallback = false);
|
public function getDefinition(string $fqn, bool $globalFallback = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns 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
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
* @return string[]
|
* @return \Generator yields string
|
||||||
*/
|
*/
|
||||||
public function getReferenceUris(string $fqn): array;
|
public function getReferenceUris(string $fqn): \Generator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,10 +226,11 @@ class TextDocument
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$refDocuments = yield Promise\all(array_map(
|
$refDocumentPromises = [];
|
||||||
[$this->documentLoader, 'getOrLoad'],
|
foreach ($this->index->getReferenceUris($fqn) as $uri) {
|
||||||
$this->index->getReferenceUris($fqn)
|
$refDocumentPromises[] = $this->documentLoader->getOrLoad($uri);
|
||||||
));
|
}
|
||||||
|
$refDocuments = yield Promise\all($refDocumentPromises);
|
||||||
foreach ($refDocuments as $document) {
|
foreach ($refDocuments as $document) {
|
||||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||||
if ($refs !== null) {
|
if ($refs !== null) {
|
||||||
|
|
|
@ -35,9 +35,15 @@ class DefinitionCollectorTest extends TestCase
|
||||||
'TestNamespace\\ChildClass',
|
'TestNamespace\\ChildClass',
|
||||||
'TestNamespace\\Example',
|
'TestNamespace\\Example',
|
||||||
'TestNamespace\\Example->__construct()',
|
'TestNamespace\\Example->__construct()',
|
||||||
'TestNamespace\\Example->__destruct()'
|
'TestNamespace\\Example->__destruct()',
|
||||||
|
'TestNamespace\\InnerNamespace',
|
||||||
|
'TestNamespace\\InnerNamespace\\InnerClass',
|
||||||
|
'TestNamespace\\InnerNamespace\\INNER_CONST',
|
||||||
|
'TestNamespace\\InnerNamespace\\inner_function()',
|
||||||
|
'TestNamespace\\InnerNamespace\\inner_function2()',
|
||||||
], array_keys($defNodes));
|
], array_keys($defNodes));
|
||||||
|
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']);
|
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']);
|
||||||
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\TestClass']);
|
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\TestClass']);
|
||||||
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']);
|
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']);
|
||||||
|
@ -53,6 +59,11 @@ class DefinitionCollectorTest extends TestCase
|
||||||
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\Example']);
|
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\Example']);
|
||||||
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__construct()']);
|
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__construct()']);
|
||||||
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__destruct()']);
|
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__destruct()']);
|
||||||
|
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\InnerNamespace\\InnerClass']);
|
||||||
|
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\InnerNamespace\INNER_CONST']);
|
||||||
|
$this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\InnerNamespace\inner_function()']);
|
||||||
|
$this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\InnerNamespace\inner_function2()']);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDoesNotCollectReferences()
|
public function testDoesNotCollectReferences()
|
||||||
|
|
|
@ -91,23 +91,28 @@ abstract class ServerTestCase extends TestCase
|
||||||
'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
||||||
|
|
||||||
// Namespaced
|
// Namespaced
|
||||||
'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 0), new Position( 2, 24))),
|
'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 0), new Position( 2, 24))),
|
||||||
'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))),
|
'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))),
|
||||||
'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
||||||
'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
||||||
'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
||||||
'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
||||||
'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||||
'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
||||||
'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
||||||
'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
||||||
'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
||||||
'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
||||||
'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
||||||
'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
||||||
'TestNamespace\\Example' => new Location($symbolsUri, new Range(new Position(101, 0), new Position(104, 1))),
|
'TestNamespace\\Example' => new Location($symbolsUri, new Range(new Position(101, 0), new Position(104, 1))),
|
||||||
'TestNamespace\\Example::__construct' => new Location($symbolsUri, new Range(new Position(102, 4), new Position(102, 36))),
|
'TestNamespace\\Example::__construct' => new Location($symbolsUri, new Range(new Position(102, 4), new Position(102, 36))),
|
||||||
'TestNamespace\\Example::__destruct' => new Location($symbolsUri, new Range(new Position(103, 4), new Position(103, 35)))
|
'TestNamespace\\Example::__destruct' => new Location($symbolsUri, new Range(new Position(103, 4), new Position(103, 35))),
|
||||||
|
'TestNamespace\\InnerNamespace' => new Location($symbolsUri, new Range(new Position(106, 0), new Position(106, 39))),
|
||||||
|
'TestNamespace\\InnerNamespace\\InnerClass' => new Location($symbolsUri, new Range(new Position(108, 0), new Position(109, 1))),
|
||||||
|
'TestNamespace\\InnerNamespace\\INNER_CONST' => new Location($symbolsUri, new Range(new Position(111, 6), new Position(111, 23))),
|
||||||
|
'TestNamespace\\InnerNamespace\\inner_function()' => new Location($symbolsUri, new Range(new Position(113, 0), new Position(113, 41))),
|
||||||
|
'TestNamespace\\InnerNamespace\\inner_function2()' => new Location($symbolsUri, new Range(new Position(114, 0), new Position(114, 42))),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->referenceLocations = [
|
$this->referenceLocations = [
|
||||||
|
|
|
@ -47,6 +47,9 @@ class CompletionTest extends TestCase
|
||||||
$this->textDocument = new Server\TextDocument($this->loader, $definitionResolver, $client, $projectIndex);
|
$this->textDocument = new Server\TextDocument($this->loader, $definitionResolver, $client, $projectIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$obj->t|`
|
||||||
|
*/
|
||||||
public function testPropertyAndMethodWithPrefix()
|
public function testPropertyAndMethodWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property_with_prefix.php');
|
||||||
|
@ -71,6 +74,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `public function a() { tes| }`
|
||||||
|
*/
|
||||||
public function testGlobalFunctionInsideNamespaceAndClass()
|
public function testGlobalFunctionInsideNamespaceAndClass()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/inside_namespace_and_method.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/inside_namespace_and_method.php');
|
||||||
|
@ -92,6 +98,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$obj->|`
|
||||||
|
*/
|
||||||
public function testPropertyAndMethodWithoutPrefix()
|
public function testPropertyAndMethodWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php');
|
||||||
|
@ -116,6 +125,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$|` when variables are defined
|
||||||
|
*/
|
||||||
public function testVariable()
|
public function testVariable()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
||||||
|
@ -148,6 +160,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$p|` when variables are defined
|
||||||
|
*/
|
||||||
public function testVariableWithPrefix()
|
public function testVariableWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable_with_prefix.php');
|
||||||
|
@ -170,6 +185,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `new|` when in a namespace and have used variables.
|
||||||
|
*/
|
||||||
public function testNewInNamespace()
|
public function testNewInNamespace()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_new.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_new.php');
|
||||||
|
@ -218,27 +236,12 @@ class CompletionTest extends TestCase
|
||||||
null,
|
null,
|
||||||
'TestClass'
|
'TestClass'
|
||||||
),
|
),
|
||||||
new CompletionItem(
|
|
||||||
'ChildClass',
|
|
||||||
CompletionItemKind::CLASS_,
|
|
||||||
'TestNamespace',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'\TestNamespace\ChildClass'
|
|
||||||
),
|
|
||||||
new CompletionItem(
|
|
||||||
'Example',
|
|
||||||
CompletionItemKind::CLASS_,
|
|
||||||
'TestNamespace',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'\TestNamespace\Example'
|
|
||||||
)
|
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `TestC|` with `use TestNamespace\TestClass`
|
||||||
|
*/
|
||||||
public function testUsedClass()
|
public function testUsedClass()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
||||||
|
@ -257,11 +260,280 @@ class CompletionTest extends TestCase
|
||||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||||
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
||||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'TestClass'
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
|
|
||||||
|
$this->assertCompletionsListDoesNotContainLabel('OtherClass', $items);
|
||||||
|
$this->assertCompletionsListDoesNotContainLabel('TestInterface', $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `IDontE|` with `use TestNamespace\IDontExist`
|
||||||
|
*/
|
||||||
|
public function testUsedClassNonExistent()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(8, 5)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'IDontExist',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace'
|
||||||
|
)
|
||||||
|
], true), $items);
|
||||||
|
|
||||||
|
$this->assertCompletionsListDoesNotContainLabel('OtherClass', $items);
|
||||||
|
$this->assertCompletionsListDoesNotContainLabel('TestInterface', $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `AliasNamespace\I|` with `use TestNamespace\InnerNamespace as AliasNamespace`
|
||||||
|
*/
|
||||||
|
public function testUsedNamespaceWithPrefix()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_namespace.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(8, 16)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'InnerClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace\\InnerNamespace',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\\InnerClass'
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'INNER_CONST',
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
|
'int',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\\INNER_CONST'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `AliasNamespace\|` with `use TestNamespace\InnerNamespace as AliasNamespace`
|
||||||
|
*/
|
||||||
|
public function testUsedNamespaceWithoutPrefix()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_namespace.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(9, 15)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'InnerClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace\InnerNamespace',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\InnerClass'
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'INNER_CONST',
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
|
'int',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\INNER_CONST'
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'inner_function',
|
||||||
|
CompletionItemKind::FUNCTION,
|
||||||
|
'mixed',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\inner_function'
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'inner_function2',
|
||||||
|
CompletionItemKind::FUNCTION,
|
||||||
|
'mixed',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'AliasNamespace\inner_function2'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use function ..inner_function`.
|
||||||
|
*/
|
||||||
|
public function testUseFunction()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_function.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(8, 7)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'inner_function',
|
||||||
|
CompletionItemKind::FUNCTION,
|
||||||
|
'TestNamespace\InnerNamespace'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use function .. as second_function;`.
|
||||||
|
*/
|
||||||
|
public function testUseFunctionAs()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_function.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(9, 8)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'second_function',
|
||||||
|
CompletionItemKind::FUNCTION,
|
||||||
|
'TestNamespace\InnerNamespace'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use function .. as second_function;`.
|
||||||
|
*/
|
||||||
|
public function testUseFunctionNotExists()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_function.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(10, 9)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'i_dont_exist',
|
||||||
|
CompletionItemKind::FUNCTION,
|
||||||
|
'TestNamespace\InnerNamespace',
|
||||||
|
''
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use const ..INNER_CONST`
|
||||||
|
*/
|
||||||
|
public function testUseConst()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_const.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(8, 7)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'INNER_CONST',
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
|
'int',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'INNER_CONST'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use const .. as ALIASED_CONST;`.
|
||||||
|
*/
|
||||||
|
public function testUseConsttAs()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_const.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(10, 9)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'INNER_CONST',
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
|
'int',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'ALIASED_CONST'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion with `use const NON_EXISTENT_CONST`
|
||||||
|
*/
|
||||||
|
public function testUseConsttNonExistent()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_const.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(12, 14)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(
|
||||||
|
new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'NON_EXISTENT_CONST',
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
|
'TestNamespace\InnerNamespace'
|
||||||
|
),
|
||||||
|
], true),
|
||||||
|
$items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `TestClass::$st|`
|
||||||
|
*/
|
||||||
public function testStaticPropertyWithPrefix()
|
public function testStaticPropertyWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_property_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_property_with_prefix.php');
|
||||||
|
@ -283,6 +555,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `TestClass::|`
|
||||||
|
*/
|
||||||
public function testStaticWithoutPrefix()
|
public function testStaticWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static.php');
|
||||||
|
@ -316,6 +591,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `TestClass::st|`
|
||||||
|
*/
|
||||||
public function testStaticMethodWithPrefix()
|
public function testStaticMethodWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_method_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_method_with_prefix.php');
|
||||||
|
@ -325,21 +603,6 @@ class CompletionTest extends TestCase
|
||||||
new Position(2, 13)
|
new Position(2, 13)
|
||||||
)->wait();
|
)->wait();
|
||||||
$this->assertCompletionsListSubset(new CompletionList([
|
$this->assertCompletionsListSubset(new CompletionList([
|
||||||
new CompletionItem(
|
|
||||||
'TEST_CLASS_CONST',
|
|
||||||
CompletionItemKind::VARIABLE,
|
|
||||||
'int',
|
|
||||||
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
|
|
||||||
),
|
|
||||||
new CompletionItem(
|
|
||||||
'staticTestProperty',
|
|
||||||
CompletionItemKind::PROPERTY,
|
|
||||||
'\TestClass[]',
|
|
||||||
'Lorem excepteur officia sit anim velit veniam enim.',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'$staticTestProperty'
|
|
||||||
),
|
|
||||||
new CompletionItem(
|
new CompletionItem(
|
||||||
'staticTestMethod',
|
'staticTestMethod',
|
||||||
CompletionItemKind::METHOD,
|
CompletionItemKind::METHOD,
|
||||||
|
@ -349,6 +612,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `TestClass::TE` at the root level.
|
||||||
|
*/
|
||||||
public function testClassConstWithPrefix()
|
public function testClassConstWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/class_const_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/class_const_with_prefix.php');
|
||||||
|
@ -363,25 +629,13 @@ class CompletionTest extends TestCase
|
||||||
CompletionItemKind::VARIABLE,
|
CompletionItemKind::VARIABLE,
|
||||||
'int',
|
'int',
|
||||||
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
|
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
|
||||||
),
|
|
||||||
new CompletionItem(
|
|
||||||
'staticTestProperty',
|
|
||||||
CompletionItemKind::PROPERTY,
|
|
||||||
'\TestClass[]',
|
|
||||||
'Lorem excepteur officia sit anim velit veniam enim.',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'$staticTestProperty'
|
|
||||||
),
|
|
||||||
new CompletionItem(
|
|
||||||
'staticTestMethod',
|
|
||||||
CompletionItemKind::METHOD,
|
|
||||||
'mixed',
|
|
||||||
'Do magna consequat veniam minim proident eiusmod incididunt aute proident.'
|
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test completion at `\TestC|` in a namespace
|
||||||
|
*/
|
||||||
public function testFullyQualifiedClass()
|
public function testFullyQualifiedClass()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/fully_qualified_class.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/fully_qualified_class.php');
|
||||||
|
@ -400,14 +654,18 @@ class CompletionTest extends TestCase
|
||||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||||
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
||||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.',
|
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'TestClass'
|
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
|
// Assert that all results are non-namespaced.
|
||||||
|
foreach ($items->items as $item) {
|
||||||
|
$this->assertSame($item->detail, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `cl|` at root level
|
||||||
|
*/
|
||||||
public function testKeywords()
|
public function testKeywords()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/keywords.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/keywords.php');
|
||||||
|
@ -422,6 +680,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion in an empty file
|
||||||
|
*/
|
||||||
public function testHtmlWithoutPrefix()
|
public function testHtmlWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html.php');
|
||||||
|
@ -444,6 +705,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion in `<|` when not within `<?php` tags
|
||||||
|
*/
|
||||||
public function testHtmlWontBeProposedWithoutCompletionContext()
|
public function testHtmlWontBeProposedWithoutCompletionContext()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
||||||
|
@ -456,6 +720,9 @@ class CompletionTest extends TestCase
|
||||||
$this->assertEquals(new CompletionList([], true), $items);
|
$this->assertEquals(new CompletionList([], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion in `<|` when not within `<?php` tags
|
||||||
|
*/
|
||||||
public function testHtmlWontBeProposedWithPrefixWithCompletionContext()
|
public function testHtmlWontBeProposedWithPrefixWithCompletionContext()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php');
|
||||||
|
@ -480,6 +747,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `<|` when not within `<?php` tags when triggered by trigger character.
|
||||||
|
*/
|
||||||
public function testHtmlPrefixShouldNotTriggerCompletion()
|
public function testHtmlPrefixShouldNotTriggerCompletion()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php');
|
||||||
|
@ -492,6 +762,9 @@ class CompletionTest extends TestCase
|
||||||
$this->assertEquals(new CompletionList([], true), $items);
|
$this->assertEquals(new CompletionList([], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `<|` when not within `<?php` tags when triggered by user input.
|
||||||
|
*/
|
||||||
public function testHtmlPrefixShouldTriggerCompletionIfManuallyInvoked()
|
public function testHtmlPrefixShouldTriggerCompletionIfManuallyInvoked()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php');
|
||||||
|
@ -515,6 +788,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `SomeNa|` when namespace `SomeNamespace` is defined
|
||||||
|
*/
|
||||||
public function testNamespace()
|
public function testNamespace()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/namespace.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/namespace.php');
|
||||||
|
@ -526,17 +802,15 @@ class CompletionTest extends TestCase
|
||||||
$this->assertCompletionsListSubset(new CompletionList([
|
$this->assertCompletionsListSubset(new CompletionList([
|
||||||
new CompletionItem(
|
new CompletionItem(
|
||||||
'SomeNamespace',
|
'SomeNamespace',
|
||||||
CompletionItemKind::MODULE,
|
CompletionItemKind::MODULE
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'SomeNamespace'
|
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBarePhp()
|
/**
|
||||||
|
* Tests completion at `echo $ab|` at the root level.
|
||||||
|
*/
|
||||||
|
public function testBarePhpVariable()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/bare_php.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/bare_php.php');
|
||||||
$this->loader->open($completionUri, file_get_contents($completionUri));
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
@ -776,6 +1050,16 @@ class CompletionTest extends TestCase
|
||||||
$this->assertEquals($subsetList->isIncomplete, $list->isIncomplete);
|
$this->assertEquals($subsetList->isIncomplete, $list->isIncomplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function assertCompletionsListDoesNotContainLabel(string $label, CompletionList $list)
|
||||||
|
{
|
||||||
|
foreach ($list->items as $item) {
|
||||||
|
$this->assertNotSame($label, $item->label, "Completion list should not contain $label.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion for `$this->|`
|
||||||
|
*/
|
||||||
public function testThisWithoutPrefix()
|
public function testThisWithoutPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this.php');
|
||||||
|
@ -812,6 +1096,9 @@ class CompletionTest extends TestCase
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$this->m|`
|
||||||
|
*/
|
||||||
public function testThisWithPrefix()
|
public function testThisWithPrefix()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_with_prefix.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_with_prefix.php');
|
||||||
|
@ -821,18 +1108,6 @@ class CompletionTest extends TestCase
|
||||||
new Position(12, 16)
|
new Position(12, 16)
|
||||||
)->wait();
|
)->wait();
|
||||||
$this->assertEquals(new CompletionList([
|
$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(
|
new CompletionItem(
|
||||||
'foo',
|
'foo',
|
||||||
CompletionItemKind::PROPERTY,
|
CompletionItemKind::PROPERTY,
|
||||||
|
@ -856,10 +1131,25 @@ class CompletionTest extends TestCase
|
||||||
CompletionItemKind::METHOD,
|
CompletionItemKind::METHOD,
|
||||||
'mixed', // Return type of the method
|
'mixed', // Return type of the method
|
||||||
null
|
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);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests completion at `$this->foo()->q|`
|
||||||
|
*/
|
||||||
public function testThisReturnValue()
|
public function testThisReturnValue()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_return_value.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_return_value.php');
|
||||||
|
@ -869,11 +1159,6 @@ class CompletionTest extends TestCase
|
||||||
new Position(17, 23)
|
new Position(17, 23)
|
||||||
)->wait();
|
)->wait();
|
||||||
$this->assertEquals(new CompletionList([
|
$this->assertEquals(new CompletionList([
|
||||||
new CompletionItem(
|
|
||||||
'foo',
|
|
||||||
CompletionItemKind::METHOD,
|
|
||||||
'$this' // Return type of the method
|
|
||||||
),
|
|
||||||
new CompletionItem(
|
new CompletionItem(
|
||||||
'bar',
|
'bar',
|
||||||
CompletionItemKind::METHOD,
|
CompletionItemKind::METHOD,
|
||||||
|
@ -883,7 +1168,12 @@ class CompletionTest extends TestCase
|
||||||
'qux',
|
'qux',
|
||||||
CompletionItemKind::METHOD,
|
CompletionItemKind::METHOD,
|
||||||
'mixed' // Return type of the method
|
'mixed' // Return type of the method
|
||||||
)
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'foo',
|
||||||
|
CompletionItemKind::METHOD,
|
||||||
|
'$this' // Return type of the method
|
||||||
|
),
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,21 +18,26 @@ class DocumentSymbolTest extends ServerTestCase
|
||||||
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait();
|
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait();
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''),
|
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''),
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
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('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), '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('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), '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('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), '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('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example')
|
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
||||||
|
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
||||||
|
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
||||||
|
new SymbolInformation('INNER_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\INNER_CONST'), 'TestNamespace\\InnerNamespace'),
|
||||||
|
new SymbolInformation('inner_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\inner_function()'), 'TestNamespace\\InnerNamespace'),
|
||||||
|
new SymbolInformation('inner_function2', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\inner_function2()'),'TestNamespace\\InnerNamespace'),
|
||||||
], $result);
|
], $result);
|
||||||
// @codingStandardsIgnoreEnd
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,42 +30,48 @@ class SymbolTest extends ServerTestCase
|
||||||
|
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''),
|
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''),
|
||||||
// Namespaced
|
// Namespaced
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
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('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), '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('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), '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('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), '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('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
||||||
// Global
|
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
new SymbolInformation('INNER_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\INNER_CONST'), 'TestNamespace\\InnerNamespace'),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''),
|
new SymbolInformation('inner_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\inner_function()'), 'TestNamespace\\InnerNamespace'),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'),
|
new SymbolInformation('inner_function2', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\inner_function2()'), 'TestNamespace\InnerNamespace'),
|
||||||
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'), ''),
|
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);
|
], $result);
|
||||||
// @codingStandardsIgnoreEnd
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue