diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/nested_functions.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/nested_functions.php.inc deleted file mode 100644 index 07fb0dd8d8e..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/nested_functions.php.inc +++ /dev/null @@ -1,30 +0,0 @@ - ------ - htmlspecialchars(...) - |> strtolower(...) - |> trim(...); - } -} - -?> diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/return_nested.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/return_nested.php.inc deleted file mode 100644 index dd9cff8fb74..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/return_nested.php.inc +++ /dev/null @@ -1,30 +0,0 @@ - ------ - htmlspecialchars(...) - |> strtolower(...) - |> trim(...); - } -} - -?> diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/skip_outer_spread_nested_call.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/skip_outer_spread_nested_call.php.inc deleted file mode 100644 index f0d8625384f..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/skip_outer_spread_nested_call.php.inc +++ /dev/null @@ -1,11 +0,0 @@ - ------ - array_unique(...); - -?> diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/two_nested_functions.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/two_nested_functions.php.inc deleted file mode 100644 index 7db9e96f346..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/two_nested_functions.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - is_string(...) - |> assert(...); - } -} - -?> diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/minimum_depth.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/minimum_depth.php.inc deleted file mode 100644 index ff430168b67..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/minimum_depth.php.inc +++ /dev/null @@ -1,30 +0,0 @@ - ------ - htmlspecialchars(...) - |> strtolower(...) - |> trim(...); - } -} - -?> diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/skip_below_minimum_depth.php.inc b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/skip_below_minimum_depth.php.inc deleted file mode 100644 index 679780a7b8d..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/skip_below_minimum_depth.php.inc +++ /dev/null @@ -1,12 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/FixtureMinimumDepth'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule_minimum_depth.php'; - } -} diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/NestedFuncCallsToPipeOperatorRectorTest.php b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/NestedFuncCallsToPipeOperatorRectorTest.php deleted file mode 100644 index 4d880a4a12c..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/NestedFuncCallsToPipeOperatorRectorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule.php b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule.php deleted file mode 100644 index 3034785d7e6..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule.php +++ /dev/null @@ -1,13 +0,0 @@ -rule(NestedFuncCallsToPipeOperatorRector::class); - - $rectorConfig->phpVersion(PhpVersion::PHP_85); -}; diff --git a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php deleted file mode 100644 index 6e27f3b3982..00000000000 --- a/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php +++ /dev/null @@ -1,15 +0,0 @@ -ruleWithConfiguration(NestedFuncCallsToPipeOperatorRector::class, [ - NestedFuncCallsToPipeOperatorRector::MINIMUM_DEPTH => 3, - ]); - - $rectorConfig->phpVersion(PhpVersion::PHP_85); -}; diff --git a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/basic.php.inc b/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/basic.php.inc deleted file mode 100644 index 4b03f35742b..00000000000 --- a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/basic.php.inc +++ /dev/null @@ -1,22 +0,0 @@ - ------ - function3(...) - |> function2(...) - |> function1(...); - -?> diff --git a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/inside_class_method.php.inc b/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/inside_class_method.php.inc deleted file mode 100644 index 82052119e7a..00000000000 --- a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/inside_class_method.php.inc +++ /dev/null @@ -1,34 +0,0 @@ - ------ - function3(...) - |> function2(...) - |> function1(...); - } -} - -?> diff --git a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/skip_if_used_elsewhere.php.inc b/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/skip_if_used_elsewhere.php.inc deleted file mode 100644 index 851bae6d480..00000000000 --- a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/Fixture/skip_if_used_elsewhere.php.inc +++ /dev/null @@ -1,9 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/config/configured_rule.php b/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/config/configured_rule.php deleted file mode 100644 index 12414980873..00000000000 --- a/rules-tests/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector/config/configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -rule(SequentialAssignmentsToPipeOperatorRector::class); -}; diff --git a/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php b/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php index 0b1bd500fa0..e4d71cd00dd 100644 --- a/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php +++ b/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php @@ -5,43 +5,32 @@ namespace Rector\Php85\Rector\Expression; use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\BinaryOp\Pipe; -use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Return_; -use PhpParser\Node\VariadicPlaceholder; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\Exception\ShouldNotHappenException; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -use Webmozart\Assert\Assert; /** - * @see \Rector\Tests\Php85\Rector\Expression\NestedFuncCallsToPipeOperatorRector\NestedFuncCallsToPipeOperatorRectorTest + * @deprecated This rule is deprecated, as unwrapping nested calls to |> pipes depends on the context of surrounding code. It can create extremely long chains that break readability, and where to draw the line is a personal preference. */ -final class NestedFuncCallsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface +final class NestedFuncCallsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface, DeprecatedInterface { + /** + * @api + */ public const string MINIMUM_DEPTH = 'minimum_depth'; - private const int DEFAULT_MINIMUM_DEPTH = 2; - - private int $minimumDepth = self::DEFAULT_MINIMUM_DEPTH; - /** * @param array $configuration */ public function configure(array $configuration): void { - $minimumDepth = $configuration[self::MINIMUM_DEPTH] ?? self::DEFAULT_MINIMUM_DEPTH; - Assert::integer($minimumDepth); - Assert::greaterThanEq($minimumDepth, 2); - - $this->minimumDepth = $minimumDepth; } public function getRuleDefinition(): RuleDefinition @@ -90,116 +79,14 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $hasChanged = false; - - if ($node->expr instanceof Assign) { - $assign = $node->expr; - - $assignedValue = $assign->expr; - $processedValue = $this->processNestedCalls($assignedValue); - - if ($processedValue instanceof Expr && $processedValue !== $assignedValue) { - $assign->expr = $processedValue; - $hasChanged = true; - } - } elseif ($node->expr instanceof FuncCall) { - $funcCall = $node->expr; - - $processedValue = $this->processNestedCalls($funcCall); - - if ($processedValue instanceof Expr && $processedValue !== $funcCall) { - $node->expr = $processedValue; - $hasChanged = true; - } - } - - if (! $hasChanged) { - return null; - } - - return $node; + throw new ShouldNotHappenException(sprintf( + '"%s" is deprecated, as the pipe chain length that stays readable depends on the context of surrounding code', + self::class + )); } public function provideMinPhpVersion(): int { return PhpVersionFeature::PIPE_OPERATOER; } - - private function buildPipeExpression(FuncCall $funcCall, Expr $expr): Pipe - { - // Recursively process inner call to unwrap all nested levels - if ($expr instanceof FuncCall) { - $processed = $this->processNestedCalls($expr, true); - $nestedInner = $processed instanceof Expr ? $processed : $expr; - } else { - $nestedInner = $expr; - } - - return new Pipe($nestedInner, $this->createPlaceholderCall($funcCall)); - } - - private function createPlaceholderCall(FuncCall $funcCall): FuncCall - { - return new FuncCall($funcCall->name, [new VariadicPlaceholder()]); - } - - private function processNestedCalls(Expr $expr, bool $deep = false): ?Expr - { - if (! $expr instanceof FuncCall) { - return null; - } - - if ($expr->isFirstClassCallable()) { - return null; - } - - if (! $deep && $this->countNestedFuncCalls($expr) < $this->minimumDepth) { - return null; - } - - if (count($expr->args) !== 1) { - return null; - } - - // Check if any argument is a function call - foreach ($expr->args as $arg) { - if (! $arg instanceof Arg) { - return null; - } - - // Spread argument can't be converted to pipe — keep the call as-is - if ($arg->unpack) { - return null; - } - - if ($arg->value instanceof FuncCall) { - return $this->buildPipeExpression($expr, $arg->value); - } - - // If we're deep in recursion and hit a non-FuncCall, this is the base - if ($deep) { - // Return a pipe with the base expression on the left - return new Pipe($arg->value, $this->createPlaceholderCall($expr)); - } - } - - return null; - } - - private function countNestedFuncCalls(FuncCall $funcCall): int - { - $depth = 1; - - while (count($funcCall->args) === 1) { - $arg = $funcCall->args[0]; - if (! $arg instanceof Arg || $arg->unpack || ! $arg->value instanceof FuncCall) { - break; - } - - ++$depth; - $funcCall = $arg->value; - } - - return $depth; - } } diff --git a/rules/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector.php b/rules/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector.php index 9340ada430d..d835d9c641e 100644 --- a/rules/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector.php +++ b/rules/Php85/Rector/StmtsAwareInterface/SequentialAssignmentsToPipeOperatorRector.php @@ -5,16 +5,8 @@ namespace Rector\Php85\Rector\StmtsAwareInterface; use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\BinaryOp\Pipe; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Stmt; -use PhpParser\Node\Stmt\Expression; -use PhpParser\Node\VariadicPlaceholder; -use Rector\NodeAnalyzer\ExprAnalyzer; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Exception\ShouldNotHappenException; use Rector\PhpParser\Enum\NodeGroup; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; @@ -24,15 +16,11 @@ /** * @see https://wiki.php.net/rfc/pipe-operator-v3 - * @see \Rector\Tests\Php85\Rector\StmtsAwareInterface\SequentialAssignmentsToPipeOperatorRector\SequentialAssignmentsToPipeOperatorRectorTest + * + * @deprecated This rule is deprecated, as merging sequential assignments into a single |> pipe removes intermediate variables that carry naming and can be re-used later. It also depends on the context of surrounding code and can create extremely long chains that break readability. */ -final class SequentialAssignmentsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface +final class SequentialAssignmentsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface, DeprecatedInterface { - public function __construct( - private readonly ExprAnalyzer $exprAnalyzer - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -75,165 +63,9 @@ public function provideMinPhpVersion(): int */ public function refactor(Node $node): ?Node { - if ($node->stmts === null) { - return null; - } - - $hasChanged = false; - - $statements = $node->stmts; - $totalStatements = count($statements) - 1; - - for ($i = 0; $i < $totalStatements; ++$i) { - $chain = $this->findAssignmentChain($statements, $i); - - if ($chain && count($chain) >= 2) { - $this->processAssignmentChain($node, $chain, $i); - $hasChanged = true; - // Skip processed statements - $i += count($chain) - 1; - } - } - - if (! $hasChanged) { - return null; - } - - return $node; - } - - /** - * @param array $statements - * @return array|null - */ - private function findAssignmentChain(array $statements, int $startIndex): ?array - { - $chain = []; - $currentIndex = $startIndex; - $totalStatements = count($statements); - - while ($currentIndex < $totalStatements) { - $stmt = $statements[$currentIndex]; - - if (! $stmt instanceof Expression) { - break; - } - - $expr = $stmt->expr; - if (! $expr instanceof Assign) { - return null; - } - - // Check if this is a simple function call with one argument - if (! $expr->expr instanceof FuncCall) { - return null; - } - - $funcCall = $expr->expr; - if (count($funcCall->args) !== 1) { - return null; - } - - $arg = $funcCall->args[0]; - if (! $arg instanceof Arg) { - return null; - } - - if ($currentIndex === $startIndex) { - - // First in chain - must be a variable or simple value - if (! $arg->value instanceof Variable && ! $this->exprAnalyzer->isDynamicExpr($arg->value)) { - return null; - } - - $chain[] = [ - 'stmt' => $stmt, - 'assign' => $expr, - 'funcCall' => $funcCall, - ]; - } else { - // Subsequent in chain - must use previous assignment's variable - $previousAssign = $chain[count($chain) - 1]['assign']; - $previousVarName = $this->getName($previousAssign->var); - - if (! $arg->value instanceof Variable || $this->getName($arg->value) !== $previousVarName) { - break; - } - - $chain[] = [ - 'stmt' => $stmt, - 'assign' => $expr, - 'funcCall' => $funcCall, - ]; - } - - ++$currentIndex; - } - - return $chain; - } - - /** - * @param StmtsAware $stmtsAware - * @param array $chain - */ - private function processAssignmentChain(Node $stmtsAware, array $chain, int $startIndex): void - { - if ($stmtsAware->stmts === null) { - return; - } - - $lastAssignment = $chain[count($chain) - 1]['assign']; - - // Get the initial value from the first function call's argument - $firstFuncCall = $chain[0]['funcCall']; - - if (! $firstFuncCall instanceof FuncCall) { - return; - } - - $firstArg = $firstFuncCall->args[0]; - if (! $firstArg instanceof Arg) { - return; - } - - $initialValue = $firstArg->value; - - // Build the pipe chain - $pipeExpression = $initialValue; - - foreach ($chain as $chainItem) { - $funcCall = $chainItem['funcCall']; - $placeholderCall = $this->createPlaceholderCall($funcCall); - $pipeExpression = new Pipe($pipeExpression, $placeholderCall); - } - - if (! $lastAssignment instanceof Assign) { - return; - } - - // Create the final assignment - $assign = new Assign($lastAssignment->var, $pipeExpression); - $finalExpression = new Expression($assign); - - // Replace the statements - $endIndex = $startIndex + count($chain) - 1; - - // Remove all intermediate statements and replace with the final pipe expression - for ($i = $startIndex; $i <= $endIndex; ++$i) { - if ($i === $startIndex) { - $stmtsAware->stmts[$i] = $finalExpression; - } else { - unset($stmtsAware->stmts[$i]); - } - } - - // Reindex the array - $stmtsAware->stmts = array_values($stmtsAware->stmts); - } - - private function createPlaceholderCall(FuncCall $funcCall): FuncCall - { - return new FuncCall($funcCall->name, [new VariadicPlaceholder()]); + throw new ShouldNotHappenException(sprintf( + '"%s" is deprecated, as it removes intermediate variables that carry naming and can be re-used later', + self::class + )); } }