Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Phalcon 4] Add SwapClassMethodArgumentsRector rule #2409

Merged
merged 3 commits into from
Dec 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/set/phalcon/phalcon40.yaml
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
38 changes: 36 additions & 2 deletions rector.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: >
<?php

class SomeClass
{
public static function run($first, $second)
{
self::run($first, $second);
}
}

code_after: >
<?php

class SomeClass
{
public static function run($second, $first)
{
self::run($second, $first);
}
}

source: # e.g. link to RFC or headline in upgrade guide, 1 or more in the list
- ""
set: "" # e.g. symfony30, target config to append this rector to
161 changes: 161 additions & 0 deletions src/Rector/StaticCall/SwapClassMethodArgumentsRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

declare(strict_types=1);

namespace Rector\Rector\StaticCall;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\ConfiguredCodeSample;
use Rector\RectorDefinition\RectorDefinition;

/**
* @see \Rector\Tests\Rector\StaticCall\SwapClassMethodArgumentsRector\SwapClassMethodArgumentsRectorTest
*/
final class SwapClassMethodArgumentsRector extends AbstractRector
{
/**
* @var int[][][]
*/
private $newArgumentPositionsByMethodAndClass = [];

/**
* @param int[][][] $newArgumentPositionsByMethodAndClass
*/
public function __construct(array $newArgumentPositionsByMethodAndClass = [])
{
$this->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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Rector\Tests\Rector\StaticCall\SwapClassMethodArgumentsRector\Fixture;

class SomeClass
{
public static function run($first, $second)
{
self::run($first, $second);
}
}

?>
-----
<?php

namespace Rector\Tests\Rector\StaticCall\SwapClassMethodArgumentsRector\Fixture;

class SomeClass
{
public static function run($second, $first)
{
self::run($second, $first);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Rector\StaticCall\SwapClassMethodArgumentsRector;

use Iterator;
use Rector\Rector\StaticCall\SwapClassMethodArgumentsRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Tests\Rector\StaticCall\SwapClassMethodArgumentsRector\Fixture\SomeClass;

final class SwapClassMethodArgumentsRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

protected function getRectorsWithConfiguration(): array
{
return [
SwapClassMethodArgumentsRector::class => [
'newArgumentPositionsByMethodAndClass' => [
SomeClass::class => [
'run' => [1, 0],
],
],
],
];
}
}
64 changes: 32 additions & 32 deletions utils/RectorGenerator/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: >
<?php

class SomeClass
{
public function run()
{
$a = $a;
}
}

code_after: >
<?php

class SomeClass
{
public function run()
{
}
}

source: # e.g. link to RFC or headline in upgrade guide, 1 or more in the list
- ""
set: "" # e.g. symfony30, target config to append this rector to
# # 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
# - ""
#
# description: "Removes unneeded $a = $a assigns"
# code_before: >
# <?php
#
# class SomeClass
# {
# public function run()
# {
# $a = $a;
# }
# }
#
# code_after: >
# <?php
#
# class SomeClass
# {
# public function run()
# {
# }
# }
#
# source: # e.g. link to RFC or headline in upgrade guide, 1 or more in the list
# - ""
# set: "" # e.g. symfony30, target config to append this rector to