[TypeDeclaration] Improve PropertyTypeDeclarationRector to work with strict types (#1494)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2021-12-14 16:15:08 +01:00 committed by GitHub
parent 7cdd03ed77
commit f6c3e95d7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 269 additions and 452 deletions

View File

@ -7760,7 +7760,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TypedPropertyRector::class)
->configure([
TypedPropertyRector::CLASS_LIKE_TYPE_ONLY => false,
TypedPropertyRector::PRIVATE_PROPERTY_ONLY => false,
]);
};

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\TypeComparator;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
@ -39,4 +40,50 @@ final class ScalarTypeComparator
return $secondType instanceof BooleanType;
}
/**
* E.g. first is string, second is bool
*/
public function areDifferentScalarTypes(Type $firstType, Type $secondType): bool
{
if (! $this->isScalarType($firstType)) {
return false;
}
if (! $this->isScalarType($secondType)) {
return false;
}
// treat class-string and string the same
if ($firstType instanceof ClassStringType && $secondType instanceof StringType) {
return false;
}
if (! $firstType instanceof StringType) {
return $firstType::class !== $secondType::class;
}
if (! $secondType instanceof ClassStringType) {
return $firstType::class !== $secondType::class;
}
return false;
}
private function isScalarType(Type $type): bool
{
if ($type instanceof StringType) {
return true;
}
if ($type instanceof FloatType) {
return true;
}
if ($type instanceof IntegerType) {
return true;
}
return $type instanceof BooleanType;
}
}

View File

@ -86,7 +86,13 @@ final class TypeComparator
$phpParserNodeType = $this->normalizeConstantBooleanType($phpParserNodeType);
$phpStanDocType = $this->normalizeConstantBooleanType($phpStanDocType);
if (! $this->areTypesEqual($phpParserNodeType, $phpStanDocType)) {
// is scalar replace by another - remove it?
$areDifferentScalarTypes = $this->scalarTypeComparator->areDifferentScalarTypes(
$phpParserNodeType,
$phpStanDocType
);
if (! $areDifferentScalarTypes && ! $this->areTypesEqual($phpParserNodeType, $phpStanDocType)) {
return false;
}

View File

@ -608,3 +608,8 @@ parameters:
-
message: '#Casting to int something that.{1}s already int#'
path: packages/NodeTypeResolver/NodeTypeResolver/ScalarTypeResolver.php #46
# will be desolved
-
path: rules/Php74/Rector/Property/TypedPropertyRector.php
message: '#Class cognitive complexity is 31, keep it under 30#'

View File

@ -3,17 +3,17 @@
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Fixture;
/**
* @template T of object
* @template TType of object
*/
final class SomeGenericObjectType
{
/**
* @var T
* @var TType
*/
private $command;
/**
* @param T $command
* @param TType $command
*/
public function __construct(object $command)
{
@ -27,17 +27,17 @@ final class SomeGenericObjectType
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Fixture;
/**
* @template T of object
* @template TType of object
*/
final class SomeGenericObjectType
{
/**
* @var T
* @var TType
*/
private object $command;
/**
* @param T $command
* @param TType $command
*/
public function __construct(object $command)
{

View File

@ -28,6 +28,6 @@ final class ImportedTest extends AbstractRectorTestCase
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/imported_type.php';
return __DIR__ . '/config/import_names.php';
}
}

View File

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
use Rector\Php74\Rector\Property\TypedPropertyRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyRector::class)
->configure([
TypedPropertyRector::CLASS_LIKE_TYPE_ONLY => true,
]);
};

View File

@ -6,12 +6,15 @@ use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
use Rector\Php74\Rector\Property\TypedPropertyRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyRector::class);
$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
// should be ignored if typed property is used
$services->set(RemoveNullPropertyInitializationRector::class);

View File

@ -3,16 +3,15 @@
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php74\Rector\Property\TypedPropertyRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
$services = $containerConfigurator->services();
$services->set(TypedPropertyRector::class)
->configure([
TypedPropertyRector::CLASS_LIKE_TYPE_ONLY => true,
]);
$services->set(TypedPropertyRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES);
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
};

View File

