From c1c452cce6e9139f3759864a1071a0465999b635 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 17 Jul 2026 19:20:22 +0700 Subject: [PATCH] Make NestedFuncCallsToPipeOperatorRector minimum depth configurable --- .../Fixture/two_nested_functions.php.inc | 29 +++++++++++ .../FixtureMinimumDepth/minimum_depth.php.inc | 30 +++++++++++ .../skip_below_minimum_depth.php.inc | 12 +++++ ...lsToPipeOperatorRectorMinimumDepthTest.php | 28 ++++++++++ .../config/configured_rule_minimum_depth.php | 15 ++++++ ...ertyAssignToConstructorPromotionRector.php | 2 +- .../NestedFuncCallsToPipeOperatorRector.php | 51 +++++++++++++++++-- 7 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/two_nested_functions.php.inc create mode 100644 rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/minimum_depth.php.inc create mode 100644 rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/skip_below_minimum_depth.php.inc create mode 100644 rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/NestedFuncCallsToPipeOperatorRectorMinimumDepthTest.php create mode 100644 rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php 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 new file mode 100644 index 00000000000..7db9e96f346 --- /dev/null +++ b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/Fixture/two_nested_functions.php.inc @@ -0,0 +1,29 @@ + +----- + 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 new file mode 100644 index 00000000000..ff430168b67 --- /dev/null +++ b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/minimum_depth.php.inc @@ -0,0 +1,30 @@ + +----- + 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 new file mode 100644 index 00000000000..679780a7b8d --- /dev/null +++ b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/FixtureMinimumDepth/skip_below_minimum_depth.php.inc @@ -0,0 +1,12 @@ +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/config/configured_rule_minimum_depth.php b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php new file mode 100644 index 00000000000..6e27f3b3982 --- /dev/null +++ b/rules-tests/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector/config/configured_rule_minimum_depth.php @@ -0,0 +1,15 @@ +ruleWithConfiguration(NestedFuncCallsToPipeOperatorRector::class, [ + NestedFuncCallsToPipeOperatorRector::MINIMUM_DEPTH => 3, + ]); + + $rectorConfig->phpVersion(PhpVersion::PHP_85); +}; diff --git a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php index 7428f0099b3..98a2ca0e1dd 100644 --- a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php +++ b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php @@ -492,7 +492,7 @@ private function shouldUsePropertyTypeForPromotedParam(Property $property, Param $paramType = TypeCombinator::union($paramType, $this->getType($param->default)); } - if (!$this->typeComparator->isSubtype($paramType, $propertyType)) { + if (! $this->typeComparator->isSubtype($paramType, $propertyType)) { return true; } diff --git a/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php b/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php index 532e453477a..0b1bd500fa0 100644 --- a/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php +++ b/rules/Php85/Rector/Expression/NestedFuncCallsToPipeOperatorRector.php @@ -13,23 +13,43 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Return_; use PhpParser\Node\VariadicPlaceholder; +use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Webmozart\Assert\Assert; /** * @see \Rector\Tests\Php85\Rector\Expression\NestedFuncCallsToPipeOperatorRector\NestedFuncCallsToPipeOperatorRectorTest */ -final class NestedFuncCallsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface +final class NestedFuncCallsToPipeOperatorRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface { + 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 { return new RuleDefinition( 'Convert multiple nested function calls in single line to |> pipe operator', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' class SomeClass { @@ -52,6 +72,10 @@ public function run($input) } } CODE_SAMPLE + , + [ + self::MINIMUM_DEPTH => 3, + ] )] ); } @@ -129,6 +153,10 @@ private function processNestedCalls(Expr $expr, bool $deep = false): ?Expr return null; } + if (! $deep && $this->countNestedFuncCalls($expr) < $this->minimumDepth) { + return null; + } + if (count($expr->args) !== 1) { return null; } @@ -157,4 +185,21 @@ private function processNestedCalls(Expr $expr, bool $deep = false): ?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; + } }