Skip to content

Commit 6e7c71d

Browse files
authored
phpactorgh-2761: introduce tolerant code action provider (phpactor#2767)
if a single code action provider fails then show an error message to the user and continue to process other code actions.
1 parent 0176e56 commit 6e7c71d

File tree

5 files changed

+88
-4
lines changed

5 files changed

+88
-4
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
## master
5+
6+
Improvements:
7+
8+
- Tolerate code action provider failures #2761 @dantleech
9+
410
## 2024-11-05
511

612
Bug fixes:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Phpactor\Extension\LanguageServer\CodeAction;
4+
5+
use Amp\CancellationToken;
6+
use Amp\Promise;
7+
use Phpactor\LanguageServerProtocol\Range;
8+
use Phpactor\LanguageServerProtocol\TextDocumentItem;
9+
use Phpactor\LanguageServer\Core\CodeAction\CodeActionProvider;
10+
use Phpactor\LanguageServer\Core\Server\ClientApi;
11+
use function Amp\call;
12+
use Throwable;
13+
14+
final class TolerantCodeActionProvider implements CodeActionProvider
15+
{
16+
public function __construct(private CodeActionProvider $provider, private ClientApi $client)
17+
{
18+
}
19+
20+
public function provideActionsFor(TextDocumentItem $textDocument, Range $range, CancellationToken $cancel): Promise
21+
{
22+
return call(function () use ($textDocument, $range, $cancel) {
23+
try {
24+
return yield $this->provider->provideActionsFor($textDocument, $range, $cancel);
25+
} catch (Throwable $error) {
26+
$this->client->window()->showMessage()->error(sprintf(
27+
'Provider %s (%s) failed: %s',
28+
$this->provider::class,
29+
$this->provider->describe(),
30+
$error->getMessage(),
31+
));
32+
return [];
33+
}
34+
});
35+
}
36+
37+
public function kinds(): array
38+
{
39+
return $this->provider->kinds();
40+
}
41+
42+
public function describe(): string
43+
{
44+
return $this->provider->describe();
45+
}
46+
}

lib/Extension/LanguageServer/LanguageServerExtension.php

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Phpactor\Extension\FilePathResolver\FilePathResolverExtension;
1111
use Phpactor\Extension\LanguageServerWorseReflection\Workspace\WorkspaceIndex;
1212
use Phpactor\Extension\LanguageServer\CodeAction\ProfilingCodeActionProvider;
13+
use Phpactor\Extension\LanguageServer\CodeAction\TolerantCodeActionProvider;
1314
use Phpactor\Extension\LanguageServer\Command\DiagnosticsCommand;
1415
use Phpactor\Extension\LanguageServer\DiagnosticProvider\OutsourcedDiagnosticsProvider;
1516
use Phpactor\Extension\LanguageServer\DiagnosticProvider\PathExcludingDiagnosticsProvider;
@@ -454,6 +455,9 @@ private function registerHandlers(ContainerBuilder $container): void
454455

455456
$container->register(CodeActionHandler::class, function (Container $container) {
456457
$services = $this->taggedServices($container, self::TAG_CODE_ACTION_PROVIDER, CodeActionProvider::class);
458+
$services = array_map(function (CodeActionProvider $provider) use ($container) {
459+
return new TolerantCodeActionProvider($provider, $container->get(ClientApi::class));
460+
}, $services);
457461
if ($container->parameter(self::PARAM_PROFILE)->bool()) {
458462
$services = array_map(
459463
fn (CodeActionProvider $provider) => new ProfilingCodeActionProvider(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Phpactor\Extension\LanguageServer\Tests\Unit\DiagnosticsProvider;
4+
5+
use Amp\CancellationTokenSource;
6+
use PHPUnit\Framework\TestCase;
7+
use Phpactor\Extension\LanguageServer\CodeAction\TolerantCodeActionProvider;
8+
use Phpactor\LanguageServer\Core\CodeAction\CodeActionProvider;
9+
use Phpactor\LanguageServer\Core\Rpc\NotificationMessage;
10+
use Phpactor\LanguageServer\LanguageServerTesterBuilder;
11+
use Phpactor\LanguageServer\Test\ProtocolFactory;
12+
use Exception;
13+
14+
class TolerantCodeActionProviderTest extends TestCase
15+
{
16+
public function testShowErrorWhenProviderFails(): void
17+
{
18+
$provider = $this->createMock(CodeActionProvider::class);
19+
$provider->method('provideActionsFor')->willThrowException(new Exception('Oh no!'));
20+
$tester = LanguageServerTesterBuilder::create();
21+
(new TolerantCodeActionProvider($provider, $tester->clientApi()))->provideActionsFor(
22+
ProtocolFactory::textDocumentItem('', ''),
23+
ProtocolFactory::range(1, 1, 1, 1),
24+
(new CancellationTokenSource())->getToken(),
25+
);
26+
$message = $tester->transmitter()->shift();
27+
self::assertInstanceOf(NotificationMessage::class, $message);
28+
$message = $message->params['message'] ?? null;
29+
self::assertIsString($message);
30+
self::assertStringContainsString('failed: Oh no!', $message);
31+
}
32+
}

lib/WorseReflection/Tests/Benchmarks/CarbonReflectBench.php

-4
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
namespace Phpactor\WorseReflection\Tests\Benchmarks;
44

5-
use PHPUnit\Framework\TestCase;
65
use Phpactor\TextDocument\TextDocumentBuilder;
7-
use Phpactor\WorseReflection\Core\ClassName;
86

97
/**
108
* @Iterations(5)
119
* @Revs(1)
1210
*/
1311
class CarbonReflectBench extends BaseBenchCase
1412
{
15-
/**
16-
*/
1713
public function benchCarbonReflection(): void
1814
{
1915
$classes = $this->getReflector()->reflectClassesIn(TextDocumentBuilder::fromUri(__DIR__ . '/fixtures/reflection/carbon.test')->build());

0 commit comments

Comments
 (0)