diff --git a/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/date_time_with_format.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/date_time_with_format.php.inc new file mode 100644 index 00000000000..182b31072b6 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/date_time_with_format.php.inc @@ -0,0 +1,23 @@ +")] + private $name; +} + +?> +----- +")] + private ?\DateTime $name = null; +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc new file mode 100644 index 00000000000..1b3453807a1 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc new file mode 100644 index 00000000000..6d05626f069 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc similarity index 51% rename from rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc rename to rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc index f23ffa7218c..bd0b1b59d49 100644 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_generic_iterable.php.inc @@ -1,6 +1,6 @@ phpVersion(PhpVersionFeature::ATTRIBUTES); - $rectorConfig->rule(TypedPropertyFromJMSSerializerAttributeTypeRector::class); + $rectorConfig->rule(ObjectTypedPropertyFromJMSSerializerAttributeTypeRector::class); }; diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_typed_assigned.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_type_assigned.php.inc similarity index 64% rename from rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_typed_assigned.php.inc rename to rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_type_assigned.php.inc index 0dbe3bdd3f7..fd1f89eb794 100644 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_typed_assigned.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_type_assigned.php.inc @@ -1,6 +1,6 @@ +----- + diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc similarity index 55% rename from rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc rename to rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc index aae9b2feee9..82202dcd380 100644 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/string_var_float.php.inc @@ -1,6 +1,6 @@ doTestFile($filePath); + } + + /** + * @return Iterator + */ + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php new file mode 100644 index 00000000000..9398214f2fa --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php @@ -0,0 +1,8 @@ +phpVersion(PhpVersionFeature::ATTRIBUTES); + $rectorConfig->rule(ScalarTypedPropertyFromJMSSerializerAttributeTypeRector::class); +}; diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc deleted file mode 100644 index affe0a80389..00000000000 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/exact_class.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc deleted file mode 100644 index 992b965592d..00000000000 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/handle_public_as_nullable.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_with_default.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_with_default.php.inc deleted file mode 100644 index bc5a94b1c65..00000000000 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/skip_with_default.php.inc +++ /dev/null @@ -1,9 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/with_format.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/with_format.php.inc deleted file mode 100644 index 9b0a4c331c6..00000000000 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Fixture/with_format.php.inc +++ /dev/null @@ -1,23 +0,0 @@ -")] - private $name; -} - -?> ------ -")] - private ?\DateTime $name = null; -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php deleted file mode 100644 index 419f60d9e89..00000000000 --- a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector/Source/SomeClassInSerializer.php +++ /dev/null @@ -1,8 +0,0 @@ -getProperties() as $property) { + if ($property->type instanceof Node) { + continue; + } + + if ($this->attributeFinder->hasAttributeByClasses($property, [ClassName::JMS_TYPE])) { + return true; + } + } + + return false; + } + + public function hasPropertyJMSTypeAttribute(Property $property): bool + { + if (! $this->phpAttributeAnalyzer->hasPhpAttribute($property, ClassName::JMS_TYPE)) { + return false; + } + + // most likely collection, not sole type + return ! $this->phpAttributeAnalyzer->hasPhpAttributes( + $property, + array_merge(CollectionMapping::TO_MANY_CLASSES, CollectionMapping::TO_ONE_CLASSES) + ); + } + + public function resolveTypeAttributeValue(Property $property): ?string + { + $jmsTypeAttribute = $this->attributeFinder->findAttributeByClass($property, ClassName::JMS_TYPE); + if (! $jmsTypeAttribute instanceof Attribute) { + return null; + } + + $typeValue = $this->valueResolver->getValue($jmsTypeAttribute->args[0]->value); + if (! is_string($typeValue)) { + return null; + } + + if (StringUtils::isMatch($typeValue, '#DateTime\<(.*?)\>#')) { + // special case for DateTime, which is not a scalar type + return 'DateTime'; + } + + return $typeValue; + } +} diff --git a/rules/TypeDeclaration/NodeFactory/JMSTypePropertyTypeFactory.php b/rules/TypeDeclaration/NodeFactory/JMSTypePropertyTypeFactory.php new file mode 100644 index 00000000000..e5d94ac1357 --- /dev/null +++ b/rules/TypeDeclaration/NodeFactory/JMSTypePropertyTypeFactory.php @@ -0,0 +1,74 @@ +scalarStringToTypeMapper->mapScalarStringToType($typeValue); + if ($type instanceof MixedType) { + // fallback to object type + $type = new ObjectType($typeValue); + } + + return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY); + } + + public function createScalarTypeNode(string $typeValue, Property $property): ?Node + { + if ($typeValue === 'float') { + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + // fallback to string, as most likely string representation of float + if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof StringType) { + $this->varTagRemover->removeVarTag($property); + return new Identifier('string'); + } + } + + if ($typeValue === 'string') { + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + // fallback to string, as most likely string representation of float + if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof FloatType) { + $this->varTagRemover->removeVarTag($property); + return new Identifier('float'); + } + } + + $type = $this->scalarStringToTypeMapper->mapScalarStringToType($typeValue); + if ($type instanceof MixedType) { + return null; + } + + return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY); + } +} diff --git a/rules/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector.php b/rules/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector.php new file mode 100644 index 00000000000..30edfc962c9 --- /dev/null +++ b/rules/TypeDeclaration/Rector/Class_/ObjectTypedPropertyFromJMSSerializerAttributeTypeRector.php @@ -0,0 +1,135 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::ATTRIBUTES; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->jmsTypeAnalyzer->hasAtLeastOneUntypedPropertyUsingJmsAttribute($node)) { + return null; + } + + $scope = ScopeFetcher::fetch($node); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + $hasChanged = false; + + foreach ($node->getProperties() as $property) { + if ($this->shouldSkipProperty($property, $classReflection)) { + continue; + } + + $typeValue = $this->jmsTypeAnalyzer->resolveTypeAttributeValue($property); + if (! is_string($typeValue)) { + continue; + } + + $propertyTypeNode = $this->jmsTypePropertyTypeFactory->createObjectTypeNode($typeValue); + if (! $propertyTypeNode instanceof FullyQualified) { + continue; + } + + $property->type = new NullableType($propertyTypeNode); + $property->props[0]->default = $this->nodeFactory->createNull(); + + $hasChanged = true; + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function shouldSkipProperty(Property $property, ClassReflection $classReflection): bool + { + if ($property->type instanceof Node || $property->props[0]->default instanceof Expr) { + return true; + } + + if (! $this->jmsTypeAnalyzer->hasPropertyJMSTypeAttribute($property)) { + return true; + } + + return ! $this->makePropertyTypedGuard->isLegal($property, $classReflection); + } +} diff --git a/rules/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector.php b/rules/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector.php new file mode 100644 index 00000000000..ed6f499c15a --- /dev/null +++ b/rules/TypeDeclaration/Rector/Class_/ScalarTypedPropertyFromJMSSerializerAttributeTypeRector.php @@ -0,0 +1,134 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::ATTRIBUTES; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->jmsTypeAnalyzer->hasAtLeastOneUntypedPropertyUsingJmsAttribute($node)) { + return null; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($node); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + $hasChanged = false; + + foreach ($node->getProperties() as $property) { + if ($this->shouldSkipProperty($property, $classReflection)) { + continue; + } + + $typeValue = $this->jmsTypeAnalyzer->resolveTypeAttributeValue($property); + if (! is_string($typeValue)) { + continue; + } + + $propertyTypeNode = $this->jmsTypePropertyTypeFactory->createScalarTypeNode($typeValue, $property); + if (! $propertyTypeNode instanceof Identifier) { + continue; + } + + $property->type = new NullableType($propertyTypeNode); + $property->props[0]->default = $this->nodeFactory->createNull(); + + $hasChanged = true; + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function shouldSkipProperty(Property $property, ClassReflection $classReflection): bool + { + if ($property->type instanceof Node || $property->props[0]->default instanceof Expr) { + return true; + } + + if (! $this->jmsTypeAnalyzer->hasPropertyJMSTypeAttribute($property)) { + return true; + } + + return ! $this->makePropertyTypedGuard->isLegal($property, $classReflection); + } +} diff --git a/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector.php b/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector.php deleted file mode 100644 index ea144b9aeab..00000000000 --- a/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromJMSSerializerAttributeTypeRector.php +++ /dev/null @@ -1,228 +0,0 @@ -> - */ - public function getNodeTypes(): array - { - return [Class_::class]; - } - - public function provideMinPhpVersion(): int - { - return PhpVersionFeature::ATTRIBUTES; - } - - /** - * @param Class_ $node - */ - public function refactor(Node $node): ?Node - { - if (! $this->hasAtLeastOneUntypedPropertyUsingJmsAttribute($node)) { - return null; - } - - $classReflection = $this->reflectionResolver->resolveClassReflection($node); - if (! $classReflection instanceof ClassReflection) { - return null; - } - - $hasChanged = false; - - foreach ($node->getProperties() as $property) { - if ($this->shouldSkipProperty($property, $classReflection)) { - continue; - } - - $typeValue = $this->resolveAttributeType($property); - if (! is_string($typeValue)) { - continue; - } - - // skip generic iterable types - if (str_contains($typeValue, '<')) { - continue; - } - - $propertyTypeNode = $this->createTypeNode($typeValue, $property); - if (! $propertyTypeNode instanceof Identifier && ! $propertyTypeNode instanceof FullyQualified) { - continue; - } - - $property->type = new NullableType($propertyTypeNode); - $property->props[0]->default = $this->nodeFactory->createNull(); - - $hasChanged = true; - } - - if ($hasChanged) { - return $node; - } - - return null; - } - - private function resolveAttributeType(Property $property): ?string - { - $jmsTypeAttribute = $this->attributeFinder->findAttributeByClass($property, ClassName::JMS_TYPE); - if (! $jmsTypeAttribute instanceof Attribute) { - return null; - } - - $typeValue = $this->valueResolver->getValue($jmsTypeAttribute->args[0]->value); - if (! is_string($typeValue)) { - return null; - } - - if (StringUtils::isMatch($typeValue, '#DateTime\<(.*?)\>#')) { - // special case for DateTime, which is not a scalar type - return 'DateTime'; - } - - return $typeValue; - } - - private function hasAtLeastOneUntypedPropertyUsingJmsAttribute(Class_ $class): bool - { - foreach ($class->getProperties() as $property) { - if ($property->type instanceof Node) { - continue; - } - - if ($this->attributeFinder->hasAttributeByClasses($property, [ClassName::JMS_TYPE])) { - return true; - } - } - - return false; - } - - private function createTypeNode(string $typeValue, Property $property): ?Node - { - if ($typeValue === 'float') { - $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); - // fallback to string, as most likely string representation of float - if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof StringType) { - $this->varTagRemover->removeVarTag($property); - return new Identifier('string'); - } - } - - if ($typeValue === 'string') { - $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); - // fallback to string, as most likely string representation of float - if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof FloatType) { - $this->varTagRemover->removeVarTag($property); - return new Identifier('float'); - } - } - - $type = $this->scalarStringToTypeMapper->mapScalarStringToType($typeValue); - if ($type instanceof MixedType) { - // fallback to object type - $type = new ObjectType($typeValue); - } - - return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY); - } - - private function shouldSkipProperty(Property $property, ClassReflection $classReflection): bool - { - if ($property->type instanceof Node || $property->props[0]->default instanceof Expr) { - return true; - } - - if (! $this->phpAttributeAnalyzer->hasPhpAttribute($property, ClassName::JMS_TYPE)) { - return true; - } - - // this will be most likely collection, not single type - if ($this->phpAttributeAnalyzer->hasPhpAttributes( - $property, - array_merge(CollectionMapping::TO_MANY_CLASSES, CollectionMapping::TO_ONE_CLASSES) - )) { - return true; - } - - return ! $this->makePropertyTypedGuard->isLegal($property, $classReflection); - } -} diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index b423a729760..67384942413 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -11,11 +11,12 @@ use Rector\TypeDeclaration\Rector\Class_\AddTestsVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\Class_\ChildDoctrineRepositoryClassTypeRector; use Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector; +use Rector\TypeDeclaration\Rector\Class_\ObjectTypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\Class_\PropertyTypeFromStrictSetterGetterRector; use Rector\TypeDeclaration\Rector\Class_\ReturnTypeFromStrictTernaryRector; +use Rector\TypeDeclaration\Rector\Class_\ScalarTypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromDocblockSetUpDefinedRector; -use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\Class_\TypedStaticPropertyInBehatContextRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamFromDimFetchKeyUseRector; @@ -152,7 +153,10 @@ final class TypeDeclarationLevel ReturnTypeFromStrictFluentReturnRector::class, ReturnNeverTypeRector::class, StrictStringParamConcatRector::class, - TypedPropertyFromJMSSerializerAttributeTypeRector::class, + + // jms attributes + ObjectTypedPropertyFromJMSSerializerAttributeTypeRector::class, + ScalarTypedPropertyFromJMSSerializerAttributeTypeRector::class, // array parameter from dim fetch assign inside StrictArrayParamDimFetchRector::class,