diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 59ac0ed55e5..51fa41a5455 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -27,6 +27,7 @@ rules: - PHPStan\Rules\Functions\InnerFunctionRule - PHPStan\Rules\Functions\NonExistentDefinedFunctionRule - PHPStan\Rules\Functions\PrintfParametersRule + - PHPStan\Rules\Functions\RandomIntParametersRule - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule - PHPStan\Rules\Properties\AccessPropertiesInAssignRule - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php new file mode 100644 index 00000000000..3b1ed4ed071 --- /dev/null +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -0,0 +1,100 @@ + + */ +class RandomIntParametersRule implements \PHPStan\Rules\Rule +{ + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } + + if (strtolower((string) $node->name) !== 'random_int') { + return []; + } + + $minType = $scope->getType($node->args[0]->value)->toInteger(); + $maxType = $scope->getType($node->args[1]->value)->toInteger(); + + if ($minType instanceof ConstantIntegerType + && $maxType instanceof ConstantIntegerType + && $minType->getValue() > $maxType->getValue() + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot call random_int() with $min parameter (%d) greater than $max parameter (%d).', + $minType->getValue(), + $maxType->getValue() + ))->build(), + ]; + } + + if ($minType instanceof IntegerRangeType + && $maxType instanceof ConstantIntegerType + && $minType->getMax() > $maxType->getValue() + ) { + $message = $minType->getMin() > $maxType->getValue() + ? 'Cannot call random_int() with $min parameter (%s) greater than $max parameter (%d).' + : 'Cannot call random_int() when $min parameter (%s) can be greater than $max parameter (%d).'; + + return [ + RuleErrorBuilder::message(sprintf( + $message, + $minType->describe(VerbosityLevel::value()), + $maxType->getValue() + ))->build(), + ]; + } + + if ($minType instanceof ConstantIntegerType + && $maxType instanceof IntegerRangeType + && $minType->getValue() > $maxType->getMin() + ) { + $message = $minType->getValue() > $maxType->getMax() + ? 'Cannot call random_int() with $max parameter (%s) less than $min parameter (%d).' + : 'Cannot call random_int() when $max parameter (%s) can be less than $min parameter (%d).'; + + return [ + RuleErrorBuilder::message(sprintf( + $message, + $maxType->describe(VerbosityLevel::value()), + $minType->getValue() + ))->build(), + ]; + } + + if ($minType instanceof IntegerRangeType + && $maxType instanceof IntegerRangeType + && $minType->getMax() > $maxType->getMin() + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot call random_int() with intersecting $min (%s) and $max (%s) parameters.', + $minType->describe(VerbosityLevel::value()), + $maxType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php new file mode 100644 index 00000000000..e7bed780d86 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php @@ -0,0 +1,54 @@ + + */ +class RandomIntParametersRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new RandomIntParametersRule(); + } + + public function testFile(): void + { + $this->analyse([__DIR__ . '/data/random-int.php'], [ + [ + 'Cannot call random_int() with $min parameter (1) greater than $max parameter (0).', + 8, + ], + [ + 'Cannot call random_int() with $min parameter (0) greater than $max parameter (-1).', + 9, + ], + [ + 'Cannot call random_int() with $max parameter (int<-10, -1>) less than $min parameter (0).', + 11, + ], + [ + 'Cannot call random_int() when $max parameter (int<-10, 10>) can be less than $min parameter (0).', + 12, + ], + [ + 'Cannot call random_int() with $min parameter (int<1, 10>) greater than $max parameter (0).', + 15, + ], + [ + 'Cannot call random_int() when $min parameter (int<-10, 10>) can be greater than $max parameter (0).', + 16, + ], + [ + 'Cannot call random_int() with intersecting $min (int<-5, 1>) and $max (int<0, 5>) parameters.', + 19, + ], + [ + 'Cannot call random_int() with intersecting $min (int<-5, 0>) and $max (int<-1, 5>) parameters.', + 20, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/random-int.php b/tests/PHPStan/Rules/Functions/data/random-int.php new file mode 100644 index 00000000000..65c9cf6500c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/random-int.php @@ -0,0 +1,32 @@ +