Skip to content

Commit

Permalink
Add rule checking coalesces
Browse files Browse the repository at this point in the history
  • Loading branch information
leongersen committed Jan 15, 2020
1 parent b1fd47b commit 90c901f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/Rules/Variables/CoalesceRule.php
@@ -0,0 +1,74 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Variables;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\NullType;
use PHPStan\Type\VerbosityLevel;

/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\BinaryOp\Coalesce>
*/
class CoalesceRule implements \PHPStan\Rules\Rule
{

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

public function processNode(Node $node, Scope $scope): array
{
$error = $this->canBeCoalesced($node->left, $scope);

if ($error === null) {
return [];
}

return [$error];
}

private function canBeCoalesced(Node $node, Scope $scope): ?RuleError
{
if ($node instanceof Node\Expr\Variable && is_string($node->name)) {

$hasVariable = $scope->hasVariableType($node->name);

if ($hasVariable->no()) {
return RuleErrorBuilder::message(
sprintf('Coalesce of undefined variable $%s.', $node->name)
)->line($node->getLine())->build();
}

$variableType = $scope->getVariableType($node->name);

if ($variableType->isSuperTypeOf(new NullType())->no()) {
return RuleErrorBuilder::message(
sprintf('Coalesce of variable $%s, which cannot be null.', $node->name)
)->line($node->getLine())->build();
}

} elseif ($node instanceof Node\Expr\ArrayDimFetch && $node->dim !== null) {
$type = $scope->getType($node->var);
$dimType = $scope->getType($node->dim);

if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($dimType)->no()) {
return RuleErrorBuilder::message(
sprintf(
'Coalesce of invalid offset %s on %s.',
$dimType->describe(VerbosityLevel::value()),
$type->describe(VerbosityLevel::value())
)
)->line($node->getLine())->build();
}

return $this->canBeCoalesced($node->var, $scope);
}

return null;
}

}
39 changes: 39 additions & 0 deletions tests/PHPStan/Rules/Variables/CoalesceRuleTest.php
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Variables;

/**
* @extends \PHPStan\Testing\RuleTestCase<CoalesceRule>
*/
class CoalesceRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new CoalesceRule();
}

public function testUnsetRule(): void
{
require_once __DIR__ . '/data/coalesce.php';
$this->analyse([__DIR__ . '/data/coalesce.php'], [
[
'Coalesce of variable $scalar, which cannot be null.',
7,
],
[
'Coalesce of invalid offset \'string\' on array(1, 2, 3).',
11,
],
[
'Coalesce of invalid offset \'string\' on array(array(1), array(2), array(3)).',
15,
],
[
'Coalesce of undefined variable $doesNotExist.',
17,
],
]);
}

}
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Variables/data/coalesce.php
@@ -0,0 +1,19 @@
<?php

function coalesce () {

$scalar = 3;

echo $scalar ?? 4;

$array = [1, 2, 3];

echo $array['string'] ?? 0;

$multiDimArray = [[1], [2], [3]];

echo $multiDimArray['string'] ?? 0;

echo $doesNotExist ?? 0;

}

0 comments on commit 90c901f

Please sign in to comment.