@ -1,128 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\Fixture;
use JMS\Serializer\Annotation as Serializer;
final class GetterType
{
/**
* @Serializer\Type("string")
*/
private $email;
/**
* @Serializer\Type("string")
*/
private $password;
/**
* @Serializer\Type("string")
*/
private $language;
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
public function hasLanguage(): bool
{
return $this->language !== null;
}
public function getLanguage()
{
return $this->language;
}
public function setLanguage(string $language): void
{
$this->language = $language;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): void
{
$this->password = $password;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\Fixture;
use JMS\Serializer\Annotation as Serializer;
final class GetterType
{
/**
* @Serializer\Type("string")
* @var string|null
*/
private $email;
/**
* @Serializer\Type("string")
* @var string|null
*/
private $password;
/**
* @Serializer\Type("string")
* @var string|null
*/
private $language;
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
public function hasLanguage(): bool
{
return $this->language !== null;
}
public function getLanguage()
{
return $this->language;
}
public function setLanguage(string $language): void
{
$this->language = $language;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): void
{
$this->password = $password;
}
}
?>

View File

@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\Fixture;
final class GetterTypeFromVarDoc
{
private $surname;
/**
* @return string
*/
public function getSurname()
{
return $this->surname;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\Fixture;
final class GetterTypeFromVarDoc
{
/**
* @var string
*/
private $surname;
/**
* @return string
*/
public function getSurname()
{
return $this->surname;
}
}
?>

View File

@ -1,8 +0,0 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\FixturePhp74;
class SkipTypedProperty
{
public string $name = 'hey';
}

View File

@ -2,10 +2,15 @@
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::TYPED_PROPERTIES - 1);
$services = $containerConfigurator->services();
$services->set(PropertyTypeDeclarationRector::class);
};

View File

@ -9,7 +9,7 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::TYPED_PROPERTIES);
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES);
$services = $containerConfigurator->services();
$services->set(PropertyTypeDeclarationRector::class);

View File

@ -0,0 +1,38 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector\FixturePhp73;
use stdClass;
final class OnlyVarDocProperty
{
private $stdClass;
public function __construct(stdClass $stdClass)
{
$this->stdClass = $stdClass;
}
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector\FixturePhp73;
use stdClass;
final class OnlyVarDocProperty
{
/**
* @var \stdClass
*/
private $stdClass;
public function __construct(stdClass $stdClass)
{
$this->stdClass = $stdClass;
}
}
?>

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector;
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ClassLikeTypesOnlyTest extends AbstractRectorTestCase
final class TypedPropertyFromStrictConstructorPhp73RectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
@ -23,11 +23,11 @@ final class ClassLikeTypesOnlyTest extends AbstractRectorTestCase
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureClassLikeTypeOnly');
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp73');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/class_types_only.php';
return __DIR__ . '/config/configured_php73.php';
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyFromStrictConstructorRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::TYPED_PROPERTIES - 1);
};

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector\FixturePhp73;
final class PrivatePropertyWithVarDoc
{
public $name;
public function getName(): string
{
return $this->name;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector\FixturePhp73;
final class PrivatePropertyWithVarDoc
{
/**
* @var string
*/
public $name;
public function getName(): string
{
return $this->name;
}
}
?>

View File

@ -2,16 +2,13 @@
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector;
namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @requires PHP 7.4
*/
final class Php74Test extends AbstractRectorTestCase
final class TypedPropertyFromStrictGetterMethodReturnTypePhp73RectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
@ -26,11 +23,11 @@ final class Php74Test extends AbstractRectorTestCase
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp74');
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp73');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/typed_property.php';
return __DIR__ . '/config/rule_php73.php';
}
}

View File

@ -2,10 +2,15 @@
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::TYPED_PROPERTIES);
};

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::TYPED_PROPERTIES - 1);
};

View File

