Merge pull request #676 from rectorphp/cq-assign-short

[CodeQuality] Add CombinedAssignRector
This commit is contained in:
Tomáš Votruba 2018-10-13 01:06:20 +08:00 committed by GitHub
commit 45d1810602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 10 deletions

View File

@ -19,19 +19,19 @@
"symfony/dependency-injection": "^3.4|^4.1", "symfony/dependency-injection": "^3.4|^4.1",
"symfony/finder": "^3.4|^4.1", "symfony/finder": "^3.4|^4.1",
"symfony/process": "^3.4|^4.1", "symfony/process": "^3.4|^4.1",
"symplify/better-phpdoc-parser": "^5.2", "symplify/better-phpdoc-parser": "^5.1",
"symplify/easy-coding-standard": "^5.2", "symplify/easy-coding-standard": "^5.1",
"symplify/package-builder": "^5.2", "symplify/package-builder": "^5.1",
"thecodingmachine/safe": "^0.1.4" "thecodingmachine/safe": "^0.1.4"
}, },
"require-dev": { "require-dev": {
"humbug/php-scoper": "^0.9.2", "humbug/php-scoper": "^0.9.2",
"phpunit/phpunit": "^7.3", "phpunit/phpunit": "^7.3",
"symplify/changelog-linker": "^5.2", "symplify/changelog-linker": "^5.1",
"symplify/monorepo-builder": "^5.2", "symplify/monorepo-builder": "^5.1",
"symplify/phpstan-extensions": "^5.2", "symplify/phpstan-extensions": "^5.1",
"thecodingmachine/phpstan-safe-rule": "^0.1.0", "thecodingmachine/phpstan-safe-rule": "^0.1.0",
"thecodingmachine/phpstan-strict-rules": "^0.10.3", "thecodingmachine/phpstan-strict-rules": "^0.10.4",
"tracy/tracy": "^2.5" "tracy/tracy": "^2.5"
}, },
"autoload": { "autoload": {
@ -121,6 +121,7 @@
"tests/Rector/Architecture/DoctrineRepositoryAsService/Wrong", "tests/Rector/Architecture/DoctrineRepositoryAsService/Wrong",
"tests/Rector/Interface_/MergeInterfacesRector/Wrong", "tests/Rector/Interface_/MergeInterfacesRector/Wrong",
"tests/Rector/Visibility/ChangeMethodVisibilityRector/Wrong", "tests/Rector/Visibility/ChangeMethodVisibilityRector/Wrong",
"tests/Rector/CodeQuality/CombinedAssignRector/Wrong",
"tests/Rector/Visibility/ChangePropertyVisibilityRector/Wrong", "tests/Rector/Visibility/ChangePropertyVisibilityRector/Wrong",
"tests/Rector/Visibility/ChangeConstantVisibilityRector/Wrong", "tests/Rector/Visibility/ChangeConstantVisibilityRector/Wrong",
"tests/Rector/Annotation/AnnotationReplacerRector/Wrong", "tests/Rector/Annotation/AnnotationReplacerRector/Wrong",
@ -218,7 +219,5 @@
"bin": ["bin/rector"], "bin": ["bin/rector"],
"config": { "config": {
"sort-packages": true "sort-packages": true
}, }
"minimum-stability": "dev",
"prefer-stable": true
} }

View File

@ -96,6 +96,9 @@ parameters:
# not really needed, empty # not really needed, empty
- '#Rector\\NodeTraverser\\RectorNodeTraverser::__construct\(\) does not call parent constructor from PhpParser\\NodeTraverser#' - '#Rector\\NodeTraverser\\RectorNodeTraverser::__construct\(\) does not call parent constructor from PhpParser\\NodeTraverser#'
# intentionally
- '#In method "(.*?)", caught "Throwable" must be rethrown. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception#'
services: services:
- -
class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension

View File

@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace Rector\Rector\CodeQuality;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp\BitwiseAnd as AssignBitwiseAnd;
use PhpParser\Node\Expr\AssignOp\BitwiseOr as AssignBitwiseOr;
use PhpParser\Node\Expr\AssignOp\BitwiseXor as AssignBitwiseXor;
use PhpParser\Node\Expr\AssignOp\Concat as AssignConcat;
use PhpParser\Node\Expr\AssignOp\Div as AssignDiv;
use PhpParser\Node\Expr\AssignOp\Minus as AssignMinus;
use PhpParser\Node\Expr\AssignOp\Mod as AssignMod;
use PhpParser\Node\Expr\AssignOp\Mul as AssignMul;
use PhpParser\Node\Expr\AssignOp\Plus as AssignPlus;
use PhpParser\Node\Expr\AssignOp\Pow as AssignPow;
use PhpParser\Node\Expr\AssignOp\ShiftLeft as AssignShiftLeft;
use PhpParser\Node\Expr\AssignOp\ShiftRight as AssignShiftRight;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp\BitwiseAnd;
use PhpParser\Node\Expr\BinaryOp\BitwiseOr;
use PhpParser\Node\Expr\BinaryOp\BitwiseXor;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\BinaryOp\Div;
use PhpParser\Node\Expr\BinaryOp\Minus;
use PhpParser\Node\Expr\BinaryOp\Mod;
use PhpParser\Node\Expr\BinaryOp\Mul;
use PhpParser\Node\Expr\BinaryOp\Plus;
use PhpParser\Node\Expr\BinaryOp\Pow;
use PhpParser\Node\Expr\BinaryOp\ShiftLeft;
use PhpParser\Node\Expr\BinaryOp\ShiftRight;
use Rector\Printer\BetterStandardPrinter;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class CombinedAssignRector extends AbstractRector
{
/**
* @var string[]
*/
private $binaryOpClassToAssignOpClass = [
BitwiseOr::class => AssignBitwiseOr::class,
BitwiseAnd::class => AssignBitwiseAnd::class,
BitwiseXor::class => AssignBitwiseXor::class,
Plus::class => AssignPlus::class,
Div::class => AssignDiv::class,
Mul::class => AssignMul::class,
Minus::class => AssignMinus::class,
Concat::class => AssignConcat::class,
Pow::class => AssignPow::class,
Mod::class => AssignMod::class,
ShiftLeft::class => AssignShiftLeft::class,
ShiftRight::class => AssignShiftRight::class,
];
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
public function __construct(BetterStandardPrinter $betterStandardPrinter)
{
$this->betterStandardPrinter = $betterStandardPrinter;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Simplify $value = $value + 5; assignments to shorter ones',
[new CodeSample('$value = $value + 5;', '$value += 5;')]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
/**
* @param Assign $assignNode
*/
public function refactor(Node $assignNode): ?Node
{
if (! $assignNode->expr instanceof BinaryOp) {
return $assignNode;
}
/** @var BinaryOp $binaryNode */
$binaryNode = $assignNode->expr;
if (! $this->areNodesEqual($assignNode->var, $binaryNode->left)) {
return $assignNode;
}
$binaryNodeClass = get_class($binaryNode);
if (! isset($this->binaryOpClassToAssignOpClass[$binaryNodeClass])) {
return $assignNode;
}
$newAssignNodeClass = $this->binaryOpClassToAssignOpClass[$binaryNodeClass];
/** @var AssignOp $newAssignNodeClass */
return new $newAssignNodeClass($assignNode->var, $binaryNode->right);
}
private function areNodesEqual(Node $firstNode, Node $secondNode): bool
{
return $this->betterStandardPrinter->prettyPrint([$firstNode]) === $this->betterStandardPrinter->prettyPrint(
[$secondNode]
);
}
}

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\CodeQuality\CombinedAssignRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Rector\CodeQuality\CombinedAssignRector
*
* Some tests used from:
* - https://github.com/doctrine/coding-standard/pull/83/files
* - https://github.com/slevomat/coding-standard/blob/master/tests/Sniffs/Operators/data/requireCombinedAssignmentOperatorErrors.php
*/
final class CombinedAssignRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideWrongToFixedFiles()
*/
public function test(string $wrong, string $fixed): void
{
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
}
public function provideWrongToFixedFiles(): Iterator
{
yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'];
yield [__DIR__ . '/Wrong/wrong2.php.inc', __DIR__ . '/Correct/correct2.php.inc'];
}
protected function provideConfig(): string
{
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
$value = 10;
$value += 2;
$foo .= '';
$bar += Something::count();
$baz **= 2;
$quux |= Something::FOO;

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\CodeQuality\CombinedAssignRector\Wrong;
class Whatever
{
public function __construct($parameter)
{
self::$a &= 2;
static::$a |= 4;
self::$$parameter .= '';
parent::${'a'} /= 10;
self::${'a'}[0] -= 100;
Anything::$a **= 2;
Something\Anything::$a %= 2;
\Something\Anything::$a *= 1000;
self::$a::$b += 4;
$this::$a <<= 2;
$this->a >>= 2;
$this->$$parameter ^= 10;
$this->{'a'} += 10;
$this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->{" $d"} *= 100;
$this->something += 10;
}
}

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
$value = 10;
$value = $value + 2;
$foo = $foo . '';
$bar = $bar + Something::count();
$baz = $baz ** 2;
$quux = $quux | Something::FOO;

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\CodeQuality\CombinedAssignRector\Wrong;
class Whatever
{
public function __construct($parameter)
{
self::$a = self::$a & 2;
static::$a = static::$a | 4;
self::$$parameter = self::$$parameter . '';
parent::${'a'} = parent::${'a'} / 10;
self::${'a'}[0] = self::${'a'}[0] - 100;
Anything::$a = Anything::$a ** 2;
Something\Anything::$a = Something\Anything::$a % 2;
\Something\Anything::$a = \Something\Anything::$a * 1000;
self::$a::$b = self::$a::$b + 4;
$this::$a = $this::$a << 2;
$this->a = $this->a >> 2;
$this->$$parameter = $this->$$parameter ^ 10;
$this->{'a'} = $this->{'a'} + 10;
$this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->{" $d"} = $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->{" $d"} * 100;
$this->something = $this->something + 10;
}
}

View File

@ -0,0 +1,2 @@
services:
Rector\Rector\CodeQuality\CombinedAssignRector: ~