[Order] Add OrderPublicInterfaceMethodRector

This commit is contained in:
TomasVotruba 2020-05-03 23:40:28 +02:00
parent 82f48f5d84
commit 68680a3cdb
10 changed files with 348 additions and 46 deletions

View File

@ -1,2 +1,3 @@
services:
Rector\Order\Rector\Class_\OrderPrivateMethodsByUseRector: null
Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector: null

View File

@ -1,4 +1,4 @@
# All 499 Rectors Overview
# All 500 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -4873,6 +4873,41 @@ Order private methods in order of their use
<br>
### `OrderPublicInterfaceMethodRector`
- class: [`Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector`](/../master/rules/order/src/Rector/Class_/OrderPublicInterfaceMethodRector.php)
- [test fixtures](/../master/rules/order/tests/Rector/Class_/OrderPublicInterfaceMethodRector/Fixture)
Order public methods required by interface in custom orderer
```yaml
services:
Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector:
$methodOrderByInterfaces:
FoodRecipeInterface:
- getDescription
- process
```
```diff
class SomeClass implements FoodRecipeInterface
{
- public function process()
+ public function getDescription()
{
}
-
- public function getDescription()
+ public function process()
{
}
}
```
<br>
## Oxid
### `OxidReplaceBackwardsCompatabilityClassRector`

View File

@ -306,3 +306,4 @@ parameters:
- '#Use explicit return value over magic &reference#'
- '#Method Rector\\Order\\Rector\\Class_\\OrderPrivateMethodsByUseRector\:\:createOldToNewKeys\(\) should return array<int\> but returns array\|false#'
- '#Method Rector\\Order\\StmtOrder\:\:createOldToNewKeys\(\) should return array<int\> but returns array\|false#'

View File

@ -0,0 +1,9 @@
services:
_defaults:
public: true
autowire: true
Rector\Order\:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'

View File

