Skip to content

Commit 64dfa93

Browse files
ArshidArshid
authored andcommitted
[php 8.5] Pipe Operator
1 parent 295bf3e commit 64dfa93

File tree

7 files changed

+177
-0
lines changed

7 files changed

+177
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
namespace Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\Fixture;
3+
4+
$result = trim(strtoupper("Hello World"));
5+
?>
6+
-----
7+
<?php
8+
namespace Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\Fixture;
9+
10+
$result = strtoupper("Hello World") |> trim(...);
11+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
namespace Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\Fixture;
3+
4+
$result = htmlspecialchars(trim(strtoupper("Hello World")));
5+
?>
6+
-----
7+
<?php
8+
namespace Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\Fixture;
9+
10+
$result = strtoupper("Hello World") |> trim(...) |> htmlspecialchars(...);
11+
?>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
namespace Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\Fixture;
3+
4+
$result = substr(strtoupper("Hello World"), 0, 5);
5+
?>
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\Php85\Rector\FuncCall\NestedFuncCallToPipeRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class NestedFuncCallToPipeRectorTest 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Php85\Rector\FuncCall\NestedFuncCallToPipeRector;
7+
use Rector\ValueObject\PhpVersion;
8+
9+
return static function (RectorConfig $rectorConfig): void {
10+
$rectorConfig->rule(NestedFuncCallToPipeRector::class);
11+
12+
$rectorConfig->phpVersion(PhpVersion::PHP_85);
13+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Php85\Rector\FuncCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\Assign;
11+
use PhpParser\Node\Expr\BinaryOp\Pipe;
12+
use PhpParser\Node\Expr\FuncCall;
13+
use PhpParser\Node\Name;
14+
use PhpParser\Node\VariadicPlaceholder;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\NodeTypeResolver\Node\AttributeKey;
17+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
18+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
19+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
20+
use Rector\ValueObject\PhpVersionFeature;
21+
22+
/**
23+
* @see \Rector\Tests\Php85\Rector\FuncCall\NestedFuncCallToPipeRector\NestedFuncCallToPipeRectorTest
24+
*/
25+
final class NestedFuncCallToPipeRector extends AbstractRector implements MinPhpVersionInterface
26+
{
27+
public function provideMinPhpVersion(): int
28+
{
29+
return PhpVersionFeature::PIPE_OPERATOR;
30+
}
31+
32+
public function getRuleDefinition(): RuleDefinition
33+
{
34+
return new RuleDefinition(
35+
'Convert nested single-argument function calls to |> pipe operator',
36+
[
37+
new CodeSample(
38+
<<<'CODE_SAMPLE'
39+
$result = trim(strtoupper("Hello World"));
40+
CODE_SAMPLE,
41+
<<<'CODE_SAMPLE'
42+
$result = "Hello World" |> strtoupper(...) |> trim(...);
43+
CODE_SAMPLE
44+
),
45+
]
46+
);
47+
}
48+
49+
public function getNodeTypes(): array
50+
{
51+
return [Assign::class, FuncCall::class];
52+
}
53+
54+
public function refactor(Node $node): ?Node
55+
{
56+
// match assignment: $result = FuncCall
57+
if (! $node instanceof Assign) {
58+
return null;
59+
}
60+
61+
if (! $node->expr instanceof FuncCall) {
62+
return null;
63+
}
64+
65+
[$seedArgs, $calls] = $this->collectChain($node->expr);
66+
67+
if (! $seedArgs || count($calls) < 2) {
68+
return null;
69+
}
70+
71+
$calls = array_reverse($calls);
72+
73+
$firstCall = array_shift($calls);
74+
$expr = new FuncCall(clone $firstCall->name, $seedArgs);
75+
76+
foreach ($calls as $call) {
77+
$expr = new Pipe(
78+
$expr,
79+
new FuncCall(clone $call->name, [new VariadicPlaceholder()])
80+
);
81+
}
82+
83+
return new Assign($node->var, $expr);
84+
}
85+
86+
protected function collectChain(FuncCall $func): array
87+
{
88+
$calls = [];
89+
$current = $func;
90+
while ($current instanceof FuncCall && count($current->args) === 1) {
91+
$calls[] = $current;
92+
$arg = $current->args[0]->value;
93+
if ($arg instanceof FuncCall && count($arg->args) === 1) {
94+
$current = $arg;
95+
continue;
96+
}
97+
return [$current->args, $calls];
98+
}
99+
return [null, []];
100+
}
101+
102+
103+
}

src/ValueObject/PhpVersionFeature.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,4 +798,10 @@ final class PhpVersionFeature
798798
* @var int
799799
*/
800800
public const DEPRECATE_NULL_ARG_IN_ARRAY_KEY_EXISTS_FUNCTION = PhpVersion::PHP_85;
801+
802+
/**
803+
* @see https://wiki.php.net/rfc/pipe-operator-v3
804+
* @var int
805+
*/
806+
public const PIPE_OPERATOR = PhpVersion::PHP_85;
801807
}

0 commit comments

Comments
 (0)