Skip to content

Commit

Permalink
UselessCastRule - respect treatPhpDocTypesAsCertain
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 20, 2020
1 parent 08f2e51 commit a670a59
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 7 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 8 additions & 1 deletion rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
-
Expand Down
34 changes: 30 additions & 4 deletions src/Rules/Cast/UselessCastRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
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;

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;
Expand All @@ -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
{
Expand All @@ -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 <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
};
return [
sprintf(
$addTip(RuleErrorBuilder::message(sprintf(
'Casting to %s something that\'s already %s.',
$castType->describe(VerbosityLevel::typeOnly()),
$expressionType->describe(VerbosityLevel::typeOnly())
),
)))->build(),
];
}

Expand Down
38 changes: 37 additions & 1 deletion tests/Rules/Cast/UselessCastRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
[
Expand Down Expand Up @@ -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 <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
],
]);
}

}
20 changes: 20 additions & 0 deletions tests/Rules/Cast/data/useless-cast-not-phpdoc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace UselessCastNotPhpDoc;

class Foo
{

/**
* @param int $phpDocInteger
*/
public function doFoo(
int $realInteger,
$phpDocInteger
): void
{
$foo = (int) $realInteger;
$bar = (int) $phpDocInteger;
}

}

0 comments on commit a670a59

Please sign in to comment.