diff --git a/README.md b/README.md index 528d150d..4593269f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `||`. * Require numeric operands or arrays in `+` and numeric operands in `-`/`*`/`/`/`**`/`%`. * Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. +* Require numeric operands or `DateTimeInterface` objects in loose comparison `==`/`!=`/`<`/`>`/`<=`/`>=`/`<=>`. Configure this by setting `allowedLooseComparison` to any (union) type (default `int|float|DateTimeInterface`). * These functions contain a `$strict` parameter for better type safety, it must be set to `true`: * `in_array` (3rd parameter) * `array_search` (3rd parameter) diff --git a/rules.neon b/rules.neon index f7fb61cd..2905f058 100644 --- a/rules.neon +++ b/rules.neon @@ -9,6 +9,10 @@ parameters: checkMissingClosureNativeReturnTypehintRule: true reportMaybesInMethodSignatures: true reportStaticMethodSignatures: true + allowedLooseComparison: int|float|DateTimeInterface + +parametersSchema: + allowedLooseComparison: string() rules: - PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule @@ -33,6 +37,14 @@ rules: - PHPStan\Rules\Operators\OperandsInArithmeticModuloRule - PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule - PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule + - PHPStan\Rules\Operators\OperandsInComparisonEqualRule + - PHPStan\Rules\Operators\OperandsInComparisonGreaterOrEqualRule + - PHPStan\Rules\Operators\OperandsInComparisonGreaterRule + - PHPStan\Rules\Operators\OperandsInComparisonNotEqualRule + - PHPStan\Rules\Operators\OperandsInComparisonSmallerOrEqualRule + - PHPStan\Rules\Operators\OperandsInComparisonSmallerRule + - PHPStan\Rules\Operators\OperandsInComparisonSpaceshipRule + - PHPStan\Rules\Properties\MissingPropertyTypehintRule - PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule - PHPStan\Rules\StrictCalls\StrictFunctionCallsRule - PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule @@ -46,6 +58,8 @@ services: class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper - class: PHPStan\Rules\Operators\OperatorRuleHelper + arguments: + allowedLooseComparison: %allowedLooseComparison% - class: PHPStan\Rules\VariableVariables\VariablePropertyFetchRule arguments: diff --git a/src/Rules/Operators/OperandsInComparisonEqualRule.php b/src/Rules/Operators/OperandsInComparisonEqualRule.php new file mode 100644 index 00000000..009e46aa --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonEqualRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\Equal::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in ==, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in ==, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonGreaterOrEqualRule.php b/src/Rules/Operators/OperandsInComparisonGreaterOrEqualRule.php new file mode 100644 index 00000000..025962f7 --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonGreaterOrEqualRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in >=, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in >=, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonGreaterRule.php b/src/Rules/Operators/OperandsInComparisonGreaterRule.php new file mode 100644 index 00000000..bf537071 --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonGreaterRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\Greater::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in >, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in >, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonNotEqualRule.php b/src/Rules/Operators/OperandsInComparisonNotEqualRule.php new file mode 100644 index 00000000..d4834115 --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonNotEqualRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\NotEqual::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\BooleanAnd $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in !=, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in !=, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonSmallerOrEqualRule.php b/src/Rules/Operators/OperandsInComparisonSmallerOrEqualRule.php new file mode 100644 index 00000000..1814463f --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonSmallerOrEqualRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in <=, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in <=, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonSmallerRule.php b/src/Rules/Operators/OperandsInComparisonSmallerRule.php new file mode 100644 index 00000000..c4cf664c --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonSmallerRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\Smaller::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in <, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in <, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperandsInComparisonSpaceshipRule.php b/src/Rules/Operators/OperandsInComparisonSpaceshipRule.php new file mode 100644 index 00000000..dc9583d6 --- /dev/null +++ b/src/Rules/Operators/OperandsInComparisonSpaceshipRule.php @@ -0,0 +1,52 @@ +helper = $helper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BinaryOp\Spaceship::class; + } + + /** + * @param \PhpParser\Node\Expr\BinaryOp\Equal $node + * @param \PHPStan\Analyser\Scope $scope + * @return string[] errors + */ + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); + + $messages = []; + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->left)) { + $messages[] = sprintf( + 'Only %s is allowed in <=>, %s given on the left side.', + $this->helper->getAllowedLooseComparison(), + $leftType->describe(VerbosityLevel::typeOnly()) + ); + } + if (!$this->helper->isValidForLooseComparisonOperation($scope, $node->right)) { + $messages[] = sprintf( + 'Only %s is allowed in <=>, %s given on the right side.', + $this->helper->getAllowedLooseComparison(), + $rightType->describe(VerbosityLevel::typeOnly()) + ); + } + + return $messages; + } + +} diff --git a/src/Rules/Operators/OperatorRuleHelper.php b/src/Rules/Operators/OperatorRuleHelper.php index ba3634ac..db017490 100644 --- a/src/Rules/Operators/OperatorRuleHelper.php +++ b/src/Rules/Operators/OperatorRuleHelper.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; @@ -18,9 +19,27 @@ class OperatorRuleHelper /** @var \PHPStan\Rules\RuleLevelHelper */ private $ruleLevelHelper; - public function __construct(RuleLevelHelper $ruleLevelHelper) + /** @var string */ + private $allowedLooseComparison; + + /** @var Type */ + private $looseComparisonAllowedType; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + TypeStringResolver $typeStringResolver, + string $allowedLooseComparison + ) { $this->ruleLevelHelper = $ruleLevelHelper; + + $this->allowedLooseComparison = $allowedLooseComparison; + $this->looseComparisonAllowedType = $typeStringResolver->resolve($allowedLooseComparison); + } + + public function getAllowedLooseComparison(): string + { + return $this->allowedLooseComparison; } public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool @@ -35,7 +54,7 @@ public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool return true; } - return $this->isSubtypeOfNumber($scope, $expr); + return $this->isSubtypeOf($scope, $expr, new UnionType([new IntegerType(), new FloatType()])); } public function isValidForIncrementOrDecrement(Scope $scope, Expr $expr): bool @@ -45,13 +64,26 @@ public function isValidForIncrementOrDecrement(Scope $scope, Expr $expr): bool return true; } - return $this->isSubtypeOfNumber($scope, $expr); + return $this->isSubtypeOf($scope, $expr, new UnionType([new IntegerType(), new FloatType()])); } - private function isSubtypeOfNumber(Scope $scope, Expr $expr): bool + public function isValidForLooseComparisonOperation(Scope $scope, Expr $expr): bool { - $acceptedType = new UnionType([new IntegerType(), new FloatType()]); + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + // already reported by PHPStan core + if ($type->toNumber() instanceof ErrorType) { + return true; + } + + return $this->isSubtypeOf($scope, $expr, $this->looseComparisonAllowedType); + } + + private function isSubtypeOf(Scope $scope, Expr $expr, Type $acceptedType): bool + { $type = $this->ruleLevelHelper->findTypeToCheck( $scope, $expr, diff --git a/tests/Rules/Operators/OperandInArithmeticIncrementOrDecrementRuleTest.php b/tests/Rules/Operators/OperandInArithmeticIncrementOrDecrementRuleTest.php index 73a8ef0f..d2593418 100644 --- a/tests/Rules/Operators/OperandInArithmeticIncrementOrDecrementRuleTest.php +++ b/tests/Rules/Operators/OperandInArithmeticIncrementOrDecrementRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,7 +13,9 @@ protected function getRule(): Rule { return $this->createRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } diff --git a/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php index 0c71907e..c87a7974 100644 --- a/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticAdditionRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticAdditionRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in +, string given on the right side.', 25, diff --git a/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php index a43a0bac..a301ed78 100644 --- a/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticDivisionRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticDivisionRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in /, string given on the right side.', 64, diff --git a/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php index f8c0f2d8..f6906359 100644 --- a/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticExponentiationRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticExponentiationRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in **, string given on the right side.', 77, diff --git a/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php index a803c97e..c5d3aba9 100644 --- a/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticModuloRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticModuloRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in %, string given on the right side.', 90, diff --git a/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php index 643a3b3d..94aeed49 100644 --- a/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticMultiplicationRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticMultiplicationRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in *, string given on the right side.', 51, diff --git a/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php b/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php index 522f2b9c..92db73f0 100644 --- a/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php +++ b/tests/Rules/Operators/OperandsInArithmeticSubtractionRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Operators; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; @@ -12,14 +13,16 @@ protected function getRule(): Rule { return new OperandsInArithmeticSubtractionRule( new OperatorRuleHelper( - new RuleLevelHelper($this->createBroker(), true, false, true) + new RuleLevelHelper($this->createBroker(), true, false, true), + $this->createMock(TypeStringResolver::class), + '' ) ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/operators.php'], [ + $this->analyse([__DIR__ . '/data/arithmetic-operators.php'], [ [ 'Only numeric types are allowed in -, string given on the right side.', 38, diff --git a/tests/Rules/Operators/OperandsInComparisonEqualRuleTest.php b/tests/Rules/Operators/OperandsInComparisonEqualRuleTest.php new file mode 100644 index 00000000..62919176 --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonEqualRuleTest.php @@ -0,0 +1,52 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in ==, string given on the right side.', + 25, + ], + [ + 'Only int|float|DateTimeInterface is allowed in ==, null given on the right side.', + 28, + ], + [ + 'Only int|float|DateTimeInterface is allowed in ==, null given on the right side.', + 34, + ], + [ + 'Only int|float|DateTimeInterface is allowed in ==, (array|false) given on the left side.', + 148, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonGreaterOrEqualRuleTest.php b/tests/Rules/Operators/OperandsInComparisonGreaterOrEqualRuleTest.php new file mode 100644 index 00000000..3202a6be --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonGreaterOrEqualRuleTest.php @@ -0,0 +1,48 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in >=, string given on the right side.', + 108, + ], + [ + 'Only int|float|DateTimeInterface is allowed in >=, null given on the right side.', + 111, + ], + [ + 'Only int|float|DateTimeInterface is allowed in >=, null given on the right side.', + 117, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonGreaterRuleTest.php b/tests/Rules/Operators/OperandsInComparisonGreaterRuleTest.php new file mode 100644 index 00000000..716ae8ec --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonGreaterRuleTest.php @@ -0,0 +1,48 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in >, string given on the right side.', + 76, + ], + [ + 'Only int|float|DateTimeInterface is allowed in >, null given on the right side.', + 79, + ], + [ + 'Only int|float|DateTimeInterface is allowed in >, null given on the right side.', + 85, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonNotEqualRuleTest.php b/tests/Rules/Operators/OperandsInComparisonNotEqualRuleTest.php new file mode 100644 index 00000000..3ee028c9 --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonNotEqualRuleTest.php @@ -0,0 +1,52 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in !=, string given on the right side.', + 41, + ], + [ + 'Only int|float|DateTimeInterface is allowed in !=, null given on the right side.', + 44, + ], + [ + 'Only int|float|DateTimeInterface is allowed in !=, null given on the right side.', + 50, + ], + [ + 'Only int|float|DateTimeInterface is allowed in !=, string given on the right side.', + 53, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonSmallerOrEqualRuleTest.php b/tests/Rules/Operators/OperandsInComparisonSmallerOrEqualRuleTest.php new file mode 100644 index 00000000..88cb5050 --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonSmallerOrEqualRuleTest.php @@ -0,0 +1,48 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in <=, string given on the right side.', + 92, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <=, null given on the right side.', + 95, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <=, null given on the right side.', + 101, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonSmallerRuleTest.php b/tests/Rules/Operators/OperandsInComparisonSmallerRuleTest.php new file mode 100644 index 00000000..9f6fc5e0 --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonSmallerRuleTest.php @@ -0,0 +1,48 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in <, string given on the right side.', + 60, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <, null given on the right side.', + 63, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <, null given on the right side.', + 69, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/OperandsInComparisonSpaceshipRuleTest.php b/tests/Rules/Operators/OperandsInComparisonSpaceshipRuleTest.php new file mode 100644 index 00000000..6b9d4892 --- /dev/null +++ b/tests/Rules/Operators/OperandsInComparisonSpaceshipRuleTest.php @@ -0,0 +1,48 @@ +createBroker(), true, false, true), + new TypeStringResolver( + new Lexer(), + new TypeParser(), + new TypeNodeResolver([], self::getContainer()) + ), + 'int|float|DateTimeInterface' + ) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comparison-operators.php'], [ + [ + 'Only int|float|DateTimeInterface is allowed in <=>, string given on the right side.', + 124, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <=>, null given on the right side.', + 127, + ], + [ + 'Only int|float|DateTimeInterface is allowed in <=>, null given on the right side.', + 133, + ], + ]); + } + +} diff --git a/tests/Rules/Operators/data/operators.php b/tests/Rules/Operators/data/arithmetic-operators.php similarity index 100% rename from tests/Rules/Operators/data/operators.php rename to tests/Rules/Operators/data/arithmetic-operators.php diff --git a/tests/Rules/Operators/data/comparison-operators.php b/tests/Rules/Operators/data/comparison-operators.php new file mode 100644 index 00000000..16515d4b --- /dev/null +++ b/tests/Rules/Operators/data/comparison-operators.php @@ -0,0 +1,149 @@ + $int; +$int <> $string; + +$int < $int; +$int < $float; +$float < $int; +$float < $float; +$intOrFloat < $int; +$int < $string; +$int < $array; +$int < $object; +$int < $null; +$int < $date; +$date < $date; +$date < $dateImm; +$date < $int; +$date < $object; +$date < $null; + +$int > $int; +$int > $float; +$float > $int; +$float > $float; +$intOrFloat > $int; +$int > $string; +$int > $array; +$int > $object; +$int > $null; +$int > $date; +$date > $date; +$date > $dateImm; +$date > $int; +$date > $object; +$date > $null; + +$int <= $int; +$int <= $float; +$float <= $int; +$float <= $float; +$intOrFloat <= $int; +$int <= $string; +$int <= $array; +$int <= $object; +$int <= $null; +$int <= $date; +$date <= $date; +$date <= $dateImm; +$date <= $int; +$date <= $object; +$date <= $null; + +$int >= $int; +$int >= $float; +$float >= $int; +$float >= $float; +$intOrFloat >= $int; +$int >= $string; +$int >= $array; +$int >= $object; +$int >= $null; +$int >= $date; +$date >= $date; +$date >= $dateImm; +$date >= $int; +$date >= $object; +$date >= $null; + +$int <=> $int; +$int <=> $float; +$float <=> $int; +$float <=> $float; +$intOrFloat <=> $int; +$int <=> $string; +$int <=> $array; +$int <=> $object; +$int <=> $null; +$int <=> $date; +$date <=> $date; +$date <=> $dateImm; +$date <=> $int; +$date <=> $object; +$date <=> $null; + +function ($mixed, int $a, string $b) { + $mixed == $mixed; + $mixed == $a; + $a == $mixed; + $mixed == $b; + $b == $mixed; +}; + +function (array $array, int $int, $mixed) { + foreach ($array as $i => $val) { + $i == $int; + } + + explode($mixed, $mixed) == $int; +};