Skip to content

Commit a3fe677

Browse files
committed
feat: add method becomes abstract rule
1 parent d039f80 commit a3fe677

File tree

6 files changed

+160
-1
lines changed

6 files changed

+160
-1
lines changed

composer.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
"autoload-dev": {
2222
"psr-4": {
2323
"Shopware\\PhpStan\\Tests\\": "tests/"
24-
}
24+
},
25+
"classmap": [
26+
"tests/data"
27+
]
2528
},
2629
"config": {
2730
"sort-packages": true,

rules.neon

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ rules:
44
- Shopware\PhpStan\Rule\DisallowFunctionsRule
55
- Shopware\PhpStan\Rule\DisallowDefaultContextCreation
66
- Shopware\PhpStan\Rule\SetForeignKeyRule
7+
- Shopware\PhpStan\Rule\MethodBecomesAbstractRule
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Shopware\PhpStan\Rule;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\Class_;
7+
use PhpParser\Node\Stmt\ClassMethod;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\ClassReflection;
10+
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
14+
/**
15+
* @implements Rule<Class_>
16+
*
17+
* @internal
18+
*/
19+
class MethodBecomesAbstractRule implements Rule
20+
{
21+
public function __construct(private readonly ReflectionProvider $reflectionProvider)
22+
{
23+
}
24+
25+
public function getNodeType(): string
26+
{
27+
return Class_::class;
28+
}
29+
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
if ($node->extends === null) {
33+
return [];
34+
}
35+
36+
$extendedClass = $this->reflectionProvider->getClass($node->extends->toString());
37+
38+
$errors = [];
39+
40+
$existingMethods = [];
41+
42+
foreach ($node->stmts as $stmt) {
43+
if ($stmt instanceof ClassMethod) {
44+
$existingMethods[] = (string)$stmt->name;
45+
}
46+
}
47+
48+
/** @var ClassReflection $parent */
49+
foreach ([$extendedClass, ...$extendedClass->getParents()] as $parent) {
50+
foreach ($parent->getNativeReflection()->getMethods() as $method) {
51+
if (
52+
!$method->isAbstract() &&
53+
($method->isPublic() || $method->isProtected()) &&
54+
str_contains($method->getDocComment(), '@abstract') &&
55+
!in_array($method->getName(), $existingMethods, true)
56+
) {
57+
$errors[] = RuleErrorBuilder::message(sprintf('Method %s::%s becomes abstract, but is not declared in the extending class. Implement the method for compatibility with next major version.', $parent->getName(), $method->getName()))
58+
->line($node->getLine())
59+
->identifier('shopware.method.becomes.abstract')
60+
->build();
61+
}
62+
}
63+
}
64+
65+
66+
return $errors;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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\MethodBecomesAbstractRule;
8+
9+
/**
10+
* @internal
11+
*
12+
* @extends RuleTestCase<MethodBecomesAbstractRule>
13+
*/
14+
class MethodBecomesAbstractRuleTest extends RuleTestCase
15+
{
16+
public function testAnalyse(): void
17+
{
18+
$this->analyse([ __DIR__ . '/../data/MethodBecomesAbstractRule/impl.php'], [
19+
[
20+
<<<EOF
21+
Method EntityExtension::new becomes abstract, but is not declared in the extending class. Implement the method for compatibility with next major version.
22+
EOF,
23+
18,
24+
],
25+
]);
26+
}
27+
28+
public function testAnalyseWithNamespace(): void
29+
{
30+
$this->analyse([ __DIR__ . '/../data/MethodBecomesAbstractRule/ns-test.php'], [
31+
[
32+
<<<EOF
33+
Method MethodBecomesAbstractRule\AbstractClassLocation\EntityExtension::new becomes abstract, but is not declared in the extending class. Implement the method for compatibility with next major version.
34+
EOF,
35+
17,
36+
],
37+
]);
38+
}
39+
40+
protected function getRule(): Rule
41+
{
42+
return new MethodBecomesAbstractRule(self::createReflectionProvider());
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
class EntityExtension
4+
{
5+
public function old(): void
6+
{
7+
}
8+
9+
/**
10+
* @abstract
11+
*/
12+
public function new(): void
13+
{
14+
15+
}
16+
}
17+
18+
class Impl extends EntityExtension
19+
{
20+
public function old(): void
21+
{
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace MethodBecomesAbstractRule\AbstractClassLocation;
4+
5+
class EntityExtension
6+
{
7+
public function old(): void {}
8+
9+
/**
10+
* @abstract
11+
*/
12+
public function new(): void {}
13+
}
14+
15+
namespace MethodBecomesAbstractRule\UsageLocation;
16+
17+
class Impl extends \MethodBecomesAbstractRule\AbstractClassLocation\EntityExtension
18+
{
19+
public function old(): void {}
20+
}

0 commit comments

Comments
 (0)