@ -12,6 +12,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Generic\TemplateObjectWithoutClassType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
@ -49,6 +50,10 @@ final class VarTagRemover
public function removeVarPhpTagValueNodeIfNotComment(Expression | Property | Param $node, Type $type): void
{
if ($type instanceof TemplateObjectWithoutClassType) {
return;
}
// keep doctrine collection narrow type
if ($this->doctrineTypeAnalyzer->isDoctrineCollectionWithIterableUnionType($type)) {
return;

View File

@ -7,12 +7,10 @@ namespace Rector\Php74\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
@ -28,10 +26,8 @@ use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Php74\TypeAnalyzer\ObjectTypeAnalyzer;
use Rector\Php74\TypeAnalyzer\PropertyUnionTypeResolver;
use Rector\PHPStanStaticTypeMapper\DoctrineTypeAnalyzer;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use Rector\VendorLocker\VendorLockResolver;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@ -45,25 +41,14 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
* @see \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\ClassLikeTypesOnlyTest
* @see \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\DoctrineTypedPropertyRectorTest
* @see \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\ImportedTest
* @see \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\UnionTypedPropertyRectorTest
*/
final class TypedPropertyRector extends AbstractRector implements AllowEmptyConfigurableRectorInterface, MinPhpVersionInterface
{
/**
* @var string
*/
final public const CLASS_LIKE_TYPE_ONLY = 'class_like_type_only';
/**
* @var string
*/
final public const PRIVATE_PROPERTY_ONLY = 'PRIVATE_PROPERTY_ONLY';
/**
* Useful for refactoring of huge applications. Taking types first narrows scope
*/
private bool $classLikeTypeOnly = false;
/**
* If want to keep BC, it can be set to true
* @see https://3v4l.org/spl4P
@ -75,11 +60,9 @@ final class TypedPropertyRector extends AbstractRector implements AllowEmptyConf
private readonly VendorLockResolver $vendorLockResolver,
private readonly DoctrineTypeAnalyzer $doctrineTypeAnalyzer,
private readonly VarTagRemover $varTagRemover,
private readonly ReflectionProvider $reflectionProvider,
private readonly PropertyFetchAnalyzer $propertyFetchAnalyzer,
private readonly FamilyRelationsAnalyzer $familyRelationsAnalyzer,
private readonly PropertyAnalyzer $propertyAnalyzer,
private readonly PropertyUnionTypeResolver $propertyUnionTypeResolver,
private readonly AstResolver $astResolver,
private readonly ObjectTypeAnalyzer $objectTypeAnalyzer
) {
@ -109,7 +92,6 @@ final class SomeClass
CODE_SAMPLE
,
[
self::CLASS_LIKE_TYPE_ONLY => false,
self::PRIVATE_PROPERTY_ONLY => false,
]
),
@ -160,7 +142,7 @@ CODE_SAMPLE
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($varType, TypeKind::PROPERTY());
if ($this->isNullOrNonClassLikeTypeOrMixedOrVendorLockedIn($propertyTypeNode, $node, $varType)) {
if ($this->isNullOrNonClassLikeTypeOrMixedOrVendorLockedIn($propertyTypeNode, $node)) {
return null;
}
@ -190,7 +172,6 @@ CODE_SAMPLE
*/
public function configure(array $configuration): void
{
$this->classLikeTypeOnly = $configuration[self::CLASS_LIKE_TYPE_ONLY] ?? false;
$this->privatePropertyOnly = $configuration[self::PRIVATE_PROPERTY_ONLY] ?? false;
}
@ -202,19 +183,11 @@ CODE_SAMPLE
private function isNullOrNonClassLikeTypeOrMixedOrVendorLockedIn(
Name | ComplexType | null $node,
Property $property,
Type $type
): bool {
if (! $node instanceof Node) {
return true;
}
$type = $this->propertyUnionTypeResolver->resolve($node, $type);
// is not class-type and should be skipped
if ($this->shouldSkipNonClassLikeType($node, $type)) {
return true;
}
// false positive
if (! $node instanceof Name) {
return $this->vendorLockResolver->isPropertyTypeChangeVendorLockedIn($property);
@ -227,29 +200,6 @@ CODE_SAMPLE
return true;
}
private function shouldSkipNonClassLikeType(Name|ComplexType $node, Type $type): bool
{
// unwrap nullable type
if ($node instanceof NullableType) {
$node = $node->type;
}
$typeName = $this->getName($node);
if ($typeName === null) {
return false;
}
if (! $this->classLikeTypeOnly) {
return false;
}
if ($type instanceof AliasedObjectType) {
$typeName = $type->getFullyQualifiedName();
}
return ! $this->reflectionProvider->hasClass($typeName);
}
private function removeDefaultValueForDoctrineCollection(Property $property, Type $propertyType): void
{
if (! $this->doctrineTypeAnalyzer->isDoctrineCollectionWithIterableUnionType($propertyType)) {

View File

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Php74\TypeAnalyzer;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
final class PropertyUnionTypeResolver
{
public function resolve(Name|ComplexType $phpUnionType, Type $possibleUnionType): Type
{
if (! $phpUnionType instanceof NullableType) {
return $possibleUnionType;
}
if (! $possibleUnionType instanceof UnionType) {
return $possibleUnionType;
}
$types = $possibleUnionType->getTypes();
foreach ($types as $type) {
if (! $type instanceof NullType) {
return $type;
}
}
return $possibleUnionType;
}
}

View File

@ -6,16 +6,21 @@ namespace Rector\TypeDeclaration\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\UnionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @deprecated Split to smaller specific rules.
* @see \Rector\Tests\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector\PropertyTypeDeclarationRectorTest
*/
final class PropertyTypeDeclarationRector extends AbstractRector
@ -96,6 +101,10 @@ CODE_SAMPLE
return null;
}
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
return $this->completeTypedProperty($type, $node, $phpDocInfo);
}
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $type);
return $node;
@ -112,4 +121,21 @@ CODE_SAMPLE
return false;
}
private function completeTypedProperty(Type $type, Property $property, PhpDocInfo $phpDocInfo): Property
{
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY());
if ($propertyTypeNode instanceof UnionType) {
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
$property->type = $propertyTypeNode;
return $property;
}
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $type);
return $property;
}
$property->type = $propertyTypeNode;
return $property;
}
}

