Skip to content

Commit 6639d5c

Browse files
committed
[exp] [unambiguous] Add FluentSettersToStandaloneCallMethodRector
1 parent 623d90f commit 6639d5c

File tree

8 files changed

+251
-2
lines changed

8 files changed

+251
-2
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Fixture;
4+
5+
use Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Source\SomeSetterClass;
6+
7+
final class SomeClass
8+
{
9+
public function setup()
10+
{
11+
return (new SomeSetterClass())
12+
->setName('John')
13+
->setSurname('Doe');
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Fixture;
22+
23+
use Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Source\SomeSetterClass;
24+
25+
final class SomeClass
26+
{
27+
public function setup()
28+
{
29+
$someSetterClass = new SomeSetterClass();
30+
$someSetterClass->setSurname('Doe');
31+
$someSetterClass->setName('John');
32+
}
33+
}
34+
35+
?>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Fixture;
4+
5+
use Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Source\SomeSetterClass;
6+
7+
final class SkipSoleSetter
8+
{
9+
public function setup()
10+
{
11+
return (new SomeSetterClass())
12+
->setName('John');
13+
}
14+
}
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\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class FluentSettersToStandaloneCallMethodRectorTest 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\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\Source;
4+
5+
final class SomeSetterClass
6+
{
7+
private ?string $name = null;
8+
9+
private ?string $surname = null;
10+
11+
public function setName(string $name): self
12+
{
13+
$this->name = $name;
14+
return $this;
15+
}
16+
17+
public function setSurname(?string $surname): self
18+
{
19+
$this->surname = $surname;
20+
return $this;
21+
}
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([FluentSettersToStandaloneCallMethodRector::class]);

rules/Naming/Naming/PropertyNaming.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Rector\Naming\Naming;
66

77
use Nette\Utils\Strings;
8+
use PhpParser\Node\Name;
89
use PHPStan\Type\Generic\GenericObjectType;
910
use PHPStan\Type\ObjectType;
1011
use PHPStan\Type\StaticType;
@@ -116,8 +117,12 @@ public function getExpectedNameFromType(Type $type): ?ExpectedName
116117
return new ExpectedName($originalName, $this->rectorNamingInflector->singularize($originalName));
117118
}
118119

119-
public function fqnToVariableName(ThisType | ObjectType | string $objectType): string
120+
public function fqnToVariableName(ThisType | ObjectType | Name | string $objectType): string
120121
{
122+
if ($objectType instanceof Name) {
123+
$objectType = $objectType->toString();
124+
}
125+
121126
if ($objectType instanceof ThisType) {
122127
$objectType = $objectType->getStaticObjectType();
123128
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Unambiguous\Rector\Expression;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Assign;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Expr\New_;
12+
use PhpParser\Node\Expr\Variable;
13+
use PhpParser\Node\Name;
14+
use PhpParser\Node\Stmt\Expression;
15+
use Rector\Naming\Naming\PropertyNaming;
16+
use Rector\NodeTypeResolver\Node\AttributeKey;
17+
use Rector\Rector\AbstractRector;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
21+
/**
22+
* @experimental since 2025-11
23+
*
24+
* @see \Rector\Tests\Unambiguous\Rector\Expression\FluentSettersToStandaloneCallMethodRector\FluentSettersToStandaloneCallMethodRectorTest
25+
*/
26+
final class FluentSettersToStandaloneCallMethodRector extends AbstractRector
27+
{
28+
public function __construct(
29+
private readonly PropertyNaming $propertyNaming
30+
) {
31+
}
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition(
36+
'Change fluent setter chain calls, to standalone line of setters',
37+
[
38+
new CodeSample(
39+
<<<'CODE_SAMPLE'
40+
class SomeClass
41+
{
42+
public function run()
43+
{
44+
return (new SomeFluentClass())
45+
->setName('John')
46+
->setAge(30);
47+
}
48+
}
49+
CODE_SAMPLE
50+
,
51+
<<<'CODE_SAMPLE'
52+
class SomeClass
53+
{
54+
public function run()
55+
{
56+
$someFluentClass = new SomeFluentClass();
57+
$someFluentClass->setName('John');
58+
$someFluentClass->setAge(30);
59+
60+
return $someFluentClass;
61+
}
62+
}
63+
CODE_SAMPLE
64+
),
65+
]
66+
);
67+
}
68+
69+
/**
70+
* @return array<class-string<\PhpParser\Node>>
71+
*/
72+
public function getNodeTypes(): array
73+
{
74+
return [Expression::class, Node\Stmt\Return_::class];
75+
}
76+
77+
/**
78+
* @param Expression|Node\Stmt\Return_ $node
79+
*/
80+
public function refactor(Node $node): ?array
81+
{
82+
if (! $node->expr instanceof MethodCall) {
83+
return null;
84+
}
85+
86+
$firstMethodCall = $node->expr;
87+
88+
// must be nested method call, so we avoid only single one
89+
if (! $firstMethodCall->var instanceof MethodCall) {
90+
return null;
91+
}
92+
93+
/** @var MethodCall[] $methodCalls */
94+
$methodCalls = [];
95+
96+
$currentMethodCall = $firstMethodCall;
97+
while ($currentMethodCall instanceof MethodCall) {
98+
$methodCalls[] = $currentMethodCall;
99+
$currentMethodCall = $currentMethodCall->var;
100+
}
101+
102+
// at least 2 method calls
103+
if (count($methodCalls) < 1) {
104+
return null;
105+
}
106+
107+
$rootExpr = $currentMethodCall;
108+
109+
$variableName = $this->resolveVariableName($rootExpr);
110+
$someVariable = new Variable($variableName);
111+
112+
$firstAssign = new Assign($someVariable, $rootExpr);
113+
$stmts = [new Expression($firstAssign)];
114+
115+
foreach ($methodCalls as $methodCall) {
116+
$methodCall->var = $someVariable;
117+
// inlines indent and removes () around first expr
118+
$methodCall->setAttribute(AttributeKey::ORIGINAL_NODE, null);
119+
$stmts[] = new Expression($methodCall);
120+
}
121+
122+
$node->expr = $someVariable;
123+
124+
return $stmts;
125+
}
126+
127+
private function resolveVariableName(Expr $expr): string
128+
{
129+
if ($expr instanceof New_) {
130+
if ($expr->class instanceof Name) {
131+
return $this->propertyNaming->fqnToVariableName($expr->class);
132+
}
133+
}
134+
135+
return 'someVariable';
136+
}
137+
}

stubs/Doctrine/ORM/QueryBuilder.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@
1010

1111
class QueryBuilder
1212
{
13-
1413
}

0 commit comments

Comments
 (0)