Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions src/Rules/Methods/NullsafeMethodCallRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
Expand All @@ -17,24 +18,50 @@
final class NullsafeMethodCallRule implements Rule
{

public function __construct(
#[AutowiredParameter]
private bool $treatPhpDocTypesAsCertain,
#[AutowiredParameter(ref: '%tips.treatPhpDocTypesAsCertain%')]
private bool $treatPhpDocTypesAsCertainTip,
)
{
}

public function getNodeType(): string
{
return Node\Expr\NullsafeMethodCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
$calledOnType = $scope->getScopeType($node->var);
$calledOnType = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($node->var) : $scope->getScopeNativeType($node->var);
if (!$calledOnType->isNull()->no()) {
return [];
}

return [
RuleErrorBuilder::message(sprintf('Using nullsafe method call on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))
->line($node->name->getStartLine())
->identifier('nullsafe.neverNull')
->build(),
];
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
if (!$this->treatPhpDocTypesAsCertain || !$this->treatPhpDocTypesAsCertainTip) {
return $ruleErrorBuilder;
}

$calledOnNativeType = $scope->getScopeNativeType($node->var);
if ($calledOnNativeType->isNull()->no()) {
return $ruleErrorBuilder;
}

return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip();
};

$ruleErrorBuilder = $addTip(
RuleErrorBuilder::message(sprintf(
'Using nullsafe method call on non-nullable type %s. Use -> instead.',
$calledOnType->describe(VerbosityLevel::typeOnly()),
)),
)
->line($node->name->getStartLine())
->identifier('nullsafe.neverNull');

return [$ruleErrorBuilder->build()];
}

}
39 changes: 31 additions & 8 deletions src/Rules/Properties/NullsafePropertyFetchRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
Expand All @@ -17,7 +18,12 @@
final class NullsafePropertyFetchRule implements Rule
{

public function __construct()
public function __construct(
#[AutowiredParameter]
private bool $treatPhpDocTypesAsCertain,
#[AutowiredParameter(ref: '%tips.treatPhpDocTypesAsCertain%')]
private bool $treatPhpDocTypesAsCertainTip,
)
{
}

Expand All @@ -28,7 +34,7 @@ public function getNodeType(): string

public function processNode(Node $node, Scope $scope): array
{
$calledOnType = $scope->getScopeType($node->var);
$calledOnType = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($node->var) : $scope->getScopeNativeType($node->var);
if (!$calledOnType->isNull()->no()) {
return [];
}
Expand All @@ -37,12 +43,29 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

return [
RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))
->line($node->name->getStartLine())
->identifier('nullsafe.neverNull')
->build(),
];
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
if (!$this->treatPhpDocTypesAsCertain || !$this->treatPhpDocTypesAsCertainTip) {
return $ruleErrorBuilder;
}

$calledOnNativeType = $scope->getScopeNativeType($node->var);
if ($calledOnNativeType->isNull()->no()) {
return $ruleErrorBuilder;
}

return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip();
};

$ruleErrorBuilder = $addTip(
RuleErrorBuilder::message(sprintf(
'Using nullsafe property access on non-nullable type %s. Use -> instead.',
$calledOnType->describe(VerbosityLevel::typeOnly()),
)),
)
->line($node->name->getStartLine())
->identifier('nullsafe.neverNull');

return [$ruleErrorBuilder->build()];
}

}
29 changes: 28 additions & 1 deletion tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,24 @@
class NullsafeMethodCallRuleTest extends RuleTestCase
{

private bool $treatPhpDocTypesAsCertain;

protected function getRule(): Rule
{
return new NullsafeMethodCallRule();
return new NullsafeMethodCallRule(
$this->treatPhpDocTypesAsCertain,
true,
);
}

protected function shouldTreatPhpDocTypesAsCertain(): bool
{
return $this->treatPhpDocTypesAsCertain;
}

public function testRule(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/nullsafe-method-call-rule.php'], [
[
'Using nullsafe method call on non-nullable type Exception. Use -> instead.',
Expand All @@ -29,54 +40,70 @@ public function testRule(): void

public function testNullsafeVsScalar(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/nsrt/nullsafe-vs-scalar.php'], []);
}

public function testBug8664(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/data/bug-8664.php'], []);
}

public function testBug14150(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [
[
'Using nullsafe method call on non-nullable type $this(Bug14150NullsafeMethod\HelloWorld). Use -> instead.',
21,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
]);
}

public function testBug14150WithoutCertainPhpDocTypes(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug9293(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-9293.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug6922b(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-6922b.php'], []);
}

public function testBug8523(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-8523.php'], []);
}

public function testBug8523b(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-8523b.php'], []);
}

public function testBug8523c(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-8523c.php'], []);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug12222(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-12222.php'], []);
}

Expand Down
33 changes: 32 additions & 1 deletion tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,24 @@
class NullsafePropertyFetchRuleTest extends RuleTestCase
{

private bool $treatPhpDocTypesAsCertain;

protected function getRule(): Rule
{
return new NullsafePropertyFetchRule();
return new NullsafePropertyFetchRule(
$this->treatPhpDocTypesAsCertain,
true,
);
}

protected function shouldTreatPhpDocTypesAsCertain(): bool
{
return $this->treatPhpDocTypesAsCertain;
}

public function testRule(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/nullsafe-property-fetch-rule.php'], [
[
'Using nullsafe property access on non-nullable type Exception. Use -> instead.',
Expand All @@ -29,34 +40,40 @@ public function testRule(): void

public function testBug6020(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-6020.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug7109(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-7109.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug5172(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-5172.php'], []);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug7980(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/data/bug-7980.php'], []);
}

public function testBug8517(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8517.php'], []);
}

public function testBug14150(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [
[
'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.',
Expand All @@ -65,19 +82,33 @@ public function testBug14150(): void
[
'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.',
27,
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
]);
}

public function testBug14150WithoutCertainPhpDocTypes(): void
{
$this->treatPhpDocTypesAsCertain = false;
$this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [
[
'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.',
20,
],
]);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug9105(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-9105.php'], []);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug6922(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-6922.php'], []);
}

Expand Down
Loading