diff --git a/conf/config.level2.neon b/conf/config.level2.neon index c60247afb1..e43d074210 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -65,6 +65,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% + PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: @@ -127,6 +129,9 @@ services: reportMaybes: %reportMaybes% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule - class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule - diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 8fee0f5f9b..fc6a44ee28 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -54,6 +54,7 @@ use PHPStan\Rules\Generics\MethodSignatureVarianceRule; use PHPStan\Rules\Generics\MethodTagTemplateTypeCheck; use PHPStan\Rules\Generics\MethodTagTemplateTypeRule; +use PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule; use PHPStan\Rules\Generics\MethodTemplateTypeRule; use PHPStan\Rules\Generics\TemplateTypeCheck; use PHPStan\Rules\Generics\TraitTemplateTypeRule; @@ -257,6 +258,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); $rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); + $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); } return new DirectRuleRegistry($rules); diff --git a/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php b/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php new file mode 100644 index 0000000000..d0235e975c --- /dev/null +++ b/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php @@ -0,0 +1,52 @@ + + */ +final class MethodTagTemplateTypeTraitRule implements Rule +{ + + public function __construct( + private MethodTagTemplateTypeCheck $check, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->check( + $this->reflectionProvider->getClass($traitName->toString()), + $scope, + $node, + $docComment->getText(), + ); + } + +} diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php new file mode 100644 index 0000000000..773f6c30c3 --- /dev/null +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php @@ -0,0 +1,63 @@ + + */ +class MethodTagTemplateTypeTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $reflectionProvider); + + return new MethodTagTemplateTypeTraitRule( + new MethodTagTemplateTypeCheck( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, + ), + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/method-tag-trait-template.php'], [ + [ + 'PHPDoc tag @method template U for method MethodTagTraitTemplate\HelloWorld::sayHello() has invalid bound type MethodTagTraitTemplate\Nonexisting.', + 11, + ], + [ + 'PHPDoc tag @method template for method MethodTagTraitTemplate\HelloWorld::sayHello() cannot have existing class stdClass as its name.', + 11, + ], + [ + 'PHPDoc tag @method template T for method MethodTagTraitTemplate\HelloWorld::sayHello() shadows @template T for class MethodTagTraitTemplate\HelloWorld.', + 11, + ], + [ + 'PHPDoc tag @method template for method MethodTagTraitTemplate\HelloWorld::typeAlias() cannot have existing type alias TypeAlias as its name.', + 11, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php b/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php new file mode 100644 index 0000000000..57a93beb5a --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php @@ -0,0 +1,13 @@ +(T $a, U $b, stdClass $c) + * @method void typeAlias(TypeAlias $a) + */ +trait HelloWorld +{ +}