Merge branch 'master' into keyword_completion2
|
@ -11,6 +11,7 @@ install:
|
||||||
- composer install
|
- composer install
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- vendor/bin/phpcs -n
|
||||||
- vendor/bin/phpunit --coverage-clover=coverage.xml
|
- vendor/bin/phpunit --coverage-clover=coverage.xml
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
|
119
README.md
|
@ -8,9 +8,109 @@
|
||||||
[](https://github.com/felixfbecker/php-language-server/blob/master/LICENSE.txt)
|
[](https://github.com/felixfbecker/php-language-server/blob/master/LICENSE.txt)
|
||||||
[](https://gitter.im/felixfbecker/php-language-server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://gitter.im/felixfbecker/php-language-server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
A pure PHP implementation of the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).
|
A pure PHP implementation of the open [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).
|
||||||
|
Provides static code analysis for PHP for any IDE.
|
||||||
|
|
||||||

|
Uses the great [PHP-Parser](https://github.com/nikic/PHP-Parser),
|
||||||
|
[phpDocumentor's DocBlock reflection](https://github.com/phpDocumentor/ReflectionDocBlock)
|
||||||
|
and an [event loop](http://sabre.io/event/loop/) for concurrency.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### [Go To Definition](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#goto-definition-request)
|
||||||
|

|
||||||
|
|
||||||
|
### [Find References](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#find-references-request)
|
||||||
|

|
||||||
|
|
||||||
|
### [Hover](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#hover-request)
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A hover request returns a declaration line (marked with language `php`) and the summary of the docblock.
|
||||||
|
For Parameters, it will return the `@param` tag.
|
||||||
|
|
||||||
|
### [Document Symbols](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#document-symbols-request)
|
||||||
|

|
||||||
|
|
||||||
|
### [Workspace Symbols](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#workspace-symbols-request)
|
||||||
|

|
||||||
|
|
||||||
|
The query is matched case-insensitively against the fully qualified name of the symbol.
|
||||||
|
Non-Standard: An empty query will return _all_ symbols found in the workspace.
|
||||||
|
|
||||||
|
### [Document Formatting](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#document-formatting-request)
|
||||||
|

|
||||||
|
|
||||||
|
### Error reporting through [Publish Diagnostics](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#publishdiagnostics-notification)
|
||||||
|

|
||||||
|
|
||||||
|
PHP parse errors are reported as errors, parse errors of docblocks are reported as warnings.
|
||||||
|
|
||||||
|
### What is considered a definition?
|
||||||
|
|
||||||
|
Globally searchable definitions are:
|
||||||
|
- classes
|
||||||
|
- interfaces
|
||||||
|
- traits
|
||||||
|
- properties
|
||||||
|
- methods
|
||||||
|
- class constants
|
||||||
|
- constants with `const` keyword
|
||||||
|
|
||||||
|
Definitions resolved just-in-time when needed:
|
||||||
|
- variable assignments
|
||||||
|
- parameters
|
||||||
|
- closure `use` statements
|
||||||
|
|
||||||
|
Not supported yet:
|
||||||
|
- constants with `define()`
|
||||||
|
|
||||||
|
Namespaces are not considerd a declaration by design because they only make up a part of the fully qualified name
|
||||||
|
and don't map to one unique declaration.
|
||||||
|
|
||||||
|
### What is considered a reference?
|
||||||
|
|
||||||
|
Definitions/references/hover currently work for
|
||||||
|
- class instantiations
|
||||||
|
- static method calls
|
||||||
|
- class constant access
|
||||||
|
- static property access
|
||||||
|
- parameter type hints
|
||||||
|
- return type hints
|
||||||
|
- method calls, if the variable was assigned to a new object in the same scope
|
||||||
|
- property access, if the variable was assigned to a new object in the same scope
|
||||||
|
- variables
|
||||||
|
- parameters
|
||||||
|
- imported closure variables (`use`)
|
||||||
|
- `use` statements for classes, constants and functions
|
||||||
|
- class-like after `implements`/`extends`
|
||||||
|
- function calls
|
||||||
|
- constant access
|
||||||
|
- `instanceof` checks
|
||||||
|
|
||||||
|
They do not work yet for:
|
||||||
|
- Reassigned variables
|
||||||
|
- Nested access/calls on return values or properties
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Upon initialization, the server will recursively scan the project directory for PHP files, parse them and add all definitions
|
||||||
|
and references to an in-memory index.
|
||||||
|
The time this takes depends on the project size.
|
||||||
|
At the time of writing, this project contains 78 files + 1560 files in dependencies which take 97s to parse
|
||||||
|
and consume 76 MB on a Surface Pro 3.
|
||||||
|
The language server is fully operational while indexing and can respond to requests with the definitions already indexed.
|
||||||
|
Follow-up requests will be almost instant because the index is kept in memory.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
This project follows [semver](http://semver.org/) for the protocol communication and command line parameters,
|
||||||
|
e.g. a major version increase of the LSP will result in a major version increase of the PHP LS.
|
||||||
|
New features like request implementations will result in a new minor version.
|
||||||
|
Everything else will be a patch release.
|
||||||
|
All classes are considered internal and are not subject to semver.
|
||||||
|
|
||||||
## Used by
|
## Used by
|
||||||
- [vscode-php-intellisense](https://github.com/felixfbecker/vscode-php-intellisense)
|
- [vscode-php-intellisense](https://github.com/felixfbecker/vscode-php-intellisense)
|
||||||
|
@ -26,20 +126,27 @@ to install dependencies.
|
||||||
|
|
||||||
Run the tests with
|
Run the tests with
|
||||||
|
|
||||||
vendor/bin/phpunit --bootstrap vendor/autoload.php tests
|
vendor/bin/phpunit
|
||||||
|
|
||||||
|
Lint with
|
||||||
|
|
||||||
|
vendor/bin/phpcs
|
||||||
|
|
||||||
## Command line arguments
|
## Command line arguments
|
||||||
|
|
||||||
###### --tcp=host:port (optional)
|
### `--tcp=host:port` (optional)
|
||||||
Causes the server to use a tcp connection for communicating with the language client instead of using STDIN/STDOUT.
|
Causes the server to use a tcp connection for communicating with the language client instead of using STDIN/STDOUT.
|
||||||
The server will try to connect to the specified address.
|
The server will try to connect to the specified address.
|
||||||
|
Strongly recommended on Windows because of blocking STDIO.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
php bin/php-language-server.php --tcp=127.0.0.1:12345
|
php bin/php-language-server.php --tcp=127.0.0.1:12345
|
||||||
|
|
||||||
###### --memory-limit=integer (optional)
|
### `--memory-limit=integer` (optional)
|
||||||
Sets memory limit for language server. Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) *php.ini* directive. By default there is no memory limit.
|
Sets memory limit for language server.
|
||||||
|
Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive.
|
||||||
|
By default there is no memory limit.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DI
|
||||||
|
|
||||||
ErrorHandler::register();
|
ErrorHandler::register();
|
||||||
|
|
||||||
cli_set_process_title('PHP Language Server');
|
@cli_set_process_title('PHP Language Server');
|
||||||
|
|
||||||
if (!empty($options['tcp'])) {
|
if (!empty($options['tcp'])) {
|
||||||
$address = $options['tcp'];
|
$address = $options['tcp'];
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
"bin": ["bin/php-language-server.php"],
|
"bin": ["bin/php-language-server.php"],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.0",
|
"php": ">=7.0",
|
||||||
"nikic/php-parser": "dev-master#90834bff8eaf7b7f893253f312e73d8f532341ca",
|
"nikic/php-parser": "dev-master#c0f0edf0441f0ddcff0604407b7600a40993faf2",
|
||||||
"phpdocumentor/reflection-docblock": "^3.0",
|
"phpdocumentor/reflection-docblock": "^3.0",
|
||||||
"sabre/event": "^4.0",
|
"sabre/event": "^5.0",
|
||||||
"felixfbecker/advanced-json-rpc": "^2.0",
|
"felixfbecker/advanced-json-rpc": "^2.0",
|
||||||
"squizlabs/php_codesniffer" : "^2.7",
|
"squizlabs/php_codesniffer" : "^2.7",
|
||||||
"symfony/debug": "^3.1"
|
"symfony/debug": "^3.1"
|
||||||
|
|
|
@ -28,3 +28,9 @@ $fn = function() use ($var) {
|
||||||
};
|
};
|
||||||
|
|
||||||
echo TEST_CONST;
|
echo TEST_CONST;
|
||||||
|
|
||||||
|
use function test_function;
|
||||||
|
|
||||||
|
if ($abc instanceof TestInterface) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -28,3 +28,9 @@ $fn = function() use ($var) {
|
||||||
};
|
};
|
||||||
|
|
||||||
echo TEST_CONST;
|
echo TEST_CONST;
|
||||||
|
|
||||||
|
use function TestNamespace\test_function;
|
||||||
|
|
||||||
|
if ($abc instanceof TestInterface) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 250 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset name="PHP Language Server">
|
||||||
|
<file>src</file>
|
||||||
|
<file>tests</file>
|
||||||
|
<rule ref="PSR2">
|
||||||
|
<exclude name="PSR2.Namespaces.UseDeclaration.MultipleDeclarations"/>
|
||||||
|
<exclude name="PSR2.ControlStructures.ElseIfDeclaration.NotAllowed"/>
|
||||||
|
<exclude name="PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace"/>
|
||||||
|
</rule>
|
||||||
|
</ruleset>
|
|
@ -85,5 +85,4 @@ abstract class Formatter
|
||||||
$standard = PHP_CodeSniffer::getConfigData('default_standard') ?? 'PSR2';
|
$standard = PHP_CodeSniffer::getConfigData('default_standard') ?? 'PSR2';
|
||||||
return explode(',', $standard);
|
return explode(',', $standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ function getReferencedFqn(Node $node)
|
||||||
|| $parent instanceof Node\Expr\StaticCall
|
|| $parent instanceof Node\Expr\StaticCall
|
||||||
|| $parent instanceof Node\Expr\ClassConstFetch
|
|| $parent instanceof Node\Expr\ClassConstFetch
|
||||||
|| $parent instanceof Node\Expr\StaticPropertyFetch
|
|| $parent instanceof Node\Expr\StaticPropertyFetch
|
||||||
|
|| $parent instanceof Node\Expr\Instanceof_
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// For extends, implements, type hints and classes of classes of static calls use the name directly
|
// For extends, implements, type hints and classes of classes of static calls use the name directly
|
||||||
|
@ -45,6 +46,8 @@ function getReferencedFqn(Node $node)
|
||||||
$grandParent = $parent->getAttribute('parentNode');
|
$grandParent = $parent->getAttribute('parentNode');
|
||||||
if ($grandParent instanceof Node\Stmt\GroupUse) {
|
if ($grandParent instanceof Node\Stmt\GroupUse) {
|
||||||
$name = $grandParent->prefix . '\\' . $name;
|
$name = $grandParent->prefix . '\\' . $name;
|
||||||
|
} else if ($grandParent instanceof Node\Stmt\Use_ && $grandParent->type === Node\Stmt\Use_::TYPE_FUNCTION) {
|
||||||
|
$name .= '()';
|
||||||
}
|
}
|
||||||
// Only the name node should be considered a reference, not the New_ node itself
|
// Only the name node should be considered a reference, not the New_ node itself
|
||||||
} else if ($parent instanceof Node\Expr\New_) {
|
} else if ($parent instanceof Node\Expr\New_) {
|
||||||
|
|
|
@ -103,7 +103,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
|
|
||||||
// start building project index
|
// start building project index
|
||||||
if ($rootPath !== null) {
|
if ($rootPath !== null) {
|
||||||
$this->restoreCache();
|
|
||||||
$this->indexProject();
|
$this->indexProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +138,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
*/
|
*/
|
||||||
public function shutdown()
|
public function shutdown()
|
||||||
{
|
{
|
||||||
if ($this->rootPath !== null) {
|
|
||||||
$this->saveCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,69 +181,14 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fileNum % 1000 === 0) {
|
|
||||||
$this->saveCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
Loop\setTimeout($processFile, 0);
|
Loop\setTimeout($processFile, 0);
|
||||||
} else {
|
} else {
|
||||||
$duration = (int)(microtime(true) - $startTime);
|
$duration = (int)(microtime(true) - $startTime);
|
||||||
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
||||||
$this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated.");
|
$this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated.");
|
||||||
$this->saveCache();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Loop\setTimeout($processFile, 0);
|
Loop\setTimeout($processFile, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores the definition and reference index from the .phpls cache directory, if available
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function restoreCache()
|
|
||||||
{
|
|
||||||
$cacheDir = $this->rootPath . '/.phpls';
|
|
||||||
if (is_dir($cacheDir)) {
|
|
||||||
if (file_exists($cacheDir . '/symbols')) {
|
|
||||||
$symbols = unserialize(file_get_contents($cacheDir . '/symbols'));
|
|
||||||
$count = count($symbols);
|
|
||||||
$this->project->setSymbols($symbols);
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Restoring $count symbols");
|
|
||||||
}
|
|
||||||
if (file_exists($cacheDir . '/references')) {
|
|
||||||
$references = unserialize(file_get_contents($cacheDir . '/references'));
|
|
||||||
$count = array_sum(array_map('count', $references));
|
|
||||||
$this->project->setReferenceUris($references);
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Restoring $count references");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, 'No cache found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the definition and reference index to the .phpls cache directory
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function saveCache()
|
|
||||||
{
|
|
||||||
// Cache definitions, references
|
|
||||||
$cacheDir = $this->rootPath . '/.phpls';
|
|
||||||
if (!is_dir($cacheDir)) {
|
|
||||||
mkdir($cacheDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
$symbols = $this->project->getSymbols();
|
|
||||||
$count = count($symbols);
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Saving $count symbols to cache");
|
|
||||||
file_put_contents($cacheDir . "/symbols", serialize($symbols));
|
|
||||||
|
|
||||||
$references = $this->project->getReferenceUris();
|
|
||||||
$count = array_sum(array_map('count', $references));
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Saving $count references to cache");
|
|
||||||
file_put_contents($cacheDir . "/references", serialize($references));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ use LanguageServer\NodeVisitor\{
|
||||||
ReferencesCollector,
|
ReferencesCollector,
|
||||||
VariableReferencesCollector
|
VariableReferencesCollector
|
||||||
};
|
};
|
||||||
use PhpParser\{Error, Node, NodeTraverser, Parser};
|
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn};
|
use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn};
|
||||||
|
@ -142,19 +142,12 @@ class PhpDocument
|
||||||
$this->completionReporter = new CompletionReporter($this);
|
$this->completionReporter = new CompletionReporter($this);
|
||||||
|
|
||||||
$stmts = null;
|
$stmts = null;
|
||||||
$errors = [];
|
|
||||||
try {
|
|
||||||
$stmts = $this->parser->parse($content);
|
|
||||||
} catch (\PhpParser\Error $e) {
|
|
||||||
// Lexer can throw errors. e.g for unterminated comments
|
|
||||||
// unfortunately we don't get a location back
|
|
||||||
$errors[] = $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$errors = array_merge($this->parser->getErrors(), $errors);
|
$errorHandler = new ErrorHandler\Collecting;
|
||||||
|
$stmts = $this->parser->parse($content, $errorHandler);
|
||||||
|
|
||||||
$diagnostics = [];
|
$diagnostics = [];
|
||||||
foreach ($errors as $error) {
|
foreach ($errorHandler->getErrors() as $error) {
|
||||||
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +156,7 @@ class PhpDocument
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
|
|
||||||
// Resolve aliased names to FQNs
|
// Resolve aliased names to FQNs
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver($errorHandler));
|
||||||
|
|
||||||
// Add parentNode, previousSibling, nextSibling attributes
|
// Add parentNode, previousSibling, nextSibling attributes
|
||||||
$traverser->addVisitor(new ReferencesAdder($this));
|
$traverser->addVisitor(new ReferencesAdder($this));
|
||||||
|
|
|
@ -5,7 +5,6 @@ namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\Protocol\SymbolInformation;
|
use LanguageServer\Protocol\SymbolInformation;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use PhpParser\{ParserFactory, Lexer};
|
|
||||||
|
|
||||||
class Project
|
class Project
|
||||||
{
|
{
|
||||||
|
@ -34,7 +33,7 @@ class Project
|
||||||
/**
|
/**
|
||||||
* Instance of the PHP parser
|
* Instance of the PHP parser
|
||||||
*
|
*
|
||||||
* @var ParserAbstract
|
* @var Parser
|
||||||
*/
|
*/
|
||||||
private $parser;
|
private $parser;
|
||||||
|
|
||||||
|
@ -56,8 +55,7 @@ class Project
|
||||||
{
|
{
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
|
|
||||||
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos']]);
|
$this->parser = new Parser;
|
||||||
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, ['throwOnError' => false]);
|
|
||||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +176,8 @@ class Project
|
||||||
* @param string $fqn The fully qualified name of the symbol
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function removeSymbol(string $fqn) {
|
public function removeSymbol(string $fqn)
|
||||||
|
{
|
||||||
unset($this->symbols[$fqn]);
|
unset($this->symbols[$fqn]);
|
||||||
unset($this->references[$fqn]);
|
unset($this->references[$fqn]);
|
||||||
}
|
}
|
||||||
|
@ -207,7 +206,8 @@ class Project
|
||||||
* @param string $uri The URI
|
* @param string $uri The URI
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function removeReferenceUri(string $fqn, string $uri) {
|
public function removeReferenceUri(string $fqn, string $uri)
|
||||||
|
{
|
||||||
if (!isset($this->references[$fqn])) {
|
if (!isset($this->references[$fqn])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ use PhpParser\Node;
|
||||||
/**
|
/**
|
||||||
* The kind of a completion entry.
|
* The kind of a completion entry.
|
||||||
*/
|
*/
|
||||||
abstract class CompletionItemKind {
|
abstract class CompletionItemKind
|
||||||
|
{
|
||||||
const TEXT = 1;
|
const TEXT = 1;
|
||||||
const METHOD = 2;
|
const METHOD = 2;
|
||||||
const FUNCTION = 3;
|
const FUNCTION = 3;
|
||||||
|
|
|
@ -7,16 +7,13 @@ use LanguageServer\Protocol\Message;
|
||||||
use AdvancedJsonRpc\Message as MessageBody;
|
use AdvancedJsonRpc\Message as MessageBody;
|
||||||
use Sabre\Event\Loop;
|
use Sabre\Event\Loop;
|
||||||
|
|
||||||
abstract class ParsingMode
|
|
||||||
{
|
|
||||||
const HEADERS = 1;
|
|
||||||
const BODY = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProtocolStreamReader implements ProtocolReader
|
class ProtocolStreamReader implements ProtocolReader
|
||||||
{
|
{
|
||||||
|
const PARSE_HEADERS = 1;
|
||||||
|
const PARSE_BODY = 2;
|
||||||
|
|
||||||
private $input;
|
private $input;
|
||||||
private $parsingMode = ParsingMode::HEADERS;
|
private $parsingMode = self::PARSE_HEADERS;
|
||||||
private $buffer = '';
|
private $buffer = '';
|
||||||
private $headers = [];
|
private $headers = [];
|
||||||
private $contentLength;
|
private $contentLength;
|
||||||
|
@ -28,13 +25,14 @@ class ProtocolStreamReader implements ProtocolReader
|
||||||
public function __construct($input)
|
public function __construct($input)
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
|
|
||||||
Loop\addReadStream($this->input, function () {
|
Loop\addReadStream($this->input, function () {
|
||||||
while (($c = fgetc($this->input)) !== false && $c !== '') {
|
while (($c = fgetc($this->input)) !== false && $c !== '') {
|
||||||
$this->buffer .= $c;
|
$this->buffer .= $c;
|
||||||
switch ($this->parsingMode) {
|
switch ($this->parsingMode) {
|
||||||
case ParsingMode::HEADERS:
|
case self::PARSE_HEADERS:
|
||||||
if ($this->buffer === "\r\n") {
|
if ($this->buffer === "\r\n") {
|
||||||
$this->parsingMode = ParsingMode::BODY;
|
$this->parsingMode = self::PARSE_BODY;
|
||||||
$this->contentLength = (int)$this->headers['Content-Length'];
|
$this->contentLength = (int)$this->headers['Content-Length'];
|
||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
} else if (substr($this->buffer, -2) === "\r\n") {
|
} else if (substr($this->buffer, -2) === "\r\n") {
|
||||||
|
@ -43,14 +41,14 @@ class ProtocolStreamReader implements ProtocolReader
|
||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ParsingMode::BODY:
|
case self::PARSE_BODY:
|
||||||
if (strlen($this->buffer) === $this->contentLength) {
|
if (strlen($this->buffer) === $this->contentLength) {
|
||||||
if (isset($this->listener)) {
|
if (isset($this->listener)) {
|
||||||
$msg = new Message(MessageBody::parse($this->buffer), $this->headers);
|
$msg = new Message(MessageBody::parse($this->buffer), $this->headers);
|
||||||
$listener = $this->listener;
|
$listener = $this->listener;
|
||||||
$listener($msg);
|
$listener($msg);
|
||||||
}
|
}
|
||||||
$this->parsingMode = ParsingMode::HEADERS;
|
$this->parsingMode = self::PARSE_HEADERS;
|
||||||
$this->headers = [];
|
$this->headers = [];
|
||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,24 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\Protocol\Message;
|
use LanguageServer\Protocol\Message;
|
||||||
|
use Sabre\Event\{
|
||||||
|
Loop,
|
||||||
|
Promise
|
||||||
|
};
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
class ProtocolStreamWriter implements ProtocolWriter
|
class ProtocolStreamWriter implements ProtocolWriter
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var resource $output
|
||||||
|
*/
|
||||||
private $output;
|
private $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $messages
|
||||||
|
*/
|
||||||
|
private $messages = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $output
|
* @param resource $output
|
||||||
*/
|
*/
|
||||||
|
@ -22,26 +34,58 @@ class ProtocolStreamWriter implements ProtocolWriter
|
||||||
* Sends a Message to the client
|
* Sends a Message to the client
|
||||||
*
|
*
|
||||||
* @param Message $msg
|
* @param Message $msg
|
||||||
* @return void
|
* @return Promise Resolved when the message has been fully written out to the output stream
|
||||||
*/
|
*/
|
||||||
public function write(Message $msg)
|
public function write(Message $msg)
|
||||||
{
|
{
|
||||||
$data = (string)$msg;
|
// if the message queue is currently empty, register a write handler.
|
||||||
$msgSize = strlen($data);
|
if (empty($this->messages)) {
|
||||||
$totalBytesWritten = 0;
|
Loop\addWriteStream($this->output, function () {
|
||||||
|
$this->flush();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
while ($totalBytesWritten < $msgSize) {
|
$promise = new Promise();
|
||||||
error_clear_last();
|
$this->messages[] = [
|
||||||
$bytesWritten = @fwrite($this->output, substr($data, $totalBytesWritten));
|
'message' => (string)$msg,
|
||||||
if ($bytesWritten === false) {
|
'promise' => $promise
|
||||||
$error = error_get_last();
|
];
|
||||||
if ($error !== null) {
|
return $promise;
|
||||||
throw new RuntimeException('Could not write message: ' . error_get_last()['message']);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes pending messages to the output stream.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function flush()
|
||||||
|
{
|
||||||
|
$keepWriting = true;
|
||||||
|
while ($keepWriting) {
|
||||||
|
$message = $this->messages[0]['message'];
|
||||||
|
$promise = $this->messages[0]['promise'];
|
||||||
|
|
||||||
|
$bytesWritten = @fwrite($this->output, $message);
|
||||||
|
|
||||||
|
if ($bytesWritten > 0) {
|
||||||
|
$message = substr($message, $bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if this message was completely sent
|
||||||
|
if (strlen($message) === 0) {
|
||||||
|
array_shift($this->messages);
|
||||||
|
|
||||||
|
// This was the last message in the queue, remove the write handler.
|
||||||
|
if (count($this->messages) === 0) {
|
||||||
|
Loop\removeWriteStream($this->output);
|
||||||
|
$keepWriting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise->fulfill();
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException('Could not write message');
|
$this->messages[0]['message'] = $message;
|
||||||
}
|
$keepWriting = false;
|
||||||
}
|
}
|
||||||
$totalBytesWritten += $bytesWritten;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ use InvalidArgumentException;
|
||||||
* @param string $pattern
|
* @param string $pattern
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
function findFilesRecursive(string $path, string $pattern): array {
|
function findFilesRecursive(string $path, string $pattern): array
|
||||||
|
{
|
||||||
$dir = new \RecursiveDirectoryIterator($path);
|
$dir = new \RecursiveDirectoryIterator($path);
|
||||||
$ite = new \RecursiveIteratorIterator($dir);
|
$ite = new \RecursiveIteratorIterator($dir);
|
||||||
$files = new \RegexIterator($ite, $pattern, \RegexIterator::GET_MATCH);
|
$files = new \RegexIterator($ite, $pattern, \RegexIterator::GET_MATCH);
|
||||||
|
@ -29,7 +30,8 @@ function findFilesRecursive(string $path, string $pattern): array {
|
||||||
* @param string $filepath
|
* @param string $filepath
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function pathToUri(string $filepath): string {
|
function pathToUri(string $filepath): string
|
||||||
|
{
|
||||||
$filepath = trim(str_replace('\\', '/', $filepath), '/');
|
$filepath = trim(str_replace('\\', '/', $filepath), '/');
|
||||||
$parts = explode('/', $filepath);
|
$parts = explode('/', $filepath);
|
||||||
// Don't %-encode the colon after a Windows drive letter
|
// Don't %-encode the colon after a Windows drive letter
|
||||||
|
|
|
@ -36,4 +36,3 @@ class MockProtocolStream implements ProtocolReader, ProtocolWriter
|
||||||
$this->listener = $listener;
|
$this->listener = $listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PhpParser\{ParserFactory, NodeTraverser, Node};
|
use PhpParser\{NodeTraverser, Node};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
use LanguageServer\{LanguageClient, Project, PhpDocument};
|
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser};
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
@ -17,7 +17,7 @@ class DefinitionCollectorTest extends TestCase
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream());
|
$client = new LanguageClient(new MockProtocolStream());
|
||||||
$project = new Project($client);
|
$project = new Project($client);
|
||||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
$parser = new Parser;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
||||||
$document = $project->loadDocument($uri);
|
$document = $project->loadDocument($uri);
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
|
@ -56,7 +56,7 @@ class DefinitionCollectorTest extends TestCase
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream());
|
$client = new LanguageClient(new MockProtocolStream());
|
||||||
$project = new Project($client);
|
$project = new Project($client);
|
||||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
$parser = new Parser;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
$uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
||||||
$document = $project->loadDocument($uri);
|
$document = $project->loadDocument($uri);
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
|
|
|
@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\ProtocolStreamWriter;
|
use LanguageServer\ProtocolStreamWriter;
|
||||||
use LanguageServer\Protocol\Message;
|
use LanguageServer\Protocol\Message;
|
||||||
use AdvancedJsonRpc\{Request as RequestBody};
|
use AdvancedJsonRpc\{Request as RequestBody};
|
||||||
|
use Sabre\Event\Loop;
|
||||||
|
|
||||||
class ProtocolStreamWriterTest extends TestCase
|
class ProtocolStreamWriterTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -21,7 +22,14 @@ class ProtocolStreamWriterTest extends TestCase
|
||||||
$msg = new Message(new RequestBody(1, 'aMethod', ['arg' => str_repeat('X', 100000)]));
|
$msg = new Message(new RequestBody(1, 'aMethod', ['arg' => str_repeat('X', 100000)]));
|
||||||
$msgString = (string)$msg;
|
$msgString = (string)$msg;
|
||||||
|
|
||||||
$writer->write($msg);
|
$promise = $writer->write($msg);
|
||||||
|
$promise->then(function () {
|
||||||
|
Loop\stop();
|
||||||
|
}, function () {
|
||||||
|
Loop\stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
Loop\run();
|
||||||
|
|
||||||
fclose($writeHandle);
|
fclose($writeHandle);
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ abstract class ServerTestCase extends TestCase
|
||||||
$this->project->loadDocument($globalReferencesUri);
|
$this->project->loadDocument($globalReferencesUri);
|
||||||
$this->project->loadDocument($useUri);
|
$this->project->loadDocument($useUri);
|
||||||
|
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
$this->definitionLocations = [
|
$this->definitionLocations = [
|
||||||
|
|
||||||
// Global
|
// Global
|
||||||
|
@ -105,7 +106,8 @@ abstract class ServerTestCase extends TestCase
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestInterface' => [
|
'TestNamespace\\TestInterface' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||||
1 => new Location($symbolsUri, new Range(new Position(57, 48), new Position(57, 61))) // public function testMethod($testParameter): TestInterface
|
1 => new Location($symbolsUri, new Range(new Position(57, 48), new Position(57, 61))), // public function testMethod($testParameter): TestInterface
|
||||||
|
2 => new Location($referencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::TEST_CLASS_CONST' => [
|
'TestNamespace\\TestClass::TEST_CLASS_CONST' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
||||||
|
@ -125,7 +127,8 @@ abstract class ServerTestCase extends TestCase
|
||||||
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 18)))
|
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 18)))
|
||||||
],
|
],
|
||||||
'TestNamespace\\test_function()' => [
|
'TestNamespace\\test_function()' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13)))
|
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
|
1 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
],
|
],
|
||||||
|
|
||||||
// Global
|
// Global
|
||||||
|
@ -143,7 +146,8 @@ abstract class ServerTestCase extends TestCase
|
||||||
],
|
],
|
||||||
'TestInterface' => [
|
'TestInterface' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements 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, 48), 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' => [
|
'TestClass::TEST_CLASS_CONST' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
||||||
|
@ -163,9 +167,11 @@ abstract class ServerTestCase extends TestCase
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 18)))
|
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 18)))
|
||||||
],
|
],
|
||||||
'test_function()' => [
|
'test_function()' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13)))
|
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
|
1 => new Location($globalReferencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDefinitionLocation(string $fqn): Location
|
protected function getDefinitionLocation(string $fqn): Location
|
||||||
|
|
|
@ -9,13 +9,15 @@ use function LanguageServer\pathToUri;
|
||||||
|
|
||||||
class GlobalTest extends ServerTestCase
|
class GlobalTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testDefinitionFileBeginning() {
|
public function testDefinitionFileBeginning()
|
||||||
|
{
|
||||||
// |<?php
|
// |<?php
|
||||||
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(0, 0));
|
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(0, 0));
|
||||||
$this->assertEquals([], $result);
|
$this->assertEquals([], $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionEmptyResult() {
|
public function testDefinitionEmptyResult()
|
||||||
|
{
|
||||||
// namespace keyword
|
// namespace keyword
|
||||||
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(2, 4));
|
$result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(2, 4));
|
||||||
$this->assertEquals([], $result);
|
$this->assertEquals([], $result);
|
||||||
|
@ -200,4 +202,22 @@ class GlobalTest extends ServerTestCase
|
||||||
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start);
|
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start);
|
||||||
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDefinitionForUseFunctions()
|
||||||
|
{
|
||||||
|
// use function test_function;
|
||||||
|
// Get definition for test_function
|
||||||
|
$reference = $this->getReferenceLocations('test_function()')[1];
|
||||||
|
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDefinitionForInstanceOf()
|
||||||
|
{
|
||||||
|
// if ($abc instanceof TestInterface) {
|
||||||
|
// Get definition for TestInterface
|
||||||
|
$reference = $this->getReferenceLocations('TestInterface')[2];
|
||||||
|
$result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class DocumentSymbolTest extends ServerTestCase
|
||||||
// Request symbols
|
// Request symbols
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
|
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||||
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri));
|
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri));
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||||
|
@ -28,5 +29,6 @@ class DocumentSymbolTest extends ServerTestCase
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
||||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||||
], $result);
|
], $result);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ class ParseErrorsTest extends TestCase
|
||||||
$this->textDocument = new Server\TextDocument($project, $client);
|
$this->textDocument = new Server\TextDocument($project, $client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function openFile($file) {
|
private function openFile($file)
|
||||||
|
{
|
||||||
$textDocumentItem = new TextDocumentItem();
|
$textDocumentItem = new TextDocumentItem();
|
||||||
$textDocumentItem->uri = 'whatever';
|
$textDocumentItem->uri = 'whatever';
|
||||||
$textDocumentItem->languageId = 'php';
|
$textDocumentItem->languageId = 'php';
|
||||||
|
|
|
@ -101,6 +101,9 @@ class GlobalTest extends ServerTestCase
|
||||||
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php'));
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php'));
|
||||||
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($symbolsUri), new Position(78, 16));
|
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($symbolsUri), new Position(78, 16));
|
||||||
$this->assertEquals([new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13)))], $result);
|
$this->assertEquals([
|
||||||
|
new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
|
new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
|
], $result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class SymbolTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
// Request symbols
|
// Request symbols
|
||||||
$result = $this->workspace->symbol('');
|
$result = $this->workspace->symbol('');
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
// Namespaced
|
// Namespaced
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
|
@ -42,17 +43,20 @@ class SymbolTest extends ServerTestCase
|
||||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''),
|
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''),
|
||||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), '')
|
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), '')
|
||||||
], $result);
|
], $result);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testQueryFiltersResults()
|
public function testQueryFiltersResults()
|
||||||
{
|
{
|
||||||
// Request symbols
|
// Request symbols
|
||||||
$result = $this->workspace->symbol('testmethod');
|
$result = $this->workspace->symbol('testmethod');
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass')
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass')
|
||||||
], $result);
|
], $result);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|