From a670a59aff7cf96f75d21b974860ada10e25b2ee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Jan 2020 14:08:52 +0100 Subject: [PATCH] UselessCastRule - respect treatPhpDocTypesAsCertain --- composer.json | 2 +- rules.neon | 9 ++++- src/Rules/Cast/UselessCastRule.php | 34 +++++++++++++++-- tests/Rules/Cast/UselessCastRuleTest.php | 38 ++++++++++++++++++- .../Cast/data/useless-cast-not-phpdoc.php | 20 ++++++++++ 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 tests/Rules/Cast/data/useless-cast-not-phpdoc.php diff --git a/composer.json b/composer.json index 174f34b1..bc9ecd3e 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "~7.1", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^0.12.6" }, "require-dev": { "consistence/coding-standard": "^3.0.1", diff --git a/rules.neon b/rules.neon index 04acf2f0..4796ef00 100644 --- a/rules.neon +++ b/rules.neon @@ -17,7 +17,6 @@ rules: - PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule - PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule - PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule - - PHPStan\Rules\Cast\UselessCastRule - PHPStan\Rules\Classes\RequireParentConstructCallRule - PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule - PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule @@ -45,6 +44,14 @@ rules: services: - class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper + + - + class: PHPStan\Rules\Cast\UselessCastRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Operators\OperatorRuleHelper - diff --git a/src/Rules/Cast/UselessCastRule.php b/src/Rules/Cast/UselessCastRule.php index 14c5f0dd..272c17c9 100644 --- a/src/Rules/Cast/UselessCastRule.php +++ b/src/Rules/Cast/UselessCastRule.php @@ -5,6 +5,8 @@ use PhpParser\Node; use PhpParser\Node\Expr\Cast; use PHPStan\Analyser\Scope; +use PHPStan\Rules\RuleError; +use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ErrorType; use PHPStan\Type\TypeUtils; use PHPStan\Type\VerbosityLevel; @@ -12,6 +14,14 @@ class UselessCastRule implements \PHPStan\Rules\Rule { + /** @var bool */ + private $treatPhpDocTypesAsCertain; + + public function __construct(bool $treatPhpDocTypesAsCertain) + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + public function getNodeType(): string { return Cast::class; @@ -20,7 +30,7 @@ public function getNodeType(): string /** * @param \PhpParser\Node\Expr\Cast $node * @param \PHPStan\Analyser\Scope $scope - * @return string[] errors + * @return RuleError[] errors */ public function processNode(Node $node, Scope $scope): array { @@ -30,14 +40,30 @@ public function processNode(Node $node, Scope $scope): array } $castType = TypeUtils::generalizeType($castType); - $expressionType = $scope->getType($node->expr); + if ($this->treatPhpDocTypesAsCertain) { + $expressionType = $scope->getType($node->expr); + } else { + $expressionType = $scope->getNativeType($node->expr); + } if ($castType->isSuperTypeOf($expressionType)->yes()) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $castType): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $expressionTypeWithoutPhpDoc = $scope->getNativeType($node->expr); + if ($castType->isSuperTypeOf($expressionTypeWithoutPhpDoc)->yes()) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; return [ - sprintf( + $addTip(RuleErrorBuilder::message(sprintf( 'Casting to %s something that\'s already %s.', $castType->describe(VerbosityLevel::typeOnly()), $expressionType->describe(VerbosityLevel::typeOnly()) - ), + )))->build(), ]; } diff --git a/tests/Rules/Cast/UselessCastRuleTest.php b/tests/Rules/Cast/UselessCastRuleTest.php index cf220fc8..9d8bcf0a 100644 --- a/tests/Rules/Cast/UselessCastRuleTest.php +++ b/tests/Rules/Cast/UselessCastRuleTest.php @@ -5,14 +5,23 @@ class UselessCastRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $treatPhpDocTypesAsCertain; + protected function getRule(): \PHPStan\Rules\Rule { - return new UselessCastRule(); + return new UselessCastRule($this->treatPhpDocTypesAsCertain); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; } public function testUselessCast(): void { require_once __DIR__ . '/data/useless-cast.php'; + $this->treatPhpDocTypesAsCertain = true; $this->analyse( [__DIR__ . '/data/useless-cast.php'], [ @@ -40,4 +49,31 @@ public function testUselessCast(): void ); } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/useless-cast-not-phpdoc.php'], [ + [ + 'Casting to int something that\'s already int.', + 16, + ], + ]); + } + + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/useless-cast-not-phpdoc.php'], [ + [ + 'Casting to int something that\'s already int.', + 16, + ], + [ + 'Casting to int something that\'s already int.', + 17, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + } diff --git a/tests/Rules/Cast/data/useless-cast-not-phpdoc.php b/tests/Rules/Cast/data/useless-cast-not-phpdoc.php new file mode 100644 index 00000000..aef39506 --- /dev/null +++ b/tests/Rules/Cast/data/useless-cast-not-phpdoc.php @@ -0,0 +1,20 @@ +