[Polyfill] Add UnwrapFutureCompatibleIfRector

This commit is contained in:
TomasVotruba 2020-01-06 12:54:01 +01:00
parent 84e1e98a51
commit a37a7d1dad
8 changed files with 270 additions and 3 deletions

View File

@ -105,7 +105,8 @@
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src",
"Rector\\Phalcon\\": "packages/Phalcon/src",
"Rector\\DoctrineGedmoToKnplabs\\": "packages/DoctrineGedmoToKnplabs/src",
"Rector\\MinimalScope\\": "packages/MinimalScope/src"
"Rector\\MinimalScope\\": "packages/MinimalScope/src",
"Rector\\Polyfill\\": "packages/Polyfill/src"
}
},
"autoload-dev": {
@ -166,7 +167,8 @@
"Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests",
"Rector\\Phalcon\\Tests\\": "packages/Phalcon/tests",
"Rector\\DoctrineGedmoToKnplabs\\Tests\\": "packages/DoctrineGedmoToKnplabs/tests",
"Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests"
"Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests",
"Rector\\Polyfill\\Tests\\": "packages/Polyfill/tests"
},
"classmap": [
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",

View File

@ -0,0 +1,2 @@
services:
Rector\Polyfill\Rector\If_\UnwrapFutureCompatibleIfRector: null

View File

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

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Rector\Polyfill\FeatureSupport;
use Rector\Php\PhpVersionProvider;
final class FunctionSupportResolver
{
/**
* @var string[][]
*/
private const FUNCTIONS_BY_VERSION = [
'5.6' => ['session_abort'],
];
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
public function __construct(PhpVersionProvider $phpVersionProvider)
{
$this->phpVersionProvider = $phpVersionProvider;
}
public function isFunctionSupported(string $desiredFunction): bool
{
foreach (self::FUNCTIONS_BY_VERSION as $version => $functions) {
foreach ($functions as $function) {
if ($desiredFunction !== $function) {
continue;
}
if (! $this->phpVersionProvider->isAtLeast($version)) {
continue;
}
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Rector\Polyfill\Rector\If_;
use PhpParser\Node;
use PhpParser\Node\Stmt\If_;
use Rector\PhpParser\Node\Manipulator\IfManipulator;
use Rector\Polyfill\FeatureSupport\FunctionSupportResolver;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\UnwrapFutureCompatibleIfRectorTest
*/
final class UnwrapFutureCompatibleIfRector extends AbstractRector
{
/**
* @var IfManipulator
*/
private $ifManipulator;
/**
* @var FunctionSupportResolver
*/
private $functionSupportResolver;
public function __construct(IfManipulator $ifManipulator, FunctionSupportResolver $functionSupportResolver)
{
$this->ifManipulator = $ifManipulator;
$this->functionSupportResolver = $functionSupportResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Remove functions exists if with else for always existing', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function run()
{
// session locking trough other addons
if (function_exists('session_abort')) {
session_abort();
} else {
session_write_close();
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function run()
{
// session locking trough other addons
session_abort();
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [If_::class];
}
/**
* @param If_ $node
*/
public function refactor(Node $node): ?Node
{
$match = $this->ifManipulator->isIfElseWithFunctionCondition($node, 'function_exists');
if ($match === false) {
return null;
}
/** @var Node\Expr\FuncCall $funcCall */
$funcCall = $node->cond;
$functionToExistName = $this->getValue($funcCall->args[0]->value);
if (! is_string($functionToExistName)) {
return null;
}
if (! $this->functionSupportResolver->isFunctionSupported($functionToExistName)) {
return null;
}
foreach ($node->stmts as $key => $ifStmt) {
if ($key === 0) {
// move comment from if to first element to keep it
$ifStmt->setAttribute('comments', $node->getComments());
}
$this->addNodeAfterNode($ifStmt, $node);
}
$this->removeNode($node);
return null;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\Fixture;
class SomeClass
{
public function run()
{
// session locking trough other addons
if (function_exists('session_abort')) {
session_abort();
} else {
session_write_close();
}
}
}
?>
-----
<?php
namespace Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\Fixture;
class SomeClass
{
public function run()
{
// session locking trough other addons
session_abort();
}
}
?>

View File

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

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Continue_;
use PhpParser\Node\Stmt\Do_;
@ -18,6 +19,7 @@ use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\While_;
use PhpParser\NodeTraverser;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\PhpParser\Printer\BetterStandardPrinter;
@ -53,16 +55,23 @@ final class IfManipulator
*/
private $stmtsManipulator;
/**
* @var NameResolver
*/
private $nameResolver;
public function __construct(
BetterStandardPrinter $betterStandardPrinter,
ConstFetchManipulator $constFetchManipulator,
CallableNodeTraverser $callableNodeTraverser,
StmtsManipulator $stmtsManipulator
StmtsManipulator $stmtsManipulator,
NameResolver $nameResolver
) {
$this->betterStandardPrinter = $betterStandardPrinter;
$this->constFetchManipulator = $constFetchManipulator;
$this->callableNodeTraverser = $callableNodeTraverser;
$this->stmtsManipulator = $stmtsManipulator;
$this->nameResolver = $nameResolver;
}
/**
@ -233,6 +242,29 @@ final class IfManipulator
return true;
}
/**
* Matches:
* if (<some_function>) {
* } else {
* }
*/
public function isIfElseWithFunctionCondition(If_ $if, string $functionName): bool
{
if (! $this->isIfWithElse($if)) {
return false;
}
if (! $if->cond instanceof FuncCall) {
return false;
}
if (! $this->nameResolver->isName($if->cond, $functionName)) {
return false;
}
return true;
}
private function matchComparedAndReturnedNode(NotIdentical $notIdentical, Return_ $returnNode): ?Expr
{
if ($this->betterStandardPrinter->areNodesEqual($notIdentical->left, $returnNode->expr)) {