[TypeDeclaratoin] Add ReturnTypeFromStrictTypedPropertyRector (#5438)

This commit is contained in:
Tomas Votruba 2021-02-06 23:09:45 +01:00 committed by GitHub
parent 946b1ac84b
commit d402f52238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 392 additions and 42 deletions

View File

@ -12032,7 +12032,7 @@ Add null default to properties with PHP 7.4 property nullable type
Add typed properties based only on strict constructor types
- class: `Rector\Php74\Rector\Property\TypedPropertyFromStrictConstructorRector`
- class: `Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector`
```diff
class SomeObject

View File

@ -39,7 +39,6 @@ use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
use ReflectionMethod;
/**
@ -330,11 +329,7 @@ final class NodeRepository
return [];
}
if ($propertyFetcheeType instanceof ShortenedObjectType) {
$className = $propertyFetcheeType->getFullyQualifiedName();
} else {
$className = $propertyFetcheeType->getClassName();
}
$className = $this->nodeTypeResolver->getFullyQualifiedClassName($propertyFetcheeType);
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($propertyFetch);
@ -539,6 +534,27 @@ final class NodeRepository
return $constructorClassMethod;
}
public function findPropertyByPropertyFetch(PropertyFetch $propertyFetch): ?Property
{
$propertyCallerType = $this->nodeTypeResolver->getStaticType($propertyFetch->var);
if (! $propertyCallerType instanceof TypeWithClassName) {
return null;
}
$className = $this->nodeTypeResolver->getFullyQualifiedClassName($propertyCallerType);
$class = $this->findClass($className);
if (! $class instanceof Class_) {
return null;
}
$propertyName = $this->nodeNameResolver->getName($propertyFetch->name);
if ($propertyName === null) {
return null;
}
return $class->getProperty($propertyName);
}
private function addMethod(ClassMethod $classMethod): void
{
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);

View File

@ -24,13 +24,12 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* All parsed nodes grouped type
* @template TNodeType of Node
* @see https://phpstan.org/blog/generics-in-php-using-phpdocs
*/
final class ParsedNodeCollector
{
/**
* @var class-string[]
* @var class-string<Node>[]
*/
private const COLLECTABLE_NODE_TYPES = [
Class_::class,

View File

@ -9,13 +9,12 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\TypeWithClassName;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class SetterClassMethodAnalyzer
@ -30,10 +29,19 @@ final class SetterClassMethodAnalyzer
*/
private $nodeNameResolver;
public function __construct(NodeTypeResolver $nodeTypeResolver, NodeNameResolver $nodeNameResolver)
{
/**
* @var NodeRepository
*/
private $nodeRepository;
public function __construct(
NodeTypeResolver $nodeTypeResolver,
NodeNameResolver $nodeNameResolver,
NodeRepository $nodeRepository
) {
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodeNameResolver = $nodeNameResolver;
$this->nodeRepository = $nodeRepository;
}
public function matchNullalbeClassMethodProperty(ClassMethod $classMethod): ?Property
@ -43,7 +51,7 @@ final class SetterClassMethodAnalyzer
return null;
}
return $this->getPropertyByPropertyFetch($classMethod, $propertyFetch);
return $this->nodeRepository->findPropertyByPropertyFetch($propertyFetch);
}
public function matchDateTimeSetterProperty(ClassMethod $classMethod): ?Property
@ -53,7 +61,7 @@ final class SetterClassMethodAnalyzer
return null;
}
return $this->getPropertyByPropertyFetch($classMethod, $propertyFetch);
return $this->nodeRepository->findPropertyByPropertyFetch($propertyFetch);
}
/**
@ -80,21 +88,6 @@ final class SetterClassMethodAnalyzer
return $propertyFetch;
}
private function getPropertyByPropertyFetch(ClassMethod $classMethod, PropertyFetch $propertyFetch): ?Property
{
$classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return null;
}
$propertyName = $this->nodeNameResolver->getName($propertyFetch);
if ($propertyName === null) {
return null;
}
return $classLike->getProperty($propertyName);
}
private function matchDateTimeSetterPropertyFetch(ClassMethod $classMethod): ?PropertyFetch
{
$propertyFetch = $this->matchSetterOnlyPropertyFetch($classMethod);

View File

@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\UnionType;
use PHPStan\Type\MixedType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\ReturnTypeFromStrictTypedPropertyRectorTest
*/
final class ReturnTypeFromStrictTypedPropertyRector extends AbstractRector
{
/**
* @var TypeFactory
*/
private $typeFactory;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add return method return type based on strict typed property', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
private int $age = 100;
public function getAge()
{
return $this->age;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
private int $age = 100;
public function getAge(): int
{
return $this->age;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
return null;
}
if ($node->returnType !== null) {
return null;
}
$propertyTypeNodes = $this->resolveReturnPropertyTypeNodes($node);
if ($propertyTypeNodes === []) {
return null;
}
$propertyTypes = [];
foreach ($propertyTypeNodes as $propertyTypeNode) {
$propertyTypes[] = $this->staticTypeMapper->mapPhpParserNodePHPStanType($propertyTypeNode);
}
// add type to return type
$propertyType = $this->typeFactory->createMixedPassedOrUnionType($propertyTypes);
if ($propertyType instanceof MixedType) {
return null;
}
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType);
if ($propertyTypeNode === null) {
return null;
}
$node->returnType = $propertyTypeNode;
return $node;
}
/**
* @return array<Identifier|Name|NullableType|UnionType>
*/
private function resolveReturnPropertyTypeNodes(ClassMethod $classMethod): array
{
/** @var Return_[] $returns */
$returns = $this->betterNodeFinder->findInstanceOf($classMethod, Return_::class);
$propertyTypes = [];
foreach ($returns as $return) {
if ($return->expr === null) {
return [];
}
if (! $return->expr instanceof PropertyFetch) {
return [];
}
$property = $this->nodeRepository->findPropertyByPropertyFetch($return->expr);
if (! $property instanceof Property) {
return [];
}
if ($property->type === null) {
return [];
}
$propertyTypes[] = $property->type;
}
return $propertyTypes;
}
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Php74\Rector\Property;
namespace Rector\TypeDeclaration\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
@ -16,7 +16,7 @@ use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\TypedPropertyFromStrictConstructorRectorTest
* @see \Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\TypedPropertyFromStrictConstructorRectorTest
*/
final class TypedPropertyFromStrictConstructorRector extends AbstractRector
{

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class SkipDocType
{
/**
* @var int
*/
private $age = 100;
public function getAge()
{
return $this->age;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class SkipOneMissingType
{
private int $age = 100;
/**
* @var string
*/
private $name = 'Elon';
public function getValue()
{
if (mt_rand(0, 100)) {
return $this->age;
}
return $this->name;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class SkipSingleVoid
{
private int $age = 100;
public function getAge()
{
if (mt_rand(0, 100)) {
return;
}
return $this->age;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class SomeClass
{
private int $age = 100;
public function getAge()
{
return $this->age;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class SomeClass
{
private int $age = 100;
public function getAge(): int
{
return $this->age;
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class TwoReturnsUnionType
{
private int $age = 100;
private string $name = 'Elon';
public function getValue()
{
if (mt_rand(0, 100)) {
return $this->age;
}
return $this->name;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class TwoReturnsUnionType
{
private int $age = 100;
private string $name = 'Elon';
public function getValue(): int|string
{
if (mt_rand(0, 100)) {
return $this->age;
}
return $this->name;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class UnionType
{
private int|string $age = 100;
public function getAge()
{
return $this->age;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector\Fixture;
final class UnionType
{
private int|string $age = 100;
public function getAge(): int|string
{
return $this->age;
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ReturnTypeFromStrictTypedPropertyRectorTest extends AbstractRectorTestCase
{
/**
* @requires PHP 8.0
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return ReturnTypeFromStrictTypedPropertyRector::class;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
use DateTimeInterface;
@ -23,7 +23,7 @@ final class IterableInterfaceVarTag
-----
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
use DateTimeInterface;

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
class SkipAlreadyType
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
class SkipDoc
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
class SomeClass
{
@ -19,7 +19,7 @@ class SomeClass
-----
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
class SomeClass
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
final class VariadicConstructorParam
{
@ -17,7 +17,7 @@ final class VariadicConstructorParam
-----
<?php
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
final class VariadicConstructorParam
{

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector;
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Iterator;
use Rector\Php74\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Symplify\SmartFileSystem\SmartFileInfo;
/**