Skip to content

Commit dffd2c2

Browse files
committed
Bleeding edge - check classes extending @final classes
1 parent c5f9d24 commit dffd2c2

File tree

6 files changed

+62
-4
lines changed

6 files changed

+62
-4
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ parameters:
2323
neverInGenericReturnType: true
2424
validateOverridingMethodsInStubs: true
2525
crossCheckInterfaces: true
26+
finalByPhpDocTag: true
2627
stubFiles:
2728
- ../stubs/arrayFunctions.stub

conf/config.level0.neon

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ rules:
4242
- PHPStan\Rules\Classes\DuplicateDeclarationRule
4343
- PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule
4444
- PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule
45-
- PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
4645
- PHPStan\Rules\Classes\ExistingClassInTraitUseRule
4746
- PHPStan\Rules\Classes\InstantiationRule
4847
- PHPStan\Rules\Classes\InvalidPromotedPropertiesRule
@@ -98,6 +97,13 @@ services:
9897
-
9998
class: PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule
10099

100+
-
101+
class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
102+
arguments:
103+
checkFinalByPhpDocTag: %featureToggles.finalByPhpDocTag%
104+
tags:
105+
- phpstan.rules.rule
106+
101107
-
102108
class: PHPStan\Rules\Classes\ExistingClassInInstanceOfRule
103109
tags:

conf/config.neon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ parameters:
4646
neverInGenericReturnType: false
4747
validateOverridingMethodsInStubs: false
4848
crossCheckInterfaces: false
49+
finalByPhpDocTag: false
4950
fileExtensions:
5051
- php
5152
checkAlwaysTrueCheckTypeFunctionCall: false
@@ -219,7 +220,8 @@ parametersSchema:
219220
deepInspectTypes: bool(),
220221
neverInGenericReturnType: bool(),
221222
validateOverridingMethodsInStubs: bool(),
222-
crossCheckInterfaces: bool()
223+
crossCheckInterfaces: bool(),
224+
finalByPhpDocTag: bool()
223225
])
224226
fileExtensions: listOf(string())
225227
checkAlwaysTrueCheckTypeFunctionCall: bool()

src/Rules/Classes/ExistingClassInClassExtendsRule.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ class ExistingClassInClassExtendsRule implements \PHPStan\Rules\Rule
1919

2020
private ReflectionProvider $reflectionProvider;
2121

22+
private bool $checkFinalByPhpDocTag;
23+
2224
public function __construct(
2325
ClassCaseSensitivityCheck $classCaseSensitivityCheck,
24-
ReflectionProvider $reflectionProvider
26+
ReflectionProvider $reflectionProvider,
27+
bool $checkFinalByPhpDocTag = false
2528
)
2629
{
2730
$this->classCaseSensitivityCheck = $classCaseSensitivityCheck;
2831
$this->reflectionProvider = $reflectionProvider;
32+
$this->checkFinalByPhpDocTag = $checkFinalByPhpDocTag;
2933
}
3034

3135
public function getNodeType(): string
@@ -72,6 +76,12 @@ public function processNode(Node $node, Scope $scope): array
7276
$currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class',
7377
$extendedClassName
7478
))->nonIgnorable()->build();
79+
} elseif ($this->checkFinalByPhpDocTag && $reflection->isFinal()) {
80+
$messages[] = RuleErrorBuilder::message(sprintf(
81+
'%s extends @final class %s.',
82+
$currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class',
83+
$extendedClassName
84+
))->build();
7585
}
7686
}
7787

tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ protected function getRule(): Rule
1616
$broker = $this->createReflectionProvider();
1717
return new ExistingClassInClassExtendsRule(
1818
new ClassCaseSensitivityCheck($broker),
19-
$broker
19+
$broker,
20+
true
2021
);
2122
}
2223

@@ -27,6 +28,10 @@ public function testRule(): void
2728
'Class ExtendsImplements\Foo referenced with incorrect case: ExtendsImplements\FOO.',
2829
15,
2930
],
31+
[
32+
'Class ExtendsImplements\ExtendsFinalWithAnnotation extends @final class ExtendsImplements\FinalWithAnnotation.',
33+
43,
34+
],
3035
]);
3136
}
3237

@@ -61,4 +66,14 @@ public function testRuleExtendsError(): void
6166
]);
6267
}
6368

69+
public function testFinalByTag(): void
70+
{
71+
$this->analyse([__DIR__ . '/data/extends-final-by-tag.php'], [
72+
[
73+
'Class ExtendsFinalByTag\Bar2 extends @final class ExtendsFinalByTag\Bar.',
74+
21,
75+
],
76+
]);
77+
}
78+
6479
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace ExtendsFinalByTag;
4+
5+
class Foo
6+
{
7+
8+
}
9+
10+
/** @final */
11+
class Bar
12+
{
13+
14+
}
15+
16+
class Foo2 extends Foo
17+
{
18+
19+
}
20+
21+
class Bar2 extends Bar
22+
{
23+
24+
}

0 commit comments

Comments
 (0)