Skip to content

Commit d25db46

Browse files
authored
Fix missing implements code action (phpactor#2668)
Consider the resolved name of the interface instead of `getText`
1 parent ac99b4d commit d25db46

File tree

6 files changed

+70
-4
lines changed

6 files changed

+70
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Bug fixes:
1717

1818
- Only filter new object expression names in contextual completion #2603
1919
- Fixing include and exclude patterns #2593 @mamazu
20+
- Fix missing @implements code action #2668 @dantleech
2021

2122
## 2024-03-09
2223

lib/CodeTransform/Adapter/WorseReflection/Transformer/UpdateDocblockGenericTransformer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public function transform(SourceCode $code): Promise
4949
$builder->use($classType->name()->__toString());
5050
}
5151

52-
5352
$tag = match($diagnostic->isExtends()) {
5453
true => new ExtendsTagPrototype(
5554
$diagnostic->missingGenericType(),

lib/CodeTransform/Tests/Adapter/WorseReflection/Transformer/UpdateDocblockGenericTransformerTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public function testTransform(string $example, string $expected): void
2828
'Example2.php',
2929
'<?php /** @template T of object */class NeedsObject{ }'
3030
);
31+
$this->workspace()->put(
32+
'Example3.php',
33+
'<?php /** @template T */interface GenericInterface{ }'
34+
);
35+
$this->workspace()->put(
36+
'Example4.php',
37+
'<?php /** @template T of object */interface NeedsObjectInterface{ }'
38+
);
3139
$reflector = $this->reflectorForWorkspace($example);
3240
$transformer = $this->createTransformer($reflector);
3341
$transformed = wait($transformer->transform($source))->apply($source);
@@ -165,6 +173,47 @@ class Foobar extends NeedsObject {
165173
}
166174
EOT
167175
];
176+
yield 'implements' => [
177+
<<<'EOT'
178+
<?php
179+
180+
namespace Foo;
181+
use \GenericInterface;
182+
183+
class Foobar implements GenericInterface {
184+
}
185+
EOT
186+
,
187+
<<<'EOT'
188+
<?php
189+
190+
namespace Foo;
191+
use \GenericInterface;
192+
/**
193+
* @implements GenericInterface<mixed>
194+
*/
195+
class Foobar implements GenericInterface {
196+
}
197+
EOT
198+
];
199+
yield 'implements of' => [
200+
<<<'EOT'
201+
<?php
202+
203+
class Foobar implements NeedsObjectInterface {
204+
}
205+
EOT
206+
,
207+
<<<'EOT'
208+
<?php
209+
210+
/**
211+
* @implements NeedsObjectInterface<object>
212+
*/
213+
class Foobar implements NeedsObjectInterface {
214+
}
215+
EOT
216+
];
168217
}
169218

170219
private function createTransformer(Reflector $reflector): UpdateDocblockGenericTransformer

lib/CodeTransform/Tests/Adapter/WorseReflection/WorseTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Phpactor\WorseReflection\Bridge\Phpactor\MemberProvider\DocblockMemberProvider;
88
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\AssignmentToMissingPropertyProvider;
99
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\DocblockMissingExtendsTagProvider;
10+
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\DocblockMissingImplementsTagProvider;
1011
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\DocblockMissingParamProvider;
1112
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\DocblockMissingReturnTypeProvider;
1213
use Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics\MissingMemberProvider;
@@ -31,6 +32,7 @@ public function reflectorForWorkspace(?string $source = null): Reflector
3132
$builder->addDiagnosticProvider(new UnusedImportProvider());
3233
$builder->addDiagnosticProvider(new DocblockMissingParamProvider());
3334
$builder->addDiagnosticProvider(new DocblockMissingExtendsTagProvider());
35+
$builder->addDiagnosticProvider(new DocblockMissingImplementsTagProvider());
3436

3537
foreach ((array)glob($this->workspace()->path('/*.php')) as $file) {
3638
if ($file === false) {

lib/WorseReflection/Bridge/TolerantParser/Diagnostics/Docblock/ClassGenericDiagnosticHelper.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ public function diagnosticsForExtends(
3838
public function diagnosticsForImplements(Reflector $reflector, ByteOffsetRange $range, ReflectionClassLike $class, ?ReflectionClassLike $genericClass): Generator
3939
{
4040
if ($class instanceof ReflectionClass) {
41-
yield from $this->fromReflectionClass($reflector, $range, $class, $genericClass, $class->docblock()->implements(), '@implements');
41+
yield from $this->fromReflectionClass(
42+
$reflector,
43+
$range,
44+
$class,
45+
$genericClass,
46+
$class->docblock()->implements(),
47+
'@implements'
48+
);
4249
}
4350
}
4451

@@ -64,7 +71,10 @@ private function fromReflectionClass(
6471
return;
6572
}
6673

67-
$genericTypes = array_filter($genericTypes, fn (Type $extendTagType) => $parentClass->type()->accepts($extendTagType)->isTrue());
74+
$genericTypes = array_filter(
75+
$genericTypes,
76+
fn (Type $extendTagType) => $parentClass->type()->accepts($extendTagType)->isTrue()
77+
);
6878

6979
$defaultGenericType = new GenericClassType(
7080
$reflector,

lib/WorseReflection/Bridge/TolerantParser/Diagnostics/DocblockMissingImplementsTagProvider.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Phpactor\WorseReflection\Bridge\TolerantParser\Diagnostics;
44

55
use Microsoft\PhpParser\Node;
6+
use Microsoft\PhpParser\Node\QualifiedName;
67
use Microsoft\PhpParser\Node\Statement\ClassDeclaration;
78
use PHPUnit\Framework\Assert;
89
use Phpactor\TextDocument\ByteOffsetRange;
@@ -53,8 +54,12 @@ public function exit(NodeContextResolver $resolver, Frame $frame, Node $node): i
5354
if ($class instanceof ReflectionClass) {
5455
/** @phpstan-ignore-next-line TP Lies */
5556
foreach ($node->classInterfaceClause?->interfaceNameList?->getChildNodes() ?? [] as $implementedInterface) {
57+
if (!$implementedInterface instanceof QualifiedName) {
58+
continue;
59+
}
5660
try {
57-
$implementedInterface = $resolver->reflector()->reflectClassLike($implementedInterface->getText());
61+
$name = (string)$implementedInterface->getResolvedName();
62+
$implementedInterface = $resolver->reflector()->reflectClassLike($name);
5863
} catch (NotFound) {
5964
continue;
6065
}

0 commit comments

Comments
 (0)