@ -11,12 +11,23 @@ use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Order\StmtOrder;
/**
* @see \Rector\Order\Tests\Rector\Class_\OrderPrivateMethodsByUseRector\OrderPrivateMethodsByUseRectorTest
*/
final class OrderPrivateMethodsByUseRector extends AbstractRector
{
/**
* @var StmtOrder
*/
private $stmtOrder;
public function __construct(StmtOrder $stmtOrder)
{
$this->stmtOrder = $stmtOrder;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Order private methods in order of their use', [
@ -77,26 +88,10 @@ PHP
public function refactor(Node $node): ?Node
{
$desiredClassMethodOrder = $this->getLocalMethodCallOrder($node);
$privateClassMethods = [];
foreach ($node->stmts as $key => $classStmt) {
if (! $classStmt instanceof ClassMethod) {
continue;
}
if (! $classStmt->isPrivate()) {
continue;
}
/** @var string $classMethodName */
$classMethodName = $this->getName($classStmt);
$privateClassMethods[$key] = $classMethodName;
}
//$privateClassMethodsNames = array_keys($privateClassMethods);
$privateClassMethodsByKey = $this->resolvePrivateClassMethods($node);
// order is correct, nothing to change
if ($privateClassMethods === $desiredClassMethodOrder) {
if ($privateClassMethodsByKey === $desiredClassMethodOrder) {
return null;
}
@ -105,19 +100,9 @@ PHP
return null;
}
$oldToNewKeys = $this->createOldToNewKeys($desiredClassMethodOrder, $privateClassMethods);
$oldToNewKeys = $this->stmtOrder->createOldToNewKeys($desiredClassMethodOrder, $privateClassMethodsByKey);
foreach ($node->stmts as $key => $stmt) {
if (! isset($oldToNewKeys[$key])) {
continue;
}
// reodre here
$newKey = $oldToNewKeys[$key];
$node->stmts[$newKey] = $stmt;
}
return $node;
return $this->stmtOrder->reorderClassStmtsByOldToNewKeys($node, $oldToNewKeys);
}
/**
@ -149,24 +134,24 @@ PHP
return array_unique($localMethodCallInOrder);
}
/**
* @param string[] $desiredClassMethodOrder
* @return int[]
*/
private function createOldToNewKeys(array $desiredClassMethodOrder, array $privateClassMethods): array
private function resolvePrivateClassMethods(Class_ $class): array
{
$newKeys = [];
foreach ($desiredClassMethodOrder as $desiredClassMethod) {
foreach ($privateClassMethods as $currentKey => $classMethodName) {
if ($classMethodName === $desiredClassMethod) {
$newKeys[] = $currentKey;
}
$privateClassMethods = [];
foreach ($class->stmts as $key => $classStmt) {
if (! $classStmt instanceof ClassMethod) {
continue;
}
if (! $classStmt->isPrivate()) {
continue;
}
/** @var string $classMethodName */
$classMethodName = $this->getName($classStmt);
$privateClassMethods[$key] = $classMethodName;
}
$oldKeys = array_values($newKeys);
sort($oldKeys);
return array_combine($oldKeys, $newKeys);
return $privateClassMethods;
}
}

View File

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Order\StmtOrder;
/**
* @see \Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\OrderPublicInterfaceMethodRectorTest
*/
final class OrderPublicInterfaceMethodRector extends AbstractRector
{
/**
* @var string[][]
*/
private $methodOrderByInterfaces = [];
/**
* @var ClassManipulator
*/
private $classManipulator;
/**
* @var StmtOrder
*/
private $stmtOrder;
/**
* @param string[][] $methodOrderByInterfaces
*/
public function __construct(
ClassManipulator $classManipulator,
StmtOrder $stmtOrder,
array $methodOrderByInterfaces = []
) {
$this->classManipulator = $classManipulator;
$this->methodOrderByInterfaces = $methodOrderByInterfaces;
$this->stmtOrder = $stmtOrder;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Order public methods required by interface in custom orderer', [
new ConfiguredCodeSample(
<<<'PHP'
class SomeClass implements FoodRecipeInterface
{
public function process()
{
}
public function getDescription()
{
}
}
PHP
,
<<<'PHP'
class SomeClass implements FoodRecipeInterface
{
public function getDescription()
{
}
public function process()
{
}
}
PHP
, [
'$methodOrderByInterfaces' => [
'FoodRecipeInterface' => ['getDescription', 'process'],
],
]
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$implementedInterfaces = $this->classManipulator->getImplementedInterfaceNames($node);
$publicMethodOrderByKey = $this->collectPublicMethods($node);
foreach ($implementedInterfaces as $implementedInterface) {
$methodOrder = $this->methodOrderByInterfaces[$implementedInterface] ?? null;
if ($methodOrder === null) {
continue;
}
$oldToNewKeys = $this->stmtOrder->createOldToNewKeys($publicMethodOrderByKey, $methodOrder);
$this->stmtOrder->reorderClassStmtsByOldToNewKeys($node, $oldToNewKeys);
break;
}
return $node;
}
/**
* @return string[]
*/
private function collectPublicMethods(Class_ $class): array
{
$publicClassMethods = [];
foreach ($class->stmts as $key => $classStmt) {
if (! $classStmt instanceof ClassMethod) {
continue;
}
if (! $classStmt->isPublic()) {
continue;
}
/** @var string $classMethodName */
$classMethodName = $this->getName($classStmt);
$publicClassMethods[$key] = $classMethodName;
}
return $publicClassMethods;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Rector\Order;
use PhpParser\Node\Stmt\Class_;
final class StmtOrder
{
/**
* @param string[] $desiredStmtOrder
* @return int[]
*/
public function createOldToNewKeys(array $desiredStmtOrder, array $currentStmtOrder): array
{
$newKeys = [];
foreach ($desiredStmtOrder as $desiredClassMethod) {
foreach ($currentStmtOrder as $currentKey => $classMethodName) {
if ($classMethodName === $desiredClassMethod) {
$newKeys[] = $currentKey;
}
}
}
$oldKeys = array_values($newKeys);
sort($oldKeys);
return array_combine($oldKeys, $newKeys);
}
public function reorderClassStmtsByOldToNewKeys(Class_ $node, array $oldToNewKeys): Class_
{
foreach ($node->stmts as $key => $stmt) {
if (! isset($oldToNewKeys[$key])) {
continue;
}
// reorder here
$newKey = $oldToNewKeys[$key];
$node->stmts[$newKey] = $stmt;
}
return $node;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Source\FoodRecipeInterface;
class SomeClass implements FoodRecipeInterface
{
public function process()
{
}
public function getDescription()
{
}
}
?>
-----
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Source\FoodRecipeInterface;
class SomeClass implements FoodRecipeInterface
{
public function getDescription()
{
}
public function process()
{
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector;
use Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Source\FoodRecipeInterface;
final class OrderPublicInterfaceMethodRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorsWithConfiguration(): array
{
return [
OrderPublicInterfaceMethodRector::class => [
'$methodOrderByInterfaces' => [
FoodRecipeInterface::class => ['getDescription', 'process'],
],
],
];
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Tests\Rector\Class_\OrderPublicInterfaceMethodRector\Source;
interface FoodRecipeInterface
{
public function getDescription();
public function process();
}