decouple ComplexNodeTypeResolver, add PHP 7.4 type resolving from assigns

This commit is contained in:
Tomas Votruba 2019-02-04 05:08:52 +01:00
parent bc9370dfda
commit b98213de1f
6 changed files with 173 additions and 98 deletions

View File

@ -0,0 +1,96 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\Node\NodeToStringTypeResolver;
use Rector\NodeTypeResolver\Php\VarTypeInfo;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PhpParser\Node\Resolver\NameResolver;
final class ComplexNodeTypeResolver
{
/**
* @var NodeToStringTypeResolver
*/
private $nodeToStringTypeResolver;
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var NodeTypeAnalyzer
*/
private $nodeTypeAnalyzer;
public function __construct(
NodeToStringTypeResolver $nodeToStringTypeResolver,
NameResolver $nameResolver,
BetterNodeFinder $betterNodeFinder,
NodeTypeAnalyzer $nodeTypeAnalyzer
) {
$this->nodeToStringTypeResolver = $nodeToStringTypeResolver;
$this->nameResolver = $nameResolver;
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeTypeAnalyzer = $nodeTypeAnalyzer;
}
/**
* Based on static analysis of code, looking for property assigns
*/
public function resolvePropertyTypeInfo(Property $propertyNode): ?VarTypeInfo
{
$types = [];
$propertyDefault = $propertyNode->props[0]->default;
if ($propertyDefault) {
$types[] = $this->nodeToStringTypeResolver->resolver($propertyDefault);
}
$classNode = $propertyNode->getAttribute(Attribute::CLASS_NODE);
if (! $classNode instanceof Class_) {
throw new ShouldNotHappenException();
}
$propertyName = $this->nameResolver->resolve($propertyNode);
if ($propertyName === null) {
return null;
}
/** @var Assign[] $propertyAssignNodes */
$propertyAssignNodes = $this->betterNodeFinder->find([$classNode], function (Node $node) use (
$propertyName
): bool {
if ($node instanceof Assign && $node->var instanceof PropertyFetch) {
// is property match
return $this->nameResolver->isName($node->var, $propertyName);
}
return false;
});
foreach ($propertyAssignNodes as $propertyAssignNode) {
$types = array_merge(
$types,
$this->nodeTypeAnalyzer->resolveSingleTypeToStrings($propertyAssignNode->expr)
);
}
$types = array_filter($types);
return new VarTypeInfo($types, $types, true);
}
}

View File

@ -3,17 +3,10 @@
namespace Rector\Php\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\ComplexNodeTypeResolver;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\Node\NodeToStringTypeResolver;
use Rector\NodeTypeResolver\NodeTypeAnalyzer;
use Rector\NodeTypeResolver\Php\VarTypeInfo;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -26,30 +19,14 @@ final class CompleteVarDocTypePropertyRector extends AbstractRector
private $docBlockAnalyzer;
/**
* @var BetterNodeFinder
* @var ComplexNodeTypeResolver
*/
private $betterNodeFinder;
private $complexNodeTypeResolver;
/**
* @var NodeTypeAnalyzer
*/
private $nodeTypeAnalyzer;
/**
* @var NodeToStringTypeResolver
*/
private $nodeToStringTypeResolver;
public function __construct(
DocBlockAnalyzer $docBlockAnalyzer,
BetterNodeFinder $betterNodeFinder,
NodeTypeAnalyzer $nodeTypeAnalyzer,
NodeToStringTypeResolver $nodeToStringTypeResolver
) {
public function __construct(DocBlockAnalyzer $docBlockAnalyzer, ComplexNodeTypeResolver $complexNodeTypeResolver)
{
$this->docBlockAnalyzer = $docBlockAnalyzer;
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeTypeAnalyzer = $nodeTypeAnalyzer;
$this->nodeToStringTypeResolver = $nodeToStringTypeResolver;
$this->complexNodeTypeResolver = $complexNodeTypeResolver;
}
public function getDefinition(): RectorDefinition
@ -104,7 +81,7 @@ CODE_SAMPLE
return null;
}
$varTypeInfo = $this->resolveStaticVarTypeInfo($node);
$varTypeInfo = $this->complexNodeTypeResolver->resolvePropertyTypeInfo($node);
if ($varTypeInfo === null) {
return null;
}
@ -121,50 +98,4 @@ CODE_SAMPLE
return $node;
}
/**
* Based on static analysis of code, looking for property assigns
*/
private function resolveStaticVarTypeInfo(Property $propertyNode): ?VarTypeInfo
{
$types = [];
$propertyDefault = $propertyNode->props[0]->default;
if ($propertyDefault) {
$types[] = $this->nodeToStringTypeResolver->resolver($propertyDefault);
}
$classNode = $propertyNode->getAttribute(Attribute::CLASS_NODE);
if (! $classNode instanceof Class_) {
throw new ShouldNotHappenException();
}
$propertyName = $this->getName($propertyNode);
if ($propertyName === null) {
return null;
}
/** @var Assign[] $propertyAssignNodes */
$propertyAssignNodes = $this->betterNodeFinder->find([$classNode], function (Node $node) use (
$propertyName
): bool {
if ($node instanceof Assign && $node->var instanceof PropertyFetch) {
// is property match
return $this->isName($node->var, $propertyName);
}
return false;
});
foreach ($propertyAssignNodes as $propertyAssignNode) {
$types = array_merge(
$types,
$this->nodeTypeAnalyzer->resolveSingleTypeToStrings($propertyAssignNode->expr)
);
}
$types = array_filter($types);
return new VarTypeInfo($types, $types, true);
}
}

