Skip to content

Commit befac07

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

File tree

8 files changed

+254
-2
lines changed

8 files changed

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

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)