View File

@ -8,23 +8,24 @@ use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\ConstructorPropertyTypeInferer;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector\TypedPropertyFromStrictConstructorRectorTest
*/
final class TypedPropertyFromStrictConstructorRector extends AbstractRector implements MinPhpVersionInterface
final class TypedPropertyFromStrictConstructorRector extends AbstractRector
{
public function __construct(
private readonly ConstructorPropertyTypeInferer $constructorPropertyTypeInferer,
private readonly VarTagRemover $varTagRemover
private readonly VarTagRemover $varTagRemover,
private readonly PhpDocTypeChanger $phpDocTypeChanger
) {
}
@ -86,8 +87,14 @@ CODE_SAMPLE
return null;
}
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($varType, TypeKind::PROPERTY());
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $varType);
return $node;
}
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($varType, TypeKind::PROPERTY());
if (! $propertyTypeNode instanceof Node) {
return null;
}
@ -95,7 +102,6 @@ CODE_SAMPLE
$node->type = $propertyTypeNode;
$node->props[0]->default = null;
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node);
return $node;

View File

@ -11,23 +11,25 @@ use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\GetterTypeDeclarationPropertyTypeInferer;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector\TypedPropertyFromStrictGetterMethodReturnTypeRectorTest
* @todo make generic
*/
final class TypedPropertyFromStrictGetterMethodReturnTypeRector extends AbstractRector implements MinPhpVersionInterface
final class TypedPropertyFromStrictGetterMethodReturnTypeRector extends AbstractRector
{
public function __construct(
private readonly GetterTypeDeclarationPropertyTypeInferer $getterTypeDeclarationPropertyTypeInferer,
private readonly PhpDocTypeChanger $phpDocTypeChanger,
private readonly VarTagRemover $varTagRemover
) {
}
@ -90,6 +92,12 @@ CODE_SAMPLE
return null;
}
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $getterReturnType);
return $node;
}
// if property is public, it should be nullable
if ($node->isPublic() && ! TypeCombinator::containsNull($getterReturnType)) {
$getterReturnType = TypeCombinator::addNull($getterReturnType);
@ -103,6 +111,9 @@ CODE_SAMPLE
$node->type = $propertyType;
$this->decorateDefaultNull($getterReturnType, $node);
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node);
return $node;
}

View File

