Add CompleteVarDocTypePropertyRector

This commit is contained in:
Tomas Votruba 2018-12-22 12:30:07 +01:00
parent b956ccd87b
commit 48cb147db4
8 changed files with 333 additions and 6 deletions

View File

@ -0,0 +1,54 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Node;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Scalar\LNumber;
use Rector\NodeTypeResolver\NodeTypeAnalyzer;
use Rector\PhpParser\Node\Maintainer\ConstFetchMaintainer;
final class NodeToStringTypeResolver
{
/**
* @var NodeTypeAnalyzer
*/
private $nodeTypeAnalyzer;
/**
* @var ConstFetchMaintainer
*/
private $constFetchMaintainer;
public function __construct(NodeTypeAnalyzer $nodeTypeAnalyzer, ConstFetchMaintainer $constFetchMaintainer)
{
$this->nodeTypeAnalyzer = $nodeTypeAnalyzer;
$this->constFetchMaintainer = $constFetchMaintainer;
}
public function resolver(Node $node): string
{
if ($node instanceof LNumber) {
return 'int';
}
if ($node instanceof Array_) {
return 'mixed[]';
}
if ($node instanceof DNumber) {
return 'float';
}
if ($this->nodeTypeAnalyzer->isStringType($node)) {
return 'string';
}
if ($this->constFetchMaintainer->isBool($node)) {
return 'bool';
}
return '';
}
}

View File

@ -209,6 +209,8 @@ final class DocBlockAnalyzer
public function addVarTag(Node $node, string $type): void
{
// there might be no phpdoc at all
if ($node->getDocComment()) {
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$phpDocNode = $phpDocInfo->getPhpDocNode();
@ -216,6 +218,11 @@ final class DocBlockAnalyzer
$phpDocNode->children[] = new PhpDocTagNode('@var', $varTagValueNode);
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
} else {
// create completely new docblock
$varDocComment = sprintf("/**\n * @var %s\n */", $type);
$node->setDocComment(new Doc($varDocComment));
}
}
/**

View File

@ -0,0 +1,165 @@
<?php declare(strict_types=1);
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\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;
final class CompleteVarDocTypePropertyRector extends AbstractRector
{
/**
* @var DocBlockAnalyzer
*/
private $docBlockAnalyzer;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var NodeTypeAnalyzer
*/
private $nodeTypeAnalyzer;
/**
* @var NodeToStringTypeResolver
*/
private $nodeToStringTypeResolver;
public function __construct(
DocBlockAnalyzer $docBlockAnalyzer,
BetterNodeFinder $betterNodeFinder,
NodeTypeAnalyzer $nodeTypeAnalyzer,
NodeToStringTypeResolver $nodeToStringTypeResolver
) {
$this->docBlockAnalyzer = $docBlockAnalyzer;
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeTypeAnalyzer = $nodeTypeAnalyzer;
$this->nodeToStringTypeResolver = $nodeToStringTypeResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Complete property `@var` annotations for missing one, yet known.', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
private $eventDispatcher;
public function __construct(EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @var EventDispatcher
*/
private $eventDispatcher;
public function __construct(EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Property::class];
}
/**
* @param Property $node
*/
public function refactor(Node $node): ?Node
{
$varTypeInfo = $this->docBlockAnalyzer->getVarTypeInfo($node);
if ($varTypeInfo) {
return null;
}
$varTypeInfo = $this->resolveStaticVarTypeInfo($node);
if ($varTypeInfo === null) {
return null;
}
if ($varTypeInfo->getType() === null) {
return null;
}
$this->docBlockAnalyzer->addVarTag($node, $varTypeInfo->getType());
$node->setAttribute(Attribute::ORIGINAL_NODE, null);
return $node;
}
/**
* @todo extract
* 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);
}
/** @var Class_ $classNode */
$classNode = $propertyNode->getAttribute(Attribute::CLASS_NODE);
$propertyName = $this->getName($propertyNode);
/** @var Assign[] $propertyAssignNodes */
$propertyAssignNodes = $this->betterNodeFinder->find([$classNode], function (Node $node) use (
$propertyName
): bool {
if ($node instanceof Assign) {
if ($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);
}
}

View File

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector;
use Rector\Php\Rector\Property\CompleteVarDocTypePropertyRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class CompleteVarDocTypePropertyRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/property_assign.php.inc',
__DIR__ . '/Fixture/default_value.php.inc',
]);
}
protected function getRectorClass(): string
{
return CompleteVarDocTypePropertyRector::class;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class DefaultValue
{
private $number = 5;
private $maybe = false;
private $dreams = [];
private $name = 'John';
private $longName = 'Elton' . 'John';
}
?>
-----
<?php
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class DefaultValue
{
/**
* @var int
*/
private $number = 5;
/**
* @var bool
*/
private $maybe = false;
/**
* @var array
*/
private $dreams = [];
/**
* @var string
*/
private $name = 'John';
/**
* @var string
*/
private $longName = 'Elton' . 'John';
}
?>

View File

@ -0,0 +1,34 @@
<?php
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class PropertyAssign
{
private $eventDispatcher;
public function __construct(\EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
}
?>
-----
<?php
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class PropertyAssign
{
/**
* @var \EventDispatcher
*/
private $eventDispatcher;
public function __construct(\EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
}
?>

View File

@ -29,6 +29,7 @@ final class FunctionLikeMaintainer
}
/**
* @todo extract
* Based on static analysis of code, looking for return types
* @param ClassMethod|Function_ $node
*/