Skip to content

Commit 1f137a0

Browse files
committed
feat: add ClassExtendUsesAbstractClassWhenExisting rule with tests
1 parent c7e0b43 commit 1f137a0

File tree

4 files changed

+109
-0
lines changed

4 files changed

+109
-0
lines changed

rules.neon

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
rules:
2+
- Shopware\PhpStan\Rule\ClassExtendUsesAbstractClassWhenExisting
23
- Shopware\PhpStan\Rule\DisallowDefaultContextCreation
34
- Shopware\PhpStan\Rule\ForbidGlobBraceRule
45
- Shopware\PhpStan\Rule\DisallowFunctionsRule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Shopware\PhpStan\Rule;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\Class_;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\ReflectionProvider;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @implements Rule<Class_>
14+
*/
15+
class ClassExtendUsesAbstractClassWhenExisting implements Rule
16+
{
17+
private ReflectionProvider $reflectionProvider;
18+
19+
public function __construct(ReflectionProvider $reflectionProvider)
20+
{
21+
$this->reflectionProvider = $reflectionProvider;
22+
}
23+
24+
/**
25+
* This rule is applied to class nodes.
26+
*/
27+
public function getNodeType(): string
28+
{
29+
return Class_::class;
30+
}
31+
32+
public function processNode(Node $node, Scope $scope): array
33+
{
34+
// Only proceed if the class extends a parent.
35+
if ($node->extends === null) {
36+
return [];
37+
}
38+
39+
$parentClassName = (string) $node->extends;
40+
$parentClassReflection = $this->reflectionProvider->getClass($parentClassName);
41+
42+
if (!$parentClassReflection->hasMethod('getDecorated')) {
43+
return [];
44+
}
45+
46+
foreach ($parentClassReflection->getParents() as $parent) {
47+
if ($parent->isAbstract()) {
48+
return [
49+
RuleErrorBuilder::message(sprintf('Class %s should extend %s to not break typehints', (string) $node->name, $parent->getName()))
50+
->line($node->getLine())
51+
->identifier('shopware.class.extends.abstract')
52+
->build(),
53+
];
54+
}
55+
}
56+
57+
return [];
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Shopware\PhpStan\Tests\Rule;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
use Shopware\PhpStan\Rule\ClassExtendUsesAbstractClassWhenExisting;
8+
9+
class ClassExtendUsesAbstractClassWhenExistingTest extends RuleTestCase
10+
{
11+
public function testAnalyse(): void
12+
{
13+
$this->analyse([__DIR__ . '/../data/ClassExtendUsesAbstractClassWhenExisting/extends.php'], [
14+
[
15+
<<<EOF
16+
Class Plugin should extend AbstractCore to not break typehints
17+
EOF,
18+
16,
19+
],
20+
]);
21+
}
22+
23+
protected function getRule(): Rule
24+
{
25+
return new ClassExtendUsesAbstractClassWhenExisting(self::createReflectionProvider());
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
abstract class AbstractCore
4+
{
5+
abstract public function getDecorated(): AbstractCore;
6+
}
7+
8+
class Core extends AbstractCore
9+
{
10+
public function getDecorated(): Core
11+
{
12+
return new Core();
13+
}
14+
}
15+
16+
class Plugin extends Core
17+
{
18+
public function getDecorated(): Plugin
19+
{
20+
return new Plugin();
21+
}
22+
}

0 commit comments

Comments
 (0)