@ -30,10 +30,9 @@ use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
final class ConstructorPropertyTypeInferer implements PropertyTypeInfererInterface
final class ConstructorPropertyTypeInferer
{
public function __construct(
private readonly ClassMethodPropertyFetchManipulator $classMethodPropertyFetchManipulator,
@ -75,11 +74,6 @@ final class ConstructorPropertyTypeInferer implements PropertyTypeInfererInterfa
return null;
}
public function getPriority(): int
{
return 800;
}
private function resolveFromParamType(Param $param, ClassMethod $classMethod, string $propertyName): Type
{
$type = $this->resolveParamTypeToPHPStanType($param);

View File

@ -19,6 +19,9 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
/**
* @deprecated Move to rector-doctrine, under code quality
*/
final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInterface
{
/**

View File

@ -19,6 +19,9 @@ use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\PhpDoc\ShortClassExpander;
/**
* @deprecated Move to rector-doctrine, under code quality
*/
final class DoctrineRelationPropertyTypeInferer implements PropertyTypeInfererInterface
{
/**

View File

@ -1,120 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\FunctionLikeReturnTypeResolver;
use Rector\TypeDeclaration\NodeAnalyzer\ClassMethodAndPropertyAnalyzer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnedNodesReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTagReturnTypeInferer;
final class GetterPropertyTypeInferer implements PropertyTypeInfererInterface
{
public function __construct(
private readonly ReturnTagReturnTypeInferer $returnTagReturnTypeInferer,
private readonly ReturnedNodesReturnTypeInferer $returnedNodesReturnTypeInferer,
private readonly FunctionLikeReturnTypeResolver $functionLikeReturnTypeResolver,
private readonly ClassMethodAndPropertyAnalyzer $classMethodAndPropertyAnalyzer,
private readonly NodeNameResolver $nodeNameResolver,
private readonly BetterNodeFinder $betterNodeFinder,
private readonly AstResolver $astResolver
) {
}
public function inferProperty(Property $property): ?Type
{
$class = $this->betterNodeFinder->findParentType($property, Class_::class);
if (! $class instanceof Class_) {
return null;
}
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($property);
$returnTypes = [];
$classAndTraitMethods = $this->resolveClassAndTraitMethods($class);
foreach ($classAndTraitMethods as $classAndTraitMethod) {
if (! $this->classMethodAndPropertyAnalyzer->hasClassMethodOnlyStatementReturnOfPropertyFetch(
$classAndTraitMethod,
$propertyName
)) {
continue;
}
$returnType = $this->inferClassMethodReturnType($classAndTraitMethod);
if ($returnType instanceof MixedType) {
continue;
}
$returnTypes[] = $returnType;
}
if ($returnTypes === []) {
return null;
}
if (count($returnTypes) === 1) {
return $returnTypes[0];
}
return new UnionType($returnTypes);
}
public function getPriority(): int
{
return 1700;
}
private function inferClassMethodReturnType(ClassMethod $classMethod): Type
{
$returnTypeDeclarationType = $this->functionLikeReturnTypeResolver->resolveFunctionLikeReturnTypeToPHPStanType(
$classMethod
);
if (! $returnTypeDeclarationType instanceof MixedType) {
return $returnTypeDeclarationType;
}
$inferedType = $this->returnedNodesReturnTypeInferer->inferFunctionLike($classMethod);
if (! $inferedType instanceof MixedType) {
return $inferedType;
}
return $this->returnTagReturnTypeInferer->inferFunctionLike($classMethod);
}
/**
* @return ClassMethod[]
*/
private function resolveClassAndTraitMethods(Class_ $class): array
{
$classAndTraitMethods = $class->getMethods();
foreach ($class->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $traitName) {
$trait = $this->astResolver->resolveClassFromName($traitName->toString());
if (! $trait instanceof Trait_) {
continue;
}
$classAndTraitMethods = array_merge($classAndTraitMethods, $trait->getMethods());
}
}
return $classAndTraitMethods;
}
}

View File

@ -12,11 +12,10 @@ use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface;
use Rector\TypeDeclaration\FunctionLikeReturnTypeResolver;
use Rector\TypeDeclaration\NodeAnalyzer\ClassMethodAndPropertyAnalyzer;
final class GetterTypeDeclarationPropertyTypeInferer implements PropertyTypeInfererInterface
final class GetterTypeDeclarationPropertyTypeInferer
{
public function __construct(
private readonly FunctionLikeReturnTypeResolver $functionLikeReturnTypeResolver,
@ -60,9 +59,4 @@ final class GetterTypeDeclarationPropertyTypeInferer implements PropertyTypeInfe
return null;
}
public function getPriority(): int
{
return 630;
}
}

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
use Rector\Php74\Rector\Property\TypedPropertyRector;
use Rector\Php80\Rector\Class_\StringableForToStringRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(StringableForToStringRector::class);
$services->set(TypedPropertyRector::class);
$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
};