Skip to content

Commit 8c5e9e1

Browse files
authored
[DowngradePhp82] Add DowngradeUnionIntersectionRector (#363)
* [DowngradePhp82] Add DowngradeUnionIntersectionRector * add fixture for skip other union
1 parent b0da459 commit 8c5e9e1

File tree

9 files changed

+267
-1
lines changed

9 files changed

+267
-1
lines changed

config/set/downgrade-php82.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Rector\Config\RectorConfig;
66
use Rector\ValueObject\PhpVersion;
77
use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector;
8+
use Rector\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector;
89
use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector;
910
use Rector\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector;
1011

@@ -14,5 +15,6 @@
1415
DowngradeReadonlyClassRector::class,
1516
DowngradeStandaloneNullTrueFalseReturnTypeRector::class,
1617
DowngradeIteratorCountToArrayRector::class,
18+
DowngradeUnionIntersectionRector::class,
1719
]);
1820
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DowngradeUnionIntersectionRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
4+
5+
function onParamFunction((A&B)|C $foo)
6+
{
7+
}
8+
9+
?>
10+
-----
11+
<?php
12+
13+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
14+
15+
/**
16+
* @param (\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\A & \Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\B)|\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\C $foo
17+
*/
18+
function onParamFunction($foo)
19+
{
20+
}
21+
22+
?>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
4+
5+
final class OnProperty
6+
{
7+
public (A&B)|C $foo;
8+
}
9+
10+
?>
11+
-----
12+
<?php
13+
14+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
15+
16+
final class OnProperty
17+
{
18+
/**
19+
* @var (\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\A & \Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\B)|\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\C
20+
*/
21+
public $foo;
22+
}
23+
24+
?>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
4+
5+
function onReturnFunction(): (A&B)|C
6+
{
7+
}
8+
9+
?>
10+
-----
11+
<?php
12+
13+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
14+
15+
/**
16+
* @return (\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\A & \Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\B)|\Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture\C
17+
*/
18+
function onReturnFunction()
19+
{
20+
}
21+
22+
?>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\Fixture;
4+
5+
final class SkipOtherUnion
6+
{
7+
public A|B|C $foo;
8+
}
9+
10+
?>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(DowngradeUnionIntersectionRector::class);
10+
};

rules/DowngradePhp80/Rector/Expression/DowngradeThrowExprRector.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ public function refactor(Node $node): Node|array|null
108108
foreach ($args as $arg) {
109109
if ($arg->value instanceof Ternary && $arg->value->else instanceof Throw_) {
110110
$refactorTernary = $this->refactorTernary($arg->value, null, true);
111-
if (is_array($refactorTernary) && $refactorTernary[0] instanceof Expression && $refactorTernary[0]->expr instanceof Assign) {
111+
if (is_array(
112+
$refactorTernary
113+
) && $refactorTernary[0] instanceof Expression && $refactorTernary[0]->expr instanceof Assign) {
112114
$arg->value = $refactorTernary[0]->expr->var;
113115

114116
return [...$refactorTernary, $node];
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DowngradePhp82\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\ArrowFunction;
9+
use PhpParser\Node\Expr\Closure;
10+
use PhpParser\Node\IntersectionType;
11+
use PhpParser\Node\Stmt\ClassLike;
12+
use PhpParser\Node\Stmt\ClassMethod;
13+
use PhpParser\Node\Stmt\Function_;
14+
use PhpParser\Node\UnionType;
15+
use Rector\NodeManipulator\PropertyDecorator;
16+
use Rector\PhpDocDecorator\PhpDocFromTypeDeclarationDecorator;
17+
use Rector\Rector\AbstractRector;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
use Webmozart\Assert\Assert;
21+
22+
/**
23+
* @changelog https://php.watch/versions/8.2/dnf-types
24+
*
25+
* @see \Rector\Tests\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector\DowngradeUnionIntersectionRectorTest
26+
*/
27+
final class DowngradeUnionIntersectionRector extends AbstractRector
28+
{
29+
public function __construct(
30+
private readonly PropertyDecorator $propertyDecorator,
31+
private readonly PhpDocFromTypeDeclarationDecorator $phpDocFromTypeDeclarationDecorator
32+
) {
33+
}
34+
35+
public function getRuleDefinition(): RuleDefinition
36+
{
37+
return new RuleDefinition(
38+
'Remove the union type with intersection, use docblock based',
39+
[
40+
new CodeSample(
41+
<<<'CODE_SAMPLE'
42+
final class SomeClass
43+
{
44+
public (A&B)|C $foo;
45+
}
46+
CODE_SAMPLE
47+
,
48+
<<<'CODE_SAMPLE'
49+
final class SomeClass
50+
{
51+
/**
52+
* @var (A&B)|C
53+
*/
54+
public $foo;
55+
}
56+
CODE_SAMPLE
57+
),
58+
]
59+
);
60+
}
61+
62+
/**
63+
* @return array<class-string<Node>>
64+
*/
65+
public function getNodeTypes(): array
66+
{
67+
return [ClassLike::class, ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class];
68+
}
69+
70+
/**
71+
* @param ClassLike|ClassMethod|Function_|Closure|ArrowFunction $node
72+
*/
73+
public function refactor(Node $node): ?Node
74+
{
75+
if (! $node instanceof ClassLike) {
76+
return $this->processFunctionLike($node);
77+
}
78+
79+
return $this->processClassLike($node);
80+
}
81+
82+
private function processFunctionLike(
83+
ClassMethod|Function_|Closure|ArrowFunction $functionLike
84+
): null|ClassMethod|Function_|Closure|ArrowFunction {
85+
$paramDecorated = false;
86+
foreach ($functionLike->getParams() as $param) {
87+
if (! $this->isUnionIntersection($param->type)) {
88+
continue;
89+
}
90+
91+
Assert::isInstanceOf($param->type, UnionType::class);
92+
$this->phpDocFromTypeDeclarationDecorator->decorateParam(
93+
$param,
94+
$functionLike,
95+
[\PHPStan\Type\UnionType::class]
96+
);
97+
$paramDecorated = true;
98+
}
99+
100+
if (! $this->isUnionIntersection($functionLike->returnType)) {
101+
if ($paramDecorated) {
102+
return $functionLike;
103+
}
104+
105+
return null;
106+
}
107+
108+
Assert::isInstanceOf($functionLike->returnType, UnionType::class);
109+
$this->phpDocFromTypeDeclarationDecorator->decorateReturn($functionLike);
110+
111+
return $functionLike;
112+
}
113+
114+
private function processClassLike(ClassLike $classLike): ?ClassLike
115+
{
116+
$hasChanged = false;
117+
foreach ($classLike->getProperties() as $property) {
118+
if (! $this->isUnionIntersection($property->type)) {
119+
continue;
120+
}
121+
122+
Assert::isInstanceOf($property->type, UnionType::class);
123+
$this->propertyDecorator->decorateWithDocBlock($property, $property->type);
124+
125+
$property->type = null;
126+
$hasChanged = true;
127+
}
128+
129+
return $hasChanged ? $classLike : null;
130+
}
131+
132+
private function isUnionIntersection(?Node $node): bool
133+
{
134+
if (! $node instanceof UnionType) {
135+
return false;
136+
}
137+
138+
foreach ($node->types as $type) {
139+
if ($type instanceof IntersectionType) {
140+
return true;
141+
}
142+
}
143+
144+
return false;
145+
}
146+
}

0 commit comments

Comments
 (0)