add GetAttributeReturnTypeExtension

This commit is contained in:
Tomas Votruba 2019-01-25 01:49:05 +01:00
parent 753a478e7a
commit 45df82424c
3 changed files with 121 additions and 6 deletions

View File

@ -59,6 +59,7 @@
},
"autoload-dev": {
"psr-4": {
"Rector\\PHPStanExtensions\\": "utils/phpstan/src",
"Rector\\Tests\\": "tests",
"Rector\\NodeTypeResolver\\Tests\\": "packages/NodeTypeResolver/tests",
"Rector\\CakePHP\\Tests\\": "packages/CakePHP/tests",

View File

@ -5,7 +5,7 @@ includes:
parameters:
# to allow intalling with various phsptan versions without reporting old errors here
reportUnmatchedIgnoredErrors: false
# reportUnmatchedIgnoredErrors: false
level: 7
excludes_analyse:
@ -63,7 +63,6 @@ parameters:
- '#Access to an undefined property PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Stmt\\ClassMethod::\$params#'
- '#Cannot call method getName\(\) on PHPStan\\Reflection\\ClassReflection\|null#'
- '#Cannot call method getAttribute\(\) on PhpParser\\Node\\Name\|null#'
- '#Cannot call method getText\(\) on PhpParser\\Comment\\Doc\|null#'
- '#Method Rector\\PhpParser\\Node\\Maintainer\\PropertyMaintainer::getAllPropertyFetch\(\) should return array<PhpParser\\Node\\Expr\\PropertyFetch> but returns array<PhpParser\\Node>#'
@ -101,8 +100,10 @@ parameters:
- '#Strict comparison using === between PhpParser\\Node\\Expr and null will always evaluate to false#'
- '#Cannot cast array<string>\|string\|null to string#'
# false positive on anonymous classes
- '#http\:\/\/bit\.ly\/typehintarray#'
services:
-
class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
- { class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }
# $node->geAttribute($1) => Type|null by $1
- { class: Rector\PHPStanExtensions\Rector\Type\GetAttributeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] }

View File

@ -0,0 +1,113 @@
<?php declare(strict_types=1);
namespace Rector\PHPStanExtensions\Rector\Type;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Use_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Php\PhpTypeSupport;
use Rector\Php\TypeAnalyzer;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* @var string[]
*/
private $argumentKeyToReturnType = [
'Rector\NodeTypeResolver\Node\Attribute::FILE_INFO' => SmartFileInfo::class,
'Rector\NodeTypeResolver\Node\Attribute::RESOLVED_NAME' => Name::class,
'Rector\NodeTypeResolver\Node\Attribute::CLASS_NODE' => ClassLike::class,
'Rector\NodeTypeResolver\Node\Attribute::METHOD_NODE' => ClassMethod::class,
'Rector\NodeTypeResolver\Node\Attribute::CURRENT_EXPRESSION' => Expression::class,
'Rector\NodeTypeResolver\Node\Attribute::PREVIOUS_EXPRESSION' => Expression::class,
'Rector\NodeTypeResolver\Node\Attribute::SCOPE' => Scope::class,
# Node
'Rector\NodeTypeResolver\Node\Attribute::ORIGINAL_NODE' => Node::class,
'Rector\NodeTypeResolver\Node\Attribute::PARENT_NODE' => Node::class,
'Rector\NodeTypeResolver\Node\Attribute::NEXT_NODE' => Node::class,
'Rector\NodeTypeResolver\Node\Attribute::PREVIOUS_NODE' => Node::class,
'Rector\NodeTypeResolver\Node\Attribute::USE_NODES' => [Use_::class],
# scalars
'Rector\NodeTypeResolver\Node\Attribute::PARENT_CLASS_NAME' => 'string',
'Rector\NodeTypeResolver\Node\Attribute::NAMESPACE_NAME' => 'string',
'Rector\NodeTypeResolver\Node\Attribute::CLASS_NAME' => 'string',
'Rector\NodeTypeResolver\Node\Attribute::METHOD_NAME' => 'string',
];
public function getClass(): string
{
return Node::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'getAttribute';
}
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
$argumentValue = $this->resolveArgumentValue($methodCall->args[0]->value);
if ($argumentValue === null) {
return $returnType;
}
if (! isset($this->argumentKeyToReturnType[$argumentValue])) {
return $returnType;
}
$returnType = $this->argumentKeyToReturnType[$argumentValue];
if ($returnType === 'string') {
return new UnionType([new StringType(), new NullType()]);
}
if (is_array($returnType) && count($returnType) === 1) {
$arrayType = new ArrayType(new IntegerType(), new ObjectType($returnType[0]));
return new UnionType([$arrayType, new NullType()]);
}
return new UnionType([new ObjectType($returnType), new NullType()]);
}
private function resolveArgumentValue(Expr $node): ?string
{
$value = null;
if ($node instanceof ClassConstFetch) {
if ($node->class instanceof Name) {
$value = $node->class->toString();
} else {
return null;
}
if ($node->name instanceof Identifier) {
$value .= '::' . $node->name->toString();
} else {
return null;
}
}
return $value;
}
}