Skip to content

Commit a5f7c06

Browse files
committed
Bleeding edge - report new static() in static method of abstract class
1 parent 2f66ec6 commit a5f7c06

7 files changed

+135
-0
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ parameters:
66
stricterFunctionMap: true
77
reportPreciseLineForUnusedFunctionParameter: true
88
internalTag: true
9+
newStaticInAbstractClassStaticMethod: true

conf/config.level0.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ conditionalTags:
110110
phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag%
111111
PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension:
112112
phpstan.restrictedFunctionUsageExtension: %featureToggles.internalTag%
113+
PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule:
114+
phpstan.rules.rule: %featureToggles.newStaticInAbstractClassStaticMethod%
113115

114116
services:
115117
-
@@ -178,6 +180,9 @@ services:
178180
checkFunctionNameCase: %checkFunctionNameCase%
179181
discoveringSymbolsTip: %tips.discoveringSymbols%
180182

183+
-
184+
class: PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule
185+
181186
-
182187
class: PHPStan\Rules\Constants\OverridingConstantRule
183188
arguments:

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ parameters:
2727
stricterFunctionMap: false
2828
reportPreciseLineForUnusedFunctionParameter: false
2929
internalTag: false
30+
newStaticInAbstractClassStaticMethod: false
3031
fileExtensions:
3132
- php
3233
checkAdvancedIsset: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ parametersSchema:
3333
stricterFunctionMap: bool()
3434
reportPreciseLineForUnusedFunctionParameter: bool()
3535
internalTag: bool()
36+
newStaticInAbstractClassStaticMethod: bool()
3637
])
3738
fileExtensions: listOf(string())
3839
checkAdvancedIsset: bool()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use function sprintf;
11+
use function strtolower;
12+
13+
/**
14+
* @implements Rule<Node\Expr\New_>
15+
*/
16+
final class NewStaticInAbstractClassStaticMethodRule implements Rule
17+
{
18+
19+
public function getNodeType(): string
20+
{
21+
return Node\Expr\New_::class;
22+
}
23+
24+
public function processNode(Node $node, Scope $scope): array
25+
{
26+
if (!$node->class instanceof Node\Name) {
27+
return [];
28+
}
29+
30+
if (!$scope->isInClass()) {
31+
return [];
32+
}
33+
34+
if (strtolower($node->class->toString()) !== 'static') {
35+
return [];
36+
}
37+
38+
$classReflection = $scope->getClassReflection();
39+
if (!$classReflection->isAbstract()) {
40+
return [];
41+
}
42+
43+
$inMethod = $scope->getFunction();
44+
if (!$inMethod instanceof PhpMethodFromParserNodeReflection) {
45+
return [];
46+
}
47+
48+
if (!$inMethod->isStatic()) {
49+
return [];
50+
}
51+
52+
return [
53+
RuleErrorBuilder::message(sprintf(
54+
'Unsafe usage of new static() in abstract class %s in static method %s().',
55+
$classReflection->getDisplayName(),
56+
$inMethod->getName(),
57+
))
58+
->identifier('new.staticInAbstractClassStaticMethod')
59+
->tip(sprintf('Direct call to %s::%s() would crash because an abstract class cannot be instantiated.', $classReflection->getName(), $inMethod->getName()))
60+
->build(),
61+
];
62+
}
63+
64+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<NewStaticInAbstractClassStaticMethodRule>
10+
*/
11+
class NewStaticInAbstractClassStaticMethodRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return self::getContainer()->getByType(NewStaticInAbstractClassStaticMethodRule::class);
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/new-static-in-abstract-class-static-method.php'], [
22+
[
23+
'Unsafe usage of new static() in abstract class NewStaticInAbstractClassStaticMethod\Bar in static method staticDoFoo().',
24+
30,
25+
'Direct call to NewStaticInAbstractClassStaticMethod\Bar::staticDoFoo() would crash because an abstract class cannot be instantiated.',
26+
],
27+
]);
28+
}
29+
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace NewStaticInAbstractClassStaticMethod;
4+
5+
class Foo
6+
{
7+
8+
public function doFoo(): void
9+
{
10+
new static();
11+
}
12+
13+
public static function staticDoFoo(): void
14+
{
15+
new static();
16+
}
17+
18+
}
19+
20+
abstract class Bar
21+
{
22+
23+
public function doFoo(): void
24+
{
25+
new static();
26+
}
27+
28+
public static function staticDoFoo(): void
29+
{
30+
new static();
31+
}
32+
33+
}

0 commit comments

Comments
 (0)