From 892b319f25f04bc1b55c3d0063b607909612fe6d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 17:14:49 +0200 Subject: [PATCH] Bleeding edge - MissingMethodSelfOutTypeRule --- conf/config.level6.neon | 7 ++ src/PhpDoc/StubValidator.php | 5 ++ .../Methods/MissingMethodSelfOutTypeRule.php | 85 +++++++++++++++++++ .../MissingMethodSelfOutTypeRuleTest.php | 39 +++++++++ .../data/missing-method-self-out-type.php | 35 ++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/Rules/Methods/MissingMethodSelfOutTypeRule.php create mode 100644 tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php diff --git a/conf/config.level6.neon b/conf/config.level6.neon index 545fac6ad2..1029bcdba0 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -13,6 +13,10 @@ rules: - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule - PHPStan\Rules\Properties\MissingPropertyTypehintRule +conditionalTags: + PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + services: - class: PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule @@ -27,3 +31,6 @@ services: paramOut: %featureToggles.paramOutType% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index fb5a46f209..8d61831c38 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -51,6 +51,7 @@ use PHPStan\Rules\Methods\MethodSignatureRule; use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule; use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule; +use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule; use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; @@ -228,6 +229,10 @@ private function getRuleRegistry(Container $container): RuleRegistry ); } + if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { + $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); + } + return new DirectRuleRegistry($rules); } diff --git a/src/Rules/Methods/MissingMethodSelfOutTypeRule.php b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php new file mode 100644 index 0000000000..4b602b5fa1 --- /dev/null +++ b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php @@ -0,0 +1,85 @@ + + */ +final class MissingMethodSelfOutTypeRule implements Rule +{ + + public function __construct( + private MissingTypehintCheck $missingTypehintCheck, + ) + { + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $methodReflection = $node->getMethodReflection(); + $selfOutType = $methodReflection->getSelfOutType(); + + if ($selfOutType === null) { + return []; + } + + $classReflection = $methodReflection->getDeclaringClass(); + $phpDocTagMessage = 'PHPDoc tag @phpstan-self-out'; + + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($selfOutType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with no value type specified in iterable type %s.', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($selfOutType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with generic %s but does not specify its types: %s', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($selfOutType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with no signature specified for %s.', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + + return $messages; + } + +} diff --git a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php new file mode 100644 index 0000000000..373e46494a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php @@ -0,0 +1,39 @@ + + */ +class MissingMethodSelfOutTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, true, true, [])); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-method-self-out-type.php'], [ + [ + 'Method MissingMethodSelfOutType\Foo::doFoo() has PHPDoc tag @phpstan-self-out with no value type specified in iterable type array.', + 14, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Method MissingMethodSelfOutType\Foo::doFoo2() has PHPDoc tag @phpstan-self-out with generic class MissingMethodSelfOutType\Foo but does not specify its types: T', + 22, + ], + [ + 'Method MissingMethodSelfOutType\Foo::doFoo3() has PHPDoc tag @phpstan-self-out with no signature specified for callable.', + 30, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php b/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php new file mode 100644 index 0000000000..a0c83d7e3f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php @@ -0,0 +1,35 @@ + + */ + public function doFoo(): void + { + + } + + /** + * @phpstan-self-out self + */ + public function doFoo2(): void + { + + } + + /** + * @phpstan-self-out Foo&callable + */ + public function doFoo3(): void + { + + } + +}