[NodeTypeResolver] decouple ConstructorPropertyTypesExtractor

This commit is contained in:
TomasVotruba 2017-08-21 11:24:07 +02:00
parent 58fef98d4e
commit b6345de749
4 changed files with 112 additions and 79 deletions

View File

@ -45,10 +45,14 @@ final class ClassLikeTypeResolver extends NodeVisitorAbstract
{
if ($node instanceof ClassLike) {
$this->typeContext->enterClass($node);
return;
}
if ($node instanceof FunctionLike) {
$this->typeContext->enterFunction($node);
return;
}
if ($node instanceof Variable) {

View File

@ -2,13 +2,10 @@
namespace Rector\NodeTypeResolver;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use ReflectionClass;
use Rector\NodeTypeResolver\TypesExtractor\ConstructorPropertyTypesExtractor;
use ReflectionFunction;
use ReflectionMethod;
@ -28,10 +25,20 @@ final class TypeContext
private $classProperties = [];
/**
* @var ClassLike|nnull
* @var ClassLike|null
*/
private $classLikeNode;
/**
* @var ConstructorPropertyTypesExtractor
*/
private $constructorPropertyTypesExtractor;
public function __construct(ConstructorPropertyTypesExtractor $constructorPropertyTypesExtractor)
{
$this->constructorPropertyTypesExtractor = $constructorPropertyTypesExtractor;
}
public function startFile(): void
{
$this->types = [];
@ -50,7 +57,7 @@ final class TypeContext
$this->classLikeNode = $classLikeNode;
if ($classLikeNode instanceof Class_) {
$this->classProperties = $this->prepareConstructorTypesFromClass($classLikeNode);
$this->classProperties = $this-> constructorPropertyTypesExtractor->extractFromClassNode($classLikeNode);
}
}
@ -100,76 +107,4 @@ final class TypeContext
return new ReflectionFunction((string) $functionLikeNode->name);
}
/**
* @return string[]
*/
private function prepareConstructorTypesFromClass(Class_ $classNode): array
{
$constructorParametersWithTypes = $this->getConstructorParametersWithTypes($classNode);
if (! count($constructorParametersWithTypes)) {
return [];
}
$propertiesWithTypes = [];
foreach ($classNode->stmts as $inClassNode) {
if (! $inClassNode instanceof ClassMethod) {
continue;
}
if ((string) $inClassNode->name !== '__construct') {
continue;
}
foreach ($inClassNode->stmts as $inConstructorNode) {
if (! $inConstructorNode->expr instanceof Assign) {
continue;
}
if (! $inConstructorNode->expr->var instanceof PropertyFetch) {
continue;
}
if ($inConstructorNode->expr->var->var->name !== 'this') {
continue;
}
/** @var PropertyFetch $propertyFetchNode */
$propertyFetchNode = $inConstructorNode->expr->var;
$propertyName = (string) $propertyFetchNode->name;
$propertyType = $constructorParametersWithTypes[$propertyName] ?? null;
if ($propertyName && $propertyType) {
$propertiesWithTypes[$propertyName] = $propertyType;
}
}
}
return $propertiesWithTypes;
}
/**
* @return string[]
*/
private function getConstructorParametersWithTypes(Class_ $classNode): array
{
$className = $classNode->namespacedName->toString();
if (! class_exists($className)) {
return [];
}
$constructorMethod = (new ReflectionClass($className))->getConstructor();
$parametersWithTypes = [];
if ($constructorMethod) {
foreach ($constructorMethod->getParameters() as $parameterReflection) {
$parameterName = $parameterReflection->getName();
$parameterType = (string) $parameterReflection->getType();
$parametersWithTypes[$parameterName] = $parameterType;
}
}
return $parametersWithTypes;
}
}

View File

@ -0,0 +1,94 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\TypesExtractor;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
final class ConstructorPropertyTypesExtractor
{
/**
* @return string[] { propertyName => propertyType }
*/
public function extractFromClassNode(Class_ $classNode): array
{
$constructorParametersWithTypes = $this->getConstructorParametersWithTypes($classNode);
if (! count($constructorParametersWithTypes)) {
return [];
}
$propertiesWithTypes = [];
foreach ($classNode->stmts as $inClassNode) {
if (! $this->isContructorMethodNode($inClassNode)) {
continue;
}
foreach ($inClassNode->stmts as $inConstructorNode) {
if (! $this->isAssignThisNode($inConstructorNode)) {
continue;
}
/** @var PropertyFetch $propertyFetchNode */
$propertyFetchNode = $inConstructorNode->expr->var;
$propertyName = (string) $propertyFetchNode->name;
$propertyType = $constructorParametersWithTypes[$propertyName] ?? null;
if ($propertyName && $propertyType) {
$propertiesWithTypes[$propertyName] = $propertyType;
}
}
}
return $propertiesWithTypes;
}
/**
* @return string[] { parameterName => parameterType }
*/
private function getConstructorParametersWithTypes(Class_ $classNode): array
{
$className = $classNode->namespacedName->toString();
if (! class_exists($className)) {
return [];
}
$constructorMethod = (new \ReflectionClass($className))->getConstructor();
$parametersWithTypes = [];
if ($constructorMethod) {
foreach ($constructorMethod->getParameters() as $parameterReflection) {
$parameterName = $parameterReflection->getName();
$parameterType = (string) $parameterReflection->getType();
$parametersWithTypes[$parameterName] = $parameterType;
}
}
return $parametersWithTypes;
}
private function isContructorMethodNode(Node $node): bool
{
if (! $node instanceof ClassMethod) {
return false;
}
return (string) $node->name === '__construct';
}
private function isAssignThisNode(Node $node): bool
{
if (! $node->expr instanceof Assign) {
return false;
}
if (! $node->expr->var instanceof PropertyFetch) {
return false;
}
return $node->expr->var->var->name === 'this';
}
}

View File

@ -52,7 +52,7 @@ final class ClassLikeTypeResolverTest extends AbstractContainerAwareTestCase
$propertyFetchNode = $nodes[1]->stmts[1]->stmts[2]->stmts[0]->expr;
$this->assertSame(Html::class, $propertyFetchNode->getAttribute('type'));
// @todo
// @todo test asl well
//$propertyNode = $nodes[1]->stmts[1]->stmts[0];
// $constructorVariableNode = $nodes[1]->stmts[1]->stmts[1]->params[0]->var;
}