Skip to content

Commit 32b9ebf

Browse files
committed
Updated Rector to commit c010b0244dd064fde1351141906f88fd1df20114
rectorphp/rector-src@c010b02 split TypedPropertyFromJMSSerializerAttributeTypeRector to scalar and object types (#7602)
1 parent f109ac8 commit 32b9ebf

File tree

9 files changed

+418
-231
lines changed

9 files changed

+418
-231
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare (strict_types=1);
4+
namespace Rector\TypeDeclaration\NodeAnalyzer;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Attribute;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\Node\Stmt\Property;
10+
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;
11+
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
12+
use Rector\Enum\ClassName;
13+
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
14+
use Rector\PhpParser\Node\Value\ValueResolver;
15+
use Rector\Util\StringUtils;
16+
final class JMSTypeAnalyzer
17+
{
18+
/**
19+
* @readonly
20+
*/
21+
private AttributeFinder $attributeFinder;
22+
/**
23+
* @readonly
24+
*/
25+
private PhpAttributeAnalyzer $phpAttributeAnalyzer;
26+
/**
27+
* @readonly
28+
*/
29+
private ValueResolver $valueResolver;
30+
public function __construct(AttributeFinder $attributeFinder, PhpAttributeAnalyzer $phpAttributeAnalyzer, ValueResolver $valueResolver)
31+
{
32+
$this->attributeFinder = $attributeFinder;
33+
$this->phpAttributeAnalyzer = $phpAttributeAnalyzer;
34+
$this->valueResolver = $valueResolver;
35+
}
36+
public function hasAtLeastOneUntypedPropertyUsingJmsAttribute(Class_ $class): bool
37+
{
38+
foreach ($class->getProperties() as $property) {
39+
if ($property->type instanceof Node) {
40+
continue;
41+
}
42+
if ($this->attributeFinder->hasAttributeByClasses($property, [ClassName::JMS_TYPE])) {
43+
return \true;
44+
}
45+
}
46+
return \false;
47+
}
48+
public function hasPropertyJMSTypeAttribute(Property $property): bool
49+
{
50+
if (!$this->phpAttributeAnalyzer->hasPhpAttribute($property, ClassName::JMS_TYPE)) {
51+
return \false;
52+
}
53+
// most likely collection, not sole type
54+
return !$this->phpAttributeAnalyzer->hasPhpAttributes($property, array_merge(CollectionMapping::TO_MANY_CLASSES, CollectionMapping::TO_ONE_CLASSES));
55+
}
56+
public function resolveTypeAttributeValue(Property $property): ?string
57+
{
58+
$jmsTypeAttribute = $this->attributeFinder->findAttributeByClass($property, ClassName::JMS_TYPE);
59+
if (!$jmsTypeAttribute instanceof Attribute) {
60+
return null;
61+
}
62+
$typeValue = $this->valueResolver->getValue($jmsTypeAttribute->args[0]->value);
63+
if (!is_string($typeValue)) {
64+
return null;
65+
}
66+
if (StringUtils::isMatch($typeValue, '#DateTime\<(.*?)\>#')) {
67+
// special case for DateTime, which is not a scalar type
68+
return 'DateTime';
69+
}
70+
return $typeValue;
71+
}
72+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare (strict_types=1);
4+
namespace Rector\TypeDeclaration\NodeFactory;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Identifier;
8+
use PhpParser\Node\Stmt\Property;
9+
use PHPStan\Type\FloatType;
10+
use PHPStan\Type\MixedType;
11+
use PHPStan\Type\ObjectType;
12+
use PHPStan\Type\StringType;
13+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
14+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
15+
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
16+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
17+
use Rector\StaticTypeMapper\Mapper\ScalarStringToTypeMapper;
18+
use Rector\StaticTypeMapper\StaticTypeMapper;
19+
final class JMSTypePropertyTypeFactory
20+
{
21+
/**
22+
* @readonly
23+
*/
24+
private ScalarStringToTypeMapper $scalarStringToTypeMapper;
25+
/**
26+
* @readonly
27+
*/
28+
private StaticTypeMapper $staticTypeMapper;
29+
/**
30+
* @readonly
31+
*/
32+
private PhpDocInfoFactory $phpDocInfoFactory;
33+
/**
34+
* @readonly
35+
*/
36+
private VarTagRemover $varTagRemover;
37+
public function __construct(ScalarStringToTypeMapper $scalarStringToTypeMapper, StaticTypeMapper $staticTypeMapper, PhpDocInfoFactory $phpDocInfoFactory, VarTagRemover $varTagRemover)
38+
{
39+
$this->scalarStringToTypeMapper = $scalarStringToTypeMapper;
40+
$this->staticTypeMapper = $staticTypeMapper;
41+
$this->phpDocInfoFactory = $phpDocInfoFactory;
42+
$this->varTagRemover = $varTagRemover;
43+
}
44+
public function createObjectTypeNode(string $typeValue): ?Node
45+
{
46+
// skip generic iterable types
47+
if (strpos($typeValue, '<') !== \false) {
48+
return null;
49+
}
50+
$type = $this->scalarStringToTypeMapper->mapScalarStringToType($typeValue);
51+
if ($type instanceof MixedType) {
52+
// fallback to object type
53+
$type = new ObjectType($typeValue);
54+
}
55+
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY);
56+
}
57+
public function createScalarTypeNode(string $typeValue, Property $property): ?Node
58+
{
59+
if ($typeValue === 'float') {
60+
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property);
61+
// fallback to string, as most likely string representation of float
62+
if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof StringType) {
63+
$this->varTagRemover->removeVarTag($property);
64+
return new Identifier('string');
65+
}
66+
}
67+
if ($typeValue === 'string') {
68+
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property);
69+
// fallback to string, as most likely string representation of float
70+
if ($propertyPhpDocInfo instanceof PhpDocInfo && $propertyPhpDocInfo->getVarType() instanceof FloatType) {
71+
$this->varTagRemover->removeVarTag($property);
72+
return new Identifier('float');
73+
}
74+
}
75+
$type = $this->scalarStringToTypeMapper->mapScalarStringToType($typeValue);
76+
if ($type instanceof MixedType) {
77+
return null;
78+
}
79+
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY);
80+
}
81+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare (strict_types=1);
4+
namespace Rector\TypeDeclaration\Rector\Class_;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Name\FullyQualified;
9+
use PhpParser\Node\NullableType;
10+
use PhpParser\Node\Stmt\Class_;
11+
use PhpParser\Node\Stmt\Property;
12+
use PHPStan\Reflection\ClassReflection;
13+
use Rector\Php74\Guard\MakePropertyTypedGuard;
14+
use Rector\PHPStan\ScopeFetcher;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\TypeDeclaration\NodeAnalyzer\JMSTypeAnalyzer;
17+
use Rector\TypeDeclaration\NodeFactory\JMSTypePropertyTypeFactory;
18+
use Rector\ValueObject\PhpVersionFeature;
19+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
20+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
21+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
22+
/**
23+
* @see \Rector\Tests\TypeDeclaration\Rector\Class_\ObjectTypedPropertyFromJMSSerializerAttributeTypeRector\ObjectTypedPropertyFromJMSSerializerAttributeTypeRectorTest
24+
*/
25+
final class ObjectTypedPropertyFromJMSSerializerAttributeTypeRector extends AbstractRector implements MinPhpVersionInterface
26+
{
27+
/**
28+
* @readonly
29+
*/
30+
private MakePropertyTypedGuard $makePropertyTypedGuard;
31+
/**
32+
* @readonly
33+
*/
34+
private JMSTypeAnalyzer $jmsTypeAnalyzer;
35+
/**
36+
* @readonly
37+
*/
38+
private JMSTypePropertyTypeFactory $jmsTypePropertyTypeFactory;
39+
public function __construct(MakePropertyTypedGuard $makePropertyTypedGuard, JMSTypeAnalyzer $jmsTypeAnalyzer, JMSTypePropertyTypeFactory $jmsTypePropertyTypeFactory)
40+
{
41+
$this->makePropertyTypedGuard = $makePropertyTypedGuard;
42+
$this->jmsTypeAnalyzer = $jmsTypeAnalyzer;
43+
$this->jmsTypePropertyTypeFactory = $jmsTypePropertyTypeFactory;
44+
}
45+
public function getRuleDefinition(): RuleDefinition
46+
{
47+
return new RuleDefinition('Add object typed property from JMS Serializer Type attribute', [new CodeSample(<<<'CODE_SAMPLE'
48+
use JMS\Serializer\Annotation\Type;
49+
50+
final class SomeClass
51+
{
52+
#[Type(Product::class)]
53+
private $product;
54+
}
55+
CODE_SAMPLE
56+
, <<<'CODE_SAMPLE'
57+
use JMS\Serializer\Annotation\Type;
58+
59+
final class SomeClass
60+
{
61+
#[Type(Product::class)]
62+
private ?Product $product = null;
63+
}
64+
CODE_SAMPLE
65+
)]);
66+
}
67+
/**
68+
* @return array<class-string<Node>>
69+
*/
70+
public function getNodeTypes(): array
71+
{
72+
return [Class_::class];
73+
}
74+
public function provideMinPhpVersion(): int
75+
{
76+
return PhpVersionFeature::ATTRIBUTES;
77+
}
78+
/**
79+
* @param Class_ $node
80+
*/
81+
public function refactor(Node $node): ?Node
82+
{
83+
if (!$this->jmsTypeAnalyzer->hasAtLeastOneUntypedPropertyUsingJmsAttribute($node)) {
84+
return null;
85+
}
86+
$scope = ScopeFetcher::fetch($node);
87+
$classReflection = $scope->getClassReflection();
88+
if (!$classReflection instanceof ClassReflection) {
89+
return null;
90+
}
91+
$hasChanged = \false;
92+
foreach ($node->getProperties() as $property) {
93+
if ($this->shouldSkipProperty($property, $classReflection)) {
94+
continue;
95+
}
96+
$typeValue = $this->jmsTypeAnalyzer->resolveTypeAttributeValue($property);
97+
if (!is_string($typeValue)) {
98+
continue;
99+
}
100+
$propertyTypeNode = $this->jmsTypePropertyTypeFactory->createObjectTypeNode($typeValue);
101+
if (!$propertyTypeNode instanceof FullyQualified) {
102+
continue;
103+
}
104+
$property->type = new NullableType($propertyTypeNode);
105+
$property->props[0]->default = $this->nodeFactory->createNull();
106+
$hasChanged = \true;
107+
}
108+
if ($hasChanged) {
109+
return $node;
110+
}
111+
return null;
112+
}
113+
private function shouldSkipProperty(Property $property, ClassReflection $classReflection): bool
114+
{
115+
if ($property->type instanceof Node || $property->props[0]->default instanceof Expr) {
116+
return \true;
117+
}
118+
if (!$this->jmsTypeAnalyzer->hasPropertyJMSTypeAttribute($property)) {
119+
return \true;
120+
}
121+
return !$this->makePropertyTypedGuard->isLegal($property, $classReflection);
122+
}
123+
}

0 commit comments

Comments
 (0)