diff --git a/composer.json b/composer.json index 3e7243ea..c9dae583 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "rector/rector": "^1.1", "symplify/easy-coding-standard": "^12.1", "phpstan/extension-installer": "^1.3", - "tomasvotruba/class-leak": "^0.2" + "tomasvotruba/class-leak": "^0.2", + "tracy/tracy": "^2.10" }, "autoload": { "psr-4": { diff --git a/config/extension.neon b/config/extension.neon index d8587a08..668cc7fb 100644 --- a/config/extension.neon +++ b/config/extension.neon @@ -51,7 +51,7 @@ rules: - Rector\TypePerfect\Rules\NarrowPublicClassMethodParamTypeRule - Rector\TypePerfect\Rules\NarrowPrivateClassMethodParamTypeRule - Rector\TypePerfect\Rules\NarrowReturnObjectTypeRule - - Rector\TypePerfect\Rules\NoReturnFalseInNonBoolClassMethodRule + - Rector\TypePerfect\Rules\ReturnNullOverFalseRule # no mixed - Rector\TypePerfect\Rules\NoMixedPropertyFetcherRule diff --git a/phpstan.neon b/phpstan.neon index fa36c730..8d75e428 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -18,5 +18,5 @@ parameters: - '#Method (.*?)::getCollectors\(\) return type with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue#' # overly detailed generics - - '#Class Rector\\TypePerfect\\Tests\\Rules\\(.*?) extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule#' - - '#Method Rector\\TypePerfect\\Tests\\Rules\\(.*?)\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType#' + - '#Rector\\TypePerfect\\Tests\\Rules\\(.*?) generic (class|interface)#' + - '#Method Rector\\TypePerfect\\Tests\\Rules\\(.*?)testRule\(\) has parameter \$expectedErrorsWithLines with no value type specified in iterable type array#' diff --git a/src/Rules/NoMixedMethodCallerRule.php b/src/Rules/NoMixedMethodCallerRule.php index 8e16fde2..f5878682 100644 --- a/src/Rules/NoMixedMethodCallerRule.php +++ b/src/Rules/NoMixedMethodCallerRule.php @@ -49,6 +49,7 @@ public function processNode(Node $node, Scope $scope): array } $callerType = $scope->getType($node->var); + if (! $callerType instanceof MixedType) { return []; } @@ -57,10 +58,30 @@ public function processNode(Node $node, Scope $scope): array return []; } + // if error, skip as well for false positive + if ($this->isPreviousTypeErrorType($node, $scope)) { + return []; + } + $printedMethodCall = $this->printerStandard->prettyPrintExpr($node->var); return [ sprintf(self::ERROR_MESSAGE, $printedMethodCall), ]; } + + private function isPreviousTypeErrorType(MethodCall $methodCall, Scope $scope): bool + { + $currentMethodCall = $methodCall; + while ($currentMethodCall->var instanceof MethodCall) { + $previousType = $scope->getType($currentMethodCall->var); + if ($previousType instanceof ErrorType) { + return true; + } + + $currentMethodCall = $currentMethodCall->var; + } + + return false; + } } diff --git a/src/Rules/NoReturnFalseInNonBoolClassMethodRule.php b/src/Rules/ReturnNullOverFalseRule.php similarity index 96% rename from src/Rules/NoReturnFalseInNonBoolClassMethodRule.php rename to src/Rules/ReturnNullOverFalseRule.php index ef8db8ff..79770ad1 100644 --- a/src/Rules/NoReturnFalseInNonBoolClassMethodRule.php +++ b/src/Rules/ReturnNullOverFalseRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -final readonly class NoReturnFalseInNonBoolClassMethodRule implements Rule +final readonly class ReturnNullOverFalseRule implements Rule { /** * @api diff --git a/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php b/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php new file mode 100644 index 00000000..ecf6e76e --- /dev/null +++ b/tests/Rules/NoMixedMethodCallerRule/Fixture/SkipPHPUnitMock.php @@ -0,0 +1,21 @@ +createMock(SomeFinalClass::class); + + $someClassMock->expects($this->once()) + ->method('some') + ->with($this->any()) + ->willReturn(1000); + } +} diff --git a/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php b/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php index 48fe0a2f..1e8d2749 100644 --- a/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php +++ b/tests/Rules/NoMixedMethodCallerRule/NoMixedMethodCallerRuleTest.php @@ -12,9 +12,6 @@ final class NoMixedMethodCallerRuleTest extends RuleTestCase { - /** - * @param mixed[]|array> $expectedErrorsWithLines - */ #[DataProvider('provideData')] public function testRule(string $filePath, array $expectedErrorsWithLines): void { @@ -25,6 +22,7 @@ public static function provideData(): Iterator { yield [__DIR__ . '/Fixture/SkipKnownCallerType.php', []]; yield [__DIR__ . '/Fixture/SkipMockObject.php', []]; + yield [__DIR__ . '/Fixture/SkipPHPUnitMock.php', []]; $errorMessage = sprintf(NoMixedMethodCallerRule::ERROR_MESSAGE, '$someType'); yield [__DIR__ . '/Fixture/MagicMethodName.php', [[$errorMessage, 11]]]; diff --git a/tests/Rules/NoMixedMethodCallerRule/Source/SomeFinalClass.php b/tests/Rules/NoMixedMethodCallerRule/Source/SomeFinalClass.php new file mode 100644 index 00000000..c03aa2bd --- /dev/null +++ b/tests/Rules/NoMixedMethodCallerRule/Source/SomeFinalClass.php @@ -0,0 +1,10 @@ +> $expectedErrorsWithLines - */ #[DataProvider('provideData')] public function testRule(string $filePath, array $expectedErrorsWithLines): void { diff --git a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/ReturnFalseOnly.php b/tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php similarity index 68% rename from tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/ReturnFalseOnly.php rename to tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php index 734de54a..0b91cf97 100644 --- a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/ReturnFalseOnly.php +++ b/tests/Rules/ReturnNullOverFalseRule/Fixture/ReturnFalseOnly.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\TypePerfect\Tests\Rules\NoReturnFalseInNonBoolClassMethodRule\Fixture; +namespace Rector\TypePerfect\Tests\Rules\ReturnNullOverFalseRule\Fixture; final class ReturnFalseOnly { diff --git a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/SkipReturnBool.php b/tests/Rules/ReturnNullOverFalseRule/Fixture/SkipReturnBool.php similarity index 69% rename from tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/SkipReturnBool.php rename to tests/Rules/ReturnNullOverFalseRule/Fixture/SkipReturnBool.php index 0e98c9d0..b9e77c76 100644 --- a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/Fixture/SkipReturnBool.php +++ b/tests/Rules/ReturnNullOverFalseRule/Fixture/SkipReturnBool.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\TypePerfect\Tests\Rules\NoReturnFalseInNonBoolClassMethodRule\Fixture; +namespace Rector\TypePerfect\Tests\Rules\ReturnNullOverFalseRule\Fixture; final class SkipReturnBool { diff --git a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/NoReturnFalseInNonBoolClassMethodRuleTest.php b/tests/Rules/ReturnNullOverFalseRule/ReturnNullOverFalseRuleTest.php similarity index 69% rename from tests/Rules/NoReturnFalseInNonBoolClassMethodRule/NoReturnFalseInNonBoolClassMethodRuleTest.php rename to tests/Rules/ReturnNullOverFalseRule/ReturnNullOverFalseRuleTest.php index 78252b48..5e23b31d 100644 --- a/tests/Rules/NoReturnFalseInNonBoolClassMethodRule/NoReturnFalseInNonBoolClassMethodRuleTest.php +++ b/tests/Rules/ReturnNullOverFalseRule/ReturnNullOverFalseRuleTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace Rector\TypePerfect\Tests\Rules\NoReturnFalseInNonBoolClassMethodRule; +namespace Rector\TypePerfect\Tests\Rules\ReturnNullOverFalseRule; use Iterator; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPUnit\Framework\Attributes\DataProvider; -use Rector\TypePerfect\Rules\NoReturnFalseInNonBoolClassMethodRule; +use Rector\TypePerfect\Rules\ReturnNullOverFalseRule; -final class NoReturnFalseInNonBoolClassMethodRuleTest extends RuleTestCase +final class ReturnNullOverFalseRuleTest extends RuleTestCase { /** * @param mixed[] $expectedErrorMessagesWithLines @@ -25,7 +25,7 @@ public static function provideData(): Iterator { yield [ __DIR__ . '/Fixture/ReturnFalseOnly.php', - [[NoReturnFalseInNonBoolClassMethodRule::ERROR_MESSAGE, 9]], + [[ReturnNullOverFalseRule::ERROR_MESSAGE, 9]], ]; yield [__DIR__ . '/Fixture/SkipReturnBool.php', []]; @@ -41,6 +41,6 @@ public static function getAdditionalConfigFiles(): array protected function getRule(): Rule { - return self::getContainer()->getByType(NoReturnFalseInNonBoolClassMethodRule::class); + return self::getContainer()->getByType(ReturnNullOverFalseRule::class); } }