Skip to content

Commit

Permalink
GetTemplateTypeRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 2, 2023
1 parent b73a8aa commit 01b4d4e
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Expand Up @@ -31,6 +31,7 @@ rules:
- PHPStan\Rules\Api\ApiMethodCallRule
- PHPStan\Rules\Api\ApiStaticCallRule
- PHPStan\Rules\Api\ApiTraitUseRule
- PHPStan\Rules\Api\GetTemplateTypeRule
- PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule
- PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule
- PHPStan\Rules\Arrays\EmptyArrayItemRule
Expand Down
81 changes: 81 additions & 0 deletions src/Rules/Api/GetTemplateTypeRule.php
@@ -0,0 +1,81 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Type;
use function count;
use function sprintf;

/**
* @implements Rule<MethodCall>
*/
class GetTemplateTypeRule implements Rule
{

public function __construct(private ReflectionProvider $reflectionProvider)
{
}

public function getNodeType(): string
{
return MethodCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
$args = $node->getArgs();
if (count($args) < 2) {
return [];
}
if (!$node->name instanceof Node\Identifier) {
return [];
}

if ($node->name->toLowerString() !== 'gettemplatetype') {
return [];
}

$calledOnType = $scope->getType($node->var);
$methodReflection = $scope->getMethodReflection($calledOnType, $node->name->toString());
if ($methodReflection === null) {
return [];
}

if (!$methodReflection->getDeclaringClass()->is(Type::class)) {
return [];
}

$classType = $scope->getType($args[0]->value);
$templateType = $scope->getType($args[1]->value);
$errors = [];
foreach ($classType->getConstantStrings() as $classNameType) {
if (!$this->reflectionProvider->hasClass($classNameType->getValue())) {
continue;
}
$classReflection = $this->reflectionProvider->getClass($classNameType->getValue());
$templateTypeMap = $classReflection->getTemplateTypeMap();
foreach ($templateType->getConstantStrings() as $templateTypeName) {
if ($templateTypeMap->hasType($templateTypeName->getValue())) {
continue;
}

$errors[] = RuleErrorBuilder::message(sprintf(
'Call to %s::%s() references unknown template type %s on class %s.',
$methodReflection->getDeclaringClass()->getDisplayName(),
$methodReflection->getName(),
$templateTypeName->getValue(),
$classReflection->getDisplayName(),
))->build();
}
}

return $errors;
}

}
33 changes: 33 additions & 0 deletions tests/PHPStan/Rules/Api/GetTemplateTypeRuleTest.php
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<GetTemplateTypeRule>
*/
class GetTemplateTypeRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new GetTemplateTypeRule($this->createReflectionProvider());
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/get-template-type.php'], [
[
'Call to PHPStan\Type\Type::getTemplateType() references unknown template type TSendd on class Generator.',
15,
],
[
'Call to PHPStan\Type\ObjectType::getTemplateType() references unknown template type TSendd on class Generator.',
21,
],
]);
}

}
24 changes: 24 additions & 0 deletions tests/PHPStan/Rules/Api/data/get-template-type.php
@@ -0,0 +1,24 @@
<?php

namespace GetTemplateType;

use Generator;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

class Foo
{

public function doFoo(Type $type): void
{
$type->getTemplateType(Generator::class, 'TSend');
$type->getTemplateType(Generator::class, 'TSendd');
}

public function doBar(ObjectType $type): void
{
$type->getTemplateType(Generator::class, 'TSend');
$type->getTemplateType(Generator::class, 'TSendd');
}

}

0 comments on commit 01b4d4e

Please sign in to comment.