Skip to content

Commit

Permalink
Refactoring: introduce MethodTagTemplateTypeCheck
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 3, 2024
1 parent 777a82a commit 47a85bf
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 58 deletions.
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,9 @@ services:
-
class: PHPStan\Rules\Generics\GenericObjectTypeCheck

-
class: PHPStan\Rules\Generics\MethodTagTemplateTypeCheck

-
class: PHPStan\Rules\Generics\TemplateTypeCheck
arguments:
Expand Down
80 changes: 80 additions & 0 deletions src/Rules/Generics/MethodTagTemplateTypeCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Generics;

use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Analyser\Scope;
use PHPStan\Internal\SprintfHelper;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Rules\IdentifierRuleError;
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;

final class MethodTagTemplateTypeCheck
{

public function __construct(
private FileTypeMapper $fileTypeMapper,
private TemplateTypeCheck $templateTypeCheck,
)
{
}

/**
* @return list<IdentifierRuleError>
*/
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;
}

}
54 changes: 5 additions & 49 deletions src/Rules/Generics/MethodTagTemplateTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<InClassNode>
Expand All @@ -22,8 +14,7 @@ final class MethodTagTemplateTypeRule implements Rule
{

public function __construct(
private FileTypeMapper $fileTypeMapper,
private TemplateTypeCheck $templateTypeCheck,
private MethodTagTemplateTypeCheck $check,
)
{
}
Expand All @@ -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;
}

}
20 changes: 11 additions & 9 deletions tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
);
}
Expand Down

0 comments on commit 47a85bf

Please sign in to comment.