feat(completion): complete for used namespaces
parent
6858bd3513
commit
d6b4e79491
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use TestNamespace\InnerNamespace as AliasNamespace;
|
||||||
|
|
||||||
|
AliasNamespace\I
|
||||||
|
|
||||||
|
class IDontShowUpInCompletion {}
|
|
@ -103,3 +103,8 @@ class Example {
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
public function __destruct() {}
|
public function __destruct() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace TestNamespace\InnerNamespace;
|
||||||
|
|
||||||
|
class InnerClass {
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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;
|
||||||
|
|
||||||
class CompletionProvider
|
class CompletionProvider
|
||||||
|
@ -281,10 +282,32 @@ class CompletionProvider
|
||||||
// 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 */
|
$filterNameTokens = static function ($tokens) {
|
||||||
$prefix = $nameNode instanceof Node\QualifiedName
|
return array_values(
|
||||||
? (string)PhpParser\ResolvedName::buildName($nameNode->nameParts, $nameNode->getFileContents())
|
array_filter(
|
||||||
: $nameNode->getText($node->getFileContents());
|
$tokens,
|
||||||
|
static function ($token): bool {
|
||||||
|
return $token->kind === PhpParser\TokenKind::Name;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @var string[] The written name, exploded by \ */
|
||||||
|
$prefix = array_map(
|
||||||
|
static function ($part) use ($node) : string {
|
||||||
|
return $part->getText($node->getFileContents());
|
||||||
|
},
|
||||||
|
$filterNameTokens(
|
||||||
|
$nameNode instanceof Node\QualifiedName
|
||||||
|
? $nameNode->nameParts
|
||||||
|
: [$nameNode]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($prefix === ['']) {
|
||||||
|
$prefix = [];
|
||||||
|
}
|
||||||
|
|
||||||
/** Whether the prefix is qualified (contains at least one backslash) */
|
/** Whether the prefix is qualified (contains at least one backslash) */
|
||||||
$isQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isQualifiedName();
|
$isQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isQualifiedName();
|
||||||
|
@ -295,103 +318,100 @@ class CompletionProvider
|
||||||
/** The closest NamespaceDefinition Node */
|
/** The closest NamespaceDefinition Node */
|
||||||
$namespaceNode = $node->getNamespaceDefinition();
|
$namespaceNode = $node->getNamespaceDefinition();
|
||||||
|
|
||||||
if ($nameNode instanceof Node\QualifiedName) {
|
|
||||||
/** @var array For Psr\Http\Mess this will be ['Psr', 'Http'] */
|
|
||||||
$namePartsWithoutLast = $nameNode->nameParts;
|
|
||||||
array_pop($namePartsWithoutLast);
|
|
||||||
/** @var string When typing \Foo\Bar\Fooba, this will be Foo\Bar */
|
|
||||||
$prefixParentNamespace = (string)PhpParser\ResolvedName::buildName(
|
|
||||||
$namePartsWithoutLast,
|
|
||||||
$node->getFileContents()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Not qualified, parent namespace is root.
|
|
||||||
$prefixParentNamespace = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var string[] Namespaces to search completions in. */
|
|
||||||
$namespacesToSearch = [];
|
|
||||||
if ($namespaceNode && !$isFullyQualified) {
|
|
||||||
/** @var string Declared namespace of the file (or section) */
|
|
||||||
$currentNamespace = (string)PhpParser\ResolvedName::buildName(
|
|
||||||
$namespaceNode->name->nameParts,
|
|
||||||
$namespaceNode->getFileContents()
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($prefixParentNamespace === '') {
|
|
||||||
$namespacesToSearch[] = $currentNamespace;
|
|
||||||
} else {
|
|
||||||
// Partially qualified, concatenate with current namespace.
|
|
||||||
$namespacesToSearch[] = $currentNamespace . '\\' . $prefixParentNamespace;
|
|
||||||
}
|
|
||||||
/** @var string Prefix with namespace inferred. */
|
|
||||||
$namespacedPrefix = $currentNamespace . '\\' . $prefix;
|
|
||||||
} else {
|
|
||||||
// In the global namespace, prefix parent refers to global namespace,
|
|
||||||
// OR completing a fully qualified name, prefix parent starts from the global namespace.
|
|
||||||
$namespacesToSearch[] = $prefixParentNamespace;
|
|
||||||
$namespacedPrefix = $prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$isQualified && $namespacesToSearch[0] !== '' && ($prefix === '' || !isset($creation))) {
|
|
||||||
// Also search the global namespace for non-qualified completions, as roamed
|
|
||||||
// definitions may be found. Also, without a prefix, suggest completions from the global namespace.
|
|
||||||
// Since only functions and constants can be roamed, don't search the global namespace for creation
|
|
||||||
// with a prefix.
|
|
||||||
$namespacesToSearch[] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var int Length of $namespacedPrefix */
|
|
||||||
$namespacedPrefixLen = strlen($namespacedPrefix);
|
|
||||||
/** @var int Length of $prefix */
|
|
||||||
$prefixLen = strlen($prefix);
|
|
||||||
|
|
||||||
// Get the namespace use statements
|
// Get the namespace use statements
|
||||||
// TODO: use function statements, use const statements
|
// TODO: use function statements, use const statements
|
||||||
|
|
||||||
/** @var string[] $aliases A map from local alias to fully qualified name */
|
/** @var string[] $aliases A map from local alias to fully qualified name */
|
||||||
list($aliases,,) = $node->getImportTablesForCurrentScope();
|
list($aliases,,) = $node->getImportTablesForCurrentScope();
|
||||||
|
|
||||||
// If there is a prefix that does not start with a slash, suggest `use`d symbols
|
/** @var array Array of [fqn=string, requiresRoaming=bool] the prefix may represent. */
|
||||||
|
$possibleFqns = [];
|
||||||
|
|
||||||
|
if ($isFullyQualified) {
|
||||||
|
// Case \Microsoft\PhpParser\Res|
|
||||||
|
$possibleFqns[] = [$prefix, false];
|
||||||
|
} else if ($fqnAfterAlias = $this->tryApplyAlias($aliases, $prefix)) {
|
||||||
|
// Cases handled here: (i.e. all namespaces involving use clauses)
|
||||||
|
//
|
||||||
|
// use Microsoft\PhpParser\Node; //Note that Node is both a class and a namespace.
|
||||||
|
// Nod|
|
||||||
|
// Node\Qual|
|
||||||
|
//
|
||||||
|
// use Microsoft\PhpParser as TheParser;
|
||||||
|
// TheParser\Nod|
|
||||||
|
$possibleFqns[] = [$fqnAfterAlias, false];
|
||||||
|
} else if ($namespaceNode) {
|
||||||
|
// Cases handled here:
|
||||||
|
//
|
||||||
|
// namespace Foo;
|
||||||
|
// Microsoft\PhpParser\Nod| // Can refer only to \Foo\Microsoft, not to \Microsoft.
|
||||||
|
//
|
||||||
|
// namespace Foo;
|
||||||
|
// Test| // Can refer either to functions or constants at the global scope, or to
|
||||||
|
// // everything below \Foo. (Global fallback / roaming)
|
||||||
|
/** @var \Microsoft\PhpParser\ResolvedName Declared namespace of the file (or section) */
|
||||||
|
$namespacedFqn = array_merge(
|
||||||
|
array_map(
|
||||||
|
static function ($token) use ($namespaceNode): string {
|
||||||
|
return $token->getText($namespaceNode->getFileContents());
|
||||||
|
},
|
||||||
|
$filterNameTokens($namespaceNode->name->nameParts)
|
||||||
|
),
|
||||||
|
$prefix
|
||||||
|
);
|
||||||
|
$possibleFqns[] = [$namespacedFqn, false];
|
||||||
|
if (!$isQualified) {
|
||||||
|
// Case of global fallback. If nothing is entered, also complete for root-level classnames.
|
||||||
|
// If something has been entered, complete root-level roamed symbols only.
|
||||||
|
$possibleFqns[] = [$prefix, !empty($prefix)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Case handled here: (no namespace declaration in file)
|
||||||
|
//
|
||||||
|
// Microsoft\PhpParser\N|
|
||||||
|
$possibleFqns[] = [$prefix, false];
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefixStr = implode('\\', $prefix);
|
||||||
|
/** @var int Length of $prefix */
|
||||||
|
$prefixLen = strlen($prefixStr);
|
||||||
|
|
||||||
|
// If there is a prefix that does not contain a slash, suggest used names.
|
||||||
if (!$isQualified) {
|
if (!$isQualified) {
|
||||||
foreach ($aliases as $alias => $fqn) {
|
foreach ($aliases as $alias => $fqn) {
|
||||||
// Suggest symbols that have been `use`d and match the prefix
|
// Suggest symbols that have been `use`d and match the prefix
|
||||||
if (substr($alias, 0, $prefixLen) === $prefix
|
if (substr($alias, 0, $prefixLen) === $prefixStr
|
||||||
&& ($def = $this->index->getDefinition((string)$fqn))) {
|
&& ($def = $this->index->getDefinition((string)$fqn))) {
|
||||||
// TODO: complete even when getDefinition($fqn) fails, e.g. complete definitions that are were
|
$list->items[] = CompletionItem::fromDefinition($def);
|
||||||
// not found in the files parsed.
|
|
||||||
$item = CompletionItem::fromDefinition($def);
|
|
||||||
$item->insertText = $alias;
|
|
||||||
$list->items[] = $item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($namespacesToSearch as $namespaceToSearch) {
|
foreach ($possibleFqns as list ($fqnToSearch, $requiresRoaming)) {
|
||||||
|
$namespaceToSearch = $fqnToSearch;
|
||||||
|
array_pop($namespaceToSearch);
|
||||||
|
$namespaceToSearch = implode('\\', $namespaceToSearch);
|
||||||
|
$fqnToSearch = implode('\\', $fqnToSearch);
|
||||||
|
$fqnToSearchLen = strlen($fqnToSearch);
|
||||||
foreach ($this->index->getChildDefinitionsForFqn($namespaceToSearch) as $fqn => $def) {
|
foreach ($this->index->getChildDefinitionsForFqn($namespaceToSearch) as $fqn => $def) {
|
||||||
if (isset($creation) && !$def->canBeInstantiated) {
|
if (isset($creation) && !$def->canBeInstantiated) {
|
||||||
// Only suggest classes for `new`
|
// Only suggest classes for `new`
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($requiresRoaming && !$def->roamed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix;
|
if (substr($fqn, 0, $fqnToSearchLen) === $fqnToSearch) {
|
||||||
$fqnStartsWithNamespacedPrefix = substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix;
|
|
||||||
|
|
||||||
if (
|
|
||||||
// No prefix - return all,
|
|
||||||
$prefix === ''
|
|
||||||
// or FQN starts with namespaced prefix,
|
|
||||||
|| $fqnStartsWithNamespacedPrefix
|
|
||||||
// or a roamed definition (i.e. global fallback to a constant or a function) matches prefix.
|
|
||||||
|| ($def->roamed && $fqnStartsWithPrefix)
|
|
||||||
) {
|
|
||||||
$item = CompletionItem::fromDefinition($def);
|
$item = CompletionItem::fromDefinition($def);
|
||||||
// Find the shortest name to reference the symbol
|
if (($aliasMatch = $this->tryMatchAlias($aliases, $fqn)) !== null) {
|
||||||
if ($namespaceNode && ($alias = array_search($fqn, $aliases, true)) !== false) {
|
$item->insertText = $aliasMatch;
|
||||||
// $alias is the name under which this definition is aliased in the current namespace
|
} else if ($namespaceNode && (empty($prefix) || $requiresRoaming)) {
|
||||||
$item->insertText = $alias;
|
// Insert the global FQN with a leading backslash.
|
||||||
} else if ($namespaceNode && !($prefix && $isFullyQualified)) {
|
// For empty prefix: Assume that the user wants an FQN. They have not
|
||||||
// Insert the global FQN with a leading backslash
|
// started writing anything yet, so we are not second-guessing.
|
||||||
|
// For roaming: Second-guess that the user doesn't want to depend on
|
||||||
|
// roaming.
|
||||||
$item->insertText = '\\' . $fqn;
|
$item->insertText = '\\' . $fqn;
|
||||||
} else {
|
} else {
|
||||||
// Insert the FQN without a leading backslash
|
// Insert the FQN without a leading backslash
|
||||||
|
@ -410,7 +430,7 @@ class CompletionProvider
|
||||||
// Suggest keywords
|
// Suggest keywords
|
||||||
if (!$isQualified && !isset($creation)) {
|
if (!$isQualified && !isset($creation)) {
|
||||||
foreach (self::KEYWORDS as $keyword) {
|
foreach (self::KEYWORDS as $keyword) {
|
||||||
if (substr($keyword, 0, $prefixLen) === $prefix) {
|
if (substr($keyword, 0, $prefixLen) === $prefixStr) {
|
||||||
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||||
$item->insertText = $keyword;
|
$item->insertText = $keyword;
|
||||||
$list->items[] = $item;
|
$list->items[] = $item;
|
||||||
|
@ -422,6 +442,62 @@ class CompletionProvider
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function tryMatchAlias(
|
||||||
|
array $aliases,
|
||||||
|
string $fullyQualifiedName
|
||||||
|
): ?string {
|
||||||
|
$fullyQualifiedName = explode('\\', $fullyQualifiedName);
|
||||||
|
$aliasMatch = null;
|
||||||
|
$aliasMatchLength = null;
|
||||||
|
foreach ($aliases as $alias => $aliasFqn) {
|
||||||
|
$aliasFqn = $aliasFqn->getNameParts();
|
||||||
|
$aliasFqnLength = count($aliasFqn);
|
||||||
|
if ($aliasMatchLength && $aliasFqnLength < $aliasFqnLength) {
|
||||||
|
// Find the longest possible match. This one won't do.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$fqnStart = array_slice($fullyQualifiedName, 0, $aliasFqnLength);
|
||||||
|
if ($fqnStart === $aliasFqn) {
|
||||||
|
$aliasMatch = $alias;
|
||||||
|
$aliasMatchLength = $aliasFqnLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($aliasMatch === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fqnNoAlias = array_slice($fullyQualifiedName, $aliasMatchLength);
|
||||||
|
return join('\\', array_merge([$aliasMatch], $fqnNoAlias));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to convert a partially qualified name to an FQN using aliases.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* use Microsoft\PhpParser as TheParser;
|
||||||
|
* "TheParser\Node" will convert to "Microsoft\PhpParser\Node"
|
||||||
|
*
|
||||||
|
* @param \Microsoft\PhpParser\ResolvedName[] $aliases
|
||||||
|
* Aliases available in the scope of resolution. Keyed by alias.
|
||||||
|
* @param string[] $partiallyQualifiedName
|
||||||
|
**/
|
||||||
|
private function tryApplyAlias(
|
||||||
|
array $aliases,
|
||||||
|
array $partiallyQualifiedName
|
||||||
|
): ?array {
|
||||||
|
if (empty($partiallyQualifiedName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$head = $partiallyQualifiedName[0];
|
||||||
|
$tail = array_slice($partiallyQualifiedName, 1);
|
||||||
|
if (!isset($aliases[$head])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return array_merge($aliases[$head]->getNameParts(), $tail);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Yields FQNs from an array along with the FQNs of all parent classes
|
* Yields FQNs from an array along with the FQNs of all parent classes
|
||||||
*
|
*
|
||||||
|
|
|
@ -35,7 +35,9 @@ 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',
|
||||||
], array_keys($defNodes));
|
], array_keys($defNodes));
|
||||||
|
|
||||||
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']);
|
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']);
|
||||||
|
@ -53,6 +55,7 @@ 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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDoesNotCollectReferences()
|
public function testDoesNotCollectReferences()
|
||||||
|
|
|
@ -107,7 +107,9 @@ abstract class ServerTestCase extends TestCase
|
||||||
'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))),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->referenceLocations = [
|
$this->referenceLocations = [
|
||||||
|
|
|
@ -213,10 +213,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -239,10 +236,28 @@ 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.'
|
||||||
|
)
|
||||||
|
], true), $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsedNamespace()
|
||||||
|
{
|
||||||
|
$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(6, 16)
|
||||||
|
)->wait();
|
||||||
|
$this->assertCompletionsListSubset(new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'InnerClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace\\InnerNamespace',
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
'TestClass'
|
null,
|
||||||
|
'AliasNamespace\\InnerClass'
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,9 @@ class DocumentSymbolTest extends ServerTestCase
|
||||||
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'),
|
||||||
], $result);
|
], $result);
|
||||||
// @codingStandardsIgnoreEnd
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ 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'),
|
||||||
|
@ -46,6 +46,8 @@ class SymbolTest extends ServerTestCase
|
||||||
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('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
||||||
// Global
|
// Global
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
||||||
|
|
Loading…
Reference in New Issue