diff --git a/conf/config.neon b/conf/config.neon index dc3e3b85f4..704b061171 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1005,6 +1005,9 @@ services: - class: PHPStan\Rules\Generics\GenericObjectTypeCheck + - + class: PHPStan\Rules\Generics\MethodTagTemplateTypeCheck + - class: PHPStan\Rules\Generics\TemplateTypeCheck arguments: diff --git a/src/Rules/Generics/MethodTagTemplateTypeCheck.php b/src/Rules/Generics/MethodTagTemplateTypeCheck.php new file mode 100644 index 0000000000..b0b6441c92 --- /dev/null +++ b/src/Rules/Generics/MethodTagTemplateTypeCheck.php @@ -0,0 +1,80 @@ + + */ + public function check( + ClassReflection $classReflection, + Scope $scope, + ClassLike $node, + string $docComment, + ): array + { + $className = $classReflection->getDisplayName(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + null, + $docComment, + ); + + $messages = []; + $escapedClassName = SprintfHelper::escapeFormatString($className); + $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); + + foreach ($resolvedPhpDoc->getMethodTags() as $methodName => $methodTag) { + $methodTemplateTags = $methodTag->getTemplateTags(); + $escapedMethodName = SprintfHelper::escapeFormatString($methodName); + + $messages = array_merge($messages, $this->templateTypeCheck->check( + $scope, + $node, + TemplateTypeScope::createWithMethod($className, $methodName), + $methodTemplateTags, + sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing class %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), + )); + + foreach (array_keys($methodTemplateTags) as $name) { + if (!isset($classTemplateTypes[$name])) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false))) + ->identifier('methodTag.shadowTemplate') + ->build(); + } + } + + return $messages; + } + +} diff --git a/src/Rules/Generics/MethodTagTemplateTypeRule.php b/src/Rules/Generics/MethodTagTemplateTypeRule.php index eafb0e946d..b2f3e2a08c 100644 --- a/src/Rules/Generics/MethodTagTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTagTemplateTypeRule.php @@ -4,16 +4,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateTypeScope; -use PHPStan\Type\VerbosityLevel; -use function array_keys; -use function array_merge; -use function sprintf; /** * @implements Rule @@ -22,8 +14,7 @@ final class MethodTagTemplateTypeRule implements Rule { public function __construct( - private FileTypeMapper $fileTypeMapper, - private TemplateTypeCheck $templateTypeCheck, + private MethodTagTemplateTypeCheck $check, ) { } @@ -40,47 +31,12 @@ public function processNode(Node $node, Scope $scope): array return []; } - $classReflection = $node->getClassReflection(); - $className = $classReflection->getDisplayName(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $classReflection->getName(), - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - null, + return $this->check->check( + $node->getClassReflection(), + $scope, + $node->getOriginalNode(), $docComment->getText(), ); - - $messages = []; - $escapedClassName = SprintfHelper::escapeFormatString($className); - $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); - - foreach ($resolvedPhpDoc->getMethodTags() as $methodName => $methodTag) { - $methodTemplateTags = $methodTag->getTemplateTags(); - $escapedMethodName = SprintfHelper::escapeFormatString($methodName); - - $messages = array_merge($messages, $this->templateTypeCheck->check( - $scope, - $node, - TemplateTypeScope::createWithMethod($className, $methodName), - $methodTemplateTags, - sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing class %%s as its name.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), - )); - - foreach (array_keys($methodTemplateTags) as $name) { - if (!isset($classTemplateTypes[$name])) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false))) - ->identifier('methodTag.shadowTemplate') - ->build(); - } - } - - return $messages; } } diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php index e754794566..1db002fd94 100644 --- a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php @@ -21,16 +21,18 @@ protected function getRule(): Rule $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $reflectionProvider); return new MethodTagTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck( - $reflectionProvider, - new ClassNameCheck( - new ClassCaseSensitivityCheck($reflectionProvider, true), - new ClassForbiddenNameCheck(self::getContainer()), + new MethodTagTemplateTypeCheck( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, ), - new GenericObjectTypeCheck(), - $typeAliasResolver, - true, ), ); }