Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: phpstan/phpstan-strict-rules
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.0.3
Choose a base ref
...
head repository: phpstan/phpstan-strict-rules
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2.0.4
Choose a head ref
  • 5 commits
  • 10 files changed
  • 2 contributors

Commits on Jan 28, 2025

  1. Update LICENSE

    ondrejmirtes authored Jan 28, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    jmikola Jeremy Mikola
    Copy the full SHA
    a0e1926 View commit details
  2. Update LICENSE

    ondrejmirtes authored Jan 28, 2025
    Copy the full SHA
    342fa26 View commit details

Commits on Mar 6, 2025

  1. Fix build

    ondrejmirtes committed Mar 6, 2025
    Copy the full SHA
    51abacd View commit details

Commits on Mar 12, 2025

  1. Fix build

    ondrejmirtes committed Mar 12, 2025
    Copy the full SHA
    b20f78c View commit details

Commits on Mar 18, 2025

  1. Check for boolean in while conditions

    VincentLanglet authored Mar 18, 2025
    Copy the full SHA
    3e139cb View commit details
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2016 Ondřej Mirtes
Copyright (c) 2025 PHPStan s.r.o.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
[PHPStan](https://phpstan.org/) focuses on finding bugs in your code. But in PHP there's a lot of leeway in how stuff can be written. This repository contains additional rules that revolve around strictly and strongly typed code with no loose casting for those who want additional safety in extremely defensive programming:

* Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `||`.
* Require booleans in `while` and `do while` loop conditions.
* Require numeric operands or arrays in `+` and numeric operands in `-`/`*`/`/`/`**`/`%`.
* Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`.
* These functions contain a `$strict` parameter for better type safety, it must be set to `true`:
@@ -64,6 +65,7 @@ parameters:
strictRules:
disallowedLooseComparison: false
booleansInConditions: false
booleansInLoopConditions: false
uselessCast: false
requireParentConstructorCall: false
disallowedBacktick: false
12 changes: 12 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ parameters:
allRules: true
disallowedLooseComparison: %strictRules.allRules%
booleansInConditions: %strictRules.allRules%
booleansInLoopConditions: [%strictRules.allRules%, %featureToggles.bleedingEdge%]
uselessCast: %strictRules.allRules%
requireParentConstructorCall: %strictRules.allRules%
disallowedBacktick: %strictRules.allRules%
@@ -37,6 +38,7 @@ parametersSchema:
allRules: anyOf(bool(), arrayOf(bool())),
disallowedLooseComparison: anyOf(bool(), arrayOf(bool())),
booleansInConditions: anyOf(bool(), arrayOf(bool()))
booleansInLoopConditions: anyOf(bool(), arrayOf(bool()))
uselessCast: anyOf(bool(), arrayOf(bool()))
requireParentConstructorCall: anyOf(bool(), arrayOf(bool()))
disallowedBacktick: anyOf(bool(), arrayOf(bool()))
@@ -64,12 +66,16 @@ conditionalTags:
phpstan.rules.rule: %strictRules.booleansInConditions%
PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule:
phpstan.rules.rule: %strictRules.booleansInConditions%
PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule:
phpstan.rules.rule: %strictRules.booleansInLoopConditions%
PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule:
phpstan.rules.rule: %strictRules.booleansInConditions%
PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule:
phpstan.rules.rule: %strictRules.booleansInConditions%
PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule:
phpstan.rules.rule: %strictRules.booleansInConditions%
PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule:
phpstan.rules.rule: %strictRules.booleansInLoopConditions%
PHPStan\Rules\Cast\UselessCastRule:
phpstan.rules.rule: %strictRules.uselessCast%
PHPStan\Rules\Classes\RequireParentConstructCallRule:
@@ -163,6 +169,9 @@ services:
-
class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule

-
class: PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule

-
class: PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule

@@ -172,6 +181,9 @@ services:
-
class: PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule

-
class: PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule

-
class: PHPStan\Rules\Cast\UselessCastRule
arguments:
46 changes: 46 additions & 0 deletions src/Rules/BooleansInConditions/BooleanInDoWhileConditionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\BooleansInConditions;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

/**
* @implements Rule<Node\Stmt\Do_>
*/
class BooleanInDoWhileConditionRule implements Rule
{

private BooleanRuleHelper $helper;

public function __construct(BooleanRuleHelper $helper)
{
$this->helper = $helper;
}

public function getNodeType(): string
{
return Node\Stmt\Do_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if ($this->helper->passesAsBoolean($scope, $node->cond)) {
return [];
}

$conditionExpressionType = $scope->getType($node->cond);

return [
RuleErrorBuilder::message(sprintf(
'Only booleans are allowed in a do-while condition, %s given.',
$conditionExpressionType->describe(VerbosityLevel::typeOnly()),
))->identifier('doWhile.condNotBoolean')->build(),
];
}

}
46 changes: 46 additions & 0 deletions src/Rules/BooleansInConditions/BooleanInWhileConditionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\BooleansInConditions;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

/**
* @implements Rule<Node\Stmt\While_>
*/
class BooleanInWhileConditionRule implements Rule
{

private BooleanRuleHelper $helper;

public function __construct(BooleanRuleHelper $helper)
{
$this->helper = $helper;
}

public function getNodeType(): string
{
return Node\Stmt\While_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if ($this->helper->passesAsBoolean($scope, $node->cond)) {
return [];
}

$conditionExpressionType = $scope->getType($node->cond);

return [
RuleErrorBuilder::message(sprintf(
'Only booleans are allowed in a while condition, %s given.',
$conditionExpressionType->describe(VerbosityLevel::typeOnly()),
))->identifier('while.condNotBoolean')->build(),
];
}

}
8 changes: 2 additions & 6 deletions src/Rules/VariableVariables/VariablePropertyFetchRule.php
Original file line number Diff line number Diff line change
@@ -71,8 +71,7 @@ private function isSimpleXMLElement(
ClassReflection $classReflection
): bool
{
return $classReflection->getName() === SimpleXMLElement::class
|| $classReflection->isSubclassOf(SimpleXMLElement::class);
return $classReflection->is(SimpleXMLElement::class);
}

private function isUniversalObjectCrate(
@@ -84,10 +83,7 @@ private function isUniversalObjectCrate(
continue;
}

if (
$classReflection->getName() === $className
|| $classReflection->isSubclassOf($className)
) {
if ($classReflection->is($className)) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\BooleansInConditions;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<BooleanInDoWhileConditionRule>
*/
class BooleanInDoWhileConditionRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new BooleanInDoWhileConditionRule(
new BooleanRuleHelper(
self::getContainer()->getByType(RuleLevelHelper::class),
),
);
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/conditions.php'], [
[
'Only booleans are allowed in a do-while condition, string given.',
60,
],
]);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\BooleansInConditions;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<BooleanInWhileConditionRule>
*/
class BooleanInWhileConditionRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new BooleanInWhileConditionRule(
new BooleanRuleHelper(
self::getContainer()->getByType(RuleLevelHelper::class),
),
);
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/conditions.php'], [
[
'Only booleans are allowed in a while condition, string given.',
55,
],
]);
}

}
10 changes: 10 additions & 0 deletions tests/Rules/BooleansInConditions/data/conditions.php
Original file line number Diff line number Diff line change
@@ -48,3 +48,13 @@
$explicitMixed and $bool;
$bool or $explicitMixed;
$explicitMixed or $bool;

$someBool = true;
$someString = 'string';
while ($someBool) { $someBool = !$someBool; }
while ($someString) { $someString = ''; }

$someBool = true;
$someString = 'string';
do { $someBool = !$someBool; } while ($someBool);
do { $someString = ''; } while ($someString);
Original file line number Diff line number Diff line change
@@ -62,4 +62,9 @@ public function testRule(): void
]);
}

protected function shouldPolluteScopeWithLoopInitialAssignments(): bool
{
return false;
}

}