Adopt Microsoft/tolerant-php-parser (#357)
parent
08cf1a3fd7
commit
7f427a1215
|
@ -5,3 +5,4 @@ vendor/
|
|||
.phpls/
|
||||
composer.lock
|
||||
stubs
|
||||
*.ast
|
|
@ -0,0 +1,27 @@
|
|||
[submodule "validation/frameworks/php-language-server"]
|
||||
path = validation/frameworks/php-language-server
|
||||
url = https://github.com/felixfbecker/php-language-server
|
||||
[submodule "validation/frameworks/wordpress"]
|
||||
path = validation/frameworks/wordpress
|
||||
url = https://github.com/wordpress/wordpress
|
||||
[submodule "validation/frameworks/drupal"]
|
||||
path = validation/frameworks/drupal
|
||||
url = https://github.com/drupal/drupal
|
||||
[submodule "validation/frameworks/tolerant-php-parser"]
|
||||
path = validation/frameworks/tolerant-php-parser
|
||||
url = https://github.com/microsoft/tolerant-php-parser
|
||||
[submodule "validation/frameworks/symfony"]
|
||||
path = validation/frameworks/symfony
|
||||
url = https://github.com/symfony/symfony
|
||||
[submodule "validation/frameworks/math-php"]
|
||||
path = validation/frameworks/math-php
|
||||
url = https://github.com/markrogoyski/math-php
|
||||
[submodule "validation/frameworks/codeigniter"]
|
||||
path = validation/frameworks/codeigniter
|
||||
url = https://github.com/bcit-ci/codeigniter
|
||||
[submodule "validation/frameworks/cakephp"]
|
||||
path = validation/frameworks/cakephp
|
||||
url = https://github.com/cakephp/cakephp
|
||||
[submodule "validation/frameworks/phpunit"]
|
||||
path = validation/frameworks/phpunit
|
||||
url = https://github.com/sebastianbergmann/phpunit
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\Tests;
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Exception;
|
||||
use LanguageServer\Index\Index;
|
||||
use LanguageServer\PhpDocument;
|
||||
use LanguageServer\DefinitionResolver;
|
||||
use Microsoft\PhpParser;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
$totalSize = 0;
|
||||
|
||||
$frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "CodeIgniter", "cakephp"];
|
||||
|
||||
foreach($frameworks as $framework) {
|
||||
$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->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
if (count($testProviderArray) === 0) {
|
||||
throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download.");
|
||||
}
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
foreach ($testProviderArray as $idx => $testCaseFile) {
|
||||
if (filesize($testCaseFile) > 10000) {
|
||||
continue;
|
||||
}
|
||||
if ($idx % 1000 === 0) {
|
||||
echo "$idx\n";
|
||||
}
|
||||
|
||||
$fileContents = file_get_contents($testCaseFile);
|
||||
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$index = new Index;
|
||||
$maxRecursion = [];
|
||||
$definitions = [];
|
||||
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$parser = new PhpParser\Parser();
|
||||
|
||||
try {
|
||||
$document = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||
} catch (\Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
echo "------------------------------\n";
|
||||
|
||||
echo "Time [$framework]: " . (microtime(true) - $start) . PHP_EOL;
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"nikic/php-parser": "^3.0.5",
|
||||
"phpdocumentor/reflection-docblock": "^3.0",
|
||||
"sabre/event": "^5.0",
|
||||
"felixfbecker/advanced-json-rpc": "^2.0",
|
||||
|
@ -38,7 +37,8 @@
|
|||
"webmozart/glob": "^4.1",
|
||||
"sabre/uri": "^2.0",
|
||||
"jetbrains/phpstorm-stubs": "dev-master",
|
||||
"composer/composer": "^1.3"
|
||||
"composer/composer": "^1.3",
|
||||
"Microsoft/tolerant-php-parser": "^0.0.2"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
|
@ -47,7 +47,9 @@
|
|||
"LanguageServer\\": "src/"
|
||||
},
|
||||
"files" : [
|
||||
"src/utils.php"
|
||||
"src/utils.php",
|
||||
"src/FqnUtilities.php",
|
||||
"src/ParserHelpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<ruleset name="PHP Language Server">
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
<exclude-pattern>tests/Validation/cases</exclude-pattern>
|
||||
<rule ref="PSR2">
|
||||
<exclude name="PSR2.Namespaces.UseDeclaration.MultipleDeclarations"/>
|
||||
<exclude name="PSR2.ControlStructures.ElseIfDeclaration.NotAllowed"/>
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
</whitelist>
|
||||
</filter>
|
||||
<php>
|
||||
<ini name="memory_limit" value="256M"/>
|
||||
<ini name="memory_limit" value="1024M"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
|
@ -3,7 +3,6 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use LanguageServer\Index\ReadableIndex;
|
||||
use LanguageServer\Protocol\{
|
||||
TextEdit,
|
||||
|
@ -13,6 +12,8 @@ use LanguageServer\Protocol\{
|
|||
CompletionItem,
|
||||
CompletionItemKind
|
||||
};
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
class CompletionProvider
|
||||
{
|
||||
|
@ -104,7 +105,7 @@ class CompletionProvider
|
|||
|
||||
/**
|
||||
* @param DefinitionResolver $definitionResolver
|
||||
* @param ReadableIndex $index
|
||||
* @param ReadableIndex $index
|
||||
*/
|
||||
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index)
|
||||
{
|
||||
|
@ -121,45 +122,74 @@ class CompletionProvider
|
|||
*/
|
||||
public function provideCompletion(PhpDocument $doc, Position $pos): CompletionList
|
||||
{
|
||||
// This can be made much more performant if the tree follows specific invariants.
|
||||
$node = $doc->getNodeAtPosition($pos);
|
||||
|
||||
if ($node instanceof Node\Expr\Error) {
|
||||
$node = $node->getAttribute('parentNode');
|
||||
$offset = $node === null ? -1 : $pos->toOffset($node->getFileContents());
|
||||
if (
|
||||
$node !== null
|
||||
&& $offset > $node->getEndPosition()
|
||||
&& $node->parent !== null
|
||||
&& $node->parent->getLastChild() instanceof PhpParser\MissingToken
|
||||
) {
|
||||
$node = $node->parent;
|
||||
}
|
||||
|
||||
$list = new CompletionList;
|
||||
$list->isIncomplete = true;
|
||||
|
||||
// A non-free node means we do NOT suggest global symbols
|
||||
if (
|
||||
$node instanceof Node\Expr\MethodCall
|
||||
|| $node instanceof Node\Expr\PropertyFetch
|
||||
|| $node instanceof Node\Expr\StaticCall
|
||||
|| $node instanceof Node\Expr\StaticPropertyFetch
|
||||
|| $node instanceof Node\Expr\ClassConstFetch
|
||||
if ($node instanceof Node\Expression\Variable &&
|
||||
$node->parent instanceof Node\Expression\ObjectCreationExpression &&
|
||||
$node->name instanceof PhpParser\MissingToken
|
||||
) {
|
||||
// If the name is an Error node, just filter by the class
|
||||
if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
|
||||
// For instances, resolve the variable type
|
||||
$prefixes = DefinitionResolver::getFqnsFromType(
|
||||
$this->definitionResolver->resolveExpressionNodeToType($node->var)
|
||||
$node = $node->parent;
|
||||
}
|
||||
|
||||
if ($node === null || $node instanceof Node\Statement\InlineHtml || $pos == new Position(0, 0)) {
|
||||
$item = new CompletionItem('<?php', CompletionItemKind::KEYWORD);
|
||||
$item->textEdit = new TextEdit(
|
||||
new Range($pos, $pos),
|
||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), '<?php')
|
||||
);
|
||||
$list->items[] = $item;
|
||||
} /*
|
||||
|
||||
VARIABLES */
|
||||
elseif (
|
||||
$node instanceof Node\Expression\Variable &&
|
||||
!(
|
||||
$node->parent instanceof Node\Expression\ScopedPropertyAccessExpression &&
|
||||
$node->parent->memberName === $node)
|
||||
) {
|
||||
// Find variables, parameters and use statements in the scope
|
||||
$namePrefix = $node->getName() ?? '';
|
||||
foreach ($this->suggestVariablesAtNode($node, $namePrefix) as $var) {
|
||||
$item = new CompletionItem;
|
||||
$item->kind = CompletionItemKind::VARIABLE;
|
||||
$item->label = '$' . $var->getName();
|
||||
$item->documentation = $this->definitionResolver->getDocumentationFromNode($var);
|
||||
$item->detail = (string)$this->definitionResolver->getTypeFromNode($var);
|
||||
$item->textEdit = new TextEdit(
|
||||
new Range($pos, $pos),
|
||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), $item->label)
|
||||
);
|
||||
} else {
|
||||
// Static member reference
|
||||
$prefixes = [$node->class instanceof Node\Name ? (string)$node->class : ''];
|
||||
$list->items[] = $item;
|
||||
}
|
||||
} /*
|
||||
|
||||
MEMBER ACCESS EXPRESSIONS
|
||||
$a->c#
|
||||
$a-># */
|
||||
elseif ($node instanceof Node\Expression\MemberAccessExpression) {
|
||||
$prefixes = FqnUtilities\getFqnsFromType(
|
||||
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
||||
);
|
||||
$prefixes = $this->expandParentFqns($prefixes);
|
||||
// If we are just filtering by the class, add the appropiate operator to the prefix
|
||||
// to filter the type of symbol
|
||||
|
||||
foreach ($prefixes as &$prefix) {
|
||||
if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
|
||||
$prefix .= '->';
|
||||
} else if ($node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\ClassConstFetch) {
|
||||
$prefix .= '::';
|
||||
} else if ($node instanceof Node\Expr\StaticPropertyFetch) {
|
||||
$prefix .= '::$';
|
||||
}
|
||||
$prefix .= '->';
|
||||
}
|
||||
|
||||
unset($prefix);
|
||||
|
||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||
|
@ -169,122 +199,114 @@ class CompletionProvider
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
// A ConstFetch means any static reference, like a class, interface, etc. or keyword
|
||||
($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch)
|
||||
|| $node instanceof Node\Expr\New_
|
||||
} /*
|
||||
|
||||
SCOPED PROPERTY ACCESS EXPRESSIONS
|
||||
A\B\C::$a#
|
||||
A\B\C::#
|
||||
A\B\C::$#
|
||||
A\B\C::foo#
|
||||
TODO: $a::# */
|
||||
elseif (
|
||||
($scoped = $node->parent) instanceof Node\Expression\ScopedPropertyAccessExpression ||
|
||||
($scoped = $node) instanceof Node\Expression\ScopedPropertyAccessExpression
|
||||
) {
|
||||
$prefix = '';
|
||||
$prefixLen = 0;
|
||||
if ($node instanceof Node\Name) {
|
||||
$isFullyQualified = $node->isFullyQualified();
|
||||
$prefix = (string)$node;
|
||||
$prefixLen = strlen($prefix);
|
||||
$namespacedPrefix = (string)$node->getAttribute('namespacedName');
|
||||
$namespacedPrefixLen = strlen($prefix);
|
||||
$prefixes = FqnUtilities\getFqnsFromType(
|
||||
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
||||
);
|
||||
|
||||
$prefixes = $this->expandParentFqns($prefixes);
|
||||
|
||||
foreach ($prefixes as &$prefix) {
|
||||
$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 ?? null, $use->name);
|
||||
if ($def = $this->index->getDefinition($fqn)) {
|
||||
$aliasedDefs[$use->alias] = $def;
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
|
||||
unset($prefix);
|
||||
|
||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && !$def->isGlobal) {
|
||||
$list->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
|
||||
} elseif (ParserHelpers\isConstantFetch($node) ||
|
||||
($creation = $node->parent) instanceof Node\Expression\ObjectCreationExpression ||
|
||||
(($creation = $node) instanceof Node\Expression\ObjectCreationExpression)) {
|
||||
$class = isset($creation) ? $creation->classTypeDesignator : $node;
|
||||
|
||||
$prefix = $class instanceof Node\QualifiedName
|
||||
? (string)PhpParser\ResolvedName::buildName($class->nameParts, $class->getFileContents())
|
||||
: $class->getText($node->getFileContents());
|
||||
|
||||
$namespaceDefinition = $node->getNamespaceDefinition();
|
||||
|
||||
list($namespaceImportTable,,) = $node->getImportTablesForCurrentScope();
|
||||
foreach ($namespaceImportTable as $alias => $name) {
|
||||
$namespaceImportTable[$alias] = (string)$name;
|
||||
}
|
||||
|
||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||
if (
|
||||
$def->isGlobal // exclude methods, properties etc.
|
||||
&& (
|
||||
!$prefix
|
||||
|| (
|
||||
((!$namespace || $isFullyQualified) && substr($fqn, 0, $prefixLen) === $prefix)
|
||||
|| (
|
||||
$namespace
|
||||
&& !$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 && !($prefix && $isFullyQualified)) {
|
||||
// Insert the global FQN with trailing backslash
|
||||
$item->insertText = '\\' . $fqn;
|
||||
} else {
|
||||
// Insert the FQN without trailing backlash
|
||||
$item->insertText = $fqn;
|
||||
$fqnStartsWithPrefix = substr($fqn, 0, strlen($prefix)) === $prefix;
|
||||
$fqnContainsPrefix = empty($prefix) || strpos($fqn, $prefix) !== false;
|
||||
if (($def->canBeInstantiated || ($def->isGlobal && !isset($creation))) && $fqnContainsPrefix) {
|
||||
if ($namespaceDefinition !== null && $namespaceDefinition->name !== null) {
|
||||
$namespacePrefix = (string)PhpParser\ResolvedName::buildName($namespaceDefinition->name->nameParts, $node->getFileContents());
|
||||
|
||||
$isAliased = false;
|
||||
|
||||
$isNotFullyQualified = !($class instanceof Node\QualifiedName) || !$class->isFullyQualifiedName();
|
||||
if ($isNotFullyQualified) {
|
||||
foreach ($namespaceImportTable as $alias => $name) {
|
||||
if (substr($fqn, 0, strlen($name)) === $name) {
|
||||
$fqn = $alias;
|
||||
$isAliased = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prefixWithNamespace = $namespacePrefix . "\\" . $prefix;
|
||||
$fqnMatchesPrefixWithNamespace = substr($fqn, 0, strlen($prefixWithNamespace)) === $prefixWithNamespace;
|
||||
$isFullyQualifiedAndPrefixMatches = !$isNotFullyQualified && ($fqnStartsWithPrefix || $fqnMatchesPrefixWithNamespace);
|
||||
if (!$isFullyQualifiedAndPrefixMatches && !$isAliased) {
|
||||
if (!array_search($fqn, array_values($namespaceImportTable))) {
|
||||
if (empty($prefix)) {
|
||||
$fqn = '\\' . $fqn;
|
||||
} elseif ($fqnMatchesPrefixWithNamespace) {
|
||||
$fqn = substr($fqn, strlen($namespacePrefix) + 1);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} elseif ($fqnStartsWithPrefix && $class instanceof Node\QualifiedName && $class->isFullyQualifiedName()) {
|
||||
$fqn = '\\' . $fqn;
|
||||
}
|
||||
|
||||
$item = CompletionItem::fromDefinition($def);
|
||||
|
||||
$item->insertText = $fqn;
|
||||
$list->items[] = $item;
|
||||
}
|
||||
}
|
||||
// Suggest keywords
|
||||
if ($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch) {
|
||||
|
||||
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;
|
||||
}
|
||||
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||
$item->insertText = $keyword . ' ';
|
||||
$list->items[] = $item;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
$node instanceof Node\Expr\Variable
|
||||
|| ($node && $node->getAttribute('parentNode') instanceof Node\Expr\Variable)
|
||||
) {
|
||||
// Find variables, parameters and use statements in the scope
|
||||
// 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->kind = CompletionItemKind::VARIABLE;
|
||||
$item->label = '$' . ($var instanceof Node\Expr\ClosureUse ? $var->var : $var->name);
|
||||
$item->documentation = $this->definitionResolver->getDocumentationFromNode($var);
|
||||
$item->detail = (string)$this->definitionResolver->getTypeFromNode($var);
|
||||
$item->textEdit = new TextEdit(
|
||||
new Range($pos, $pos),
|
||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), $item->label)
|
||||
);
|
||||
} elseif (ParserHelpers\isConstantFetch($node)) {
|
||||
$prefix = (string) ($node->getResolvedName() ?? PhpParser\ResolvedName::buildName($node->nameParts, $node->getFileContents()));
|
||||
foreach (self::KEYWORDS as $keyword) {
|
||||
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||
$item->insertText = $keyword . ' ';
|
||||
$list->items[] = $item;
|
||||
}
|
||||
} else if ($node instanceof Node\Stmt\InlineHTML || $pos == new Position(0, 0)) {
|
||||
$item = new CompletionItem('<?php', CompletionItemKind::KEYWORD);
|
||||
$item->textEdit = new TextEdit(
|
||||
new Range($pos, $pos),
|
||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), '<?php')
|
||||
);
|
||||
$list->items[] = $item;
|
||||
}
|
||||
|
||||
return $list;
|
||||
|
@ -302,7 +324,7 @@ class CompletionProvider
|
|||
foreach ($fqns as $fqn) {
|
||||
$def = $this->index->getDefinition($fqn);
|
||||
if ($def) {
|
||||
foreach ($this->expandParentFqns($def->extends) as $parent) {
|
||||
foreach ($this->expandParentFqns($def->extends ?? []) as $parent) {
|
||||
$expanded[] = $parent;
|
||||
}
|
||||
}
|
||||
|
@ -335,30 +357,34 @@ class CompletionProvider
|
|||
|
||||
// Walk the AST upwards until a scope boundary is met
|
||||
$level = $node;
|
||||
while ($level && !($level instanceof Node\FunctionLike)) {
|
||||
while ($level && !ParserHelpers\isFunctionLike($level)) {
|
||||
// Walk siblings before the node
|
||||
$sibling = $level;
|
||||
while ($sibling = $sibling->getAttribute('previousSibling')) {
|
||||
while ($sibling = $sibling->getPreviousSibling()) {
|
||||
// Collect all variables inside the sibling node
|
||||
foreach ($this->findVariableDefinitionsInNode($sibling, $namePrefix) as $var) {
|
||||
$vars[$var->name] = $var;
|
||||
$vars[$var->getName()] = $var;
|
||||
}
|
||||
}
|
||||
$level = $level->getAttribute('parentNode');
|
||||
$level = $level->parent;
|
||||
}
|
||||
|
||||
// If the traversal ended because a function was met,
|
||||
// also add its parameters and closure uses to the result list
|
||||
if ($level instanceof Node\FunctionLike) {
|
||||
foreach ($level->params as $param) {
|
||||
if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) {
|
||||
$vars[$param->name] = $param;
|
||||
if ($level && ParserHelpers\isFunctionLike($level) && $level->parameters !== null) {
|
||||
foreach ($level->parameters->getValues() as $param) {
|
||||
$paramName = $param->getName();
|
||||
if (empty($namePrefix) || strpos($paramName, $namePrefix) !== false) {
|
||||
$vars[$paramName] = $param;
|
||||
}
|
||||
}
|
||||
if ($level instanceof Node\Expr\Closure) {
|
||||
foreach ($level->uses as $use) {
|
||||
if (!isset($vars[$use->var]) && substr($use->var, 0, strlen($namePrefix)) === $namePrefix) {
|
||||
$vars[$use->var] = $use;
|
||||
|
||||
if ($level instanceof Node\Expression\AnonymousFunctionCreationExpression && $level->anonymousFunctionUseClause !== null &&
|
||||
$level->anonymousFunctionUseClause->useVariableNameList !== null) {
|
||||
foreach ($level->anonymousFunctionUseClause->useVariableNameList->getValues() as $use) {
|
||||
$useName = $use->getName();
|
||||
if (empty($namePrefix) || strpos($useName, $namePrefix) !== false) {
|
||||
$vars[$useName] = $use;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,38 +398,36 @@ class CompletionProvider
|
|||
*
|
||||
* @param Node $node
|
||||
* @param string $namePrefix Prefix to filter
|
||||
* @return Node\Expr\Variable[]
|
||||
* @return Node\Expression\Variable[]
|
||||
*/
|
||||
private function findVariableDefinitionsInNode(Node $node, string $namePrefix = ''): array
|
||||
{
|
||||
$vars = [];
|
||||
// If the child node is a variable assignment, save it
|
||||
$parent = $node->getAttribute('parentNode');
|
||||
if (
|
||||
$node instanceof Node\Expr\Variable
|
||||
&& ($parent instanceof Node\Expr\Assign || $parent instanceof Node\Expr\AssignOp)
|
||||
&& is_string($node->name) // Variable variables are of no use
|
||||
&& substr($node->name, 0, strlen($namePrefix)) === $namePrefix
|
||||
) {
|
||||
$vars[] = $node;
|
||||
}
|
||||
// Iterate over subnodes
|
||||
foreach ($node->getSubNodeNames() as $attr) {
|
||||
if (!isset($node->$attr)) {
|
||||
continue;
|
||||
}
|
||||
$children = is_array($node->$attr) ? $node->$attr : [$node->$attr];
|
||||
foreach ($children as $child) {
|
||||
// Dont try to traverse scalars
|
||||
// Dont traverse functions, the contained variables are in a different scope
|
||||
if (!($child instanceof Node) || $child instanceof Node\FunctionLike) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->findVariableDefinitionsInNode($child, $namePrefix) as $var) {
|
||||
$vars[] = $var;
|
||||
|
||||
$isAssignmentToVariable = function ($node) {
|
||||
return $node instanceof Node\Expression\AssignmentExpression;
|
||||
};
|
||||
|
||||
if ($this->isAssignmentToVariableWithPrefix($node, $namePrefix)) {
|
||||
$vars[] = $node->leftOperand;
|
||||
} else {
|
||||
// Get all descendent variables, then filter to ones that start with $namePrefix.
|
||||
// Avoiding closure usage in tight loop
|
||||
foreach ($node->getDescendantNodes($isAssignmentToVariable) as $descendantNode) {
|
||||
if ($this->isAssignmentToVariableWithPrefix($descendantNode, $namePrefix)) {
|
||||
$vars[] = $descendantNode->leftOperand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
private function isAssignmentToVariableWithPrefix(Node $node, string $namePrefix): bool
|
||||
{
|
||||
return $node instanceof Node\Expression\AssignmentExpression
|
||||
&& $node->leftOperand instanceof Node\Expression\Variable
|
||||
&& ($namePrefix === '' || strpos($node->leftOperand->getName(), $namePrefix) !== false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use phpDocumentor\Reflection\DocBlockFactory;
|
|||
use Webmozart\PathUtil\Path;
|
||||
use Sabre\Uri;
|
||||
use function Sabre\Event\coroutine;
|
||||
use Microsoft\PhpParser;
|
||||
|
||||
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
|
||||
if (file_exists($file)) {
|
||||
|
@ -29,7 +30,7 @@ class ComposerScripts
|
|||
$finder = new FileSystemFilesFinder;
|
||||
$contentRetriever = new FileSystemContentRetriever;
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$parser = new Parser;
|
||||
$parser = new PhpParser\Parser();
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
|
||||
$stubsLocation = null;
|
||||
|
|
|
@ -3,7 +3,6 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
||||
use LanguageServer\Protocol\SymbolInformation;
|
||||
use Exception;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\FqnUtilities;
|
||||
|
||||
use phpDocumentor\Reflection\{Type, Types};
|
||||
use Microsoft\PhpParser;
|
||||
|
||||
/**
|
||||
* Returns all possible FQNs in a type
|
||||
*
|
||||
* @param Type|null $type
|
||||
* @return string[]
|
||||
*/
|
||||
function getFqnsFromType($type): array
|
||||
{
|
||||
$fqns = [];
|
||||
if ($type instanceof Types\Object_) {
|
||||
$fqsen = $type->getFqsen();
|
||||
if ($fqsen !== null) {
|
||||
$fqns[] = substr((string)$fqsen, 1);
|
||||
}
|
||||
}
|
||||
if ($type instanceof Types\Compound) {
|
||||
for ($i = 0; $t = $type->get($i); $i++) {
|
||||
foreach (getFqnsFromType($type) as $fqn) {
|
||||
$fqns[] = $fqn;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $fqns;
|
||||
}
|
|
@ -150,6 +150,17 @@ class Index implements ReadableIndex, \Serializable
|
|||
return $this->references[$fqn] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* For test use.
|
||||
* Returns all references, keyed by fqn.
|
||||
*
|
||||
* @return string[][]
|
||||
*/
|
||||
public function getReferences(): array
|
||||
{
|
||||
return $this->references;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a document URI as a referencee of a specific symbol
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace LanguageServer;
|
|||
use LanguageServer\Cache\Cache;
|
||||
use LanguageServer\FilesFinder\FilesFinder;
|
||||
use LanguageServer\Index\{DependenciesIndex, Index};
|
||||
use LanguageServer\Protocol\Message;
|
||||
use LanguageServer\Protocol\MessageType;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
|
|
@ -8,10 +8,7 @@ use LanguageServer\Protocol\{
|
|||
ClientCapabilities,
|
||||
TextDocumentSyncKind,
|
||||
Message,
|
||||
MessageType,
|
||||
InitializeResult,
|
||||
SymbolInformation,
|
||||
TextDocumentIdentifier,
|
||||
CompletionOptions
|
||||
};
|
||||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
||||
|
@ -19,12 +16,10 @@ use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, F
|
|||
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
||||
use LanguageServer\Cache\{FileSystemCache, ClientCache};
|
||||
use AdvancedJsonRpc;
|
||||
use Sabre\Event\{Loop, Promise};
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Sabre\Uri;
|
||||
|
||||
class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||
{
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
|
||||
class ColumnCalculator extends NodeVisitorAbstract
|
||||
{
|
||||
private $code;
|
||||
private $codeLength;
|
||||
|
||||
public function __construct($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
$this->codeLength = strlen($code);
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$startFilePos = $node->getAttribute('startFilePos');
|
||||
$endFilePos = $node->getAttribute('endFilePos');
|
||||
|
||||
if ($startFilePos > $this->codeLength || $endFilePos > $this->codeLength) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
||||
$startLinePos = strrpos($this->code, "\n", $startFilePos - $this->codeLength);
|
||||
if ($startLinePos === false) {
|
||||
$startLinePos = -1;
|
||||
}
|
||||
|
||||
$endLinePos = strrpos($this->code, "\n", $endFilePos - $this->codeLength);
|
||||
if ($endLinePos === false) {
|
||||
$endLinePos = -1;
|
||||
}
|
||||
|
||||
$node->setAttribute('startColumn', $startFilePos - $startLinePos);
|
||||
$node->setAttribute('endColumn', $endFilePos - $endLinePos);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
use LanguageServer\{Definition, DefinitionResolver};
|
||||
use LanguageServer\Protocol\SymbolInformation;
|
||||
|
||||
/**
|
||||
* Collects definitions of classes, interfaces, traits, methods, properties and constants
|
||||
* Depends on ReferencesAdder and NameResolver
|
||||
*/
|
||||
class DefinitionCollector extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* Map from fully qualified name (FQN) to Definition
|
||||
*
|
||||
* @var Definition[]
|
||||
*/
|
||||
public $definitions = [];
|
||||
|
||||
/**
|
||||
* Map from fully qualified name (FQN) to Node
|
||||
*
|
||||
* @var Node[]
|
||||
*/
|
||||
public $nodes = [];
|
||||
|
||||
private $definitionResolver;
|
||||
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
// Only index definitions with an FQN (no variables)
|
||||
if ($fqn === null) {
|
||||
return;
|
||||
}
|
||||
$this->nodes[$fqn] = $node;
|
||||
$this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn);
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\{NodeVisitorAbstract, Node, Comment};
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Decorates all nodes with a docBlock attribute that is an instance of phpDocumentor\Reflection\DocBlock
|
||||
*/
|
||||
class DocBlockParser extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var DocBlockFactory
|
||||
*/
|
||||
private $docBlockFactory;
|
||||
|
||||
/**
|
||||
* The current namespace context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* Prefix from a parent group use declaration
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* Namespace aliases in the current context
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $aliases;
|
||||
|
||||
/**
|
||||
* @var PhpParser\Error[]
|
||||
*/
|
||||
public $errors = [];
|
||||
|
||||
public function __construct(DocBlockFactory $docBlockFactory)
|
||||
{
|
||||
$this->docBlockFactory = $docBlockFactory;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes)
|
||||
{
|
||||
$this->namespace = '';
|
||||
$this->prefix = '';
|
||||
$this->aliases = [];
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Stmt\Namespace_) {
|
||||
$this->namespace = (string)$node->name;
|
||||
} else if ($node instanceof Node\Stmt\GroupUse) {
|
||||
$this->prefix = (string)$node->prefix . '\\';
|
||||
} else if ($node instanceof Node\Stmt\UseUse) {
|
||||
$this->aliases[$node->alias] = $this->prefix . (string)$node->name;
|
||||
}
|
||||
$docComment = $node->getDocComment();
|
||||
if ($docComment === null) {
|
||||
return;
|
||||
}
|
||||
$context = new Context($this->namespace, $this->aliases);
|
||||
try {
|
||||
$docBlock = $this->docBlockFactory->create($docComment->getText(), $context);
|
||||
$node->setAttribute('docBlock', $docBlock);
|
||||
} catch (Exception $e) {
|
||||
$this->errors[] = new PhpParser\Error($e->getMessage(), [
|
||||
'startFilePos' => $docComment->getFilePos(),
|
||||
'endFilePos' => $docComment->getFilePos() + strlen($docComment->getText()),
|
||||
'startLine' => $docComment->getLine(),
|
||||
'endLine' => $docComment->getLine() + preg_match_all('/[\\n\\r]/', $docComment->getText()) + 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Stmt\Namespace_) {
|
||||
$this->namespace = '';
|
||||
$this->aliases = [];
|
||||
} else if ($node instanceof Node\Stmt\GroupUse) {
|
||||
$this->prefix = '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node, NodeTraverser};
|
||||
use LanguageServer\Protocol\{Position, Range};
|
||||
|
||||
/**
|
||||
* Finds the Node at a specified position
|
||||
* Depends on ColumnCalculator
|
||||
*/
|
||||
class NodeAtPositionFinder extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* The node at the position, if found
|
||||
*
|
||||
* @var Node|null
|
||||
*/
|
||||
public $node;
|
||||
|
||||
/**
|
||||
* @var Position
|
||||
*/
|
||||
private $position;
|
||||
|
||||
/**
|
||||
* @param Position $position The position where the node is located
|
||||
*/
|
||||
public function __construct(Position $position)
|
||||
{
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
if ($this->node === null) {
|
||||
$range = Range::fromNode($node);
|
||||
if ($range->includes($this->position)) {
|
||||
$this->node = $node;
|
||||
return NodeTraverser::STOP_TRAVERSAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
|
||||
/**
|
||||
* Decorates all nodes with parent and sibling references (similar to DOM nodes)
|
||||
*/
|
||||
class ReferencesAdder extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var Node[]
|
||||
*/
|
||||
private $stack = [];
|
||||
|
||||
/**
|
||||
* @var Node
|
||||
*/
|
||||
private $previous;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $document;
|
||||
|
||||
/**
|
||||
* @param mixed $document The value for the ownerDocument attribute
|
||||
*/
|
||||
public function __construct($document = null)
|
||||
{
|
||||
$this->document = $document;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$node->setAttribute('ownerDocument', $this->document);
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parentNode', end($this->stack));
|
||||
}
|
||||
if (isset($this->previous) && $this->previous->getAttribute('parentNode') === $node->getAttribute('parentNode')) {
|
||||
$node->setAttribute('previousSibling', $this->previous);
|
||||
$this->previous->setAttribute('nextSibling', $node);
|
||||
}
|
||||
$this->stack[] = $node;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
$this->previous = $node;
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
use LanguageServer\DefinitionResolver;
|
||||
|
||||
/**
|
||||
* Collects references to classes, interfaces, traits, methods, properties and constants
|
||||
* Depends on ReferencesAdder and NameResolver
|
||||
*/
|
||||
class ReferencesCollector extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* Map from fully qualified name (FQN) to array of nodes that reference the symbol
|
||||
*
|
||||
* @var Node[][]
|
||||
*/
|
||||
public $nodes = [];
|
||||
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
|
||||
/**
|
||||
* @param DefinitionResolver $definitionResolver The DefinitionResolver to resolve reference nodes to definitions
|
||||
*/
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Check if the node references any global symbol
|
||||
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
||||
if ($fqn) {
|
||||
$parent = $node->getAttribute('parentNode');
|
||||
$grandParent = $parent ? $parent->getAttribute('parentNode') : null;
|
||||
$this->addReference($fqn, $node);
|
||||
if (
|
||||
$node instanceof Node\Name
|
||||
&& $node->isQualified()
|
||||
&& !($parent instanceof Node\Stmt\Namespace_ && $parent->name === $node)
|
||||
) {
|
||||
// Add references for each referenced namespace
|
||||
$ns = $fqn;
|
||||
while (($pos = strrpos($ns, '\\')) !== false) {
|
||||
$ns = substr($ns, 0, $pos);
|
||||
$this->addReference($ns, $node);
|
||||
}
|
||||
}
|
||||
// Namespaced constant access and function calls also need to register a reference
|
||||
// to the global version because PHP falls back to global at runtime
|
||||
// http://php.net/manual/en/language.namespaces.fallback.php
|
||||
if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) {
|
||||
$parts = explode('\\', $fqn);
|
||||
if (count($parts) > 1) {
|
||||
$globalFqn = end($parts);
|
||||
$this->addReference($globalFqn, $node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addReference(string $fqn, Node $node)
|
||||
{
|
||||
if (!isset($this->nodes[$fqn])) {
|
||||
$this->nodes[$fqn] = [];
|
||||
}
|
||||
$this->nodes[$fqn][] = $node;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node, NodeTraverser};
|
||||
|
||||
/**
|
||||
* Collects all references to a variable
|
||||
*/
|
||||
class VariableReferencesCollector extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* Array of references to the variable
|
||||
*
|
||||
* @var Node\Expr\Variable[]
|
||||
*/
|
||||
public $nodes = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @param string $name The variable name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Expr\Variable && $node->name === $this->name) {
|
||||
$this->nodes[] = $node;
|
||||
} else if ($node instanceof Node\FunctionLike) {
|
||||
// If we meet a function node, dont traverse its statements, they are in another scope
|
||||
// except it is a closure that has imported the variable through use
|
||||
if ($node instanceof Node\Expr\Closure) {
|
||||
foreach ($node->uses as $use) {
|
||||
if ($use->var === $this->name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer;
|
||||
|
||||
use PhpParser;
|
||||
|
||||
/**
|
||||
* Custom PHP Parser class configured for our needs
|
||||
*/
|
||||
class Parser extends PhpParser\Parser\Php7
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$lexer = new PhpParser\Lexer([
|
||||
'usedAttributes' => [
|
||||
'comments',
|
||||
'startLine',
|
||||
'endLine',
|
||||
'startFilePos',
|
||||
'endFilePos'
|
||||
]
|
||||
]);
|
||||
parent::__construct($lexer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LanguageServer\ParserHelpers;
|
||||
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
function isConstantFetch(Node $node) : bool
|
||||
{
|
||||
$parent = $node->parent;
|
||||
return
|
||||
(
|
||||
$node instanceof Node\QualifiedName &&
|
||||
(
|
||||
$parent instanceof Node\Expression ||
|
||||
$parent instanceof Node\DelimitedList\ExpressionList ||
|
||||
$parent instanceof Node\ArrayElement ||
|
||||
($parent instanceof Node\Parameter && $node->parent->default === $node) ||
|
||||
$parent instanceof Node\StatementNode ||
|
||||
$parent instanceof Node\CaseStatementNode
|
||||
) &&
|
||||
!(
|
||||
$parent instanceof Node\Expression\MemberAccessExpression ||
|
||||
$parent instanceof Node\Expression\CallExpression ||
|
||||
$parent instanceof Node\Expression\ObjectCreationExpression ||
|
||||
$parent instanceof Node\Expression\ScopedPropertyAccessExpression ||
|
||||
isFunctionLike($parent) ||
|
||||
(
|
||||
$parent instanceof Node\Expression\BinaryExpression &&
|
||||
$parent->operator->kind === PhpParser\TokenKind::InstanceOfKeyword
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
function getFunctionLikeDeclarationFromParameter(Node\Parameter $node)
|
||||
{
|
||||
return $node->parent->parent;
|
||||
}
|
||||
|
||||
function isFunctionLike(Node $node)
|
||||
{
|
||||
return
|
||||
$node instanceof Node\Statement\FunctionDeclaration ||
|
||||
$node instanceof Node\MethodDeclaration ||
|
||||
$node instanceof Node\Expression\AnonymousFunctionCreationExpression;
|
||||
}
|
||||
|
||||
function isBooleanExpression($expression) : bool
|
||||
{
|
||||
if (!($expression instanceof Node\Expression\BinaryExpression)) {
|
||||
return false;
|
||||
}
|
||||
switch ($expression->operator->kind) {
|
||||
case PhpParser\TokenKind::InstanceOfKeyword:
|
||||
case PhpParser\TokenKind::GreaterThanToken:
|
||||
case PhpParser\TokenKind::GreaterThanEqualsToken:
|
||||
case PhpParser\TokenKind::LessThanToken:
|
||||
case PhpParser\TokenKind::LessThanEqualsToken:
|
||||
case PhpParser\TokenKind::AndKeyword:
|
||||
case PhpParser\TokenKind::AmpersandAmpersandToken:
|
||||
case PhpParser\TokenKind::LessThanEqualsGreaterThanToken:
|
||||
case PhpParser\TokenKind::OrKeyword:
|
||||
case PhpParser\TokenKind::BarBarToken:
|
||||
case PhpParser\TokenKind::XorKeyword:
|
||||
case PhpParser\TokenKind::ExclamationEqualsEqualsToken:
|
||||
case PhpParser\TokenKind::ExclamationEqualsToken:
|
||||
case PhpParser\TokenKind::CaretToken:
|
||||
case PhpParser\TokenKind::EqualsEqualsEqualsToken:
|
||||
case PhpParser\TokenKind::EqualsToken:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to get the parent property declaration given a Node
|
||||
* @param Node $node
|
||||
* @return Node\PropertyDeclaration | null $node
|
||||
*/
|
||||
function tryGetPropertyDeclaration(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Expression\Variable &&
|
||||
(($propertyDeclaration = $node->parent->parent) instanceof Node\PropertyDeclaration ||
|
||||
($propertyDeclaration = $propertyDeclaration->parent) instanceof Node\PropertyDeclaration)
|
||||
) {
|
||||
return $propertyDeclaration;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the parent ConstDeclaration or ClassConstDeclaration given a Node
|
||||
* @param Node $node
|
||||
* @return Node\Statement\ConstDeclaration | Node\ClassConstDeclaration | null $node
|
||||
*/
|
||||
function tryGetConstOrClassConstDeclaration(Node $node)
|
||||
{
|
||||
if (
|
||||
$node instanceof Node\ConstElement && (
|
||||
($constDeclaration = $node->parent->parent) instanceof Node\ClassConstDeclaration ||
|
||||
$constDeclaration instanceof Node\Statement\ConstDeclaration )
|
||||
) {
|
||||
return $constDeclaration;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is a usage of `define`.
|
||||
* e.g. define('TEST_DEFINE_CONSTANT', false);
|
||||
* @param Node $node
|
||||
* @return bool
|
||||
*/
|
||||
function isConstDefineExpression(Node $node): bool
|
||||
{
|
||||
return $node instanceof Node\Expression\CallExpression
|
||||
&& $node->callableExpression instanceof Node\QualifiedName
|
||||
&& strtolower($node->callableExpression->getText()) === 'define'
|
||||
&& isset($node->argumentExpressionList->children[0])
|
||||
&& $node->argumentExpressionList->children[0]->expression instanceof Node\StringLiteral
|
||||
&& isset($node->argumentExpressionList->children[2]);
|
||||
}
|
|
@ -3,27 +3,20 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit};
|
||||
use LanguageServer\NodeVisitor\{
|
||||
NodeAtPositionFinder,
|
||||
ReferencesAdder,
|
||||
DocBlockParser,
|
||||
DefinitionCollector,
|
||||
ColumnCalculator,
|
||||
ReferencesCollector
|
||||
};
|
||||
use LanguageServer\Index\Index;
|
||||
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use LanguageServer\Protocol\{
|
||||
Diagnostic, Position, Range
|
||||
};
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use Sabre\Uri;
|
||||
|
||||
class PhpDocument
|
||||
{
|
||||
/**
|
||||
* The PHPParser instance
|
||||
*
|
||||
* @var Parser
|
||||
* @var PhpParser\Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
|
@ -63,7 +56,7 @@ class PhpDocument
|
|||
/**
|
||||
* The AST of the document
|
||||
*
|
||||
* @var Node[]
|
||||
* @var Node
|
||||
*/
|
||||
private $stmts;
|
||||
|
||||
|
@ -77,7 +70,7 @@ class PhpDocument
|
|||
/**
|
||||
* Map from fully qualified name (FQN) to Node
|
||||
*
|
||||
* @var Node[]
|
||||
* @var Node
|
||||
*/
|
||||
private $definitionNodes;
|
||||
|
||||
|
@ -96,18 +89,18 @@ class PhpDocument
|
|||
private $diagnostics;
|
||||
|
||||
/**
|
||||
* @param string $uri The URI of the document
|
||||
* @param string $content The content of the document
|
||||
* @param Index $index The Index to register definitions and references to
|
||||
* @param Parser $parser The PHPParser instance
|
||||
* @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks
|
||||
* @param string $uri The URI of the document
|
||||
* @param string $content The content of the document
|
||||
* @param Index $index The Index to register definitions and references to
|
||||
* @param PhpParser\Parser $parser The PhpParser instance
|
||||
* @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks
|
||||
* @param DefinitionResolver $definitionResolver The DefinitionResolver to resolve definitions to symbols in the workspace
|
||||
*/
|
||||
public function __construct(
|
||||
string $uri,
|
||||
string $content,
|
||||
Index $index,
|
||||
Parser $parser,
|
||||
$parser,
|
||||
DocBlockFactory $docBlockFactory,
|
||||
DefinitionResolver $definitionResolver
|
||||
) {
|
||||
|
@ -133,7 +126,7 @@ class PhpDocument
|
|||
/**
|
||||
* Updates the content on this document.
|
||||
* Re-parses a source file, updates symbols and reports parsing errors
|
||||
* that may have occured as diagnostics.
|
||||
* that may have occurred as diagnostics.
|
||||
*
|
||||
* @param string $content
|
||||
* @return void
|
||||
|
@ -160,64 +153,26 @@ class PhpDocument
|
|||
$this->definitions = null;
|
||||
$this->definitionNodes = null;
|
||||
|
||||
$errorHandler = new ErrorHandler\Collecting;
|
||||
$stmts = $this->parser->parse($content, $errorHandler);
|
||||
$treeAnalyzer = new TreeAnalyzer($this->parser, $content, $this->docBlockFactory, $this->definitionResolver, $this->uri);
|
||||
|
||||
$this->diagnostics = [];
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
||||
$this->diagnostics = $treeAnalyzer->getDiagnostics();
|
||||
|
||||
$this->definitions = $treeAnalyzer->getDefinitions();
|
||||
|
||||
$this->definitionNodes = $treeAnalyzer->getDefinitionNodes();
|
||||
|
||||
$this->referenceNodes = $treeAnalyzer->getReferenceNodes();
|
||||
|
||||
foreach ($this->definitions as $fqn => $definition) {
|
||||
$this->index->setDefinition($fqn, $definition);
|
||||
}
|
||||
|
||||
// $stmts can be null in case of a fatal parsing error
|
||||
if ($stmts) {
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Resolve aliased names to FQNs
|
||||
$traverser->addVisitor(new NameResolver($errorHandler));
|
||||
|
||||
// Add parentNode, previousSibling, nextSibling attributes
|
||||
$traverser->addVisitor(new ReferencesAdder($this));
|
||||
|
||||
// Add column attributes to nodes
|
||||
$traverser->addVisitor(new ColumnCalculator($content));
|
||||
|
||||
// Parse docblocks and add docBlock attributes to nodes
|
||||
$docBlockParser = new DocBlockParser($this->docBlockFactory);
|
||||
$traverser->addVisitor($docBlockParser);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Report errors from parsing docblocks
|
||||
foreach ($docBlockParser->errors as $error) {
|
||||
$this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php');
|
||||
}
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Collect all definitions
|
||||
$definitionCollector = new DefinitionCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
|
||||
// Collect all references
|
||||
$referencesCollector = new ReferencesCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($referencesCollector);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Register this document on the project for all the symbols defined in it
|
||||
$this->definitions = $definitionCollector->definitions;
|
||||
$this->definitionNodes = $definitionCollector->nodes;
|
||||
foreach ($definitionCollector->definitions as $fqn => $definition) {
|
||||
$this->index->setDefinition($fqn, $definition);
|
||||
}
|
||||
// Register this document on the project for references
|
||||
$this->referenceNodes = $referencesCollector->nodes;
|
||||
foreach ($referencesCollector->nodes as $fqn => $nodes) {
|
||||
$this->index->addReferenceUri($fqn, $this->uri);
|
||||
}
|
||||
|
||||
$this->stmts = $stmts;
|
||||
// Register this document on the project for references
|
||||
foreach ($this->referenceNodes as $fqn => $nodes) {
|
||||
$this->index->addReferenceUri($fqn, $this->uri);
|
||||
}
|
||||
|
||||
$this->stmts = $treeAnalyzer->getStmts();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -266,9 +221,9 @@ class PhpDocument
|
|||
/**
|
||||
* Returns the AST of the document
|
||||
*
|
||||
* @return Node[]
|
||||
* @return Node | null
|
||||
*/
|
||||
public function getStmts(): array
|
||||
public function getStmts()
|
||||
{
|
||||
return $this->stmts;
|
||||
}
|
||||
|
@ -284,11 +239,13 @@ class PhpDocument
|
|||
if ($this->stmts === null) {
|
||||
return null;
|
||||
}
|
||||
$traverser = new NodeTraverser;
|
||||
$finder = new NodeAtPositionFinder($position);
|
||||
$traverser->addVisitor($finder);
|
||||
$traverser->traverse($this->stmts);
|
||||
return $finder->node;
|
||||
|
||||
$offset = $position->toOffset($this->stmts->getFileContents());
|
||||
$node = $this->stmts->getDescendantNodeAtPosition($offset);
|
||||
if ($node !== null && $node->getStart() > $offset) {
|
||||
return null;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ use LanguageServer\Index\ProjectIndex;
|
|||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
use Microsoft\PhpParser;
|
||||
|
||||
/**
|
||||
* Takes care of loading documents and managing "open" documents
|
||||
|
@ -36,6 +37,11 @@ class PhpDocumentLoader
|
|||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var PhpParser\Parser
|
||||
*/
|
||||
private $tolerantParser;
|
||||
|
||||
/**
|
||||
* @var DocBlockFactory
|
||||
*/
|
||||
|
@ -47,9 +53,10 @@ class PhpDocumentLoader
|
|||
private $definitionResolver;
|
||||
|
||||
/**
|
||||
* @param ContentRetriever $contentRetriever
|
||||
* @param ProjectIndex $project
|
||||
* @param ContentRetriever $contentRetriever
|
||||
* @param ProjectIndex $projectIndex
|
||||
* @param DefinitionResolver $definitionResolver
|
||||
* @internal param ProjectIndex $project
|
||||
*/
|
||||
public function __construct(
|
||||
ContentRetriever $contentRetriever,
|
||||
|
@ -59,7 +66,7 @@ class PhpDocumentLoader
|
|||
$this->contentRetriever = $contentRetriever;
|
||||
$this->projectIndex = $projectIndex;
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->parser = new Parser;
|
||||
$this->parser = new PhpParser\Parser();
|
||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace LanguageServer\Protocol;
|
||||
|
||||
use PhpParser\Error;
|
||||
|
||||
/**
|
||||
* Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only valid in the scope of a
|
||||
* resource.
|
||||
|
@ -47,26 +45,6 @@ class Diagnostic
|
|||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Creates a diagnostic from a PhpParser Error
|
||||
*
|
||||
* @param Error $error Message and code will be used
|
||||
* @param string $content The file content to calculate the column info
|
||||
* @param int $severity DiagnosticSeverity
|
||||
* @param string $source A human-readable string describing the source of this diagnostic
|
||||
* @return self
|
||||
*/
|
||||
public static function fromError(Error $error, string $content, int $severity = null, string $source = null): self
|
||||
{
|
||||
return new self(
|
||||
$error->getRawMessage(), // Do not include "on line ..." in the error message
|
||||
Range::fromError($error, $content),
|
||||
$error->getCode(),
|
||||
$severity,
|
||||
$source
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message The diagnostic's message
|
||||
* @param Range $range The range at which the message applies
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace LanguageServer\Protocol;
|
||||
|
||||
use PhpParser\Node;
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
/**
|
||||
* Represents a location inside a resource, such as a line inside a text file.
|
||||
|
@ -25,9 +26,13 @@ class Location
|
|||
* @param Node $node
|
||||
* @return self
|
||||
*/
|
||||
public static function fromNode(Node $node)
|
||||
public static function fromNode($node)
|
||||
{
|
||||
return new self($node->getAttribute('ownerDocument')->getUri(), Range::fromNode($node));
|
||||
$range = PhpParser\PositionUtilities::getRangeFromPosition($node->getStart(), $node->getWidth(), $node->getFileContents());
|
||||
return new self($node->getUri(), new Range(
|
||||
new Position($range->start->line, $range->start->character),
|
||||
new Position($range->end->line, $range->end->character)
|
||||
));
|
||||
}
|
||||
|
||||
public function __construct(string $uri = null, Range $range = null)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace LanguageServer\Protocol;
|
||||
|
||||
use PhpParser\{Error, Node};
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
/**
|
||||
* A range in a text document expressed as (zero-based) start and end positions.
|
||||
|
@ -31,26 +32,12 @@ class Range
|
|||
*/
|
||||
public static function fromNode(Node $node)
|
||||
{
|
||||
return new self(
|
||||
new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1),
|
||||
new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn'))
|
||||
);
|
||||
}
|
||||
$range = PhpParser\PositionUtilities::getRangeFromPosition($node->getStart(), $node->getWidth(), $node->getFileContents());
|
||||
|
||||
/**
|
||||
* Returns the range where an error occured
|
||||
*
|
||||
* @param \PhpParser\Error $error
|
||||
* @param string $content
|
||||
* @return self
|
||||
*/
|
||||
public static function fromError(Error $error, string $content)
|
||||
{
|
||||
$startLine = max($error->getStartLine() - 1, 0);
|
||||
$endLine = max($error->getEndLine() - 1, $startLine);
|
||||
$startColumn = $error->hasColumnInfo() ? $error->getStartColumn($content) - 1 : 0;
|
||||
$endColumn = $error->hasColumnInfo() ? $error->getEndColumn($content) : 0;
|
||||
return new self(new Position($startLine, $startColumn), new Position($endLine, $endColumn));
|
||||
return new self(
|
||||
new Position($range->start->line, $range->start->character),
|
||||
new Position($range->end->line, $range->end->character)
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct(Position $start = null, Position $end = null)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace LanguageServer\Protocol;
|
||||
|
||||
use PhpParser\Node;
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
|
@ -44,66 +45,68 @@ class SymbolInformation
|
|||
*
|
||||
* @param Node $node
|
||||
* @param string $fqn If given, $containerName will be extracted from it
|
||||
* @return self|null
|
||||
* @return SymbolInformation|null
|
||||
*/
|
||||
public static function fromNode(Node $node, string $fqn = null)
|
||||
public static function fromNode($node, string $fqn = null)
|
||||
{
|
||||
$parent = $node->getAttribute('parentNode');
|
||||
$symbol = new self;
|
||||
|
||||
if (
|
||||
$node instanceof Node\Expr\FuncCall
|
||||
&& $node->name instanceof Node\Name
|
||||
&& strtolower((string)$node->name) === 'define'
|
||||
&& isset($node->args[0])
|
||||
&& $node->args[0]->value instanceof Node\Scalar\String_
|
||||
&& isset($node->args[1])
|
||||
) {
|
||||
if ($node instanceof Node\Statement\ClassDeclaration) {
|
||||
$symbol->kind = SymbolKind::CLASS_;
|
||||
} else if ($node instanceof Node\Statement\TraitDeclaration) {
|
||||
$symbol->kind = SymbolKind::CLASS_;
|
||||
} else if (\LanguageServer\ParserHelpers\isConstDefineExpression($node)) {
|
||||
// constants with define() like
|
||||
// define('TEST_DEFINE_CONSTANT', false);
|
||||
$symbol->kind = SymbolKind::CONSTANT;
|
||||
$symbol->name = (string)$node->args[0]->value->value;
|
||||
} else if ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) {
|
||||
$symbol->kind = SymbolKind::CLASS_;
|
||||
} else if ($node instanceof Node\Stmt\Interface_) {
|
||||
$symbol->name = $node->argumentExpressionList->children[0]->expression->getStringContentsText();
|
||||
} else if ($node instanceof Node\Statement\InterfaceDeclaration) {
|
||||
$symbol->kind = SymbolKind::INTERFACE;
|
||||
} else if ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) {
|
||||
} else if ($node instanceof Node\Statement\NamespaceDefinition) {
|
||||
$symbol->kind = SymbolKind::NAMESPACE;
|
||||
} else if ($node instanceof Node\Stmt\Function_) {
|
||||
} else if ($node instanceof Node\Statement\FunctionDeclaration) {
|
||||
$symbol->kind = SymbolKind::FUNCTION;
|
||||
} else if ($node instanceof Node\Stmt\ClassMethod && ($node->name === '__construct' || $node->name === '__destruct')) {
|
||||
$symbol->kind = SymbolKind::CONSTRUCTOR;
|
||||
} else if ($node instanceof Node\Stmt\ClassMethod) {
|
||||
$symbol->kind = SymbolKind::METHOD;
|
||||
} else if ($node instanceof Node\Stmt\PropertyProperty) {
|
||||
} else if ($node instanceof Node\MethodDeclaration) {
|
||||
$nameText = $node->getName();
|
||||
if ($nameText === '__construct' || $nameText === '__destruct') {
|
||||
$symbol->kind = SymbolKind::CONSTRUCTOR;
|
||||
} else {
|
||||
$symbol->kind = SymbolKind::METHOD;
|
||||
}
|
||||
} else if ($node instanceof Node\Expression\Variable && $node->getFirstAncestor(Node\PropertyDeclaration::class) !== null) {
|
||||
$symbol->kind = SymbolKind::PROPERTY;
|
||||
} else if ($node instanceof Node\Const_) {
|
||||
} else if ($node instanceof Node\ConstElement) {
|
||||
$symbol->kind = SymbolKind::CONSTANT;
|
||||
} else if (
|
||||
(
|
||||
($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp)
|
||||
&& $node->var instanceof Node\Expr\Variable
|
||||
($node instanceof Node\Expression\AssignmentExpression)
|
||||
&& $node->leftOperand instanceof Node\Expression\Variable
|
||||
)
|
||||
|| $node instanceof Node\Expr\ClosureUse
|
||||
|| $node instanceof Node\Param
|
||||
|| $node instanceof Node\UseVariableName
|
||||
|| $node instanceof Node\Parameter
|
||||
) {
|
||||
$symbol->kind = SymbolKind::VARIABLE;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($symbol->name)) {
|
||||
if ($node instanceof Node\Name) {
|
||||
$symbol->name = (string)$node;
|
||||
} else if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp) {
|
||||
$symbol->name = $node->var->name;
|
||||
} else if ($node instanceof Node\Expr\ClosureUse) {
|
||||
$symbol->name = $node->var;
|
||||
} else if (isset($node->name)) {
|
||||
$symbol->name = (string)$node->name;
|
||||
} else {
|
||||
return null;
|
||||
if ($node instanceof Node\Expression\AssignmentExpression) {
|
||||
if ($node->leftOperand instanceof Node\Expression\Variable) {
|
||||
$symbol->name = $node->leftOperand->getName();
|
||||
} elseif ($node->leftOperand instanceof PhpParser\Token) {
|
||||
$symbol->name = trim($node->leftOperand->getText($node->getFileContents()), "$");
|
||||
}
|
||||
} else if ($node instanceof Node\UseVariableName) {
|
||||
$symbol->name = $node->getName();
|
||||
} else if (isset($node->name)) {
|
||||
if ($node->name instanceof Node\QualifiedName) {
|
||||
$symbol->name = (string)PhpParser\ResolvedName::buildName($node->name->nameParts, $node->getFileContents());
|
||||
} else {
|
||||
$symbol->name = ltrim((string)$node->name->getText($node->getFileContents()), "$");
|
||||
}
|
||||
} else if (isset($node->variableName)) {
|
||||
$symbol->name = $node->variableName->getText($node);
|
||||
} else if (!isset($symbol->name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$symbol->location = Location::fromNode($node);
|
||||
|
|
|
@ -3,34 +3,21 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer\Server;
|
||||
|
||||
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||
use PhpParser\{Node, NodeTraverser};
|
||||
use LanguageServer\{LanguageClient, PhpDocumentLoader, PhpDocument, DefinitionResolver, CompletionProvider};
|
||||
use LanguageServer\NodeVisitor\VariableReferencesCollector;
|
||||
use LanguageServer\Protocol\{
|
||||
SymbolLocationInformation,
|
||||
SymbolDescriptor,
|
||||
TextDocumentItem,
|
||||
TextDocumentIdentifier,
|
||||
VersionedTextDocumentIdentifier,
|
||||
Position,
|
||||
Range,
|
||||
FormattingOptions,
|
||||
TextEdit,
|
||||
Location,
|
||||
SymbolInformation,
|
||||
ReferenceContext,
|
||||
Hover,
|
||||
MarkedString,
|
||||
SymbolKind,
|
||||
CompletionItem,
|
||||
CompletionItemKind
|
||||
use LanguageServer\{
|
||||
CompletionProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\ReadableIndex;
|
||||
use LanguageServer\Protocol\{
|
||||
FormattingOptions, Hover, Location, MarkedString, Position, Range, ReferenceContext, SymbolDescriptor, SymbolLocationInformation, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier
|
||||
};
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Sabre\Event\Promise;
|
||||
use Sabre\Uri;
|
||||
use function LanguageServer\{
|
||||
isVendored, waitForEvent
|
||||
};
|
||||
use function Sabre\Event\coroutine;
|
||||
use function LanguageServer\{waitForEvent, isVendored};
|
||||
|
||||
/**
|
||||
* Provides method handlers for all textDocument/* methods
|
||||
|
@ -49,11 +36,6 @@ class TextDocument
|
|||
*/
|
||||
protected $project;
|
||||
|
||||
/**
|
||||
* @var PrettyPrinter
|
||||
*/
|
||||
protected $prettyPrinter;
|
||||
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
|
@ -80,12 +62,12 @@ class TextDocument
|
|||
protected $composerLock;
|
||||
|
||||
/**
|
||||
* @param PhpDocumentLoader $documentLoader
|
||||
* @param PhpDocumentLoader $documentLoader
|
||||
* @param DefinitionResolver $definitionResolver
|
||||
* @param LanguageClient $client
|
||||
* @param ReadableIndex $index
|
||||
* @param \stdClass $composerJson
|
||||
* @param \stdClass $composerLock
|
||||
* @param LanguageClient $client
|
||||
* @param ReadableIndex $index
|
||||
* @param \stdClass $composerJson
|
||||
* @param \stdClass $composerLock
|
||||
*/
|
||||
public function __construct(
|
||||
PhpDocumentLoader $documentLoader,
|
||||
|
@ -97,7 +79,6 @@ class TextDocument
|
|||
) {
|
||||
$this->documentLoader = $documentLoader;
|
||||
$this->client = $client;
|
||||
$this->prettyPrinter = new PrettyPrinter();
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->completionProvider = new CompletionProvider($this->definitionResolver, $index);
|
||||
$this->index = $index;
|
||||
|
@ -202,31 +183,34 @@ class TextDocument
|
|||
// Variables always stay in the boundary of the file and need to be searched inside their function scope
|
||||
// by traversing the AST
|
||||
if (
|
||||
$node instanceof Node\Expr\Variable
|
||||
|| $node instanceof Node\Param
|
||||
|| $node instanceof Node\Expr\ClosureUse
|
||||
|
||||
($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration))
|
||||
|| $node instanceof Node\Parameter
|
||||
|| $node instanceof Node\UseVariableName
|
||||
) {
|
||||
if ($node->name instanceof Node\Expr) {
|
||||
if (isset($node->name) && $node->name instanceof Node\Expression) {
|
||||
return null;
|
||||
}
|
||||
// Find function/method/closure scope
|
||||
$n = $node;
|
||||
while (isset($n) && !($n instanceof Node\FunctionLike)) {
|
||||
$n = $n->getAttribute('parentNode');
|
||||
|
||||
$n = $n->getFirstAncestor(Node\Statement\FunctionDeclaration::class, Node\MethodDeclaration::class, Node\Expression\AnonymousFunctionCreationExpression::class, Node\SourceFileNode::class);
|
||||
|
||||
if ($n === null) {
|
||||
$n = $node->getFirstAncestor(Node\Statement\ExpressionStatement::class)->getParent();
|
||||
}
|
||||
if (!isset($n)) {
|
||||
$n = $node->getAttribute('ownerDocument');
|
||||
}
|
||||
$traverser = new NodeTraverser;
|
||||
$refCollector = new VariableReferencesCollector($node->name);
|
||||
$traverser->addVisitor($refCollector);
|
||||
$traverser->traverse($n->getStmts());
|
||||
foreach ($refCollector->nodes as $ref) {
|
||||
$locations[] = Location::fromNode($ref);
|
||||
|
||||
foreach ($n->getDescendantNodes() as $descendantNode) {
|
||||
if ($descendantNode instanceof Node\Expression\Variable &&
|
||||
$descendantNode->getName() === $node->getName()
|
||||
) {
|
||||
$locations[] = Location::fromNode($descendantNode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Definition with a global FQN
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
|
||||
// Wait until indexing finished
|
||||
if (!$this->index->isComplete()) {
|
||||
yield waitForEvent($this->index, 'complete');
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit};
|
||||
use LanguageServer\Index\Index;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use Sabre\Uri;
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
class TreeAnalyzer
|
||||
{
|
||||
/** @var PhpParser\Parser */
|
||||
private $parser;
|
||||
|
||||
/** @var Node */
|
||||
private $stmts;
|
||||
|
||||
/** @var Diagnostic[] */
|
||||
private $diagnostics;
|
||||
|
||||
/** @var string */
|
||||
private $content;
|
||||
|
||||
/** @var Node[] */
|
||||
private $referenceNodes;
|
||||
|
||||
/** @var Definition[] */
|
||||
private $definitions;
|
||||
|
||||
/** @var Node[] */
|
||||
private $definitionNodes;
|
||||
|
||||
/**
|
||||
* @param PhpParser\Parser $parser
|
||||
* @param string $content
|
||||
* @param DocBlockFactory $docBlockFactory
|
||||
* @param DefinitionResolver $definitionResolver
|
||||
* @param string $uri
|
||||
*/
|
||||
public function __construct(PhpParser\Parser $parser, string $content, DocBlockFactory $docBlockFactory, DefinitionResolver $definitionResolver, string $uri)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
$this->docBlockFactory = $docBlockFactory;
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->content = $content;
|
||||
$this->stmts = $this->parser->parseSourceFile($content, $uri);
|
||||
|
||||
// TODO - docblock errors
|
||||
|
||||
$this->collectDefinitionsAndReferences($this->stmts);
|
||||
}
|
||||
|
||||
private function collectDefinitionsAndReferences(Node $stmts)
|
||||
{
|
||||
foreach ($stmts::CHILD_NAMES as $name) {
|
||||
$node = $stmts->$name;
|
||||
|
||||
if ($node === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($node)) {
|
||||
foreach ($node as $child) {
|
||||
if ($child instanceof Node) {
|
||||
$this->update($child);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node instanceof Node) {
|
||||
$this->update($node);
|
||||
}
|
||||
|
||||
if (($error = PhpParser\DiagnosticsProvider::checkDiagnostics($node)) !== null) {
|
||||
$range = PhpParser\PositionUtilities::getRangeFromPosition($error->start, $error->length, $this->content);
|
||||
|
||||
$this->diagnostics[] = new Diagnostic(
|
||||
$error->message,
|
||||
new Range(
|
||||
new Position($range->start->line, $range->start->character),
|
||||
new Position($range->end->line, $range->start->character)
|
||||
),
|
||||
null,
|
||||
DiagnosticSeverity::ERROR,
|
||||
'php'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect definitions and references for the given node
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
private function update(Node $node)
|
||||
{
|
||||
$fqn = ($this->definitionResolver)::getDefinedFqn($node);
|
||||
// Only index definitions with an FQN (no variables)
|
||||
if ($fqn !== null) {
|
||||
$this->definitionNodes[$fqn] = $node;
|
||||
$this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn);
|
||||
} else {
|
||||
$parent = $node->parent;
|
||||
if (!(
|
||||
(
|
||||
// $node->parent instanceof Node\Expression\ScopedPropertyAccessExpression ||
|
||||
($node instanceof Node\Expression\ScopedPropertyAccessExpression ||
|
||||
$node instanceof Node\Expression\MemberAccessExpression)
|
||||
&& !(
|
||||
$node->parent instanceof Node\Expression\CallExpression ||
|
||||
$node->memberName instanceof PhpParser\Token
|
||||
))
|
||||
|| ($parent instanceof Node\Statement\NamespaceDefinition && $parent->name !== null && $parent->name->getStart() === $node->getStart()))
|
||||
) {
|
||||
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
||||
if ($fqn !== null) {
|
||||
$this->addReference($fqn, $node);
|
||||
|
||||
if (
|
||||
$node instanceof Node\QualifiedName
|
||||
&& ($node->isQualifiedName() || $node->parent instanceof Node\NamespaceUseClause)
|
||||
&& !($parent instanceof Node\Statement\NamespaceDefinition && $parent->name->getStart() === $node->getStart()
|
||||
)
|
||||
) {
|
||||
// Add references for each referenced namespace
|
||||
$ns = $fqn;
|
||||
while (($pos = strrpos($ns, '\\')) !== false) {
|
||||
$ns = substr($ns, 0, $pos);
|
||||
$this->addReference($ns, $node);
|
||||
}
|
||||
}
|
||||
|
||||
// Namespaced constant access and function calls also need to register a reference
|
||||
// to the global version because PHP falls back to global at runtime
|
||||
// http://php.net/manual/en/language.namespaces.fallback.php
|
||||
if (ParserHelpers\isConstantFetch($node) ||
|
||||
($parent instanceof Node\Expression\CallExpression
|
||||
&& !(
|
||||
$node instanceof Node\Expression\ScopedPropertyAccessExpression ||
|
||||
$node instanceof Node\Expression\MemberAccessExpression
|
||||
))) {
|
||||
$parts = explode('\\', $fqn);
|
||||
if (count($parts) > 1) {
|
||||
$globalFqn = end($parts);
|
||||
$this->addReference($globalFqn, $node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->collectDefinitionsAndReferences($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Diagnostic[]
|
||||
*/
|
||||
public function getDiagnostics(): array
|
||||
{
|
||||
return $this->diagnostics ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function addReference(string $fqn, Node $node)
|
||||
{
|
||||
if (!isset($this->referenceNodes[$fqn])) {
|
||||
$this->referenceNodes[$fqn] = [];
|
||||
}
|
||||
$this->referenceNodes[$fqn][] = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Definition
|
||||
*/
|
||||
public function getDefinitions()
|
||||
{
|
||||
return $this->definitions ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node[]
|
||||
*/
|
||||
public function getDefinitionNodes()
|
||||
{
|
||||
return $this->definitionNodes ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node[]
|
||||
*/
|
||||
public function getReferenceNodes()
|
||||
{
|
||||
return $this->referenceNodes ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node[]
|
||||
*/
|
||||
public function getStmts()
|
||||
{
|
||||
return $this->stmts;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ namespace LanguageServer;
|
|||
|
||||
use Throwable;
|
||||
use InvalidArgumentException;
|
||||
use PhpParser\Node;
|
||||
use Sabre\Event\{Loop, Promise, EmitterInterface};
|
||||
use Sabre\Uri;
|
||||
|
||||
|
@ -94,23 +93,6 @@ function waitForEvent(EmitterInterface $emitter, string $event): Promise
|
|||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest node of a specific type
|
||||
*
|
||||
* @param Node $node
|
||||
* @param string $type The node class name
|
||||
* @return Node|null $type
|
||||
*/
|
||||
function getClosestNode(Node $node, string $type)
|
||||
{
|
||||
$n = $node;
|
||||
while ($n = $n->getAttribute('parentNode')) {
|
||||
if ($n instanceof $type) {
|
||||
return $n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part of $b that is not overlapped by $a
|
||||
* Example:
|
||||
|
|
|
@ -5,32 +5,33 @@ namespace LanguageServer\Tests;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Index\Index;
|
||||
use LanguageServer\{DefinitionResolver, Parser};
|
||||
use LanguageServer\DefinitionResolver;
|
||||
use Microsoft\PhpParser;
|
||||
|
||||
class DefinitionResolverTest extends TestCase
|
||||
{
|
||||
public function testCreateDefinitionFromNode()
|
||||
{
|
||||
$parser = new Parser;
|
||||
$stmts = $parser->parse("<?php\ndefine('TEST_DEFINE', true);");
|
||||
$stmts[0]->setAttribute('ownerDocument', new MockPhpDocument);
|
||||
$parser = new PhpParser\Parser;
|
||||
$doc = new MockPhpDocument;
|
||||
$stmts = $parser->parseSourceFile("<?php\ndefine('TEST_DEFINE', true);", $doc->getUri());
|
||||
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$def = $definitionResolver->createDefinitionFromNode($stmts[0], '\TEST_DEFINE');
|
||||
$def = $definitionResolver->createDefinitionFromNode($stmts->statementList[1]->expression, '\TEST_DEFINE');
|
||||
|
||||
$this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $def->type);
|
||||
}
|
||||
|
||||
public function testGetTypeFromNode()
|
||||
{
|
||||
$parser = new Parser;
|
||||
$stmts = $parser->parse("<?php\ndefine('TEST_DEFINE', true);");
|
||||
$stmts[0]->setAttribute('ownerDocument', new MockPhpDocument);
|
||||
$parser = new PhpParser\Parser;
|
||||
$doc = new MockPhpDocument;
|
||||
$stmts = $parser->parseSourceFile("<?php\ndefine('TEST_DEFINE', true);", $doc->getUri());
|
||||
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$type = $definitionResolver->getTypeFromNode($stmts[0]);
|
||||
$type = $definitionResolver->getTypeFromNode($stmts->statementList[1]->expression);
|
||||
|
||||
$this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $type);
|
||||
}
|
||||
|
@ -38,26 +39,26 @@ class DefinitionResolverTest extends TestCase
|
|||
public function testGetDefinedFqnForIncompleteDefine()
|
||||
{
|
||||
// define('XXX') (only one argument) must not introduce a new symbol
|
||||
$parser = new Parser;
|
||||
$stmts = $parser->parse("<?php\ndefine('TEST_DEFINE');");
|
||||
$stmts[0]->setAttribute('ownerDocument', new MockPhpDocument);
|
||||
$parser = new PhpParser\Parser;
|
||||
$doc = new MockPhpDocument;
|
||||
$stmts = $parser->parseSourceFile("<?php\ndefine('TEST_DEFINE');", $doc->getUri());
|
||||
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$fqn = $definitionResolver->getDefinedFqn($stmts[0]);
|
||||
$fqn = $definitionResolver->getDefinedFqn($stmts->statementList[1]->expression);
|
||||
|
||||
$this->assertNull($fqn);
|
||||
}
|
||||
|
||||
public function testGetDefinedFqnForDefine()
|
||||
{
|
||||
$parser = new Parser;
|
||||
$stmts = $parser->parse("<?php\ndefine('TEST_DEFINE', true);");
|
||||
$stmts[0]->setAttribute('ownerDocument', new MockPhpDocument);
|
||||
$parser = new PhpParser\Parser;
|
||||
$doc = new MockPhpDocument;
|
||||
$stmts = $parser->parseSourceFile("<?php\ndefine('TEST_DEFINE', true);", $doc->getUri());
|
||||
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$fqn = $definitionResolver->getDefinedFqn($stmts[0]);
|
||||
$fqn = $definitionResolver->getDefinedFqn($stmts->statementList[1]->expression);
|
||||
|
||||
$this->assertEquals('TEST_DEFINE', $fqn);
|
||||
}
|
||||
|
|
|
@ -4,39 +4,21 @@ declare(strict_types = 1);
|
|||
namespace LanguageServer\Tests\Server\TextDocument;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PhpParser\{NodeTraverser, Node};
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use LanguageServer\{LanguageClient, PhpDocument, PhpDocumentLoader, Parser, DefinitionResolver};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\ClientCapabilities;
|
||||
use LanguageServer\Index\{ProjectIndex, Index, DependenciesIndex};
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
||||
use LanguageServer\{
|
||||
DefinitionResolver, TreeAnalyzer
|
||||
};
|
||||
use LanguageServer\Index\{Index};
|
||||
use function LanguageServer\pathToUri;
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
|
||||
class DefinitionCollectorTest extends TestCase
|
||||
{
|
||||
public function testCollectsSymbols()
|
||||
{
|
||||
$path = realpath(__DIR__ . '/../../fixtures/symbols.php');
|
||||
$uri = pathToUri($path);
|
||||
$parser = new Parser;
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$content = file_get_contents($path);
|
||||
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||
$stmts = $parser->parse($content);
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector($definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
$defNodes = $definitionCollector->nodes;
|
||||
$defNodes = $this->collectDefinitions($path);
|
||||
|
||||
$this->assertEquals([
|
||||
'TestNamespace',
|
||||
|
@ -55,46 +37,48 @@ class DefinitionCollectorTest extends TestCase
|
|||
'TestNamespace\\Example->__construct()',
|
||||
'TestNamespace\\Example->__destruct()'
|
||||
], array_keys($defNodes));
|
||||
$this->assertInstanceOf(Node\Const_::class, $defNodes['TestNamespace\\TEST_CONST']);
|
||||
$this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\TestClass']);
|
||||
$this->assertInstanceOf(Node\Const_::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']);
|
||||
$this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defNodes['TestNamespace\\TestClass::$staticTestProperty']);
|
||||
$this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defNodes['TestNamespace\\TestClass->testProperty']);
|
||||
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\TestClass::staticTestMethod()']);
|
||||
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\TestClass->testMethod()']);
|
||||
$this->assertInstanceOf(Node\Stmt\Trait_::class, $defNodes['TestNamespace\\TestTrait']);
|
||||
$this->assertInstanceOf(Node\Stmt\Interface_::class, $defNodes['TestNamespace\\TestInterface']);
|
||||
$this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\test_function()']);
|
||||
$this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\ChildClass']);
|
||||
$this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\Example']);
|
||||
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\Example->__construct()']);
|
||||
$this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\Example->__destruct()']);
|
||||
|
||||
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']);
|
||||
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\TestClass']);
|
||||
$this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']);
|
||||
// TODO - should we parse properties more strictly?
|
||||
$this->assertInstanceOf(Node\Expression\Variable::class, $defNodes['TestNamespace\\TestClass::$staticTestProperty']);
|
||||
$this->assertInstanceOf(Node\Expression\Variable::class, $defNodes['TestNamespace\\TestClass->testProperty']);
|
||||
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\TestClass::staticTestMethod()']);
|
||||
$this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\TestClass->testMethod()']);
|
||||
$this->assertInstanceOf(Node\Statement\TraitDeclaration::class, $defNodes['TestNamespace\\TestTrait']);
|
||||
$this->assertInstanceOf(Node\Statement\InterfaceDeclaration::class, $defNodes['TestNamespace\\TestInterface']);
|
||||
$this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\\test_function()']);
|
||||
$this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\ChildClass']);
|
||||
$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->__destruct()']);
|
||||
}
|
||||
|
||||
public function testDoesNotCollectReferences()
|
||||
{
|
||||
$path = realpath(__DIR__ . '/../../fixtures/references.php');
|
||||
$defNodes = $this->collectDefinitions($path);
|
||||
|
||||
$this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes));
|
||||
$this->assertInstanceOf(Node\Statement\NamespaceDefinition::class, $defNodes['TestNamespace']);
|
||||
$this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\\whatever()']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*/
|
||||
private function collectDefinitions(string $path): array
|
||||
{
|
||||
$uri = pathToUri($path);
|
||||
$parser = new Parser;
|
||||
$parser = new PhpParser\Parser();
|
||||
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
$content = file_get_contents($path);
|
||||
$document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||
$stmts = $parser->parse($content);
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector($definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
$defNodes = $definitionCollector->nodes;
|
||||
|
||||
$this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes));
|
||||
$this->assertInstanceOf(Node\Name::class, $defNodes['TestNamespace']);
|
||||
$this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']->getAttribute('parentNode'));
|
||||
$this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']);
|
||||
$treeAnalyzer = new TreeAnalyzer($parser, $content, $docBlockFactory, $definitionResolver, $uri);
|
||||
return $treeAnalyzer->getDefinitionNodes();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,14 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer\Tests\Server;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentItem,
|
||||
TextDocumentIdentifier,
|
||||
SymbolKind,
|
||||
DiagnosticSeverity,
|
||||
FormattingOptions,
|
||||
ClientCapabilities
|
||||
use LanguageServer\{
|
||||
PhpDocument, PhpDocumentLoader, Project, DefinitionResolver
|
||||
};
|
||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Index\{
|
||||
DependenciesIndex, Index, ProjectIndex
|
||||
};
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function LanguageServer\pathToUri;
|
||||
|
||||
class PhpDocumentLoaderTest extends TestCase
|
||||
|
|
|
@ -3,22 +3,26 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer\Tests\Server;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\{
|
||||
PhpDocument, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{
|
||||
Index
|
||||
};
|
||||
use LanguageServer\Protocol\{
|
||||
Position
|
||||
};
|
||||
use Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{LanguageClient, PhpDocument, DefinitionResolver, Parser};
|
||||
use LanguageServer\NodeVisitor\NodeAtPositionFinder;
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use PhpParser\Node;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function LanguageServer\isVendored;
|
||||
|
||||
class PhpDocumentTest extends TestCase
|
||||
{
|
||||
public function createDocument(string $uri, string $content)
|
||||
{
|
||||
$parser = new Parser;
|
||||
$parser = new PhpParser\Parser();
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$index = new Index;
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
|
@ -36,10 +40,15 @@ class PhpDocumentTest extends TestCase
|
|||
{
|
||||
$document = $this->createDocument('whatever', "<?php\n$\$a = new SomeClass;");
|
||||
$node = $document->getNodeAtPosition(new Position(1, 13));
|
||||
$this->assertInstanceOf(Node\Name\FullyQualified::class, $node);
|
||||
$this->assertQualifiedName($node);
|
||||
$this->assertEquals('SomeClass', (string)$node);
|
||||
}
|
||||
|
||||
private function assertQualifiedName($node)
|
||||
{
|
||||
$this->assertInstanceOf(Node\QualifiedName::class, $node);
|
||||
}
|
||||
|
||||
public function testIsVendored()
|
||||
{
|
||||
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||
|
|
|
@ -5,12 +5,13 @@ namespace LanguageServer\Tests\Server;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\Index\{ProjectIndex, StubsIndex, GlobalIndex, DependenciesIndex, Index};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities};
|
||||
use LanguageServer\Protocol\{Position, Location, Range};
|
||||
use function LanguageServer\pathToUri;
|
||||
use Sabre\Event\Promise;
|
||||
|
||||
abstract class ServerTestCase extends TestCase
|
||||
{
|
||||
|
@ -87,13 +88,13 @@ abstract class ServerTestCase extends TestCase
|
|||
'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
||||
|
||||
// Namespaced
|
||||
'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 10), new Position( 2, 23))),
|
||||
'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 10), new Position( 2, 29))),
|
||||
'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\\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\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||
'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))),
|
||||
'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\\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\\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::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))),
|
||||
|
@ -112,7 +113,7 @@ abstract class ServerTestCase extends TestCase
|
|||
'TestNamespace' => [
|
||||
0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function;
|
||||
1 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass;
|
||||
2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 17))) // use TestNamespace\{TestTrait, TestInterface};
|
||||
2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 18))) // use TestNamespace\{TestTrait, TestInterface};
|
||||
],
|
||||
'TestNamespace\\TEST_CONST' => [
|
||||
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
||||
|
@ -147,16 +148,16 @@ abstract class ServerTestCase extends TestCase
|
|||
3 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
],
|
||||
'TestNamespace\\TestClass::staticTestProperty' => [
|
||||
0 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
||||
1 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
0 => new Location($referencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
||||
1 => new Location($referencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
],
|
||||
'TestNamespace\\TestClass::staticTestMethod()' => [
|
||||
0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 29)))
|
||||
0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 27)))
|
||||
],
|
||||
'TestNamespace\\TestClass::testMethod()' => [
|
||||
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod();
|
||||
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod();
|
||||
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod();
|
||||
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
||||
],
|
||||
'TestNamespace\\test_function()' => [
|
||||
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||
|
@ -186,7 +187,7 @@ abstract class ServerTestCase extends TestCase
|
|||
],
|
||||
'TestInterface' => [
|
||||
0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||
1 => new Location($globalSymbolsUri, new Range(new Position(57, 48), new Position(57, 61))), // public function testMethod($testParameter): TestInterface
|
||||
1 => new Location($globalSymbolsUri, new Range(new Position(57, 49), new Position(57, 61))), // public function testMethod($testParameter) : TestInterface
|
||||
2 => new Location($globalReferencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
||||
],
|
||||
'TestClass::TEST_CLASS_CONST' => [
|
||||
|
@ -200,16 +201,16 @@ abstract class ServerTestCase extends TestCase
|
|||
3 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
],
|
||||
'TestClass::staticTestProperty' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||
],
|
||||
'TestClass::staticTestMethod()' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 29)))
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 27)))
|
||||
],
|
||||
'TestClass::testMethod()' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod();
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod();
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod();
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
||||
],
|
||||
'test_function()' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||
|
|
|
@ -5,15 +5,16 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, CompletionProvider, DefinitionResolver};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex, GlobalIndex, StubsIndex};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentIdentifier,
|
||||
TextEdit,
|
||||
Range,
|
||||
Position,
|
||||
ClientCapabilities,
|
||||
CompletionList,
|
||||
CompletionItem,
|
||||
CompletionItemKind
|
||||
|
@ -52,7 +53,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(3, 7)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'testProperty',
|
||||
CompletionItemKind::PROPERTY,
|
||||
|
@ -76,7 +77,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(3, 6)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'testProperty',
|
||||
CompletionItemKind::PROPERTY,
|
||||
|
@ -100,7 +101,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(8, 5)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'$var',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -132,7 +133,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(8, 6)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'$param',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -154,7 +155,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(6, 10)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
// Global TestClass definition (inserted as \TestClass)
|
||||
new CompletionItem(
|
||||
'TestClass',
|
||||
|
@ -223,17 +224,20 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(6, 5)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'TestClass',
|
||||
CompletionItemKind::CLASS_,
|
||||
'TestNamespace',
|
||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\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" .
|
||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\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" .
|
||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.',
|
||||
null,
|
||||
null,
|
||||
'TestClass'
|
||||
)
|
||||
], true), $items);
|
||||
}
|
||||
|
@ -246,7 +250,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(2, 14)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'staticTestProperty',
|
||||
CompletionItemKind::PROPERTY,
|
||||
|
@ -267,7 +271,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(2, 11)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'TEST_CLASS_CONST',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -300,7 +304,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(2, 13)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'TEST_CLASS_CONST',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -333,7 +337,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(2, 13)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'TEST_CLASS_CONST',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -366,7 +370,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(6, 6)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'TestClass',
|
||||
CompletionItemKind::CLASS_,
|
||||
|
@ -392,7 +396,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(2, 1)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem('class', CompletionItemKind::KEYWORD, null, null, null, null, 'class '),
|
||||
new CompletionItem('clone', CompletionItemKind::KEYWORD, null, null, null, null, 'clone ')
|
||||
], true), $items);
|
||||
|
@ -406,7 +410,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(0, 0)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'<?php',
|
||||
CompletionItemKind::KEYWORD,
|
||||
|
@ -428,7 +432,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(0, 1)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'<?php',
|
||||
CompletionItemKind::KEYWORD,
|
||||
|
@ -450,7 +454,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(4, 6)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'SomeNamespace',
|
||||
CompletionItemKind::MODULE,
|
||||
|
@ -471,7 +475,7 @@ class CompletionTest extends TestCase
|
|||
new TextDocumentIdentifier($completionUri),
|
||||
new Position(4, 8)
|
||||
)->wait();
|
||||
$this->assertEquals(new CompletionList([
|
||||
$this->assertCompletionsListSubset(new CompletionList([
|
||||
new CompletionItem(
|
||||
'$abc2',
|
||||
CompletionItemKind::VARIABLE,
|
||||
|
@ -494,4 +498,13 @@ class CompletionTest extends TestCase
|
|||
)
|
||||
], true), $items);
|
||||
}
|
||||
|
||||
private function assertCompletionsListSubset(CompletionList $subsetList, CompletionList $list)
|
||||
{
|
||||
foreach ($subsetList->items as $expectedItem) {
|
||||
$this->assertContains($expectedItem, $list->items, null, null, false);
|
||||
}
|
||||
|
||||
$this->assertEquals($subsetList->isIncomplete, $list->isIncomplete);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
|||
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\Tests\Server\ServerTestCase;
|
||||
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities};
|
||||
use Sabre\Event\Promise;
|
||||
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location};
|
||||
|
||||
class GlobalFallbackTest extends ServerTestCase
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ class GlobalTest extends ServerTestCase
|
|||
// namespace keyword
|
||||
$result = $this->textDocument->definition(
|
||||
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
|
||||
new Position(2, 4)
|
||||
new Position(1, 0)
|
||||
)->wait();
|
||||
$this->assertEquals([], $result);
|
||||
}
|
||||
|
|
|
@ -5,17 +5,16 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentIdentifier,
|
||||
TextDocumentItem,
|
||||
VersionedTextDocumentIdentifier,
|
||||
TextDocumentContentChangeEvent,
|
||||
Range,
|
||||
Position,
|
||||
ClientCapabilities
|
||||
Position
|
||||
};
|
||||
|
||||
class DidChangeTest extends TestCase
|
||||
|
|
|
@ -5,11 +5,12 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, ClientCapabilities};
|
||||
use Exception;
|
||||
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier};
|
||||
|
||||
class DidCloseTest extends TestCase
|
||||
{
|
||||
|
|
|
@ -5,14 +5,15 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\{
|
||||
Server, LanguageClient, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentIdentifier,
|
||||
TextDocumentItem,
|
||||
FormattingOptions,
|
||||
ClientCapabilities,
|
||||
TextEdit,
|
||||
Range,
|
||||
Position
|
||||
|
|
|
@ -21,7 +21,7 @@ class HoverTest extends ServerTestCase
|
|||
$reference->range->start
|
||||
)->wait();
|
||||
$this->assertEquals(new Hover([
|
||||
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"),
|
||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||
|
@ -41,7 +41,7 @@ class HoverTest extends ServerTestCase
|
|||
$definition->range->start
|
||||
)->wait();
|
||||
$this->assertEquals(new Hover([
|
||||
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"),
|
||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||
|
@ -61,7 +61,7 @@ class HoverTest extends ServerTestCase
|
|||
$reference->range->end
|
||||
)->wait();
|
||||
$this->assertEquals(new Hover([
|
||||
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter) : \TestInterface"),
|
||||
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter): TestInterface"),
|
||||
'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.'
|
||||
], $reference->range), $result);
|
||||
}
|
||||
|
@ -165,8 +165,9 @@ class HoverTest extends ServerTestCase
|
|||
new TextDocumentIdentifier($reference->uri),
|
||||
$reference->range->end
|
||||
)->wait();
|
||||
// TODO - should pretty print with fqns, like \define, \false. Not yet supported by tolerant-php-parser
|
||||
$this->assertEquals(new Hover([
|
||||
new MarkedString('php', "<?php\n\\define('TEST_DEFINE_CONSTANT', \\false);"),
|
||||
new MarkedString('php', "<?php\ndefine('TEST_DEFINE_CONSTANT', false)"),
|
||||
'Lorem ipsum dolor sit amet, consectetur.'
|
||||
], $reference->range), $result);
|
||||
}
|
||||
|
@ -178,7 +179,7 @@ class HoverTest extends ServerTestCase
|
|||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait();
|
||||
$this->assertEquals(new Hover(
|
||||
[new MarkedString('php', "<?php\n\$var = 123;")],
|
||||
[new MarkedString('php', "<?php\n\$var = 123")],
|
||||
new Range(new Position(13, 5), new Position(13, 9))
|
||||
), $result);
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ class HoverTest extends ServerTestCase
|
|||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait();
|
||||
$this->assertEquals(new Hover(
|
||||
[
|
||||
new MarkedString('php', "<?php\n\TestNamespace\TestClass \$param"),
|
||||
new MarkedString('php', "<?php\nTestClass \$param"),
|
||||
'Adipisicing non non cillum sint incididunt cillum enim mollit.'
|
||||
],
|
||||
new Range(new Position(22, 9), new Position(22, 15))
|
||||
|
@ -205,7 +206,7 @@ class HoverTest extends ServerTestCase
|
|||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/global_symbols.php'));
|
||||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(59, 11))->wait();
|
||||
$this->assertEquals(new Hover([
|
||||
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"),
|
||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||
|
|
|
@ -5,10 +5,12 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, ClientHandler, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\{
|
||||
Server, Client, LanguageClient, ClientHandler, PhpDocumentLoader, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities};
|
||||
use LanguageServer\Protocol\{TextDocumentItem, DiagnosticSeverity};
|
||||
use Sabre\Event\Promise;
|
||||
use JsonMapper;
|
||||
|
||||
|
@ -62,7 +64,55 @@ class ParseErrorsTest extends TestCase
|
|||
'range' => [
|
||||
'start' => [
|
||||
'line' => 2,
|
||||
'character' => 10
|
||||
'character' => 9
|
||||
],
|
||||
'end' => [
|
||||
'line' => 2,
|
||||
'character' => 9
|
||||
]
|
||||
],
|
||||
'severity' => DiagnosticSeverity::ERROR,
|
||||
'code' => null,
|
||||
'source' => 'php',
|
||||
'message' => "'Name' expected."
|
||||
],
|
||||
[
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 2,
|
||||
'character' => 9
|
||||
],
|
||||
'end' => [
|
||||
'line' => 2,
|
||||
'character' => 9
|
||||
]
|
||||
],
|
||||
'severity' => DiagnosticSeverity::ERROR,
|
||||
'code' => null,
|
||||
'source' => 'php',
|
||||
'message' => "'{' expected."
|
||||
],
|
||||
[
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 2,
|
||||
'character' => 9
|
||||
],
|
||||
'end' => [
|
||||
'line' => 2,
|
||||
'character' => 9
|
||||
]
|
||||
],
|
||||
'severity' => DiagnosticSeverity::ERROR,
|
||||
'code' => null,
|
||||
'source' => 'php',
|
||||
'message' => "'}' expected."
|
||||
],
|
||||
[
|
||||
'range' => [
|
||||
'start' => [
|
||||
'line' => 2,
|
||||
'character' => 15
|
||||
],
|
||||
'end' => [
|
||||
'line' => 2,
|
||||
|
@ -72,13 +122,14 @@ class ParseErrorsTest extends TestCase
|
|||
'severity' => DiagnosticSeverity::ERROR,
|
||||
'code' => null,
|
||||
'source' => 'php',
|
||||
'message' => "Syntax error, unexpected T_CLASS, expecting T_STRING"
|
||||
'message' => "'Name' expected."
|
||||
]]
|
||||
], json_decode(json_encode($this->args), true));
|
||||
}
|
||||
|
||||
public function testParseErrorsWithOnlyStartLine()
|
||||
{
|
||||
$this->markTestIncomplete('This diagnostic not yet implemented in tolerant-php-parser');
|
||||
$this->openFile(__DIR__ . '/../../../fixtures/namespace_not_first.php');
|
||||
$this->assertEquals([
|
||||
'whatever',
|
||||
|
|
|
@ -3,12 +3,17 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer\Tests\Server\TextDocument\References;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, DefinitionResolver};
|
||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||
use LanguageServer\{
|
||||
LanguageClient, PhpDocumentLoader, Server, DefinitionResolver
|
||||
};
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities};
|
||||
use LanguageServer\Index\{
|
||||
DependenciesIndex, Index, ProjectIndex
|
||||
};
|
||||
use LanguageServer\Protocol\{
|
||||
Location, Position, Range, ReferenceContext, TextDocumentIdentifier
|
||||
};
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\Tests\Server\ServerTestCase;
|
||||
|
||||
class GlobalFallbackTest extends ServerTestCase
|
||||
|
|
|
@ -29,7 +29,7 @@ class SymbolTest extends ServerTestCase
|
|||
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||
// @codingStandardsIgnoreStart
|
||||
$this->assertEquals([
|
||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 10), new Position(2, 23))), ''),
|
||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''),
|
||||
// Namespaced
|
||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\Tests;
|
||||
|
||||
use Exception;
|
||||
use LanguageServer\Definition;
|
||||
use LanguageServer\Index\Index;
|
||||
use LanguageServer\PhpDocument;
|
||||
use LanguageServer\DefinitionResolver;
|
||||
use phpDocumentor\Reflection\DocBlock;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use LanguageServer\ClientHandler;
|
||||
use LanguageServer\Protocol\Message;
|
||||
use AdvancedJsonRpc;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Sabre\Event\Loop;
|
||||
use Microsoft\PhpParser;
|
||||
|
||||
class ValidationTest extends TestCase
|
||||
{
|
||||
public function validationTestProvider()
|
||||
{
|
||||
$testProviderArray = array();
|
||||
$testCasesDir = realpath(__DIR__ . '/cases');
|
||||
|
||||
$iterator = new RecursiveDirectoryIterator($testCasesDir);
|
||||
$disabled = json_decode(file_get_contents(__DIR__ . '/disabled.json'));
|
||||
|
||||
foreach (new RecursiveIteratorIterator($iterator) as $file) {
|
||||
if (strpos(\strrev((string)$file), \strrev(".php")) === 0 && !\in_array(basename((string)$file), $disabled)) {
|
||||
if ($file->getSize() < 100000) {
|
||||
$testProviderArray[] = [$file->getPathname()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $testProviderArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This test loads the test cases specified in .php files under cases/ and looks at the whole set of
|
||||
* Definitions and References produced. It reads the expected results from associated .json files
|
||||
* and compares to the actual result. If they don't match, the test fails and it writes the new baseline
|
||||
* to the .json file.
|
||||
* @group validation
|
||||
* @dataProvider validationTestProvider
|
||||
* @param $testCaseFile
|
||||
*/
|
||||
public function testDefinitionsAndReferences($testCaseFile)
|
||||
{
|
||||
$fileContents = file_get_contents($testCaseFile);
|
||||
$actualValues = $this->getActualTestValues($testCaseFile, $fileContents);
|
||||
|
||||
$outputFile = getExpectedValuesFile($testCaseFile);
|
||||
if (!file_exists($outputFile)) {
|
||||
file_put_contents($outputFile, json_encode($actualValues, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
$expectedValues = (array)json_decode(file_get_contents($outputFile));
|
||||
|
||||
try {
|
||||
$this->assertEquals($expectedValues['definitions'], $actualValues['definitions']);
|
||||
|
||||
try {
|
||||
$this->assertArraySubset((array)$expectedValues['references'], (array)$actualValues['references'], false, 'references don\'t match.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->assertEquals((array)$expectedValues['references'], (array)$actualValues['references'], 'references don\'t match.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$outputFile = getExpectedValuesFile($testCaseFile);
|
||||
file_put_contents($outputFile, json_encode($actualValues, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function getActualTestValues($filename, $fileContents): array
|
||||
{
|
||||
$index = new Index();
|
||||
$parser = new PhpParser\Parser();
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$definitionResolver = new DefinitionResolver($index);
|
||||
|
||||
$document = new PhpDocument($filename, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||
|
||||
$actualRefs = $index->getReferences();
|
||||
$actualDefs = $this->getTestValuesFromDefs($document->getDefinitions());
|
||||
|
||||
// There's probably a more PHP-typical way to do this. Need to compare the objects parsed from json files
|
||||
// to the real objects.
|
||||
$refsAndDefs = array(
|
||||
'references' => json_decode(json_encode($actualRefs)),
|
||||
'definitions' => json_decode(json_encode($actualDefs))
|
||||
);
|
||||
|
||||
// Turn references into relative paths
|
||||
$testCasesDir = realpath(__DIR__ . '/cases');
|
||||
foreach ($refsAndDefs['references'] as $key => $list) {
|
||||
$fixedPathRefs = array_map(function ($ref) use ($testCasesDir) {
|
||||
return str_replace($testCasesDir, '.', $ref);
|
||||
}, $list);
|
||||
|
||||
$refsAndDefs['references']->$key = $fixedPathRefs;
|
||||
}
|
||||
|
||||
// Turn def locations into relative paths
|
||||
foreach ($refsAndDefs['definitions'] as $key => $def) {
|
||||
if ($def !== null && $def->symbolInformation !== null &&
|
||||
$def->symbolInformation->location !== null && $def->symbolInformation->location->uri !== null) {
|
||||
$def->symbolInformation->location->uri = str_replace($testCasesDir, '.', $def->symbolInformation->location->uri);
|
||||
}
|
||||
}
|
||||
|
||||
return $refsAndDefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $definitions Definition[]
|
||||
* @return array|\array[]
|
||||
*/
|
||||
private function getTestValuesFromDefs($definitions): array
|
||||
{
|
||||
$propertyNames = get_class_vars(Definition::class);
|
||||
|
||||
$defsForAssert = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$fqn = $definition->fqn;
|
||||
|
||||
foreach ($propertyNames as $propertyName => $value) {
|
||||
if ($propertyName === 'symbolInformation') {
|
||||
// Range is very often different - don't check it, for now
|
||||
unset($definition->$propertyName->location->range);
|
||||
} elseif ($propertyName === 'extends') {
|
||||
$definition->$propertyName = $definition->$propertyName ?? [];
|
||||
} elseif ($propertyName === 'type' && $definition->type !== null) {
|
||||
// Class info is not captured by json_encode. It's important for 'type'.
|
||||
$defsForAssert[$fqn]['type__class'] = get_class($definition->type);
|
||||
}
|
||||
|
||||
$defsForAssert[$fqn][$propertyName] = $definition->$propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
return $defsForAssert;
|
||||
}
|
||||
}
|
||||
|
||||
function getExpectedValuesFile($testCaseFile): string
|
||||
{
|
||||
return $testCaseFile . '.expected.json';
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Fixtures\Prophecy;
|
||||
|
||||
class WithReturnTypehints extends EmptyClass
|
||||
{
|
||||
public function getSelf(): self {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return __CLASS__;
|
||||
}
|
||||
|
||||
public function getParent(): parent {
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"references": {
|
||||
"Fixtures\\Prophecy\\EmptyClass": [
|
||||
"./WithReturnTypehints.php"
|
||||
],
|
||||
"self": [
|
||||
"./WithReturnTypehints.php"
|
||||
],
|
||||
"parent": [
|
||||
"./WithReturnTypehints.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"Fixtures\\Prophecy": {
|
||||
"fqn": "Fixtures\\Prophecy",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "Fixtures\\Prophecy",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./WithReturnTypehints.php"
|
||||
},
|
||||
"containerName": "Fixtures"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace Fixtures\\Prophecy;",
|
||||
"documentation": null
|
||||
},
|
||||
"Fixtures\\Prophecy\\WithReturnTypehints": {
|
||||
"fqn": "Fixtures\\Prophecy\\WithReturnTypehints",
|
||||
"extends": [
|
||||
"Fixtures\\Prophecy\\EmptyClass"
|
||||
],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "WithReturnTypehints",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./WithReturnTypehints.php"
|
||||
},
|
||||
"containerName": "Fixtures\\Prophecy"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class WithReturnTypehints extends EmptyClass",
|
||||
"documentation": null
|
||||
},
|
||||
"Fixtures\\Prophecy\\WithReturnTypehints->getSelf()": {
|
||||
"fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getSelf()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "getSelf",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./WithReturnTypehints.php"
|
||||
},
|
||||
"containerName": "Fixtures\\Prophecy\\WithReturnTypehints"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Object_",
|
||||
"type": {},
|
||||
"declarationLine": "public function getSelf(): self {",
|
||||
"documentation": null
|
||||
},
|
||||
"Fixtures\\Prophecy\\WithReturnTypehints->getName()": {
|
||||
"fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getName()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "getName",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./WithReturnTypehints.php"
|
||||
},
|
||||
"containerName": "Fixtures\\Prophecy\\WithReturnTypehints"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\String_",
|
||||
"type": {},
|
||||
"declarationLine": "public function getName(): string {",
|
||||
"documentation": null
|
||||
},
|
||||
"Fixtures\\Prophecy\\WithReturnTypehints->getParent()": {
|
||||
"fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getParent()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "getParent",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./WithReturnTypehints.php"
|
||||
},
|
||||
"containerName": "Fixtures\\Prophecy\\WithReturnTypehints"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Object_",
|
||||
"type": {},
|
||||
"declarationLine": "public function getParent(): parent {",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
$a = new class () {
|
||||
public $a;
|
||||
const HI = 3;
|
||||
|
||||
function b () {
|
||||
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./anonymousClassMembersShouldNotBeSymbols.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
class A {
|
||||
protected $foo = ['hello' => TRUE];
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"A": {
|
||||
"fqn": "A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./arrayValueShouldBeBoolean.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"A->foo": {
|
||||
"fqn": "A->foo",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "foo",
|
||||
"kind": 7,
|
||||
"location": {
|
||||
"uri": "./arrayValueShouldBeBoolean.php"
|
||||
},
|
||||
"containerName": "A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Array_",
|
||||
"type": {},
|
||||
"declarationLine": "protected $foo;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
switch ($a) {
|
||||
case A:
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\A": [
|
||||
"./caseStatement1.php"
|
||||
],
|
||||
"A": [
|
||||
"./caseStatement1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./caseStatement1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace TestNamespace;
|
||||
|
||||
$a = new A;
|
||||
|
||||
echo $a->a;
|
||||
|
||||
class A {
|
||||
public $a = 3;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"TestNamespace\\A": [
|
||||
"./classDefinition1.php"
|
||||
],
|
||||
"TestNamespace\\A->a": [
|
||||
"./classDefinition1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"TestNamespace": {
|
||||
"fqn": "TestNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "TestNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./classDefinition1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace TestNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"TestNamespace\\A": {
|
||||
"fqn": "TestNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./classDefinition1.php"
|
||||
},
|
||||
"containerName": "TestNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"TestNamespace\\A->a": {
|
||||
"fqn": "TestNamespace\\A->a",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "a",
|
||||
"kind": 7,
|
||||
"location": {
|
||||
"uri": "./classDefinition1.php"
|
||||
},
|
||||
"containerName": "TestNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Integer",
|
||||
"type": {},
|
||||
"declarationLine": "public $a;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace TestNamespace;
|
||||
|
||||
use SomeNamespace\Goo;
|
||||
|
||||
class TestClass
|
||||
{
|
||||
public $testProperty;
|
||||
|
||||
public function testMethod($testParameter)
|
||||
{
|
||||
$testVariable = 123;
|
||||
|
||||
if (empty($testParameter)) {
|
||||
echo 'Empty';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"references": {
|
||||
"SomeNamespace\\Goo": [
|
||||
"./classProperty1.php"
|
||||
],
|
||||
"SomeNamespace": [
|
||||
"./classProperty1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"TestNamespace": {
|
||||
"fqn": "TestNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "TestNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./classProperty1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace TestNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"TestNamespace\\TestClass": {
|
||||
"fqn": "TestNamespace\\TestClass",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "TestClass",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./classProperty1.php"
|
||||
},
|
||||
"containerName": "TestNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class TestClass",
|
||||
"documentation": null
|
||||
},
|
||||
"TestNamespace\\TestClass->testProperty": {
|
||||
"fqn": "TestNamespace\\TestClass->testProperty",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "testProperty",
|
||||
"kind": 7,
|
||||
"location": {
|
||||
"uri": "./classProperty1.php"
|
||||
},
|
||||
"containerName": "TestNamespace\\TestClass"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public $testProperty;",
|
||||
"documentation": null
|
||||
},
|
||||
"TestNamespace\\TestClass->testMethod()": {
|
||||
"fqn": "TestNamespace\\TestClass->testMethod()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "testMethod",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./classProperty1.php"
|
||||
},
|
||||
"containerName": "TestNamespace\\TestClass"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public function testMethod($testParameter)",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A
|
||||
{
|
||||
public static function suite()
|
||||
{
|
||||
return [
|
||||
"hi" => BYE
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\BYE": [
|
||||
"./constants.php"
|
||||
],
|
||||
"BYE": [
|
||||
"./constants.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./constants.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./constants.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::suite()": {
|
||||
"fqn": "MyNamespace\\A::suite()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "suite",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./constants.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public static function suite()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A
|
||||
{
|
||||
public static function suite()
|
||||
{
|
||||
return [
|
||||
BYE => "hi"
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\BYE": [
|
||||
"./constants2.php"
|
||||
],
|
||||
"BYE": [
|
||||
"./constants2.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./constants2.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./constants2.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::suite()": {
|
||||
"fqn": "MyNamespace\\A::suite()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "suite",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./constants2.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public static function suite()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A
|
||||
{
|
||||
public static function suite()
|
||||
{
|
||||
return array(T_NEW);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\T_NEW": [
|
||||
"./constants3.php"
|
||||
],
|
||||
"T_NEW": [
|
||||
"./constants3.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./constants3.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./constants3.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::suite()": {
|
||||
"fqn": "MyNamespace\\A::suite()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "suite",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./constants3.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public static function suite()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A
|
||||
{
|
||||
public function suite()
|
||||
{
|
||||
return HI;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\HI": [
|
||||
"./constants4.php"
|
||||
],
|
||||
"HI": [
|
||||
"./constants4.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./constants4.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./constants4.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A->suite()": {
|
||||
"fqn": "MyNamespace\\A->suite()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "suite",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./constants4.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public function suite()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class Mbstring
|
||||
{
|
||||
const MB_CASE_FOLD = PHP_INT_MAX;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\PHP_INT_MAX": [
|
||||
"./constants5.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./constants5.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\Mbstring": {
|
||||
"fqn": "MyNamespace\\Mbstring",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "Mbstring",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./constants5.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class Mbstring",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\Mbstring::MB_CASE_FOLD": {
|
||||
"fqn": "MyNamespace\\Mbstring::MB_CASE_FOLD",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MB_CASE_FOLD",
|
||||
"kind": 14,
|
||||
"location": {
|
||||
"uri": "./constants5.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\Mbstring"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Object_",
|
||||
"type": {},
|
||||
"declarationLine": "const MB_CASE_FOLD = PHP_INT_MAX;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
interface A {
|
||||
function b ($a = MY_CONSTANT);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"references": {
|
||||
"MY_CONSTANT": [
|
||||
"./constantsInFunctionParamDefault.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"A": {
|
||||
"fqn": "A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 11,
|
||||
"location": {
|
||||
"uri": "./constantsInFunctionParamDefault.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "interface A {",
|
||||
"documentation": null
|
||||
},
|
||||
"A->b()": {
|
||||
"fqn": "A->b()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "b",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./constantsInFunctionParamDefault.php"
|
||||
},
|
||||
"containerName": "A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "function b ($a = MY_CONSTANT);",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This is a file comment, not NS comment
|
||||
*/
|
||||
namespace MyNamespace;
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./docBlocksOnNamespaceDefinition.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
try {
|
||||
|
||||
} catch (Exception $e) {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./exceptions1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
class ForLoopReference1 {
|
||||
public function getThat() {
|
||||
for ($that = $this; null !== $that; $that = $that->foo()) {
|
||||
}
|
||||
}
|
||||
|
||||
public function foo() {
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"references": {
|
||||
"ForLoopReference1->foo()": [
|
||||
"./_cases/forLoopReference1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"ForLoopReference1": {
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "ForLoopReference1",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./_cases/forLoopReference1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type__class": "LanguageServer\\Tests\\ValidationTest",
|
||||
"type": null,
|
||||
"documentation": null
|
||||
},
|
||||
"ForLoopReference1->getThat()": {
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "getThat",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./_cases/forLoopReference1.php"
|
||||
},
|
||||
"containerName": "ForLoopReference1"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"documentation": null
|
||||
},
|
||||
"ForLoopReference1->foo()": {
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "foo",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./_cases/forLoopReference1.php"
|
||||
},
|
||||
"containerName": "ForLoopReference1"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
$a = new A;
|
||||
|
||||
$b = function () use ($a) {
|
||||
echo $a->b();
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"references": {
|
||||
"A": [
|
||||
"./functionUse.php"
|
||||
],
|
||||
"A->b()": [
|
||||
"./functionUse.php"
|
||||
]
|
||||
},
|
||||
"definitions": []
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
|
||||
use function LanguageServer\{pathToUri, timeout};
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"references": {
|
||||
"LanguageServer": [
|
||||
"./functionUse2.php"
|
||||
],
|
||||
"LanguageServer\\timeout()": [
|
||||
"./functionUse2.php"
|
||||
]
|
||||
},
|
||||
"definitions": []
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
if (A) {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\A": [
|
||||
"./ifStatement1.php"
|
||||
],
|
||||
"A": [
|
||||
"./ifStatement1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./ifStatement1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
interface A {
|
||||
// props ignored in interface
|
||||
var $a = 3;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"A": {
|
||||
"fqn": "A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 11,
|
||||
"location": {
|
||||
"uri": "./interfaceProperty.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "interface A {",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace B;
|
||||
|
||||
echo __FILE__;
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"B": {
|
||||
"fqn": "B",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "B",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./magicConstantsShouldBeGlobal.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace B;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
class A {
|
||||
private static $deprecationsTriggered = array(
|
||||
__CLASS__ => true
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"A": {
|
||||
"fqn": "A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./magicConsts.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"A::$deprecationsTriggered": {
|
||||
"fqn": "A::$deprecationsTriggered",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "deprecationsTriggered",
|
||||
"kind": 7,
|
||||
"location": {
|
||||
"uri": "./magicConsts.php"
|
||||
},
|
||||
"containerName": "A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Array_",
|
||||
"type": {},
|
||||
"declarationLine": "private static $deprecationsTriggered;",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A {
|
||||
static function a() {
|
||||
$b = new a;
|
||||
$c = $b->a();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\a": [
|
||||
"./memberAccess1.php"
|
||||
],
|
||||
"MyNamespace\\a->a()": [
|
||||
"./memberAccess1.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./memberAccess1.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./memberAccess1.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::a()": {
|
||||
"fqn": "MyNamespace\\A::a()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "a",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./memberAccess1.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "static function a() {",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A {
|
||||
static function a() {
|
||||
$b = new a;
|
||||
$c = $b->a();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\a": [
|
||||
"./memberAccess2.php"
|
||||
],
|
||||
"MyNamespace\\a->a()": [
|
||||
"./memberAccess2.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./memberAccess2.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./memberAccess2.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::a()": {
|
||||
"fqn": "MyNamespace\\A::a()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "a",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./memberAccess2.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "static function a() {",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A {
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixesPsr0 = ComposerStaticInitIncludePath::$prefixesPsr0;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\ClassLoader": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"Closure::bind()": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"Closure": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"MyNamespace\\ClassLoader->prefixesPsr0": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"MyNamespace\\ComposerStaticInitIncludePath": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"MyNamespace\\ComposerStaticInitIncludePath::$prefixesPsr0": [
|
||||
"./memberAccess3.php"
|
||||
],
|
||||
"MyNamespace\\ClassLoader::class": [
|
||||
"./memberAccess3.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./memberAccess3.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./memberAccess3.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A::getInitializer()": {
|
||||
"fqn": "MyNamespace\\A::getInitializer()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": true,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "getInitializer",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./memberAccess3.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public static function getInitializer(ClassLoader $loader)",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class A {
|
||||
public function testRequest()
|
||||
{
|
||||
$request = Request::create((new Url('httpkernel_test.empty'))->toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"references": {
|
||||
"MyNamespace\\Request::create()": [
|
||||
"./memberAccess4.php"
|
||||
],
|
||||
"MyNamespace\\Request": [
|
||||
"./memberAccess4.php"
|
||||
],
|
||||
"MyNamespace\\Url->toString()": [
|
||||
"./memberAccess4.php"
|
||||
],
|
||||
"MyNamespace\\Url": [
|
||||
"./memberAccess4.php"
|
||||
]
|
||||
},
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./memberAccess4.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A": {
|
||||
"fqn": "MyNamespace\\A",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "A",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./memberAccess4.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class A {",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\A->testRequest()": {
|
||||
"fqn": "MyNamespace\\A->testRequest()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "testRequest",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./memberAccess4.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\A"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public function testRequest()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class ParseErrorsTest {
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$a = new class($this->args) { };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"references": [],
|
||||
"definitions": {
|
||||
"MyNamespace": {
|
||||
"fqn": "MyNamespace",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "MyNamespace",
|
||||
"kind": 3,
|
||||
"location": {
|
||||
"uri": "./memberAccess5.php"
|
||||
},
|
||||
"containerName": ""
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "namespace MyNamespace;",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\ParseErrorsTest": {
|
||||
"fqn": "MyNamespace\\ParseErrorsTest",
|
||||
"extends": [],
|
||||
"isGlobal": true,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": true,
|
||||
"symbolInformation": {
|
||||
"name": "ParseErrorsTest",
|
||||
"kind": 5,
|
||||
"location": {
|
||||
"uri": "./memberAccess5.php"
|
||||
},
|
||||
"containerName": "MyNamespace"
|
||||
},
|
||||
"type": null,
|
||||
"declarationLine": "class ParseErrorsTest {",
|
||||
"documentation": null
|
||||
},
|
||||
"MyNamespace\\ParseErrorsTest->setUp()": {
|
||||
"fqn": "MyNamespace\\ParseErrorsTest->setUp()",
|
||||
"extends": [],
|
||||
"isGlobal": false,
|
||||
"isStatic": false,
|
||||
"canBeInstantiated": false,
|
||||
"symbolInformation": {
|
||||
"name": "setUp",
|
||||
"kind": 6,
|
||||
"location": {
|
||||
"uri": "./memberAccess5.php"
|
||||
},
|
||||
"containerName": "MyNamespace\\ParseErrorsTest"
|
||||
},
|
||||
"type__class": "phpDocumentor\\Reflection\\Types\\Mixed",
|
||||
"type": {},
|
||||
"declarationLine": "public function setUp()",
|
||||
"documentation": null
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue