Merge pull request #3305 from rectorphp/order-property

[Order] Add OrderPropertyByComplexityRector
This commit is contained in:
kodiakhq[bot] 2020-05-04 00:53:22 +00:00 committed by GitHub
commit 093b46aa2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 482 additions and 3 deletions

View File

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

View File

@ -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
<br>
### `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;
}
```
<br>
### `OrderPublicInterfaceMethodRector`
- class: [`Rector\Order\Rector\Class_\OrderPublicInterfaceMethodRector`](/../master/rules/order/src/Rector/Class_/OrderPublicInterfaceMethodRector.php)

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Rector\Order;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Exception\NotImplementedException;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class PropertyRanker
{
public function rank(Property $property): int
{
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $property->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));
}
}

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Order\PropertyRanker;
use Rector\Order\StmtOrder;
/**
* @see \Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\OrderPropertyByComplexityRectorTest
*/
final class OrderPropertyByComplexityRector extends AbstractRector
{
/**
* @var StmtOrder
*/
private $stmtOrder;
/**
* @var PropertyRanker
*/
private $propertyRanker;
public function __construct(StmtOrder $stmtOrder, PropertyRanker $propertyRanker)
{
$this->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;
}
}

View File

@ -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;

View File

@ -0,0 +1,102 @@
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\AnotherSimpleType;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\ComplexType;
class ComplexTypes
{
/**
* Static cache
*
* @var string[][]
*/
private static $arrayOfStringsNested = [];
/**
* @var int
*/
private $integer;
/**
* @var ComplexType
*/
private $anotherObject;
/**
* @var string[]
*/
private $arrayOfStrings = [];
/**
* @var AnotherSimpleType
*/
private $someObject;
/**
* @var mixed[]
*
* Rich information about methods, e.g.:
*
* 0 => array (6)
* | start => 18
* | visibility => "public" (6)
* | static => FALSE
* | type => "method" (6)
* | name => "secondMethod" (12)
* | end => 29
*/
private $arrayOfMixedPlusContent = [];
}
?>
-----
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\AnotherSimpleType;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\ComplexType;
class ComplexTypes
{
/**
* @var int
*/
private $integer;
/**
* Static cache
*
* @var string[][]
*/
private static $arrayOfStringsNested = [];
/**
* @var string[]
*/
private $arrayOfStrings = [];
/**
* @var mixed[]
*
* Rich information about methods, e.g.:
*
* 0 => 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;
}
?>

View File

@ -0,0 +1,50 @@
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\ComplexType;
class SomeClass
{
/**
* @var string
*/
private $name;
/**
* @var ComplexType
*/
private $service;
/**
* @var int
*/
private $price;
}
?>
-----
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\ComplexType;
class SomeClass
{
/**
* @var string
*/
private $name;
/**
* @var int
*/
private $price;
/**
* @var ComplexType
*/
private $service;
}
?>

View File

@ -0,0 +1,18 @@
<?php
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Fixture;
use Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source\ComplexType;
class SkipCorrect
{
/**
* @var int
*/
private $startIndex;
/**
* @var \DateTime|null
*/
private $startBracketIndex;
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Order\Rector\Class_\OrderPropertyByComplexityRector;
final class OrderPropertyByComplexityRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return OrderPropertyByComplexityRector::class;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source;
final class AnotherSimpleType
{
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Order\Tests\Rector\Class_\OrderPropertyByComplexityRector\Source;
final class ComplexType
{
}