Skip to content

Commit f9f77ef

Browse files
VincentLangletondrejmirtes
authored andcommitted
Add numeric check on unary plus and unary minus
1 parent 1f1358d commit f9f77ef

File tree

7 files changed

+189
-1
lines changed

7 files changed

+189
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
|:---------------------------------------|:--------------------------------------------------------------------------------------------------------|
1111
| `booleansInConditions` | Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `\|\|`. |
1212
| `booleansInLoopConditions` | Require booleans in `while` and `do while` loop conditions. |
13-
| `numericOperandsInArithmeticOperators` | Require numeric operands or arrays in `+` and numeric operands in `-`/`*`/`/`/`**`/`%`. |
13+
| `numericOperandsInArithmeticOperators` | Require numeric operand in `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. |
1414
| `numericOperandsInArithmeticOperators` | Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. |
1515
| `strictFunctionCalls` | These functions contain a `$strict` parameter for better type safety, it must be set to `true`:<br>* `in_array` (3rd parameter)<br>* `array_search` (3rd parameter)<br>* `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided)<br>* `base64_decode` (2nd parameter). |
1616
| `overwriteVariablesWithLoop` | Variables assigned in `while` loop condition and `for` loop initial assignment cannot be used after the loop. |

rules.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ conditionalTags:
106106
phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators%
107107
PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule:
108108
phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators%
109+
PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule:
110+
phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%]
111+
PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule:
112+
phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%]
109113
PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule:
110114
phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators%
111115
PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule:
@@ -242,6 +246,12 @@ services:
242246
-
243247
class: PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule
244248

249+
-
250+
class: PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule
251+
252+
-
253+
class: PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule
254+
245255
-
246256
class: PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule
247257

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\UnaryMinus;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Type\VerbosityLevel;
11+
use function sprintf;
12+
13+
/**
14+
* @phpstan-implements Rule<UnaryMinus>
15+
*/
16+
class OperandInArithmeticUnaryMinusRule implements Rule
17+
{
18+
19+
private OperatorRuleHelper $helper;
20+
21+
public function __construct(OperatorRuleHelper $helper)
22+
{
23+
$this->helper = $helper;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return UnaryMinus::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
$messages = [];
34+
35+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) {
36+
$varType = $scope->getType($node->expr);
37+
38+
$messages[] = RuleErrorBuilder::message(sprintf(
39+
'Only numeric types are allowed in unary -, %s given.',
40+
$varType->describe(VerbosityLevel::typeOnly()),
41+
))->identifier('unaryMinus.nonNumeric')->build();
42+
}
43+
44+
return $messages;
45+
}
46+
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\UnaryPlus;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Type\VerbosityLevel;
11+
use function sprintf;
12+
13+
/**
14+
* @phpstan-implements Rule<UnaryPlus>
15+
*/
16+
class OperandInArithmeticUnaryPlusRule implements Rule
17+
{
18+
19+
private OperatorRuleHelper $helper;
20+
21+
public function __construct(OperatorRuleHelper $helper)
22+
{
23+
$this->helper = $helper;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return UnaryPlus::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
$messages = [];
34+
35+
if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) {
36+
$varType = $scope->getType($node->expr);
37+
38+
$messages[] = RuleErrorBuilder::message(sprintf(
39+
'Only numeric types are allowed in unary +, %s given.',
40+
$varType->describe(VerbosityLevel::typeOnly()),
41+
))->identifier('unaryPlus.nonNumeric')->build();
42+
}
43+
44+
return $messages;
45+
}
46+
47+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<OperandInArithmeticUnaryMinusRule>
11+
*/
12+
class OperandInArithmeticUnaryMinusRuleTest extends RuleTestCase
13+
{
14+
15+
protected function getRule(): Rule
16+
{
17+
return new OperandInArithmeticUnaryMinusRule(
18+
new OperatorRuleHelper(
19+
self::getContainer()->getByType(RuleLevelHelper::class),
20+
),
21+
);
22+
}
23+
24+
public function testRule(): void
25+
{
26+
$this->analyse([__DIR__ . '/data/operators.php'], [
27+
[
28+
'Only numeric types are allowed in unary -, null given.',
29+
233,
30+
],
31+
]);
32+
}
33+
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<OperandInArithmeticUnaryPlusRule>
11+
*/
12+
class OperandInArithmeticUnaryPlusRuleTest extends RuleTestCase
13+
{
14+
15+
protected function getRule(): Rule
16+
{
17+
return new OperandInArithmeticUnaryPlusRule(
18+
new OperatorRuleHelper(
19+
self::getContainer()->getByType(RuleLevelHelper::class),
20+
),
21+
);
22+
}
23+
24+
public function testRule(): void
25+
{
26+
$this->analyse([__DIR__ . '/data/operators.php'], [
27+
[
28+
'Only numeric types are allowed in unary +, null given.',
29+
225,
30+
],
31+
]);
32+
}
33+
34+
}

tests/Rules/Operators/data/operators.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,19 @@ function (array $array, int $int, $mixed) {
215215
/** @var numeric-string $numericString */
216216
$numericString = doFoo();
217217
$numericString += 1;
218+
219+
+$int;
220+
+$float;
221+
+$intOrFloat;
222+
+$string;
223+
+$array;
224+
+$object;
225+
+$null;
226+
227+
-$int;
228+
-$float;
229+
-$intOrFloat;
230+
-$string;
231+
-$array;
232+
-$object;
233+
-$null;

0 commit comments

Comments
 (0)