Merge pull request #656 from rectorphp/php-dir

[PHP] Add MultiDirnameRector
This commit is contained in:
Tomáš Votruba 2018-10-05 22:37:31 +08:00 committed by GitHub
commit 0c65b0e09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 207 additions and 0 deletions

View File

@ -3,3 +3,4 @@ services:
Rector\Php\Rector\Ternary\TernaryToNullCoalescingRector: ~
Rector\Php\Rector\FuncCall\RandomFunctionRector: ~
Rector\Php\Rector\FunctionLike\ExceptionHandlerTypehintRector: ~
Rector\Php\Rector\FuncCall\MultiDirnameRector: ~

View File

@ -107,3 +107,5 @@ parameters:
Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff:
# respects inherited pattern for better comparing
- 'src/Printer/BetterStandardPrinter.php'
PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.FoundInWhileCondition: ~

View File

@ -0,0 +1,113 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector\FuncCall;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Scalar\LNumber;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class MultiDirnameRector extends AbstractRector
{
/**
* @var int
*/
private $nestingLevel = 0;
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Changes multiple dirname() calls to one with nesting level',
[new CodeSample('dirname(dirname($path));', 'dirname($path, 2);')]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [FuncCall::class];
}
/**
* @param FuncCall $funcCallNode
*/
public function refactor(Node $funcCallNode): ?Node
{
$this->nestingLevel = 0;
if (! $this->isDirnameFuncCall($funcCallNode)) {
return $funcCallNode;
}
$activeFuncCallNode = $funcCallNode;
$lastFuncCallNode = $funcCallNode;
while ($activeFuncCallNode = $this->matchNestedDirnameFuncCall($activeFuncCallNode)) {
$lastFuncCallNode = $activeFuncCallNode;
}
// nothing to improve
if ($this->nestingLevel < 2) {
return $activeFuncCallNode;
}
$funcCallNode->args[0] = $lastFuncCallNode->args[0];
$funcCallNode->args[1] = new Arg(new LNumber($this->nestingLevel));
return $funcCallNode;
}
private function matchNestedDirnameFuncCall(Node $node): ?FuncCall
{
if (! $this->isDirnameFuncCall($node)) {
return null;
}
/** @var FuncCall $node */
if (count($node->args) >= 3) {
return null;
}
// dirname($path, <LEVEL>);
if (count($node->args) === 2) {
if (! $node->args[1]->value instanceof LNumber) {
return null;
}
/** @var LNumber $levelNumber */
$levelNumber = $node->args[1]->value;
$this->nestingLevel += $levelNumber->value;
} else {
++$this->nestingLevel;
}
if ($this->isDirnameFuncCall($node->args[0]->value)) {
return $node->args[0]->value;
}
return null;
}
private function isDirnameFuncCall(Node $node): bool
{
if (! $node instanceof FuncCall) {
return false;
}
if ((string) $node->name !== 'dirname') {
return false;
}
if (! isset($node->args[0])) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
dirname($path, 2);
new dirname(dirname($path, 2));
// untouched
dirname(dirname($path, $level));
dirname("foo/".dirname($path));
dirname(dirname($path).$foo);
foo\dirname(dirname($path));

View File

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
dirname($path, 4);
dirname($path, 5);
dirname($path, 5);

View File

@ -0,0 +1,5 @@
<?php
dirname($path, 3);
dirname(foo(dirname($path, 2)), 2);

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\FuncCall\MultiDirnameRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\FuncCall\MultiDirnameRector
*
* Some tests copied from:
* - https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/3826/files
*/
final class MultiDirnameRectorTest 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'];
yield [__DIR__ . '/Wrong/wrong3.php.inc', __DIR__ . '/Correct/correct3.php.inc'];
}
protected function provideConfig(): string
{
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
dirname(dirname($path));
new dirname(dirname(dirname($path)));
// untouched
dirname(dirname($path, $level));
dirname("foo/".dirname($path));
dirname(dirname($path).$foo);
foo\dirname(dirname($path));

View File

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
dirname(dirname($path), 3);
dirname(dirname($path, 2), 3);
dirname(dirname(dirname($path), 3));

View File

@ -0,0 +1,5 @@
<?php
dirname(\dirname(dirname($path)));
dirname(dirname(foo(dirname(dirname($path)))));

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\FuncCall\MultiDirnameRector: ~