Skip to content

Commit 142d99d

Browse files
committed
Only booleans in conditions - respect levels more
1 parent 41f3063 commit 142d99d

19 files changed

+241
-30
lines changed

rules.neon

+2
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@ rules:
2929
- PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule
3030

3131
services:
32+
-
33+
class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper
3234
-
3335
class: PHPStan\Rules\Operators\OperatorRuleHelper

src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php

+11-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInBooleanAndRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\BooleanAnd::class;
@@ -19,18 +27,17 @@ public function getNodeType(): string
1927
*/
2028
public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array
2129
{
22-
$leftType = $scope->getType($node->left);
23-
2430
$messages = [];
25-
if (!BooleanRuleHelper::passesAsBoolean($leftType)) {
31+
if (!$this->helper->passesAsBoolean($scope, $node->left)) {
32+
$leftType = $scope->getType($node->left);
2633
$messages[] = sprintf(
2734
'Only booleans are allowed in &&, %s given on the left side.',
2835
$leftType->describe(VerbosityLevel::typeOnly())
2936
);
3037
}
3138

3239
$rightType = $scope->getType($node->right);
33-
if (!BooleanRuleHelper::passesAsBoolean($rightType)) {
40+
if (!$this->helper->passesAsBoolean($scope, $node->right)) {
3441
$messages[] = sprintf(
3542
'Only booleans are allowed in &&, %s given on the right side.',
3643
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInBooleanNotRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BooleanNot::class;
@@ -19,11 +27,12 @@ public function getNodeType(): string
1927
*/
2028
public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array
2129
{
22-
$expressionType = $scope->getType($node->expr);
23-
if (BooleanRuleHelper::passesAsBoolean($expressionType)) {
30+
if ($this->helper->passesAsBoolean($scope, $node->expr)) {
2431
return [];
2532
}
2633

34+
$expressionType = $scope->getType($node->expr);
35+
2736
return [
2837
sprintf(
2938
'Only booleans are allowed in a negated boolean, %s given.',

src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php

+12-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInBooleanOrRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\BinaryOp\BooleanOr::class;
@@ -19,17 +27,17 @@ public function getNodeType(): string
1927
*/
2028
public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array
2129
{
22-
$leftType = $scope->getType($node->left);
2330
$messages = [];
24-
if (!BooleanRuleHelper::passesAsBoolean($leftType)) {
31+
if (!$this->helper->passesAsBoolean($scope, $node->left)) {
32+
$leftType = $scope->getType($node->left);
2533
$messages[] = sprintf(
2634
'Only booleans are allowed in ||, %s given on the left side.',
2735
$leftType->describe(VerbosityLevel::typeOnly())
2836
);
2937
}
3038

31-
$rightType = $scope->getType($node->right);
32-
if (!BooleanRuleHelper::passesAsBoolean($rightType)) {
39+
if (!$this->helper->passesAsBoolean($scope, $node->right)) {
40+
$rightType = $scope->getType($node->right);
3341
$messages[] = sprintf(
3442
'Only booleans are allowed in ||, %s given on the right side.',
3543
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInElseIfConditionRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Stmt\ElseIf_::class;
@@ -19,11 +27,12 @@ public function getNodeType(): string
1927
*/
2028
public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array
2129
{
22-
$conditionExpressionType = $scope->getType($node->cond);
23-
if (BooleanRuleHelper::passesAsBoolean($conditionExpressionType)) {
30+
if ($this->helper->passesAsBoolean($scope, $node->cond)) {
2431
return [];
2532
}
2633

34+
$conditionExpressionType = $scope->getType($node->cond);
35+
2736
return [
2837
sprintf(
2938
'Only booleans are allowed in an elseif condition, %s given.',

src/Rules/BooleansInConditions/BooleanInIfConditionRule.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInIfConditionRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Stmt\If_::class;
@@ -19,11 +27,12 @@ public function getNodeType(): string
1927
*/
2028
public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array
2129
{
22-
$conditionExpressionType = $scope->getType($node->cond);
23-
if (BooleanRuleHelper::passesAsBoolean($conditionExpressionType)) {
30+
if ($this->helper->passesAsBoolean($scope, $node->cond)) {
2431
return [];
2532
}
2633

34+
$conditionExpressionType = $scope->getType($node->cond);
35+
2736
return [
2837
sprintf(
2938
'Only booleans are allowed in an if condition, %s given.',

src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
class BooleanInTernaryOperatorRule implements \PHPStan\Rules\Rule
88
{
99

10+
/** @var BooleanRuleHelper */
11+
private $helper;
12+
13+
public function __construct(BooleanRuleHelper $helper)
14+
{
15+
$this->helper = $helper;
16+
}
17+
1018
public function getNodeType(): string
1119
{
1220
return \PhpParser\Node\Expr\Ternary::class;
@@ -23,11 +31,12 @@ public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scop
2331
return []; // elvis ?:
2432
}
2533

26-
$conditionExpressionType = $scope->getType($node->cond);
27-
if (BooleanRuleHelper::passesAsBoolean($conditionExpressionType)) {
34+
if ($this->helper->passesAsBoolean($scope, $node->cond)) {
2835
return [];
2936
}
3037

38+
$conditionExpressionType = $scope->getType($node->cond);
39+
3140
return [
3241
sprintf(
3342
'Only booleans are allowed in a ternary operator condition, %s given.',

src/Rules/BooleansInConditions/BooleanRuleHelper.php

+31-8
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,45 @@
22

33
namespace PHPStan\Rules\BooleansInConditions;
44

5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\RuleLevelHelper;
8+
use PHPStan\Type\BooleanType;
9+
use PHPStan\Type\ErrorType;
10+
use PHPStan\Type\MixedType;
11+
use PHPStan\Type\Type;
12+
513
class BooleanRuleHelper
614
{
715

8-
public static function passesAsBoolean(\PHPStan\Type\Type $type): bool
16+
/** @var \PHPStan\Rules\RuleLevelHelper */
17+
private $ruleLevelHelper;
18+
19+
public function __construct(RuleLevelHelper $ruleLevelHelper)
920
{
10-
if ($type instanceof \PHPStan\Type\BooleanType) {
11-
return true;
21+
$this->ruleLevelHelper = $ruleLevelHelper;
22+
}
23+
24+
public function passesAsBoolean(Scope $scope, Expr $expr): bool
25+
{
26+
$type = $scope->getType($expr);
27+
if ($type instanceof MixedType) {
28+
return !$type->isExplicitMixed();
1229
}
13-
if (
14-
$type instanceof \PHPStan\Type\MixedType
15-
&& !$type->isExplicitMixed()
16-
) {
30+
$typeToCheck = $this->ruleLevelHelper->findTypeToCheck(
31+
$scope,
32+
$expr,
33+
'',
34+
function (Type $type): bool {
35+
return $type instanceof BooleanType;
36+
}
37+
);
38+
$foundType = $typeToCheck->getType();
39+
if ($foundType instanceof ErrorType) {
1740
return true;
1841
}
1942

20-
return false;
43+
return $foundType instanceof BooleanType;
2144
}
2245

2346
}

tests/Levels/LevelsIntegrationTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public function dataTopics(): array
1212
{
1313
return [
1414
['arithmeticOperators'],
15+
['onlyBooleans'],
1516
];
1617
}
1718

tests/Levels/data/onlyBooleans-0.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Method PHPStan\\Levels\\OnlyBooleans\\Foo::doFoo() has parameter $mixed with no typehint specified",
4+
"line": 14,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Only booleans are allowed in an if condition, mixed given.",
9+
"line": 32,
10+
"ignorable": true
11+
}
12+
]

tests/Levels/data/onlyBooleans-2.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Only booleans are allowed in an if condition, int given.",
4+
"line": 26,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Only booleans are allowed in an if condition, float|string given.",
9+
"line": 38,
10+
"ignorable": true
11+
}
12+
]

tests/Levels/data/onlyBooleans-6.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "Only booleans are allowed in an if condition, int|false given.",
4+
"line": 35,
5+
"ignorable": true
6+
}
7+
]

tests/Levels/data/onlyBooleans.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Levels\OnlyBooleans;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param bool $bool
10+
* @param mixed $explicitMixed
11+
* @param int|false $intOrFalse
12+
* @param float|string $floatOrString
13+
*/
14+
public function doFoo(
15+
bool $bool,
16+
int $int,
17+
$mixed,
18+
$explicitMixed,
19+
$intOrFalse,
20+
$floatOrString
21+
): void
22+
{
23+
if ($bool) {
24+
25+
}
26+
if ($int) {
27+
28+
}
29+
if ($mixed) {
30+
31+
}
32+
if ($explicitMixed) {
33+
34+
}
35+
if ($intOrFalse) {
36+
37+
}
38+
if ($floatOrString) {
39+
40+
}
41+
}
42+
43+
}

tests/Rules/BooleansInConditions/BooleanInBooleanAndRuleTest.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
namespace PHPStan\Rules\BooleansInConditions;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class BooleanInBooleanAndRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new BooleanInBooleanAndRule();
13+
return new BooleanInBooleanAndRule(
14+
new BooleanRuleHelper(
15+
new RuleLevelHelper(
16+
$this->createBroker(),
17+
true,
18+
false,
19+
true
20+
)
21+
)
22+
);
1323
}
1424

1525
public function testRule(): void

tests/Rules/BooleansInConditions/BooleanInBooleanNotRuleTest.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
namespace PHPStan\Rules\BooleansInConditions;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67

78
class BooleanInBooleanNotRuleTest extends \PHPStan\Testing\RuleTestCase
89
{
910

1011
protected function getRule(): Rule
1112
{
12-
return new BooleanInBooleanNotRule();
13+
return new BooleanInBooleanNotRule(
14+
new BooleanRuleHelper(
15+
new RuleLevelHelper(
16+
$this->createBroker(),
17+
true,
18+
false,
19+
true
20+
)
21+
)
22+
);
1323
}
1424

1525
public function testRule(): void

0 commit comments

Comments
 (0)