-
Notifications
You must be signed in to change notification settings - Fork 471
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Do-while loop - constant condition rule
- Loading branch information
1 parent
0cde73f
commit e81ccd4
Showing
7 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Node; | ||
|
||
use PhpParser\Node\Expr; | ||
use PhpParser\NodeAbstract; | ||
use PHPStan\Analyser\StatementExitPoint; | ||
|
||
class DoWhileLoopConditionNode extends NodeAbstract implements VirtualNode | ||
{ | ||
|
||
private Expr $cond; | ||
|
||
/** @var StatementExitPoint[] */ | ||
private array $exitPoints; | ||
|
||
/** | ||
* @param StatementExitPoint[] $exitPoints | ||
*/ | ||
public function __construct(Expr $cond, array $exitPoints) | ||
{ | ||
parent::__construct($cond->getAttributes()); | ||
$this->cond = $cond; | ||
$this->exitPoints = $exitPoints; | ||
} | ||
|
||
public function getCond(): Expr | ||
{ | ||
return $this->cond; | ||
} | ||
|
||
/** | ||
* @return StatementExitPoint[] | ||
*/ | ||
public function getExitPoints(): array | ||
{ | ||
return $this->exitPoints; | ||
} | ||
|
||
public function getType(): string | ||
{ | ||
return 'PHPStan_Node_ClosureReturnStatementsNode'; | ||
} | ||
|
||
/** | ||
* @return string[] | ||
*/ | ||
public function getSubNodeNames(): array | ||
{ | ||
return []; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Comparison; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Scalar\LNumber; | ||
use PhpParser\Node\Stmt\Break_; | ||
use PhpParser\Node\Stmt\Continue_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\DoWhileLoopConditionNode; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\Constant\ConstantBooleanType; | ||
|
||
/** | ||
* @implements Rule<DoWhileLoopConditionNode> | ||
*/ | ||
class DoWhileLoopConstantConditionRule implements Rule | ||
{ | ||
|
||
private ConstantConditionRuleHelper $helper; | ||
|
||
private bool $treatPhpDocTypesAsCertain; | ||
|
||
public function __construct( | ||
ConstantConditionRuleHelper $helper, | ||
bool $treatPhpDocTypesAsCertain | ||
) | ||
{ | ||
$this->helper = $helper; | ||
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return DoWhileLoopConditionNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$exprType = $this->helper->getBooleanType($scope, $node->getCond()); | ||
if ($exprType instanceof ConstantBooleanType) { | ||
if ($exprType->getValue()) { | ||
foreach ($node->getExitPoints() as $exitPoint) { | ||
$statement = $exitPoint->getStatement(); | ||
if ($statement instanceof Break_) { | ||
return []; | ||
} | ||
if (!$statement instanceof Continue_) { | ||
return []; | ||
} | ||
if ($statement->num === null) { | ||
continue; | ||
} | ||
if (!$statement->num instanceof LNumber) { | ||
continue; | ||
} | ||
$value = $statement->num->value; | ||
if ($value === 1) { | ||
continue; | ||
} | ||
|
||
if ($value > 1) { | ||
return []; | ||
} | ||
} | ||
} | ||
|
||
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { | ||
if (!$this->treatPhpDocTypesAsCertain) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
$booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->getCond()); | ||
if ($booleanNativeType instanceof ConstantBooleanType) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
return $ruleErrorBuilder->tip('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%</>.'); | ||
}; | ||
|
||
return [ | ||
$addTip(RuleErrorBuilder::message(sprintf( | ||
'Do-while loop condition is always %s.', | ||
$exprType->getValue() ? 'true' : 'false' | ||
)))->line($node->getCond()->getLine())->build(), | ||
]; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
} |
65 changes: 65 additions & 0 deletions
65
tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Comparison; | ||
|
||
/** | ||
* @extends \PHPStan\Testing\RuleTestCase<DoWhileLoopConstantConditionRule> | ||
*/ | ||
class DoWhileLoopConstantConditionRuleTest extends \PHPStan\Testing\RuleTestCase | ||
{ | ||
|
||
/** @var bool */ | ||
private $treatPhpDocTypesAsCertain = true; | ||
|
||
protected function getRule(): \PHPStan\Rules\Rule | ||
{ | ||
return new DoWhileLoopConstantConditionRule( | ||
new ConstantConditionRuleHelper( | ||
new ImpossibleCheckTypeHelper( | ||
$this->createReflectionProvider(), | ||
$this->getTypeSpecifier(), | ||
[], | ||
$this->treatPhpDocTypesAsCertain | ||
), | ||
$this->treatPhpDocTypesAsCertain | ||
), | ||
$this->treatPhpDocTypesAsCertain | ||
); | ||
} | ||
|
||
protected function shouldTreatPhpDocTypesAsCertain(): bool | ||
{ | ||
return $this->treatPhpDocTypesAsCertain; | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/do-while-loop.php'], [ | ||
[ | ||
'Do-while loop condition is always true.', | ||
12, | ||
], | ||
[ | ||
'Do-while loop condition is always false.', | ||
37, | ||
], | ||
[ | ||
'Do-while loop condition is always false.', | ||
46, | ||
], | ||
[ | ||
'Do-while loop condition is always false.', | ||
55, | ||
], | ||
[ | ||
'Do-while loop condition is always true.', | ||
64, | ||
], | ||
[ | ||
'Do-while loop condition is always false.', | ||
73, | ||
], | ||
]); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
namespace DoWhileLoopConstantCondition; | ||
|
||
class Foo | ||
{ | ||
|
||
public function doFoo() | ||
{ | ||
do { | ||
|
||
} while (true); // report | ||
} | ||
|
||
public function doFoo2() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
return; | ||
} | ||
} while (true); // do not report | ||
} | ||
|
||
public function doFoo3() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
break; | ||
} | ||
} while (true); // do not report | ||
} | ||
|
||
public function doBar() | ||
{ | ||
do { | ||
|
||
} while (false); // report | ||
} | ||
|
||
public function doBar2() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
return; | ||
} | ||
} while (false); // report | ||
} | ||
|
||
public function doBar3() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
break; | ||
} | ||
} while (false); // report | ||
} | ||
|
||
public function doFoo4() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
continue; | ||
} | ||
} while (true); // report | ||
} | ||
|
||
public function doBar4() | ||
{ | ||
do { | ||
if (rand(0, 1)) { | ||
continue; | ||
} | ||
} while (false); // report | ||
} | ||
|
||
public function doFoo5(array $a) | ||
{ | ||
foreach ($a as $v) { | ||
do { | ||
if (rand(0, 1)) { | ||
continue 2; | ||
} | ||
} while (true); // do not report | ||
} | ||
} | ||
|
||
public function doFoo6(array $a) | ||
{ | ||
foreach ($a as $v) { | ||
do { | ||
if (rand(0, 1)) { | ||
break 2; | ||
} | ||
} while (true); // do not report | ||
} | ||
} | ||
|
||
} |