Skip to content

Commit a72aea6

Browse files
committed
NullsafePropertyFetchRule to check nullsafe operator on non-nullable expression
1 parent 9876c0c commit a72aea6

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

conf/config.level4.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ rules:
1212
- PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule
1313
- PHPStan\Rules\Methods\CallToStaticMethodStamentWithoutSideEffectsRule
1414
- PHPStan\Rules\Methods\NullsafeMethodCallRule
15+
- PHPStan\Rules\Properties\NullsafePropertyFetchRule
1516
- PHPStan\Rules\TooWideTypehints\TooWideArrowFunctionReturnTypehintRule
1617
- PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule
1718
- PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Rules\RuleErrorBuilder;
9+
use PHPStan\Type\NullType;
10+
use PHPStan\Type\VerbosityLevel;
11+
12+
/**
13+
* @implements Rule<Node\Expr\NullsafePropertyFetch>
14+
*/
15+
class NullsafePropertyFetchRule implements Rule
16+
{
17+
18+
public function getNodeType(): string
19+
{
20+
return Node\Expr\NullsafePropertyFetch::class;
21+
}
22+
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
$nullType = new NullType();
26+
$calledOnType = $scope->getType($node->var);
27+
if ($calledOnType->equals($nullType)) {
28+
return [];
29+
}
30+
31+
if (!$calledOnType->isSuperTypeOf($nullType)->no()) {
32+
return [];
33+
}
34+
35+
return [
36+
RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))->build(),
37+
];
38+
}
39+
40+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<NullsafePropertyFetchRule>
10+
*/
11+
class NullsafePropertyFetchRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new NullsafePropertyFetchRule();
17+
}
18+
19+
public function testRule(): void
20+
{
21+
if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) {
22+
$this->markTestSkipped('Test requires PHP 8.0.');
23+
}
24+
25+
$this->analyse([__DIR__ . '/data/nullsafe-property-fetch-rule.php'], [
26+
[
27+
'Using nullsafe property access on non-nullable type Exception. Use -> instead.',
28+
16,
29+
],
30+
]);
31+
}
32+
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php // lint >= 8.0
2+
3+
namespace NullsafePropertyFetchRule;
4+
5+
class Foo
6+
{
7+
8+
public function doFoo(
9+
$mixed,
10+
?\Exception $nullable,
11+
\Exception $nonNullable
12+
): void
13+
{
14+
$mixed?->foo;
15+
$nullable?->foo;
16+
$nonNullable?->foo;
17+
(null)?->foo; // reported by a different rule
18+
}
19+
20+
}

0 commit comments

Comments
 (0)