Skip to content

Commit db28e22

Browse files
committed
Publish errors as diagnostics, improve tests
1 parent 57604e6 commit db28e22

11 files changed

+408
-10
lines changed

fixtures/InvalidFile.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
interface class
4+
{
5+
6+
}

fixtures/Symbols.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace TestNamespace;
4+
5+
class TestClass
6+
{
7+
public $testProperty;
8+
9+
public function testMethod($testParameter)
10+
{
11+
$testVariable = 123;
12+
}
13+
}
14+
15+
trait TestTrait
16+
{
17+
18+
}
19+
20+
interface TestInterface
21+
{
22+
23+
}

src/Client/TextDocument.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace LanguageServer\Client;
5+
6+
use AdvancedJsonRpc\Notification as NotificationBody;
7+
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer};
8+
use PhpParser\NodeVisitor\NameResolver;
9+
use LanguageServer\ProtocolWriter;
10+
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, VersionedTextDocumentIdentifier, Message};
11+
12+
/**
13+
* Provides method handlers for all textDocument/* methods
14+
*/
15+
class TextDocument
16+
{
17+
/**
18+
* @var ProtocolWriter
19+
*/
20+
private $protocolWriter;
21+
22+
public function __construct(ProtocolWriter $protocolWriter)
23+
{
24+
$this->protocolWriter = $protocolWriter;
25+
}
26+
27+
/**
28+
* Diagnostics notification are sent from the server to the client to signal results of validation runs.
29+
*
30+
* @param string $uri
31+
* @param Diagnostic[] $diagnostics
32+
*/
33+
public function publishDiagnostics(string $uri, array $diagnostics)
34+
{
35+
$this->protocolWriter->write(new Message(new NotificationBody(
36+
'textDocument/publishDiagnostics',
37+
(object)[
38+
'uri' => $uri,
39+
'diagnostics' => $diagnostics
40+
]
41+
)));
42+
}
43+
}

src/LanguageClient.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace LanguageServer;
5+
6+
use LanguageServer\Client\TextDocument;
7+
8+
class LanguageClient
9+
{
10+
/**
11+
* Handles textDocument/* methods
12+
*
13+
* @var Client\TextDocument
14+
*/
15+
public $textDocument;
16+
17+
private $protocolWriter;
18+
19+
public function __construct(ProtocolWriter $writer)
20+
{
21+
$this->protocolWriter = $writer;
22+
$this->textDocument = new TextDocument($writer);
23+
}
24+
}

src/LanguageServer.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22

33
namespace LanguageServer;
44

5+
use LanguageServer\Server\TextDocument;
56
use LanguageServer\Protocol\{ServerCapabilities, ClientCapabilities, TextDocumentSyncKind, Message};
67
use LanguageServer\Protocol\InitializeResult;
78
use AdvancedJsonRpc\{Dispatcher, ResponseError, Response as ResponseBody, Request as RequestBody};
89

910
class LanguageServer extends \AdvancedJsonRpc\Dispatcher
1011
{
12+
/**
13+
* Handles textDocument/* method calls
14+
*
15+
* @var Server\TextDocument
16+
*/
1117
public $textDocument;
18+
1219
public $telemetry;
1320
public $window;
1421
public $workspace;
@@ -17,6 +24,7 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher
1724

1825
private $protocolReader;
1926
private $protocolWriter;
27+
private $client;
2028

2129
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
2230
{
@@ -47,7 +55,8 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
4755
}
4856
});
4957
$this->protocolWriter = $writer;
50-
$this->textDocument = new TextDocumentManager();
58+
$this->client = new LanguageClient($writer);
59+
$this->textDocument = new Server\TextDocument($this->client);
5160
}
5261

5362
/**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace LanguageServer\Protocol;
4+
5+
use AdvancedJsonRpc\Notification;
6+
7+
/**
8+
* Diagnostics notification are sent from the server to the client to signal results of validation runs.
9+
*/
10+
class PublishDiagnosticsNotification extends Notification
11+
{
12+
/**
13+
* @var PublishDiagnosticsParams
14+
*/
15+
public $params;
16+
17+
/**
18+
* @param string $uri
19+
* @param Diagnostic[] $diagnostics
20+
*/
21+
public function __construct(string $uri, array $diagnostics)
22+
{
23+
$this->method = 'textDocument/publishDiagnostics';
24+
$this->params = $params;
25+
}
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace LanguageServer\Protocol\TextDocument;
4+
5+
use LanguageServer\Protocol\Params;
6+
7+
class PublishDiagnosticsParams extends Params
8+
{
9+
/**
10+
* The URI for which diagnostic information is reported.
11+
*
12+
* @var string
13+
*/
14+
public $uri;
15+
16+
/**
17+
* An array of diagnostic information items.
18+
*
19+
* @var LanguageServer\Protocol\Diagnostic[]
20+
*/
21+
public $diagnostics;
22+
}

src/Protocol/TextDocumentIdentifier.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,12 @@ class TextDocumentIdentifier
1010
* @var string
1111
*/
1212
public $uri;
13+
14+
/**
15+
* @param string $uri The text document's URI.
16+
*/
17+
public function __construct(string $uri = null)
18+
{
19+
$this->uri = $uri;
20+
}
1321
}

