diff --git a/fixtures/global_references.php b/fixtures/global_references.php index ae9b627..8f88247 100644 --- a/fixtures/global_references.php +++ b/fixtures/global_references.php @@ -13,6 +13,12 @@ test_function(); $var = 123; echo $var; +/** + * Aute duis elit reprehenderit tempor cillum proident anim laborum eu laboris reprehenderit ea incididunt. + * + * @param TestClass $param Adipisicing non non cillum sint incididunt cillum enim mollit. + * @return TestClass + */ function whatever(TestClass $param): TestClass { echo $param; } diff --git a/fixtures/global_symbols.php b/fixtures/global_symbols.php index a8e2765..915dcfe 100644 --- a/fixtures/global_symbols.php +++ b/fixtures/global_symbols.php @@ -2,19 +2,59 @@ +/** + * Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit. + * + * @var int + */ const TEST_CONST = 123; +/** + * Pariatur ut laborum tempor voluptate consequat ea deserunt. + * + * Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud + * laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam + * veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat + * consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem + * sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit. + */ class TestClass implements TestInterface { + /** + * Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad. + * + * @var int + */ const TEST_CLASS_CONST = 123; + + /** + * Lorem excepteur officia sit anim velit veniam enim. + * + * @var TestClass + */ public static $staticTestProperty; + + /** + * Reprehenderit magna velit mollit ipsum do. + * + * @var TestClass + */ public $testProperty; + /** + * Do magna consequat veniam minim proident eiusmod incididunt aute proident. + */ public static function staticTestMethod() { } + /** + * Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet. + * + * @param TestClass $testParameter Lorem sunt velit incididunt mollit + * @return TestClass + */ public function testMethod($testParameter) { $testVariable = 123; @@ -31,6 +71,11 @@ interface TestInterface } +/** + * Officia aliquip adipisicing et nulla et laboris dolore labore. + * + * @return void + */ function test_function() { diff --git a/fixtures/references.php b/fixtures/references.php index 6cb9190..3f58d05 100644 --- a/fixtures/references.php +++ b/fixtures/references.php @@ -13,6 +13,12 @@ test_function(); $var = 123; echo $var; +/** + * Aute duis elit reprehenderit tempor cillum proident anim laborum eu laboris reprehenderit ea incididunt. + * + * @param TestClass $param Adipisicing non non cillum sint incididunt cillum enim mollit. + * @return TestClass + */ function whatever(TestClass $param): TestClass { echo $param; } diff --git a/fixtures/symbols.php b/fixtures/symbols.php index 26379e4..4904e07 100644 --- a/fixtures/symbols.php +++ b/fixtures/symbols.php @@ -2,19 +2,59 @@ namespace TestNamespace; +/** + * Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit. + * + * @var int + */ const TEST_CONST = 123; +/** + * Pariatur ut laborum tempor voluptate consequat ea deserunt. + * + * Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud + * laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam + * veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat + * consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem + * sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit. + */ class TestClass implements TestInterface { + /** + * Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad. + * + * @var int + */ const TEST_CLASS_CONST = 123; + + /** + * Lorem excepteur officia sit anim velit veniam enim. + * + * @var TestClass + */ public static $staticTestProperty; + + /** + * Reprehenderit magna velit mollit ipsum do. + * + * @var TestClass + */ public $testProperty; + /** + * Do magna consequat veniam minim proident eiusmod incididunt aute proident. + */ public static function staticTestMethod() { } + /** + * Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet. + * + * @param TestClass $testParameter Lorem sunt velit incididunt mollit + * @return TestClass + */ public function testMethod($testParameter) { $testVariable = 123; @@ -31,6 +71,11 @@ interface TestInterface } +/** + * Officia aliquip adipisicing et nulla et laboris dolore labore. + * + * @return void + */ function test_function() { diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 21c7d2b..2d74db7 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -107,6 +107,8 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher $serverCapabilities->definitionProvider = true; // Support "Find all references" $serverCapabilities->referencesProvider = true; + // Support "Hover" + $serverCapabilities->hoverProvider = true; return new InitializeResult($serverCapabilities); } diff --git a/src/NodeVisitor/DocBlockParser.php b/src/NodeVisitor/DocBlockParser.php new file mode 100644 index 0000000..6a4c77a --- /dev/null +++ b/src/NodeVisitor/DocBlockParser.php @@ -0,0 +1,84 @@ +docBlockFactory = $docBlockFactory; + } + + public function beforeTraverse(array $nodes) + { + $this->namespace = ''; + $this->aliases = []; + } + + public function enterNode(Node $node) + { + if ($node instanceof Node\Stmt\Namespace_) { + $this->namespace = (string)$node->name; + } else if ($node instanceof Node\Stmt\UseUse) { + $this->aliases[(string)$node->name] = $node->alias; + } + $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 = []; + } + } +} diff --git a/src/PhpDocument.php b/src/PhpDocument.php index fe9a1a2..e024461 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -7,6 +7,7 @@ use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, Te use LanguageServer\NodeVisitor\{ NodeAtPositionFinder, ReferencesAdder, + DocBlockParser, DefinitionCollector, ColumnCalculator, ReferencesCollector, @@ -14,6 +15,7 @@ use LanguageServer\NodeVisitor\{ }; use PhpParser\{Error, Node, NodeTraverser, Parser}; use PhpParser\NodeVisitor\NameResolver; +use phpDocumentor\Reflection\DocBlockFactory; class PhpDocument { @@ -40,6 +42,13 @@ class PhpDocument */ private $parser; + /** + * The DocBlockFactory instance to parse docblocks + * + * @var DocBlockFactory + */ + private $docBlockFactory; + /** * The URI of the document * @@ -76,18 +85,20 @@ class PhpDocument private $references; /** - * @param string $uri The URI of the document - * @param string $content The content of the document - * @param Project $project The Project this document belongs to (to register definitions etc) - * @param LanguageClient $client The LanguageClient instance (to report errors etc) - * @param Parser $parser The PHPParser instance + * @param string $uri The URI of the document + * @param string $content The content of the document + * @param Project $project The Project this document belongs to (to register definitions etc) + * @param LanguageClient $client The LanguageClient instance (to report errors etc) + * @param Parser $parser The PHPParser instance + * @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks */ - public function __construct(string $uri, string $content, Project $project, LanguageClient $client, Parser $parser) + public function __construct(string $uri, string $content, Project $project, LanguageClient $client, Parser $parser, DocBlockFactory $docBlockFactory) { $this->uri = $uri; $this->project = $project; $this->client = $client; $this->parser = $parser; + $this->docBlockFactory = $docBlockFactory; $this->updateContent($content); } @@ -127,19 +138,8 @@ class PhpDocument $diagnostics = []; foreach ($errors as $error) { - $diagnostic = new Diagnostic(); - $startLine = max($error->getStartLine() - 1, 0); - $startColumn = $error->hasColumnInfo() ? $error->getStartColumn($this->content) - 1 : 0; - $endLine = max($error->getEndLine() - 1, $startLine); - $endColumn = $error->hasColumnInfo() ? $error->getEndColumn($this->content) : 0; - $diagnostic->range = new Range(new Position($startLine, $startColumn), new Position($endLine, $endColumn)); - $diagnostic->severity = DiagnosticSeverity::ERROR; - $diagnostic->source = 'php'; - // Do not include "on line ..." in the error message - $diagnostic->message = $error->getRawMessage(); - $diagnostics[] = $diagnostic; + $diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php'); } - $this->client->textDocument->publishDiagnostics($this->uri, $diagnostics); // $stmts can be null in case of a fatal parsing error if ($stmts) { @@ -154,7 +154,17 @@ class PhpDocument // 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) { + $diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php'); + } + $traverser = new NodeTraverser; // Collect all definitions @@ -181,6 +191,8 @@ class PhpDocument $this->stmts = $stmts; } + + $this->client->textDocument->publishDiagnostics($this->uri, $diagnostics); } /** diff --git a/src/Project.php b/src/Project.php index 06b3a08..5e3b5d1 100644 --- a/src/Project.php +++ b/src/Project.php @@ -3,6 +3,7 @@ declare(strict_types = 1); namespace LanguageServer; +use phpDocumentor\Reflection\DocBlockFactory; use PhpParser\{ParserFactory, Lexer}; class Project @@ -36,6 +37,13 @@ class Project */ private $parser; + /** + * The DocBlockFactory instance to parse docblocks + * + * @var DocBlockFactory + */ + private $docBlockFactory; + /** * Reference to the language server client interface * @@ -49,6 +57,7 @@ class Project $lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos']]); $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, ['throwOnError' => false]); + $this->docBlockFactory = DocBlockFactory::createInstance(); } /** @@ -81,7 +90,7 @@ class Project $document = $this->documents[$uri]; $document->updateContent($content); } else { - $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser); + $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory); } return $document; } @@ -99,7 +108,7 @@ class Project $document = $this->documents[$uri]; $document->updateContent($content); } else { - $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser); + $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory); $this->documents[$uri] = $document; } return $document; diff --git a/src/Protocol/Diagnostic.php b/src/Protocol/Diagnostic.php index b65f288..44a24c1 100644 --- a/src/Protocol/Diagnostic.php +++ b/src/Protocol/Diagnostic.php @@ -2,6 +2,8 @@ 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. @@ -44,4 +46,40 @@ class Diagnostic * @var string */ 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 + * @param int $code The diagnostic's code + * @param int $severity DiagnosticSeverity + * @param string $source A human-readable string describing the source of this diagnostic + */ + public function __construct(string $message = null, Range $range = null, int $code = null, int $severity = null, string $source = null) + { + $this->message = $message; + $this->range = $range; + $this->code = $code; + $this->severity = $severity; + $this->source = $source; + } } diff --git a/src/Protocol/Hover.php b/src/Protocol/Hover.php index cde5d37..6e7656e 100644 --- a/src/Protocol/Hover.php +++ b/src/Protocol/Hover.php @@ -10,7 +10,7 @@ class Hover /** * The hover's content * - * @var string|string[]|MarkedString|MarkedString[] + * @var string|MarkedString|string[]|MarkedString[] */ public $contents; @@ -20,4 +20,14 @@ class Hover * @var Range|null */ public $range; + + /** + * @param string|MarkedString|string[]|MarkedString[] $contents The hover's content + * @param Range $range An optional range + */ + public function __construct($contents = null, $range = null) + { + $this->contents = $contents; + $this->range = $range; + } } diff --git a/src/Protocol/MarkedString.php b/src/Protocol/MarkedString.php new file mode 100644 index 0000000..0799f14 --- /dev/null +++ b/src/Protocol/MarkedString.php @@ -0,0 +1,22 @@ +language = $language; + $this->value = $value; + } +} diff --git a/src/Protocol/Range.php b/src/Protocol/Range.php index 17f31da..e4fe527 100644 --- a/src/Protocol/Range.php +++ b/src/Protocol/Range.php @@ -2,7 +2,7 @@ namespace LanguageServer\Protocol; -use PhpParser\Node; +use PhpParser\{Error, Node}; /** * A range in a text document expressed as (zero-based) start and end positions. @@ -37,6 +37,22 @@ class Range ); } + /** + * 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)); + } + public function __construct(Position $start = null, Position $end = null) { $this->start = $start; diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 3d14730..e515e43 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -4,16 +4,21 @@ declare(strict_types = 1); namespace LanguageServer\Server; use LanguageServer\{LanguageClient, Project}; +use PhpParser\PrettyPrinter\Standard as PrettyPrinter; +use PhpParser\Node; use LanguageServer\Protocol\{ TextDocumentItem, TextDocumentIdentifier, VersionedTextDocumentIdentifier, Position, + Range, FormattingOptions, TextEdit, Location, SymbolInformation, - ReferenceContext + ReferenceContext, + Hover, + MarkedString }; /** @@ -33,10 +38,16 @@ class TextDocument */ private $project; + /** + * @var PrettyPrinter + */ + private $prettyPrinter; + public function __construct(Project $project, LanguageClient $client) { $this->project = $project; $this->client = $client; + $this->prettyPrinter = new PrettyPrinter(); } /** @@ -148,4 +159,72 @@ class TextDocument } return Location::fromNode($def); } + + /** + * The hover request is sent from the client to the server to request hover information at a given text document position. + * + * @param TextDocumentIdentifier $textDocument The text document + * @param Position $position The position inside the text document + * @return Hover + */ + public function hover(TextDocumentIdentifier $textDocument, Position $position): Hover + { + $document = $this->project->getDocument($textDocument->uri); + // Find the node under the cursor + $node = $document->getNodeAtPosition($position); + if ($node === null) { + return new Hover([]); + } + $range = Range::fromNode($node); + // Get the definition node for whatever node is under the cursor + $def = $document->getDefinitionByNode($node); + if ($def === null) { + return new Hover([], $range); + } + $contents = []; + + // Build a declaration string + if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) { + // Properties and constants can have multiple declarations + // Use the parent node (that includes the modifiers), but only render the requested declaration + $child = $def; + $def = $def->getAttribute('parentNode'); + $defLine = clone $def; + $defLine->props = [$child]; + } else { + $defLine = clone $def; + } + // Don't include the docblock in the declaration string + $defLine->setAttribute('comments', []); + if (isset($defLine->stmts)) { + $defLine->stmts = []; + } + $defText = $this->prettyPrinter->prettyPrint([$defLine]); + $lines = explode("\n", $defText); + if (isset($lines[0])) { + $contents[] = new MarkedString('php', "getAttribute('parentNode'); + $docBlock = $fn->getAttribute('docBlock'); + if ($docBlock !== null) { + $tags = $docBlock->getTagsByName('param'); + foreach ($tags as $tag) { + if ($tag->getVariableName() === $def->name) { + $contents[] = $tag->getDescription()->render(); + break; + } + } + } + } else { + $docBlock = $def->getAttribute('docBlock'); + if ($docBlock !== null) { + $contents[] = $docBlock->getSummary(); + } + } + + return new Hover($contents, $range); + } } diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 06742d8..d08ed56 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -31,7 +31,7 @@ class LanguageServerTest extends TestCase 'capabilities' => (object)[ 'textDocumentSync' => TextDocumentSyncKind::FULL, 'documentSymbolProvider' => true, - 'hoverProvider' => null, + 'hoverProvider' => true, 'completionProvider' => null, 'signatureHelpProvider' => null, 'definitionProvider' => true, diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 011fa33..47981b9 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -62,49 +62,49 @@ abstract class ServerTestCase extends TestCase $this->definitionLocations = [ // Global - 'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 4, 6), new Position(4, 22))), - 'TestClass' => new Location($globalSymbolsUri, new Range(new Position( 6, 0), new Position(21, 1))), - 'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(23, 0), new Position(26, 1))), - 'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(28, 0), new Position(31, 1))), - 'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position( 8, 10), new Position(8, 32))), - 'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(10, 11), new Position(10, 24))), - 'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position( 9, 18), new Position(9, 37))), - 'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(12, 4), new Position(15, 5))), - 'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(17, 4), new Position(20, 5))), - 'test_function()' => new Location($globalSymbolsUri, new Range(new Position(33, 0), new Position(36, 1))), - 'whatever()' => new Location($globalReferencesUri, new Range(new Position(15, 0), new Position(17, 1))), + 'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))), + 'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))), + 'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))), + 'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))), + 'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))), + 'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(41, 11), new Position(41, 24))), + 'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position(34, 18), new Position(34, 37))), + 'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(46, 4), new Position(49, 5))), + 'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))), + 'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))), + 'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))), // Namespaced - 'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 4, 6), new Position(4, 22))), - 'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position( 6, 0), new Position(21, 1))), - 'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(28, 0), new Position(31, 1))), - 'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(23, 0), new Position(26, 1))), - 'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position( 8, 10), new Position(8, 32))), - 'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(10, 11), new Position(10, 24))), - 'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position( 9, 18), new Position(9, 37))), - 'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(12, 4), new Position(15, 5))), - 'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(17, 4), new Position(20, 5))), - 'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(33, 0), new Position(36, 1))), - 'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(15, 0), new Position(17, 1))) + '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\\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))), + 'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))), + 'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))), + 'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))), + 'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))) ]; $this->referenceLocations = [ // Namespaced 'TestNamespace\\TEST_CONST' => [ - 0 => new Location($referencesUri, new Range(new Position(23, 5), new Position(23, 15))) + 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))) ], 'TestNamespace\\TestClass' => [ 0 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); 1 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); 2 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; 3 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; - 4 => new Location($referencesUri, new Range(new Position(15, 18), new Position(15, 27))), // function whatever(TestClass $param) - 5 => new Location($referencesUri, new Range(new Position(15, 37), new Position(15, 46))), // function whatever(TestClass $param): TestClass + 4 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) + 5 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass 6 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; ], 'TestNamespace\\TestInterface' => [ - 0 => new Location($symbolsUri, new Range(new Position( 6, 27), new Position( 6, 40))) // class TestClass implements TestInterface + 0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))) // class TestClass implements TestInterface ], 'TestNamespace\\TestClass::TEST_CLASS_CONST' => [ 0 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 32))) @@ -127,19 +127,19 @@ abstract class ServerTestCase extends TestCase // Global 'TEST_CONST' => [ - 0 => new Location($referencesUri, new Range(new Position(23, 5), new Position(23, 15))), - 1 => new Location($globalReferencesUri, new Range(new Position(23, 5), new Position(23, 15))) + 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))), + 1 => new Location($globalReferencesUri, new Range(new Position(29, 5), new Position(29, 15))) ], 'TestClass' => [ 0 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); 1 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); 2 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; 3 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; - 4 => new Location($globalReferencesUri, new Range(new Position(15, 18), new Position(15, 27))), // function whatever(TestClass $param) - 5 => new Location($globalReferencesUri, new Range(new Position(15, 37), new Position(15, 46))), // function whatever(TestClass $param): TestClass + 4 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) + 5 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass ], 'TestInterface' => [ - 0 => new Location($globalSymbolsUri, new Range(new Position( 6, 27), new Position( 6, 40))) // class TestClass implements TestInterface + 0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))) // class TestClass implements TestInterface ], 'TestClass::TEST_CLASS_CONST' => [ 0 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 32))) diff --git a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php index ef3fc9a..4f18473 100644 --- a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php @@ -32,7 +32,7 @@ class GlobalFallbackTest extends ServerTestCase // echo TEST_CONST; // Get definition for TEST_CONST $result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(6, 10)); - $this->assertEquals(new Location('global_symbols', new Range(new Position(4, 6), new Position(4, 22))), $result); + $this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result); } public function testFallsBackForFunctions() @@ -40,6 +40,6 @@ class GlobalFallbackTest extends ServerTestCase // test_function(); // Get definition for test_function $result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(5, 6)); - $this->assertEquals(new Location('global_symbols', new Range(new Position(33, 0), new Position(36, 1))), $result); + $this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result); } } diff --git a/tests/Server/TextDocument/Definition/GlobalTest.php b/tests/Server/TextDocument/Definition/GlobalTest.php index 8b80abe..5c854e2 100644 --- a/tests/Server/TextDocument/Definition/GlobalTest.php +++ b/tests/Server/TextDocument/Definition/GlobalTest.php @@ -32,7 +32,7 @@ class GlobalTest extends ServerTestCase public function testDefinitionForClassOnStaticMethodCall() { - // $obj = new TestClass(); + // TestClass::staticTestMethod(); // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[1]; $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); @@ -152,8 +152,8 @@ class GlobalTest extends ServerTestCase // echo $param; // Get definition for $param $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(16, 13)); - $this->assertEquals(new Location($uri, new Range(new Position(15, 18), new Position(15, 34))), $result); + $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(22, 13)); + $this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result); } public function testDefinitionForUsedVariables() @@ -161,8 +161,8 @@ class GlobalTest extends ServerTestCase // echo $var; // Get definition for $var $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(20, 11)); - $this->assertEquals(new Location($uri, new Range(new Position(19, 22), new Position(19, 26))), $result); + $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(26, 11)); + $this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result); } public function testDefinitionForFunctions() diff --git a/tests/Server/TextDocument/HoverTest.php b/tests/Server/TextDocument/HoverTest.php new file mode 100644 index 0000000..ad7d3cf --- /dev/null +++ b/tests/Server/TextDocument/HoverTest.php @@ -0,0 +1,136 @@ +getReferenceLocations('TestClass')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForMethod() + { + // $obj->testMethod(); + // Get hover for testMethod + $reference = $this->getReferenceLocations('TestClass::testMethod()')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForProperty() + { + // echo $obj->testProperty; + // Get hover for testProperty + $reference = $this->getReferenceLocations('TestClass::testProperty')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForStaticMethod() + { + // TestClass::staticTestMethod(); + // Get hover for staticTestMethod + $reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForStaticProperty() + { + // echo TestClass::staticTestProperty; + // Get hover for staticTestProperty + $reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForClassConstant() + { + // echo TestClass::TEST_CLASS_CONST; + // Get hover for TEST_CLASS_CONST + $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForFunction() + { + // test_function(); + // Get hover for test_function + $reference = $this->getReferenceLocations('test_function()')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForConstant() + { + // echo TEST_CONST; + // Get hover for TEST_CONST + $reference = $this->getReferenceLocations('TEST_CONST')[0]; + $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + + public function testHoverForVariable() + { + // echo $var; + // Get hover for $var + $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); + $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7)); + $this->assertEquals(new Hover( + [new MarkedString('php', "textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11)); + $this->assertEquals(new Hover( + [ + new MarkedString('php', "textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(4, 13)); + $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(9, 13)); $this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result); } @@ -40,7 +40,7 @@ class GlobalFallbackTest extends ServerTestCase { // function test_function() // Get references for test_function - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(33, 16)); + $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(78, 16)); $this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result); } } diff --git a/tests/Server/TextDocument/References/GlobalTest.php b/tests/Server/TextDocument/References/GlobalTest.php index ab074e3..284c32b 100644 --- a/tests/Server/TextDocument/References/GlobalTest.php +++ b/tests/Server/TextDocument/References/GlobalTest.php @@ -77,11 +77,11 @@ class GlobalTest extends ServerTestCase // $var = 123; // Get definition for $var $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(13, 7)); + $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(12, 3)); $this->assertEquals([ new Location($uri, new Range(new Position(12, 0), new Position(12, 4))), new Location($uri, new Range(new Position(13, 5), new Position(13, 9))), - new Location($uri, new Range(new Position(20, 9), new Position(20, 13))) + new Location($uri, new Range(new Position(26, 9), new Position(26, 13))) ], $result); } @@ -90,8 +90,8 @@ class GlobalTest extends ServerTestCase // function whatever(TestClass $param): TestClass // Get references for $param $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(15, 32)); - $this->assertEquals([new Location($uri, new Range(new Position(16, 9), new Position(16, 15)))], $result); + $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(21, 32)); + $this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result); } public function testReferencesForFunctions() @@ -100,7 +100,7 @@ class GlobalTest extends ServerTestCase // Get references for test_function $referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($symbolsUri), new Position(33, 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); } }