diff --git a/config/set/phalcon/phalcon40.yaml b/config/set/phalcon/phalcon40.yaml index 5f233291496d..f923c684c2c2 100644 --- a/config/set/phalcon/phalcon40.yaml +++ b/config/set/phalcon/phalcon40.yaml @@ -1,5 +1,11 @@ # https://docs.phalcon.io/4.0/en/upgrade#general-notes services: + # !!! be careful not to run this twice, since it swaps arguments back and forth + # see https://github.com/rectorphp/rector/issues/2408#issue-534441142 + Rector\Rector\StaticCall\SwapClassMethodArgumentsRector: + Phalcon\Model: + assign: [0, 2, 1] + Rector\Renaming\Rector\Class_\RenameClassRector: Phalcon\Acl\Adapter: 'Phalcon\Acl\Adapter\AbstractAdapter' Phalcon\Acl\Resource: 'Phalcon\Acl\Component' diff --git a/packages/CodeQuality/src/Rector/FuncCall/AddPregQuoteDelimiterRector.php b/packages/CodeQuality/src/Rector/FuncCall/AddPregQuoteDelimiterRector.php index 82fc97990fbd..c6252ec87a6f 100644 --- a/packages/CodeQuality/src/Rector/FuncCall/AddPregQuoteDelimiterRector.php +++ b/packages/CodeQuality/src/Rector/FuncCall/AddPregQuoteDelimiterRector.php @@ -20,6 +20,7 @@ final class AddPregQuoteDelimiterRector extends AbstractRector { /** + * @var string * @see https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php */ private const ALL_MODIFIERS = 'imsxeADSUXJu'; diff --git a/phpstan.neon b/phpstan.neon index 1b0d2668d202..1a5229f51097 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -230,3 +230,7 @@ parameters: # mixed removed - '#In method "(.*?)", parameter (.*?) has no type\-hint and no @param annotation\. More info\: http\://bit\.ly/usetypehint#' - '#In method "(.*?)", there is no return type and no @return annotation\. More info\: http\://bit\.ly/usetypehint#' + + - + message: '#Class Rector\\Tests\\Rector\\StaticCall\\SwapClassMethodArgumentsRector\\Fixture\\SomeClass not found#' + path: tests/Rector/StaticCall/SwapClassMethodArgumentsRector/SwapClassMethodArgumentsRectorTest.php diff --git a/rector.yaml b/rector.yaml index 82a826cf4e8f..1631e8b03fe3 100644 --- a/rector.yaml +++ b/rector.yaml @@ -13,6 +13,40 @@ parameters: # so Rector code is still PHP 7.1 compatible php_version_features: '7.1' - + # @see utils/RectorGenerator/config/config.yaml rector_recipe: - package: "Utils" + # run "bin/rector create" to create a new Rector + tests from this config + package: "Rector" + name: "SwapClassMethodArgumentsRector" + node_types: + # put main node first, it is used to create namespace + - "StaticCall" + - "MethodCall" + - "ClassMethod" + + description: "Reorder class method arguments, including their calls" + code_before: > + + newArgumentPositionsByMethodAndClass = $newArgumentPositionsByMethodAndClass; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Reorder class method arguments, including their calls', [ + new ConfiguredCodeSample( + <<<'PHP' +class SomeClass +{ + public static function run($first, $second) + { + self::run($first, $second); + } +} +PHP +, + <<<'PHP' +class SomeClass +{ + public static function run($second, $first) + { + self::run($second, $first); + } +} +PHP + + , + [ + '$newArgumentPositionsByMethodAndClass' => [ + 'SomeClass' => [ + 'run' => [1, 0], + ], + ], + ]), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [StaticCall::class, MethodCall::class, ClassMethod::class]; + } + + /** + * @param StaticCall|MethodCall|ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + foreach ($this->newArgumentPositionsByMethodAndClass as $class => $methodNameAndNewArgumentPositions) { + if (! $this->isMethodStaticCallOrClassMethodObjectType($node, $class)) { + continue; + } + + foreach ($methodNameAndNewArgumentPositions as $methodName => $newArgumentPositions) { + if (! $this->isMethodStaticCallOrClassMethodName($node, $methodName)) { + continue; + } + + if ($node instanceof ClassMethod) { + $this->swapParameters($node, $newArgumentPositions); + } else { + $this->swapArguments($node, $newArgumentPositions); + } + } + } + + return $node; + } + + /** + * @param StaticCall|MethodCall|ClassMethod $node + */ + private function isMethodStaticCallOrClassMethodName(Node $node, string $methodName): bool + { + if ($node instanceof MethodCall || $node instanceof StaticCall) { + if ($node->name instanceof Expr) { + return false; + } + + return $this->isName($node->name, $methodName); + } + + if ($node instanceof ClassMethod) { + return $this->isName($node->name, $methodName); + } + + return false; + } + + /** + * @param MethodCall|StaticCall $node + * @param int[] $newArgumentPositions + */ + private function swapArguments(Node $node, array $newArgumentPositions): void + { + $newArguments = []; + foreach ($newArgumentPositions as $oldPosition => $newPosition) { + if (! isset($node->args[$oldPosition]) || ! isset($node->args[$newPosition])) { + continue; + } + + $newArguments[$newPosition] = $node->args[$oldPosition]; + } + + foreach ($newArguments as $newPosition => $argument) { + $node->args[$newPosition] = $argument; + } + } + + /** + * @param int[] $newParameterPositions + */ + private function swapParameters(ClassMethod $classMethod, array $newParameterPositions): void + { + $newArguments = []; + foreach ($newParameterPositions as $oldPosition => $newPosition) { + if (! isset($classMethod->params[$oldPosition]) || ! isset($classMethod->params[$newPosition])) { + continue; + } + + $newArguments[$newPosition] = $classMethod->params[$oldPosition]; + } + + foreach ($newArguments as $newPosition => $argument) { + $classMethod->params[$newPosition] = $argument; + } + } +} diff --git a/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/Fixture/fixture.php.inc b/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..69201c366cf8 --- /dev/null +++ b/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/SwapClassMethodArgumentsRectorTest.php b/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/SwapClassMethodArgumentsRectorTest.php new file mode 100644 index 000000000000..26ebdf3ee0fd --- /dev/null +++ b/tests/Rector/StaticCall/SwapClassMethodArgumentsRector/SwapClassMethodArgumentsRectorTest.php @@ -0,0 +1,39 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorsWithConfiguration(): array + { + return [ + SwapClassMethodArgumentsRector::class => [ + 'newArgumentPositionsByMethodAndClass' => [ + SomeClass::class => [ + 'run' => [1, 0], + ], + ], + ], + ]; + } +} diff --git a/utils/RectorGenerator/config/config.yaml b/utils/RectorGenerator/config/config.yaml index d03e9893513b..fb67f668d80d 100644 --- a/utils/RectorGenerator/config/config.yaml +++ b/utils/RectorGenerator/config/config.yaml @@ -12,35 +12,35 @@ services: parameters: rector_recipe: - # run "bin/rector create" to create a new Rector + tests from this config - package: "Celebrity" - name: "SplitToExplodeRector" - node_types: - # put main node first, it is used to create namespace - - "Assign" - - description: "Removes unneeded $a = $a assigns" - code_before: > - - +# +#