-
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.
Bleeding edge - LogicalXorConstantConditionRule
- Loading branch information
1 parent
0a3a968
commit 3a12724
Showing
8 changed files
with
211 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
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,99 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Comparison; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\BinaryOp\LogicalXor; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Parser\LastConditionVisitor; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\Constant\ConstantBooleanType; | ||
use function sprintf; | ||
|
||
/** | ||
* @implements Rule<LogicalXor> | ||
*/ | ||
class LogicalXorConstantConditionRule implements Rule | ||
{ | ||
|
||
public function __construct( | ||
private ConstantConditionRuleHelper $helper, | ||
private bool $treatPhpDocTypesAsCertain, | ||
private bool $reportAlwaysTrueInLastCondition, | ||
) | ||
{ | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return LogicalXor::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$errors = []; | ||
$leftType = $this->helper->getBooleanType($scope, $node->left); | ||
$tipText = '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%</>.'; | ||
if ($leftType instanceof ConstantBooleanType) { | ||
$addTipLeft = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $tipText, $node): RuleErrorBuilder { | ||
if (!$this->treatPhpDocTypesAsCertain) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
$booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->left); | ||
if ($booleanNativeType instanceof ConstantBooleanType) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
return $ruleErrorBuilder->tip($tipText); | ||
}; | ||
|
||
$isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); | ||
if (!$leftType->getValue() || $isLast !== true || $this->reportAlwaysTrueInLastCondition) { | ||
$errorBuilder = $addTipLeft(RuleErrorBuilder::message(sprintf( | ||
'Left side of xor is always %s.', | ||
$leftType->getValue() ? 'true' : 'false', | ||
)))->line($node->left->getLine()); | ||
if ($leftType->getValue() && $isLast === false && !$this->reportAlwaysTrueInLastCondition) { | ||
$errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); | ||
} | ||
$errors[] = $errorBuilder->build(); | ||
} | ||
} | ||
|
||
$rightType = $this->helper->getBooleanType($scope, $node->right); | ||
if ($rightType instanceof ConstantBooleanType && !$scope->isInFirstLevelStatement()) { | ||
$addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $tipText): RuleErrorBuilder { | ||
if (!$this->treatPhpDocTypesAsCertain) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
$booleanNativeType = $this->helper->getNativeBooleanType( | ||
$scope, | ||
$node->right, | ||
); | ||
if ($booleanNativeType instanceof ConstantBooleanType) { | ||
return $ruleErrorBuilder; | ||
} | ||
|
||
return $ruleErrorBuilder->tip($tipText); | ||
}; | ||
|
||
$isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); | ||
if (!$rightType->getValue() || $isLast !== true || $this->reportAlwaysTrueInLastCondition) { | ||
$errorBuilder = $addTipRight(RuleErrorBuilder::message(sprintf( | ||
'Right side of xor is always %s.', | ||
$rightType->getValue() ? 'true' : 'false', | ||
)))->line($node->right->getLine()); | ||
if ($rightType->getValue() && $isLast === false && !$this->reportAlwaysTrueInLastCondition) { | ||
$errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); | ||
} | ||
$errors[] = $errorBuilder->build(); | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
71 changes: 71 additions & 0 deletions
71
tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.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,71 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Comparison; | ||
|
||
use PHPStan\Rules\Rule as TRule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<LogicalXorConstantConditionRule> | ||
*/ | ||
class LogicalXorConstantConditionRuleTest extends RuleTestCase | ||
{ | ||
|
||
private bool $treatPhpDocTypesAsCertain; | ||
|
||
private bool $reportAlwaysTrueInLastCondition = false; | ||
|
||
protected function getRule(): TRule | ||
{ | ||
return new LogicalXorConstantConditionRule( | ||
new ConstantConditionRuleHelper( | ||
new ImpossibleCheckTypeHelper( | ||
$this->createReflectionProvider(), | ||
$this->getTypeSpecifier(), | ||
[], | ||
$this->treatPhpDocTypesAsCertain, | ||
true, | ||
), | ||
$this->treatPhpDocTypesAsCertain, | ||
true, | ||
), | ||
$this->treatPhpDocTypesAsCertain, | ||
$this->reportAlwaysTrueInLastCondition, | ||
); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
$tipText = '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%</>.'; | ||
$this->treatPhpDocTypesAsCertain = true; | ||
$this->analyse([__DIR__ . '/data/logical-xor.php'], [ | ||
[ | ||
'Left side of xor is always true.', | ||
14, | ||
], | ||
[ | ||
'Right side of xor is always false.', | ||
14, | ||
], | ||
[ | ||
'Left side of xor is always false.', | ||
17, | ||
], | ||
[ | ||
'Right side of xor is always true.', | ||
17, | ||
], | ||
[ | ||
'Left side of xor is always true.', | ||
20, | ||
$tipText, | ||
], | ||
[ | ||
'Right side of xor is always true.', | ||
20, | ||
$tipText, | ||
], | ||
]); | ||
} | ||
|
||
} |
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,25 @@ | ||
<?php | ||
|
||
namespace ConstantLogicalXor; | ||
|
||
class Foo | ||
{ | ||
|
||
/** | ||
* @param object $a | ||
* @param object $b | ||
*/ | ||
public function doFoo($a, $b) | ||
{ | ||
if (1 xor 0) { | ||
|
||
} | ||
if (0 xor 1) { | ||
|
||
} | ||
if ($a xor $b) { | ||
|
||
} | ||
} | ||
|
||
} |