diff --git a/config/set/order/order.yaml b/config/set/order/order.yaml index f6d2c242ae1..543a0be162b 100644 --- a/config/set/order/order.yaml +++ b/config/set/order/order.yaml @@ -1,3 +1,4 @@ services: Rector\Order\Rector\Class_\OrderPrivateMethodsByUseRector: null Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector: null + Rector\Order\Rector\Class_\OrderPropertyByComplexityRector: null diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index 414053743a1..eefb9a2054a 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# All 500 Rectors Overview +# All 501 Rectors Overview - [Projects](#projects) - [General](#general) @@ -4873,6 +4873,40 @@ Order private methods in order of their use
+### `OrderPropertyByComplexityRector` + +- class: [`Rector\Order\Rector\Class_\OrderPropertyByComplexityRector`](/../master/rules/order/src/Rector/Class_/OrderPropertyByComplexityRector.php) +- [test fixtures](/../master/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture) + +Order properties by complexity, from the simplest like scalars to the most complex, like union or collections + +```diff +-class SomeClass ++class SomeClass implements FoodRecipeInterface + { + /** + * @var string + */ + private $name; + + /** +- * @var Type ++ * @var int + */ +- private $service; ++ private $price; + + /** +- * @var int ++ * @var Type + */ +- private $price; ++ private $service; + } +``` + +
+ ### `OrderPublicInterfaceMethodRector` - class: [`Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector`](/../master/rules/order/src/Rector/Class_/OrderPublicInterfaceMethodRector.php) diff --git a/rules/order/src/PropertyRanker.php b/rules/order/src/PropertyRanker.php new file mode 100644 index 00000000000..3facbf5cf47 --- /dev/null +++ b/rules/order/src/PropertyRanker.php @@ -0,0 +1,54 @@ +getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { + return 1; + } + + $varType = $phpDocInfo->getVarType(); + if ($varType instanceof StringType || $varType instanceof IntegerType || $varType instanceof BooleanType || $varType instanceof FloatType) { + return 5; + } + + if ($varType instanceof ArrayType || $varType instanceof IterableType) { + return 10; + } + + if ($varType instanceof TypeWithClassName) { + return 15; + } + + if ($varType instanceof IntersectionType) { + return 20; + } + + if ($varType instanceof UnionType) { + return 25; + } + + throw new NotImplementedException(get_class($varType)); + } +} diff --git a/rules/order/src/Rector/Class_/OrderPropertyByComplexityRector.php b/rules/order/src/Rector/Class_/OrderPropertyByComplexityRector.php new file mode 100644 index 00000000000..6cf7f818145 --- /dev/null +++ b/rules/order/src/Rector/Class_/OrderPropertyByComplexityRector.php @@ -0,0 +1,156 @@ +stmtOrder = $stmtOrder; + $this->propertyRanker = $propertyRanker; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Order properties by complexity, from the simplest like scalars to the most complex, like union or collections', + [ + new CodeSample( + <<<'PHP' +class SomeClass +{ + /** + * @var string + */ + private $name; + + /** + * @var Type + */ + private $service; + + /** + * @var int + */ + private $price; +} +PHP +, + <<<'PHP' +class SomeClass implements FoodRecipeInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var int + */ + private $price; + + /** + * @var Type + */ + private $service; +} +PHP + + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + $propertyByVisibilityByPosition = $this->resolvePropertyByVisibilityByPosition($node); + + foreach ($propertyByVisibilityByPosition as $propertyByPosition) { + $propertyNameToRank = []; + $propertyPositionByName = []; + + foreach ($propertyByPosition as $position => $property) { + /** @var string $propertyName */ + $propertyName = $this->getName($property); + + $propertyPositionByName[$position] = $propertyName; + $propertyNameToRank[$propertyName] = $this->propertyRanker->rank($property); + } + + asort($propertyNameToRank); + $sortedPropertyByRank = array_keys($propertyNameToRank); + + $oldToNewKeys = $this->stmtOrder->createOldToNewKeys($propertyPositionByName, $sortedPropertyByRank); + + $this->stmtOrder->reorderClassStmtsByOldToNewKeys($node, $oldToNewKeys); + } + + return $node; + } + + private function getVisibilityAsString(Property $property): string + { + if ($property->isPrivate()) { + return 'private'; + } + + if ($property->isProtected()) { + return 'protected'; + } + + return 'public'; + } + + /** + * @return Property[][] + */ + private function resolvePropertyByVisibilityByPosition(Class_ $class): array + { + $propertyByVisibilityByPosition = []; + foreach ($class->stmts as $position => $classStmt) { + if (! $classStmt instanceof Property) { + continue; + } + + $visibility = $this->getVisibilityAsString($classStmt); + $propertyByVisibilityByPosition[$visibility][$position] = $classStmt; + } + + return $propertyByVisibilityByPosition; + } +} diff --git a/rules/order/src/StmtOrder.php b/rules/order/src/StmtOrder.php index b937a9c2807..3afe19ed0f8 100644 --- a/rules/order/src/StmtOrder.php +++ b/rules/order/src/StmtOrder.php @@ -31,14 +31,28 @@ final class StmtOrder public function reorderClassStmtsByOldToNewKeys(Class_ $node, array $oldToNewKeys): Class_ { + $reorderedStmts = []; + + $stmtCount = count($node->stmts); + foreach ($node->stmts as $key => $stmt) { - if (! isset($oldToNewKeys[$key])) { + if (! array_key_exists($key, $oldToNewKeys)) { + $reorderedStmts[$key] = $stmt; continue; } // reorder here $newKey = $oldToNewKeys[$key]; - $node->stmts[$newKey] = $stmt; + + $reorderedStmts[$newKey] = $stmt; + } + + for ($i = 0; $i < $stmtCount; ++$i) { + if (! array_key_exists($i, $reorderedStmts)) { + continue; + } + + $node->stmts[$i] = $reorderedStmts[$i]; } return $node; diff --git a/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/complex_types.php.inc b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/complex_types.php.inc new file mode 100644 index 00000000000..dfd1d07d59b --- /dev/null +++ b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/complex_types.php.inc @@ -0,0 +1,102 @@ + array (6) + * | start => 18 + * | visibility => "public" (6) + * | static => FALSE + * | type => "method" (6) + * | name => "secondMethod" (12) + * | end => 29 + */ + private $arrayOfMixedPlusContent = []; +} + +?> +----- + array (6) + * | start => 18 + * | visibility => "public" (6) + * | static => FALSE + * | type => "method" (6) + * | name => "secondMethod" (12) + * | end => 29 + */ + private $arrayOfMixedPlusContent = []; + /** + * @var ComplexType + */ + private $anotherObject; + /** + * @var AnotherSimpleType + */ + private $someObject; +} + +?> diff --git a/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/fixture.php.inc b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..b0d728da641 --- /dev/null +++ b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/fixture.php.inc @@ -0,0 +1,50 @@ + +----- + diff --git a/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/skip_correct.php.inc b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/skip_correct.php.inc new file mode 100644 index 00000000000..092e2098ee5 --- /dev/null +++ b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Fixture/skip_correct.php.inc @@ -0,0 +1,18 @@ +doTestFile($file); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return OrderPropertyByComplexityRector::class; + } +} diff --git a/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Source/AnotherSimpleType.php b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Source/AnotherSimpleType.php new file mode 100644 index 00000000000..4aaa686d189 --- /dev/null +++ b/rules/order/tests/Rector/Class_/OrderPropertyByComplexityRector/Source/AnotherSimpleType.php @@ -0,0 +1,10 @@ +