[NodeTypeResolver] add property support

This commit is contained in:
TomasVotruba 2017-08-21 01:19:14 +02:00
parent 5a5c276787
commit 58fef98d4e
5 changed files with 157 additions and 20 deletions

View File

@ -5,6 +5,7 @@ namespace Rector\NodeTypeResolver\NodeVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Name\FullyQualified;
@ -44,14 +45,10 @@ 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) {
@ -61,6 +58,10 @@ final class ClassLikeTypeResolver extends NodeVisitorAbstract
if ($node instanceof Assign) {
$this->processAssignNode($node);
}
if ($node instanceof PropertyFetch) {
$this->processPropertyFetch($node);
}
}
private function getTypeFromNewNode(New_ $newNode): string
@ -103,4 +104,18 @@ final class ClassLikeTypeResolver extends NodeVisitorAbstract
}
}
}
private function processPropertyFetch(PropertyFetch $propertyFetchNode): void
{
if ($propertyFetchNode->var->name !== 'this') {
return;
}
$propertyName = (string) $propertyFetchNode->name;
$propertyType = $this->typeContext->getTypeForProperty($propertyName);
if ($propertyType) {
$propertyFetchNode->setAttribute(self::TYPE_ATTRIBUTE, $propertyType);
}
}
}

View File

@ -2,8 +2,13 @@
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 ReflectionFunction;
use ReflectionMethod;
@ -18,20 +23,20 @@ final class TypeContext
private $types = [];
/**
* @var ClassLike|null
* @var string[]
*/
private $classLikeNode;
private $classProperties = [];
/**
* @var bool
* @var ClassLike|nnull
*/
private $isInClass = false;
private $classLikeNode;
public function startFile(): void
{
$this->types = [];
$this->classLikeNode = [];
$this->isInClass = false;
$this->classProperties = [];
$this->classLikeNode = null;
}
public function addVariableWithType(string $variableName, string $variableType): void
@ -41,9 +46,12 @@ final class TypeContext
public function enterClass(ClassLike $classLikeNode): void
{
$this->classProperties = [];
$this->classLikeNode = $classLikeNode;
$this->isInClass = true;
// @todo: add properties
if ($classLikeNode instanceof Class_) {
$this->classProperties = $this->prepareConstructorTypesFromClass($classLikeNode);
}
}
public function enterFunction(FunctionLike $functionLikeNode): void
@ -53,7 +61,7 @@ final class TypeContext
$functionReflection = $this->getFunctionReflection($functionLikeNode);
if ($functionReflection) {
foreach ($functionReflection->getParameters() as $parameterReflection) {
$this->types[$parameterReflection->getName()] = $parameterReflection->getType();
$this->types[$parameterReflection->getName()] = (string) $parameterReflection->getType();
}
}
}
@ -63,6 +71,11 @@ final class TypeContext
return $this->types[$name] ?? '';
}
public function getTypeForProperty(string $name): string
{
return $this->classProperties[$name] ?? '';
}
public function addAssign(string $newVariable, string $oldVariable): void
{
$type = $this->getTypeForVariable($oldVariable);
@ -87,4 +100,76 @@ 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,23 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\NodeVisitor\ClassLikeTypeResolverSource;
use Nette\Utils\Html;
final class PropertyType
{
/**
* @var Html
*/
private $html;
public function __construct(Html $html)
{
$this->html = $html;
}
public function getHtml(): Html
{
return $this->html;
}
}

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\NodeTypeResolverSource;
namespace Rector\NodeTypeResolver\Tests\NodeVisitor\ClassLikeTypeResolverSource;
use Nette\Utils\Html;

View File

@ -1,14 +1,15 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests;
namespace Rector\NodeTypeResolver\Tests\NodeVisitor;
use Nette\Utils\Html;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use Rector\Contract\Parser\ParserInterface;
use Rector\NodeTraverser\StandaloneTraverseNodeTraverser;
use Rector\Tests\AbstractContainerAwareTestCase;
final class NodeTypeResolverTest extends AbstractContainerAwareTestCase
final class ClassLikeTypeResolverTest extends AbstractContainerAwareTestCase
{
/**
* @var ParserInterface
@ -26,20 +27,33 @@ final class NodeTypeResolverTest extends AbstractContainerAwareTestCase
$this->standaloneTraverseNodeTraverser = $this->container->get(StandaloneTraverseNodeTraverser::class);
}
public function test(): void
public function testVariable(): void
{
$nodes = $this->parser->parseFile(__DIR__ . '/NodeTypeResolverSource/VariableType.php');
$nodes = $this->parser->parseFile(__DIR__ . '/ClassLikeTypeResolverSource/VariableType.php');
$nodes = $this->standaloneTraverseNodeTraverser->traverse($nodes);
/** @var Variable $htmlVariableNode */
$htmlVariableNode = $nodes[1]->stmts[1]->stmts[0]->stmts[0]->expr->var;
$this->assertSame(Html::class, $htmlVariableNode->getAttribute('type'));
/** @var Variable $assignedVariableNode */
$assignedVariableNode = $nodes[1]->stmts[1]->stmts[0]->stmts[1]->expr->var;
$this->assertSame(Html::class, $assignedVariableNode->getAttribute('type'));
}
public function testProperty(): void
{
$nodes = $this->parser->parseFile(__DIR__ . '/ClassLikeTypeResolverSource/PropertyType.php');
$nodes = $this->standaloneTraverseNodeTraverser->traverse($nodes);
/** @var PropertyFetch $propertyFetchNode */
$propertyFetchNode = $nodes[1]->stmts[1]->stmts[2]->stmts[0]->expr;
$this->assertSame(Html::class, $propertyFetchNode->getAttribute('type'));
// @todo
//$propertyNode = $nodes[1]->stmts[1]->stmts[0];
// $constructorVariableNode = $nodes[1]->stmts[1]->stmts[1]->params[0]->var;
}
}