Skip to content

Commit b0226f9

Browse files
committed
Add support ofr diagnostics service
1 parent e253343 commit b0226f9

12 files changed

+429
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Core\Diagnostics;
4+
5+
use Amp\Promise;
6+
use Amp\Success;
7+
use Closure;
8+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
9+
10+
class ClosureDiagnosticsProvider implements DiagnosticsProvider
11+
{
12+
/**
13+
* @var Closure
14+
*/
15+
private $closure;
16+
17+
public function __construct(Closure $closure)
18+
{
19+
$this->closure = $closure;
20+
}
21+
22+
/**
23+
* {@inheritDoc}
24+
*/
25+
public function provideDiagnostics(TextDocumentItem $textDocument): Promise
26+
{
27+
$closure = $this->closure;
28+
29+
return $closure($textDocument);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Core\Diagnostics;
4+
5+
use Generator;
6+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
7+
8+
class CodeActionDiagnosticsProvider implements DiagnosticsProvider
9+
{
10+
public function provideDiagnosticsFor(TextDocumentItem $textDocument): Generator
11+
{
12+
}
13+
}
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Core\Diagnostics;
4+
5+
use Amp\CancelledException;
6+
use Phpactor\LanguageServer\Core\Server\ClientApi;
7+
use Phpactor\LanguageServer\Event\TextDocumentSaved;
8+
use Phpactor\LanguageServer\Event\TextDocumentUpdated;
9+
use Phpactor\LanguageServer\Core\Rpc\NotificationMessage;
10+
use Amp\Promise;
11+
use Amp\CancellationToken;
12+
use Amp\Deferred;
13+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
14+
15+
class DiagnosticsEngine
16+
{
17+
/**
18+
* @var int
19+
*/
20+
private $pollTime;
21+
22+
/**
23+
* @var Deferred<TextDocumentItem>
24+
*/
25+
private $deferred;
26+
27+
/**
28+
* @var bool
29+
*/
30+
private $running = false;
31+
32+
/**
33+
* @var ?TextDocumentItem
34+
*/
35+
private $next;
36+
37+
/**
38+
* @var DiagnosticsProvider
39+
*/
40+
private $provider;
41+
42+
/**
43+
* @var ClientApi
44+
*/
45+
private $clientApi;
46+
47+
public function __construct(ClientApi $clientApi, DiagnosticsProvider $provider, int $pollTime = 100)
48+
{
49+
$this->pollTime = $pollTime;
50+
$this->deferred = new Deferred();
51+
$this->provider = $provider;
52+
$this->clientApi = $clientApi;
53+
}
54+
55+
/**
56+
* @return Promise<bool>
57+
*/
58+
public function run(CancellationToken $token): Promise
59+
{
60+
return \Amp\call(function () use ($token) {
61+
while (true) {
62+
try {
63+
$token->throwIfRequested();
64+
} catch (CancelledException $cancelled) {
65+
return;
66+
}
67+
68+
// if another update came in while doing the previous lint use
69+
// use that.
70+
if ($this->next) {
71+
$textDocument = $this->next;
72+
$this->next = null;
73+
} else {
74+
$textDocument = yield $this->deferred->promise();
75+
}
76+
77+
$this->deferred = new Deferred();
78+
79+
// after we have reset deferred, we can safely set linting to
80+
// `false` and let another resolve happen
81+
$this->running = false;
82+
83+
assert($textDocument instanceof TextDocumentItem);
84+
85+
$this->clientApi->diagnostics()->publishDiagnostics(
86+
$textDocument->uri,
87+
$textDocument->version,
88+
yield $this->provider->provideDiagnostics($textDocument)
89+
);
90+
}
91+
});
92+
}
93+
94+
public function enqueue(TextDocumentItem $textDocument): void
95+
{
96+
// if we are already linting then store whatever comes afterwards in
97+
// next, overwriting the redundant update
98+
if ($this->running === true) {
99+
$this->next = $textDocument;
100+
return;
101+
}
102+
103+
// resolving the promise will start PHPStan
104+
$this->running = true;
105+
$this->deferred->resolve($textDocument);
106+
}
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Core\Diagnostics;
4+
5+
use Amp\Promise;
6+
use Phpactor\LanguageServerProtocol\Diagnostic;
7+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
8+
use function Amp\call;
9+
10+
interface DiagnosticsProvider
11+
{
12+
/**
13+
* @return Promise<array<Diagnostic>>
14+
*/
15+
public function provideDiagnostics(TextDocumentItem $textDocument): Promise;
16+
}

lib/Event/TextDocumentSaved.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
namespace Phpactor\LanguageServer\Event;
44

55
use Phpactor\LanguageServerProtocol\TextDocumentIdentifier;
6+
use Phpactor\LanguageServerProtocol\VersionedTextDocumentIdentifier;
67

78
class TextDocumentSaved
89
{
910
/**
10-
* @var TextDocumentIdentifier
11+
* @var VersionedTextDocumentIdentifier
1112
*/
1213
private $identifier;
1314

@@ -16,13 +17,13 @@ class TextDocumentSaved
1617
*/
1718
private $text;
1819

19-
public function __construct(TextDocumentIdentifier $identifier, ?string $text = null)
20+
public function __construct(VersionedTextDocumentIdentifier $identifier, ?string $text = null)
2021
{
2122
$this->identifier = $identifier;
2223
$this->text = $text;
2324
}
2425

25-
public function identifier(): TextDocumentIdentifier
26+
public function identifier(): VersionedTextDocumentIdentifier
2627
{
2728
return $this->identifier;
2829
}

lib/LanguageServerTesterBuilder.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Phpactor\LanguageServer;
44

5+
use Amp\Loop;
56
use Phpactor\LanguageServerProtocol\ClientCapabilities;
67
use Phpactor\LanguageServerProtocol\InitializeParams;
78
use Phpactor\LanguageServer\Adapter\DTL\DTLArgumentResolver;
@@ -36,7 +37,9 @@
3637
use Phpactor\LanguageServer\Middleware\InitializeMiddleware;
3738
use Phpactor\LanguageServer\Test\LanguageServerTester;
3839
use Psr\EventDispatcher\EventDispatcherInterface;
40+
use Psr\EventDispatcher\ListenerProviderInterface;
3941
use Psr\Log\NullLogger;
42+
use Throwable;
4043

4144
final class LanguageServerTesterBuilder
4245
{
@@ -100,6 +103,11 @@ final class LanguageServerTesterBuilder
100103
*/
101104
private $enableServices = false;
102105

106+
/**
107+
* @var array<ListenerProviderInterface>
108+
*/
109+
private $listeners = [];
110+
103111
private function __construct()
104112
{
105113
$this->initializeParams = new InitializeParams(new ClientCapabilities());
@@ -160,6 +168,13 @@ public function addServiceProvider(ServiceProvider $serviceProvider): self
160168
return $this;
161169
}
162170

171+
public function addListenerProvider(ListenerProviderInterface $listenerProvider): self
172+
{
173+
$this->listeners[] = $listenerProvider;
174+
175+
return $this;
176+
}
177+
163178
/**
164179
* Add an command
165180
*/
@@ -272,7 +287,7 @@ function (MessageTransmitter $transmitter, InitializeParams $params) {
272287
}
273288
private function buildEventDispatcher(ServiceManager $serviceManager): EventDispatcherInterface
274289
{
275-
$listeners = [];
290+
$listeners = $this->listeners;
276291

277292
if ($this->enableServices) {
278293
$listeners[] = new ServiceListener($serviceManager);

lib/Service/DiagnosticsService.php

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Service;
4+
5+
use Amp\CancellationToken;
6+
use Amp\Promise;
7+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
8+
use Phpactor\LanguageServer\Core\Diagnostics\DiagnosticsEngine;
9+
use Phpactor\LanguageServer\Core\Service\ServiceProvider;
10+
use Phpactor\LanguageServer\Event\TextDocumentSaved;
11+
use Phpactor\LanguageServer\Event\TextDocumentUpdated;
12+
use Psr\EventDispatcher\ListenerProviderInterface;
13+
14+
class DiagnosticsService implements ServiceProvider, ListenerProviderInterface
15+
{
16+
/**
17+
* @var DiagnosticsEngine
18+
*/
19+
private $engine;
20+
21+
public function __construct(DiagnosticsEngine $engine)
22+
{
23+
$this->engine = $engine;
24+
}
25+
26+
/**
27+
* {@inheritDoc}
28+
*/
29+
public function services(): array
30+
{
31+
return [
32+
'diagnostics',
33+
];
34+
}
35+
36+
/**
37+
* @return Promise<bool>
38+
*/
39+
public function diagnostics(CancellationToken $cancellationToken): Promise
40+
{
41+
return $this->engine->run($cancellationToken);
42+
}
43+
44+
/**
45+
* {@inheritDoc}
46+
*/
47+
public function getListenersForEvent(object $event): iterable
48+
{
49+
if ($event instanceof TextDocumentUpdated) {
50+
yield [$this, 'enqueueUpdate'];
51+
}
52+
53+
if ($event instanceof TextDocumentSaved) {
54+
yield [$this, 'enqueueSaved'];
55+
}
56+
}
57+
58+
public function enqueueUpdate(TextDocumentUpdated $update): void
59+
{
60+
$item = new TextDocumentItem(
61+
$update->identifier()->uri,
62+
'php',
63+
$update->identifier()->version,
64+
$update->updatedText()
65+
);
66+
67+
$this->engine->enqueue($item);
68+
}
69+
70+
public function enqueueSaved(TextDocumentSaved $saved): void
71+
{
72+
$item = new TextDocumentItem(
73+
$saved->identifier()->uri,
74+
'php',
75+
$saved->identifier()->version,
76+
$saved->text()
77+
);
78+
79+
$this->engine->enqueue($item);
80+
}
81+
}

lib/Test/LanguageServerTester/TextDocumentTester.php

+14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Phpactor\LanguageServer\Test\LanguageServerTester;
44

5+
use Phpactor\LanguageServerProtocol\DidChangeTextDocumentNotification;
6+
use Phpactor\LanguageServerProtocol\DidChangeTextDocumentParams;
57
use Phpactor\LanguageServer\Test\ProtocolFactory;
68
use Phpactor\LanguageServerProtocol\DidOpenTextDocumentParams;
79
use Phpactor\LanguageServerProtocol\DidOpenTextDocumentNotification;
@@ -25,4 +27,16 @@ public function open(string $url, string $content): void
2527
ProtocolFactory::textDocumentItem($url, $content)
2628
));
2729
}
30+
31+
public function update(string $uri, string $newText): void
32+
{
33+
$this->tester->notifyAndWait(DidChangeTextDocumentNotification::METHOD, new DidChangeTextDocumentParams(
34+
ProtocolFactory::versionedTextDocumentIdentifier($uri, 1),
35+
[
36+
[
37+
'text' => $newText
38+
]
39+
]
40+
));
41+
}
2842
}

lib/Test/ProtocolFactory.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Phpactor\LanguageServer\Test;
44

55
use Phpactor\LanguageServerProtocol\ClientCapabilities;
6+
use Phpactor\LanguageServerProtocol\Diagnostic;
67
use Phpactor\LanguageServerProtocol\InitializeParams;
78
use Phpactor\LanguageServerProtocol\Position;
89
use Phpactor\LanguageServerProtocol\Range;
@@ -18,9 +19,9 @@ public static function textDocumentItem(string $uri, string $content): TextDocum
1819
return new TextDocumentItem($uri, 'php', 1, $content);
1920
}
2021

21-
public static function versionedTextDocumentIdentifier(string $string, ?int $version = null): VersionedTextDocumentIdentifier
22+
public static function versionedTextDocumentIdentifier(?string $uri = 'foobar', ?int $version = null): VersionedTextDocumentIdentifier
2223
{
23-
return new VersionedTextDocumentIdentifier('foobar');
24+
return new VersionedTextDocumentIdentifier($uri, $version);
2425
}
2526

2627
public static function textDocumentIdentifier(string $uri): TextDocumentIdentifier
@@ -53,4 +54,9 @@ public static function position(int $lineNb, int $colNb): Position
5354
{
5455
return new Position($lineNb, $colNb);
5556
}
57+
58+
public static function diagnostic(Range $range, string $message): Diagnostic
59+
{
60+
return new Diagnostic($range, $message);
61+
}
5662
}

0 commit comments

Comments
 (0)