Skip to content

Commit 59e068b

Browse files
committed
Support multiple tags and improve interface handling in @phpstan-require-extends
1 parent e468be5 commit 59e068b

File tree

2 files changed

+38
-27
lines changed

2 files changed

+38
-27
lines changed

src/Rules/PhpDoc/RequireExtendsCheck.php

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PHPStan\Rules\IdentifierRuleError;
1010
use PHPStan\Rules\RuleErrorBuilder;
1111
use PHPStan\Type\VerbosityLevel;
12+
use function array_column;
13+
use function array_map;
1214
use function array_merge;
1315
use function count;
1416
use function sprintf;
@@ -47,32 +49,39 @@ public function checkExtendsTags(Node $node, array $extendsTags): array
4749
continue;
4850
}
4951

50-
$class = $type->getObjectClassNames()[0];
51-
$referencedClassReflection = $type->getObjectClassReflections()[0] ?? null;
52+
$referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections());
53+
$referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1);
54+
foreach ($type->getObjectClassNames() as $class) {
55+
$referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null;
56+
if ($referencedClassReflection === null) {
57+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class))
58+
->discoveringSymbolsTip()
59+
->identifier('class.notFound')
60+
->build();
61+
continue;
62+
}
5263

53-
if ($referencedClassReflection === null) {
54-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class))
55-
->discoveringSymbolsTip()
56-
->identifier('class.notFound')
57-
->build();
58-
continue;
59-
}
60-
61-
if (!$referencedClassReflection->isClass()) {
62-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class))
63-
->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription())))
64-
->build();
65-
} elseif ($referencedClassReflection->isFinal()) {
66-
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class))
67-
->identifier('requireExtends.finalClass')
68-
->build();
69-
} else {
70-
$errors = array_merge(
71-
$errors,
72-
$this->classCheck->checkClassNames([
73-
new ClassNameNodePair($class, $node),
74-
], $this->checkClassCaseSensitivity),
75-
);
64+
if ($referencedClassReflection->isInterface()) {
65+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain an interface %s, expected a class.', $class))
66+
->tip('If you meant an interface, use @phpstan-require-implements instead.')
67+
->identifier('requireExtends.interface')
68+
->build();
69+
} elseif (!$referencedClassReflection->isClass()) {
70+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class))
71+
->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription())))
72+
->build();
73+
} elseif ($referencedClassReflection->isFinal()) {
74+
$errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class))
75+
->identifier('requireExtends.finalClass')
76+
->build();
77+
} else {
78+
$errors = array_merge(
79+
$errors,
80+
$this->classCheck->checkClassNames([
81+
new ClassNameNodePair($class, $node),
82+
], $this->checkClassCaseSensitivity),
83+
);
84+
}
7685
}
7786
}
7887

tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ public function testRule(): void
4545
8,
4646
],
4747
[
48-
'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeInterface.',
48+
'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\SomeInterface, expected a class.',
4949
13,
50+
'If you meant an interface, use @phpstan-require-implements instead.',
5051
],
5152
[
5253
$enumError,
@@ -75,8 +76,9 @@ public function testRule(): void
7576
121,
7677
],
7778
[
78-
'PHPDoc tag @phpstan-require-extends contains non-object type IncompatibleRequireExtends\UnresolvableExtendsInterface&stdClass.',
79+
'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\UnresolvableExtendsInterface, expected a class.',
7980
135,
81+
'If you meant an interface, use @phpstan-require-implements instead.',
8082
],
8183
[
8284
'PHPDoc tag @phpstan-require-extends can only be used once.',

0 commit comments

Comments
 (0)