src/TextDocumentManager.php renamed to src/Server/TextDocument.php

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
<?php
22

3-
namespace LanguageServer;
3+
namespace LanguageServer\Server;
44

55
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer};
66
use PhpParser\NodeVisitor\NameResolver;
7-
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, VersionedTextDocumentIdentifier};
7+
use LanguageServer\{LanguageClient, ColumnCalculator, SymbolFinder};
8+
use LanguageServer\Protocol\{
9+
TextDocumentItem,
10+
TextDocumentIdentifier,
11+
VersionedTextDocumentIdentifier,
12+
Diagnostic,
13+
DiagnosticSeverity,
14+
Range,
15+
Position
16+
};
817

918
/**
1019
* Provides method handlers for all textDocument/* methods
1120
*/
12-
class TextDocumentManager
21+
class TextDocument
1322
{
1423
/**
1524
* @var PhpParser\Parser
@@ -23,8 +32,16 @@ class TextDocumentManager
2332
*/
2433
private $asts;
2534

26-
public function __construct()
35+
/**
36+
* The lanugage client object to call methods on the client
37+
*
38+
* @var LanguageServer\LanguageClient
39+
*/
40+
private $client;
41+
42+
public function __construct(LanguageClient $client)
2743
{
44+
$this->client = $client;
2845
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos']]);
2946
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, ['throwOnError' => false]);
3047
}
@@ -74,20 +91,39 @@ public function didChange(VersionedTextDocumentIdentifier $textDocument, array $
7491
$this->updateAst($textDocument->uri, $contentChanges[0]->text);
7592
}
7693

94+
/**
95+
* Re-parses a source file, updates the AST and reports parsing errors that may occured as diagnostics
96+
*
97+
* @param string $uri The URI of the source file
98+
* @param string $content The new content of the source file
99+
* @return void
100+
*/
77101
private function updateAst(string $uri, string $content)
78102
{
79103
$stmts = $this->parser->parse($content);
80-
// TODO report errors as diagnostics
81-
// foreach ($parser->getErrors() as $error) {
82-
// error_log($error->getMessage());
83-
// }
104+
$diagnostics = [];
105+
foreach ($this->parser->getErrors() as $error) {
106+
$diagnostic = new Diagnostic();
107+
$diagnostic->range = new Range(
108+
new Position($error->getStartLine() - 1, $error->hasColumnInfo() ? $error->getStartColumn($content) - 1 : 0),
109+
new Position($error->getEndLine() - 1, $error->hasColumnInfo() ? $error->getEndColumn($content) - 1 : 0)
110+
);
111+
$diagnostic->severity = DiagnosticSeverity::ERROR;
112+
$diagnostic->source = 'php';
113+
// Do not include "on line ..." in the error message
114+
$diagnostic->message = $error->getRawMessage();
115+
$diagnostics[] = $diagnostic;
116+
}
117+
if (count($diagnostics) > 0) {
118+
$this->client->textDocument->publishDiagnostics($uri, $diagnostics);
119+
}
84120
// $stmts can be null in case of a fatal parsing error
85121
if ($stmts) {
86122
$traverser = new NodeTraverser;
87123
$traverser->addVisitor(new NameResolver);
88124
$traverser->addVisitor(new ColumnCalculator($content));
89125
$traverser->traverse($stmts);
126+
$this->asts[$uri] = $stmts;
90127
}
91-
$this->asts[$uri] = $stmts;
92128
}
93129
}

src/SymbolFinder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class SymbolFinder extends NodeVisitorAbstract
1010
{
1111
const NODE_SYMBOL_KIND_MAP = [
1212
Node\Stmt\Class_::class => SymbolKind::CLASS_,
13+
Node\Stmt\Trait_::class => SymbolKind::CLASS_,
1314
Node\Stmt\Interface_::class => SymbolKind::INTERFACE,
1415
Node\Stmt\Namespace_::class => SymbolKind::NAMESPACE,
1516
Node\Stmt\Function_::class => SymbolKind::FUNCTION,

0 commit comments

Comments
 (0)