mirror of
https://github.com/rectorphp/rector.git
synced 2024-05-31 08:20:53 +00:00
[TypeDeclaratoin] Add ReturnTypeFromStrictTypedPropertyRector (#5438)
This commit is contained in:
parent
946b1ac84b
commit
d402f52238
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
|
||||
|
||||
class SkipAlreadyType
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Property\TypedPropertyFromStrictConstructorRector\Fixture;
|
||||
|
||||
class SkipDoc
|
||||
{
|
|
@ -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
|
||||
{
|
|
@ -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
|
||||
{
|
|
@ -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;
|
||||
|
||||
/**
|
Loading…
Reference in New Issue
Block a user