View File

@ -10,6 +10,7 @@ use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Property;
use Rector\NodeTypeResolver\ComplexNodeTypeResolver;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\Php\VarTypeInfo;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
@ -40,12 +41,18 @@ final class TypedPropertyRector extends AbstractRector
*/
private $docBlockAnalyzer;
public function __construct(DocBlockAnalyzer $docBlockAnalyzer)
/**
* @var ComplexNodeTypeResolver
*/
private $complexNodeTypeResolver;
public function __construct(DocBlockAnalyzer $docBlockAnalyzer, ComplexNodeTypeResolver $complexNodeTypeResolver)
{
$this->docBlockAnalyzer = $docBlockAnalyzer;
// PHP 7.4 already knows "object"
PhpTypeSupport::enableType('object');
$this->complexNodeTypeResolver = $complexNodeTypeResolver;
}
public function getDefinition(): RectorDefinition
@ -88,26 +95,35 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if ($node->type !== null) {
return null;
}
$varTypeInfos = [];
// non FQN, so they are 1:1 to possible imported doc type
$varTypeInfo = $this->docBlockAnalyzer->getVarTypeInfo($node);
if ($varTypeInfo === null) {
return null;
$varTypeInfos[] = $this->docBlockAnalyzer->getVarTypeInfo($node);
$varTypeInfos[] = $this->complexNodeTypeResolver->resolvePropertyTypeInfo($node);
foreach ($varTypeInfos as $varTypeInfo) {
if ($varTypeInfo === null) {
continue;
}
if ($varTypeInfo->isTypehintAble() === false) {
continue;
}
if ($this->matchesDocTypeAndDefaultValueType($varTypeInfo, $node)) {
$node->type = $varTypeInfo->getTypeNode();
// invoke the print, because only attribute has changed
$node->setAttribute(Attribute::ORIGINAL_NODE, null);
return $node;
}
}
if ($varTypeInfo->isTypehintAble() === false) {
return null;
}
if (! $this->matchesDocTypeAndDefaultValueType($varTypeInfo, $node)) {
return null;
}
$node->type = $varTypeInfo->getTypeNode();
// invoke the print, because only attribute has changed
$node->setAttribute(Attribute::ORIGINAL_NODE, null);
return $node;
return null;
}
private function matchesDocTypeAndDefaultValueType(VarTypeInfo $varTypeInfo, Property $propertyNode): bool

View File

@ -86,7 +86,7 @@ final class DefaultValues
/**
* @var bool
*/
private $name = 'not_a_bool';
private string $name = 'not_a_bool';
/**
* @var bool
@ -101,7 +101,7 @@ final class DefaultValues
/**
* @var string
*/
private $size = false;
private bool $size = false;
/**
* @var array
@ -131,12 +131,12 @@ final class DefaultValues
/**
* @var float
*/
private $c = 'hey';
private string $c = 'hey';
/**
* @var int
*/
private $e = 42.42;
private float $e = 42.42;
/**
* @var int

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
final class StaticAnalysisBased
{
private $count;
public function setCount(int $count)
{
$this->count = $count;
}
}
?>
-----
<?php
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
final class StaticAnalysisBased
{
private int $count;
public function setCount(int $count)
{
$this->count = $count;
}
}
?>

View File

@ -16,6 +16,7 @@ final class TypedPropertyRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/static_property.php.inc',
__DIR__ . '/Fixture/default_values.php.inc',
__DIR__ . '/Fixture/match_types.php.inc',
__DIR__ . '/Fixture/static_analysis_based.php.inc',
]);
}