parent
e6a4103f97
commit
06636ded54
|
@ -13,5 +13,5 @@ indent_size = 2
|
||||||
[composer.json]
|
[composer.json]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[*.md]
|
[{*.md,fixtures/**}]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"bin": ["bin/php-language-server.php"],
|
"bin": ["bin/php-language-server.php"],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.0",
|
"php": ">=7.0",
|
||||||
"nikic/php-parser": "^3.0.0beta2",
|
"nikic/php-parser": "dev-master#c5cdd5ad73ac20d855b84fa6d0f1f22ebff2e302",
|
||||||
"phpdocumentor/reflection-docblock": "^3.0",
|
"phpdocumentor/reflection-docblock": "^3.0",
|
||||||
"sabre/event": "^5.0",
|
"sabre/event": "^5.0",
|
||||||
"felixfbecker/advanced-json-rpc": "^2.0",
|
"felixfbecker/advanced-json-rpc": "^2.0",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use TestNamespace\{TestClass, TestInterface};
|
||||||
|
|
||||||
|
\TestC
|
||||||
|
|
||||||
|
class OtherClass {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use TestNamespace\{TestClass, TestInterface};
|
||||||
|
|
||||||
|
TestC
|
||||||
|
|
||||||
|
class OtherClass {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Whatever;
|
||||||
|
|
||||||
|
use TestNamespace\{TestClass, TestInterface};
|
||||||
|
|
||||||
|
$obj = new
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $param A parameter
|
||||||
|
*/
|
||||||
|
function test(string $param = null)
|
||||||
|
{
|
||||||
|
$var = 123;
|
||||||
|
$p
|
||||||
|
}
|
|
@ -48,41 +48,103 @@ class CompletionProvider
|
||||||
/** @var CompletionItem[] */
|
/** @var CompletionItem[] */
|
||||||
$items = [];
|
$items = [];
|
||||||
|
|
||||||
if ($node instanceof Node\Expr\Error) {
|
// A non-free node means we do NOT suggest global symbols
|
||||||
$node = $node->getAttribute('parentNode');
|
if (
|
||||||
}
|
$node instanceof Node\Expr\MethodCall
|
||||||
|
|| $node instanceof Node\Expr\PropertyFetch
|
||||||
// If we get a property fetch node, resolve items of the class
|
|| $node instanceof Node\Expr\StaticCall
|
||||||
if ($node instanceof Node\Expr\PropertyFetch) {
|
|| $node instanceof Node\Expr\StaticPropertyFetch
|
||||||
$objType = $this->definitionResolver->resolveExpressionNodeToType($node->var);
|
|| $node instanceof Node\Expr\ClassConstFetch
|
||||||
if ($objType instanceof Types\Object_ && $objType->getFqsen() !== null) {
|
) {
|
||||||
$prefix = substr((string)$objType->getFqsen(), 1) . '::';
|
/** The FQN to be completed */
|
||||||
if (is_string($node->name)) {
|
$prefix = $this->definitionResolver->resolveReferenceNodeToFqn($node) ?? '';
|
||||||
$prefix .= $node->name;
|
|
||||||
}
|
|
||||||
$prefixLen = strlen($prefix);
|
$prefixLen = strlen($prefix);
|
||||||
foreach ($this->project->getDefinitions() as $fqn => $def) {
|
foreach ($this->project->getDefinitions() as $fqn => $def) {
|
||||||
if (substr($fqn, 0, $prefixLen) === $prefix) {
|
if (substr($fqn, 0, $prefixLen) === $prefix && !$def->isGlobal) {
|
||||||
$item = new CompletionItem;
|
$items[] = CompletionItem::fromDefinition($def);
|
||||||
$item->label = $def->symbolInformation->name;
|
|
||||||
if ($def->type) {
|
|
||||||
$item->detail = (string)$def->type;
|
|
||||||
}
|
}
|
||||||
if ($def->documentation) {
|
|
||||||
$item->documentation = $def->documentation;
|
|
||||||
}
|
}
|
||||||
if ($def->symbolInformation->kind === SymbolKind::PROPERTY) {
|
} else if (
|
||||||
$item->kind = CompletionItemKind::PROPERTY;
|
// A ConstFetch means any static reference, like a class, interface, etc.
|
||||||
} else if ($def->symbolInformation->kind === SymbolKind::METHOD) {
|
($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch)
|
||||||
$item->kind = CompletionItemKind::METHOD;
|
|| $node instanceof Node\Expr\New_
|
||||||
|
) {
|
||||||
|
$prefix = null;
|
||||||
|
if ($node instanceof Node\Name) {
|
||||||
|
$isFullyQualified = $node->isFullyQualified();
|
||||||
|
$prefix = (string)$node;
|
||||||
|
$prefixLen = strlen($prefix);
|
||||||
|
$namespacedPrefix = (string)$node->getAttribute('namespacedName');
|
||||||
|
$namespacedPrefixLen = strlen($prefix);
|
||||||
|
}
|
||||||
|
// Find closest namespace
|
||||||
|
$namespace = getClosestNode($node, Node\Stmt\Namespace_::class);
|
||||||
|
/** Map from alias to Definition */
|
||||||
|
$aliasedDefs = [];
|
||||||
|
if ($namespace) {
|
||||||
|
foreach ($namespace->stmts as $stmt) {
|
||||||
|
if ($stmt instanceof Node\Stmt\Use_ || $stmt instanceof Node\Stmt\GroupUse) {
|
||||||
|
foreach ($stmt->uses as $use) {
|
||||||
|
// Get the definition for the used namespace, class-like, function or constant
|
||||||
|
// And save it under the alias
|
||||||
|
$fqn = (string)Node\Name::concat($stmt->prefix, $use->name);
|
||||||
|
$aliasedDefs[$use->alias] = $this->project->getDefinition($fqn);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use statements are always the first statements in a namespace
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there is a prefix that does not start with a slash, suggest `use`d symbols
|
||||||
|
if ($prefix && !$isFullyQualified) {
|
||||||
|
// Suggest symbols that have been `use`d
|
||||||
|
// Search the aliases for the typed-in name
|
||||||
|
foreach ($aliasedDefs as $alias => $def) {
|
||||||
|
if (substr($alias, 0, $prefixLen) === $prefix) {
|
||||||
|
$items[] = CompletionItem::fromDefinition($def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Additionally, suggest global symbols that either
|
||||||
|
// - start with the current namespace + prefix, if the Name node is not fully qualified
|
||||||
|
// - start with just the prefix, if the Name node is fully qualified
|
||||||
|
foreach ($this->project->getDefinitions() as $fqn => $def) {
|
||||||
|
if (
|
||||||
|
$def->isGlobal // exclude methods, properties etc.
|
||||||
|
&& (
|
||||||
|
!$prefix
|
||||||
|
|| (
|
||||||
|
($isFullyQualified && substr($fqn, 0, $prefixLen) === $prefix)
|
||||||
|
|| (!$isFullyQualified && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// Only suggest classes for `new`
|
||||||
|
&& (!($node instanceof Node\Expr\New_) || $def->canBeInstantiated)
|
||||||
|
) {
|
||||||
|
$item = CompletionItem::fromDefinition($def);
|
||||||
|
// Find the shortest name to reference the symbol
|
||||||
|
if ($namespace && ($alias = array_search($def, $aliasedDefs, true)) !== false) {
|
||||||
|
// $alias is the name under which this definition is aliased in the current namespace
|
||||||
|
$item->insertText = $alias;
|
||||||
|
} else if ($namespace) {
|
||||||
|
// Insert the global FQN with trailing backslash
|
||||||
|
$item->insertText = '\\' . $fqn;
|
||||||
|
} else {
|
||||||
|
// Insert the FQN without trailing backlash
|
||||||
|
$item->insertText = $fqn;
|
||||||
}
|
}
|
||||||
$items[] = $item;
|
$items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (
|
||||||
} else {
|
$node instanceof Node\Expr\Variable
|
||||||
|
|| ($node && $node->getAttribute('parentNode') instanceof Node\Expr\Variable)
|
||||||
|
) {
|
||||||
// Find variables, parameters and use statements in the scope
|
// Find variables, parameters and use statements in the scope
|
||||||
foreach ($this->suggestVariablesAtNode($node) as $var) {
|
// If there was only a $ typed, $node will be instanceof Node\Error
|
||||||
|
$namePrefix = $node instanceof Node\Expr\Variable && is_string($node->name) ? $node->name : '';
|
||||||
|
foreach ($this->suggestVariablesAtNode($node, $namePrefix) as $var) {
|
||||||
$item = new CompletionItem;
|
$item = new CompletionItem;
|
||||||
$item->kind = CompletionItemKind::VARIABLE;
|
$item->kind = CompletionItemKind::VARIABLE;
|
||||||
$item->label = '$' . ($var instanceof Node\Expr\ClosureUse ? $var->var : $var->name);
|
$item->label = '$' . ($var instanceof Node\Expr\ClosureUse ? $var->var : $var->name);
|
||||||
|
@ -101,16 +163,17 @@ class CompletionProvider
|
||||||
* of that variable
|
* of that variable
|
||||||
*
|
*
|
||||||
* @param Node $node
|
* @param Node $node
|
||||||
|
* @param string $namePrefix Prefix to filter
|
||||||
* @return array <Node\Expr\Variable|Node\Param|Node\Expr\ClosureUse>
|
* @return array <Node\Expr\Variable|Node\Param|Node\Expr\ClosureUse>
|
||||||
*/
|
*/
|
||||||
private function suggestVariablesAtNode(Node $node): array
|
private function suggestVariablesAtNode(Node $node, string $namePrefix = ''): array
|
||||||
{
|
{
|
||||||
$vars = [];
|
$vars = [];
|
||||||
|
|
||||||
// Find variables in the node itself
|
// Find variables in the node itself
|
||||||
// When getting completion in the middle of a function, $node will be the function node
|
// When getting completion in the middle of a function, $node will be the function node
|
||||||
// so we need to search it
|
// so we need to search it
|
||||||
foreach ($this->findVariableDefinitionsInNode($node) as $var) {
|
foreach ($this->findVariableDefinitionsInNode($node, $namePrefix) as $var) {
|
||||||
// Only use the first definition
|
// Only use the first definition
|
||||||
if (!isset($vars[$var->name])) {
|
if (!isset($vars[$var->name])) {
|
||||||
$vars[$var->name] = $var;
|
$vars[$var->name] = $var;
|
||||||
|
@ -124,7 +187,7 @@ class CompletionProvider
|
||||||
$sibling = $level;
|
$sibling = $level;
|
||||||
while ($sibling = $sibling->getAttribute('previousSibling')) {
|
while ($sibling = $sibling->getAttribute('previousSibling')) {
|
||||||
// Collect all variables inside the sibling node
|
// Collect all variables inside the sibling node
|
||||||
foreach ($this->findVariableDefinitionsInNode($sibling) as $var) {
|
foreach ($this->findVariableDefinitionsInNode($sibling, $namePrefix) as $var) {
|
||||||
$vars[$var->name] = $var;
|
$vars[$var->name] = $var;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,13 +198,13 @@ class CompletionProvider
|
||||||
// also add its parameters and closure uses to the result list
|
// also add its parameters and closure uses to the result list
|
||||||
if ($level instanceof Node\FunctionLike) {
|
if ($level instanceof Node\FunctionLike) {
|
||||||
foreach ($level->params as $param) {
|
foreach ($level->params as $param) {
|
||||||
if (!isset($vars[$param->name])) {
|
if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) {
|
||||||
$vars[$param->name] = $param;
|
$vars[$param->name] = $param;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($level instanceof Node\Expr\Closure) {
|
if ($level instanceof Node\Expr\Closure) {
|
||||||
foreach ($level->uses as $use) {
|
foreach ($level->uses as $use) {
|
||||||
if (!isset($vars[$param->name])) {
|
if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) {
|
||||||
$vars[$use->var] = $use;
|
$vars[$use->var] = $use;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,9 +218,10 @@ class CompletionProvider
|
||||||
* Searches the subnodes of a node for variable assignments
|
* Searches the subnodes of a node for variable assignments
|
||||||
*
|
*
|
||||||
* @param Node $node
|
* @param Node $node
|
||||||
|
* @param string $namePrefix Prefix to filter
|
||||||
* @return Node\Expr\Variable[]
|
* @return Node\Expr\Variable[]
|
||||||
*/
|
*/
|
||||||
private function findVariableDefinitionsInNode(Node $node): array
|
private function findVariableDefinitionsInNode(Node $node, string $namePrefix = ''): array
|
||||||
{
|
{
|
||||||
$vars = [];
|
$vars = [];
|
||||||
// If the child node is a variable assignment, save it
|
// If the child node is a variable assignment, save it
|
||||||
|
@ -166,6 +230,7 @@ class CompletionProvider
|
||||||
$node instanceof Node\Expr\Variable
|
$node instanceof Node\Expr\Variable
|
||||||
&& ($parent instanceof Node\Expr\Assign || $parent instanceof Node\Expr\AssignOp)
|
&& ($parent instanceof Node\Expr\Assign || $parent instanceof Node\Expr\AssignOp)
|
||||||
&& is_string($node->name) // Variable variables are of no use
|
&& is_string($node->name) // Variable variables are of no use
|
||||||
|
&& substr($node->name, 0, strlen($namePrefix)) === $namePrefix
|
||||||
) {
|
) {
|
||||||
$vars[] = $node;
|
$vars[] = $node;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +246,7 @@ class CompletionProvider
|
||||||
if (!($child instanceof Node) || $child instanceof Node\FunctionLike) {
|
if (!($child instanceof Node) || $child instanceof Node\FunctionLike) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($this->findVariableDefinitionsInNode($child) as $var) {
|
foreach ($this->findVariableDefinitionsInNode($child, $namePrefix) as $var) {
|
||||||
$vars[] = $var;
|
$vars[] = $var;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,21 @@ class Definition
|
||||||
*/
|
*/
|
||||||
public $fqn;
|
public $fqn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only true for classes, interfaces, traits, functions and non-class constants
|
||||||
|
* This is so methods and properties are not suggested in the global scope
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $isGlobal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the Definition is a class
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $canBeInstantiated;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Protocol\SymbolInformation
|
* @var Protocol\SymbolInformation
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -91,6 +91,30 @@ class DefinitionResolver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Definition for a definition node
|
||||||
|
*
|
||||||
|
* @param Node $node
|
||||||
|
* @param string $fqn
|
||||||
|
* @return Definition
|
||||||
|
*/
|
||||||
|
public function createDefinitionFromNode(Node $node, string $fqn = null): Definition
|
||||||
|
{
|
||||||
|
$def = new Definition;
|
||||||
|
$def->canBeInstantiated = $node instanceof Node\Stmt\Class_;
|
||||||
|
$def->isGlobal = (
|
||||||
|
$node instanceof Node\Stmt\ClassLike
|
||||||
|
|| $node instanceof Node\Stmt\Function_
|
||||||
|
|| $node->getAttribute('parentNode') instanceof Node\Stmt\Const_
|
||||||
|
);
|
||||||
|
$def->fqn = $fqn;
|
||||||
|
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
||||||
|
$def->type = $this->getTypeFromNode($node);
|
||||||
|
$def->declarationLine = $this->getDeclarationLineFromNode($node);
|
||||||
|
$def->documentation = $this->getDocumentationFromNode($node);
|
||||||
|
return $def;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given any node, returns the Definition object of the symbol that is referenced
|
* Given any node, returns the Definition object of the symbol that is referenced
|
||||||
*
|
*
|
||||||
|
@ -106,16 +130,7 @@ class DefinitionResolver
|
||||||
if ($defNode === null) {
|
if ($defNode === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$def = new Definition;
|
return $this->createDefinitionFromNode($defNode);
|
||||||
// Get symbol information from node (range, symbol kind)
|
|
||||||
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
|
||||||
// Declaration line
|
|
||||||
$def->declarationLine = $this->getDeclarationLineFromNode($defNode);
|
|
||||||
// Documentation
|
|
||||||
$def->documentation = $this->getDocumentationFromNode($defNode);
|
|
||||||
// Get type from docblock
|
|
||||||
$def->type = $this->getTypeFromNode($defNode);
|
|
||||||
return $def;
|
|
||||||
}
|
}
|
||||||
// Other references are references to a global symbol that have an FQN
|
// Other references are references to a global symbol that have an FQN
|
||||||
// Find out the FQN
|
// Find out the FQN
|
||||||
|
|
|
@ -42,13 +42,6 @@ class DefinitionCollector extends NodeVisitorAbstract
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->nodes[$fqn] = $node;
|
$this->nodes[$fqn] = $node;
|
||||||
$def = new Definition;
|
$this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn);
|
||||||
$def->fqn = $fqn;
|
|
||||||
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
|
||||||
$def->type = $this->definitionResolver->getTypeFromNode($node);
|
|
||||||
$def->declarationLine = $this->definitionResolver->getDeclarationLineFromNode($node);
|
|
||||||
$def->documentation = $this->definitionResolver->getDocumentationFromNode($node);
|
|
||||||
|
|
||||||
$this->definitions[$fqn] = $def;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\Protocol;
|
namespace LanguageServer\Protocol;
|
||||||
|
|
||||||
|
use LanguageServer\Definition;
|
||||||
|
|
||||||
class CompletionItem
|
class CompletionItem
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +105,7 @@ class CompletionItem
|
||||||
* @param string|null $documentation
|
* @param string|null $documentation
|
||||||
* @param string|null $sortText
|
* @param string|null $sortText
|
||||||
* @param string|null $filterText
|
* @param string|null $filterText
|
||||||
* @param string|null $insertQuery
|
* @param string|null $insertText
|
||||||
* @param TextEdit|null $textEdit
|
* @param TextEdit|null $textEdit
|
||||||
* @param TextEdit[]|null $additionalTextEdits
|
* @param TextEdit[]|null $additionalTextEdits
|
||||||
* @param Command|null $command
|
* @param Command|null $command
|
||||||
|
@ -115,7 +118,7 @@ class CompletionItem
|
||||||
string $documentation = null,
|
string $documentation = null,
|
||||||
string $sortText = null,
|
string $sortText = null,
|
||||||
string $filterText = null,
|
string $filterText = null,
|
||||||
string $insertQuery = null,
|
string $insertText = null,
|
||||||
TextEdit $textEdit = null,
|
TextEdit $textEdit = null,
|
||||||
array $additionalTextEdits = null,
|
array $additionalTextEdits = null,
|
||||||
Command $command = null,
|
Command $command = null,
|
||||||
|
@ -127,10 +130,32 @@ class CompletionItem
|
||||||
$this->documentation = $documentation;
|
$this->documentation = $documentation;
|
||||||
$this->sortText = $sortText;
|
$this->sortText = $sortText;
|
||||||
$this->filterText = $filterText;
|
$this->filterText = $filterText;
|
||||||
$this->insertQuery = $insertQuery;
|
$this->insertText = $insertText;
|
||||||
$this->textEdit = $textEdit;
|
$this->textEdit = $textEdit;
|
||||||
$this->additionalTextEdits = $additionalTextEdits;
|
$this->additionalTextEdits = $additionalTextEdits;
|
||||||
$this->command = $command;
|
$this->command = $command;
|
||||||
$this->data = $data;
|
$this->data = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CompletionItem for a Definition
|
||||||
|
*
|
||||||
|
* @param Definition $def
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function fromDefinition(Definition $def): self
|
||||||
|
{
|
||||||
|
$item = new CompletionItem;
|
||||||
|
$item->label = $def->symbolInformation->name;
|
||||||
|
$item->kind = CompletionItemKind::fromSymbolKind($def->symbolInformation->kind);
|
||||||
|
if ($def->type) {
|
||||||
|
$item->detail = (string)$def->type;
|
||||||
|
} else if ($def->symbolInformation->containerName) {
|
||||||
|
$item->detail = $def->symbolInformation->containerName;
|
||||||
|
}
|
||||||
|
if ($def->documentation) {
|
||||||
|
$item->documentation = $def->documentation;
|
||||||
|
}
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,4 +25,46 @@ abstract class CompletionItemKind
|
||||||
const COLOR = 16;
|
const COLOR = 16;
|
||||||
const FILE = 17;
|
const FILE = 17;
|
||||||
const REFERENCE = 18;
|
const REFERENCE = 18;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the CompletionItemKind for a SymbolKind
|
||||||
|
*
|
||||||
|
* @param int $kind A SymbolKind
|
||||||
|
* @return int The CompletionItemKind
|
||||||
|
*/
|
||||||
|
public static function fromSymbolKind(int $kind): int
|
||||||
|
{
|
||||||
|
switch ($kind) {
|
||||||
|
case SymbolKind::PROPERTY:
|
||||||
|
case SymbolKind::FIELD:
|
||||||
|
return self::PROPERTY;
|
||||||
|
case SymbolKind::METHOD:
|
||||||
|
return self::METHOD;
|
||||||
|
case SymbolKind::CLASS_:
|
||||||
|
return self::CLASS_;
|
||||||
|
case SymbolKind::INTERFACE:
|
||||||
|
return self::INTERFACE;
|
||||||
|
case SymbolKind::FUNCTION:
|
||||||
|
return self::FUNCTION;
|
||||||
|
case SymbolKind::NAMESPACE:
|
||||||
|
case SymbolKind::MODULE:
|
||||||
|
case SymbolKind::PACKAGE:
|
||||||
|
return self::MODULE;
|
||||||
|
case SymbolKind::FILE:
|
||||||
|
return self::FILE;
|
||||||
|
case SymbolKind::STRING:
|
||||||
|
return self::TEXT;
|
||||||
|
case SymbolKind::NUMBER:
|
||||||
|
case SymbolKind::BOOLEAN:
|
||||||
|
case SymbolKind::ARRAY:
|
||||||
|
return self::VALUE;
|
||||||
|
case SymbolKind::ENUM:
|
||||||
|
return self::ENUM;
|
||||||
|
case SymbolKind::CONSTRUCTOR:
|
||||||
|
return self::CONSTRUCTOR;
|
||||||
|
case SymbolKind::VARIABLE:
|
||||||
|
case SymbolKind::CONSTANT:
|
||||||
|
return self::VARIABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ class CompletionTest extends TestCase
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$this->project = new Project($client, new ClientCapabilities);
|
$this->project = new Project($client, new ClientCapabilities);
|
||||||
$this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait();
|
$this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait();
|
||||||
|
$this->project->loadDocument(pathToUri(__DIR__ . '/../../../fixtures/symbols.php'))->wait();
|
||||||
$this->textDocument = new Server\TextDocument($this->project, $client);
|
$this->textDocument = new Server\TextDocument($this->project, $client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ class CompletionTest extends TestCase
|
||||||
], $items);
|
], $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testForVariables()
|
public function testVariable()
|
||||||
{
|
{
|
||||||
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable.php');
|
||||||
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
||||||
|
@ -66,4 +67,85 @@ class CompletionTest extends TestCase
|
||||||
new CompletionItem('$param', CompletionItemKind::VARIABLE, 'string|null', 'A parameter')
|
new CompletionItem('$param', CompletionItemKind::VARIABLE, 'string|null', 'A parameter')
|
||||||
], $items);
|
], $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testVariableWithPrefix()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/variable_with_prefix.php');
|
||||||
|
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(8, 5)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals([
|
||||||
|
new CompletionItem('$param', CompletionItemKind::VARIABLE, 'string|null', 'A parameter')
|
||||||
|
], $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNewInNamespace()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_new.php');
|
||||||
|
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(6, 10)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals([
|
||||||
|
// Global TestClass definition (inserted as \TestClass)
|
||||||
|
new CompletionItem(
|
||||||
|
'TestClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
null,
|
||||||
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'\TestClass'
|
||||||
|
),
|
||||||
|
// Namespaced, `use`d TestClass definition (inserted as TestClass)
|
||||||
|
new CompletionItem(
|
||||||
|
'TestClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace',
|
||||||
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'TestClass'
|
||||||
|
),
|
||||||
|
], $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsedClass()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/used_class.php');
|
||||||
|
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(6, 5)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals([
|
||||||
|
new CompletionItem(
|
||||||
|
'TestClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
'TestNamespace',
|
||||||
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
|
||||||
|
)
|
||||||
|
], $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFullyQualifiedClass()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/fully_qualified_class.php');
|
||||||
|
$this->project->openDocument($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(6, 6)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals([
|
||||||
|
new CompletionItem(
|
||||||
|
'TestClass',
|
||||||
|
CompletionItemKind::CLASS_,
|
||||||
|
null,
|
||||||
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
|
||||||
|
)
|
||||||
|
], $items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue