Merge pull request #75 from RectorPHP/dynamic-namespace-replace

Add NamespaceReplacerRector [closes #62]
This commit is contained in:
Tomáš Votruba 2017-10-07 21:36:15 +02:00 committed by GitHub
commit aa699917ee
10 changed files with 334 additions and 0 deletions

View File

@ -0,0 +1,193 @@
<?php declare(strict_types=1);
namespace Rector\Rector\Dynamic;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use Rector\Node\Attribute;
use Rector\Rector\AbstractRector;
final class NamespaceReplacerRector extends AbstractRector
{
/**
* @var string[]
*/
private $oldToNewNamespaces = [];
/**
* @param string[] $oldToNewNamespaces
*/
public function __construct(array $oldToNewNamespaces)
{
$this->oldToNewNamespaces = $oldToNewNamespaces;
}
public function isCandidate(Node $node): bool
{
if (! $this->isGivenKind($node, [Namespace_::class, Use_::class, Name::class, FullyQualified::class])) {
return false;
}
$name = $this->resolveNameFromNode($node);
if (! $this->isNamespaceToChange($name)) {
return false;
}
return ! $this->isClassFullyQualifiedName($node);
}
public function refactor(Node $node): ?Node
{
if ($node instanceof Namespace_) {
$newName = $this->resolveNewNameFromNode($node);
$node->name = new Name($newName);
return $node;
}
if ($node instanceof Use_) {
$newName = $this->resolveNewNameFromNode($node);
$node->uses[0]->name = new Name($newName);
return $node;
}
if ($node instanceof Name) {
if ($this->isPartialNamespace($node)) {
$newName = $this->resolvePartialNewName($node);
} else {
$newName = $this->resolveNewNameFromNode($node);
}
$node->parts = explode('\\', $newName);
$node->setAttribute('origNode', null);
return $node;
}
return null;
}
private function resolveNewNameFromNode(Node $node): string
{
$name = $this->resolveNameFromNode($node);
[$oldNamespace, $newNamespace] = $this->getNewNamespaceForOldOne($name);
return str_replace($oldNamespace, $newNamespace, $name);
}
private function resolveNameFromNode(Node $node): string
{
if ($node instanceof Namespace_) {
return $node->name->toString();
}
if ($node instanceof Use_) {
return $node->uses[0]->name->toString();
}
if ($node instanceof Name) {
/** @var FullyQualified|null $resolveName */
$resolveName = $node->getAttribute(Attribute::RESOLVED_NAME);
if ($resolveName) {
return $resolveName->toString();
}
return $node->toString();
}
}
private function isNamespaceToChange(string $namespace): bool
{
return (bool) $this->getNewNamespaceForOldOne($namespace);
}
/**
* @return string[]
*/
private function getNewNamespaceForOldOne(string $namespace): array
{
foreach ($this->oldToNewNamespaces as $oldNamespace => $newNamespace) {
if (Strings::startsWith($namespace, $oldNamespace)) {
return [$oldNamespace, $newNamespace];
}
}
return [];
}
/**
* @param string[] $types
*/
private function isGivenKind(Node $node, array $types): bool
{
foreach ($types as $type) {
if (is_a($node, $type, true)) {
return true;
}
}
return false;
}
/**
* Checks for "new \ClassNoNamespace;"
* This should be skipped, not a namespace.
*/
private function isClassFullyQualifiedName(Node $node): bool
{
$parentNode = $node->getAttribute(Attribute::PARENT_NODE);
if ($parentNode === null) {
return false;
}
if (! $parentNode instanceof New_) {
return false;
}
$newClassName = $parentNode->class->toString();
foreach ($this->oldToNewNamespaces as $oldNamespace => $newNamespace) {
if ($newClassName === $oldNamespace) {
return true;
}
}
return false;
}
private function isPartialNamespace(Name $nameNode): bool
{
$resolvedName = $nameNode->getAttribute(Attribute::RESOLVED_NAME);
if ($resolvedName === null) {
return false;
}
$nodeName = $nameNode->toString();
if ($resolvedName instanceof FullyQualified) {
return $nodeName !== $resolvedName->toString();
}
return false;
}
private function resolvePartialNewName(Name $nameNode): string
{
/** @var FullyQualified $resolvedName */
$resolvedName = $nameNode->getAttribute(Attribute::RESOLVED_NAME);
$fullyQualifiedName = $resolvedName->toString();
$completeNewName = $this->resolveNewNameFromNode($resolvedName);
// first dummy implementation - improve
$cutOffFromTheLeft = strlen($fullyQualifiedName) - strlen($nameNode->toString());
return substr($completeNewName, $cutOffFromTheLeft);
}
}

View File

@ -0,0 +1,3 @@
rectors:
Rector\Rector\Dynamic\NamespaceReplacerRector:
'BetterReflection': 'Roave\BetterReflection'

View File

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\Dynamic\NamespaceReplacerRector;
use PHPUnit\Framework\TestCase;
use Rector\Application\FileProcessor;
use Rector\DependencyInjection\ContainerFactory;
use Rector\Rector\Dynamic\NamespaceReplacerRector;
use SplFileInfo;
final class Test extends TestCase
{
/**
* @var FileProcessor
*/
private $fileProcessor;
protected function setUp(): void
{
$container = (new ContainerFactory)->createWithConfig(
__DIR__ . '/config/rector.yml'
);
$this->fileProcessor = $container->get(FileProcessor::class);
}
/**
* @dataProvider provideTestFiles()
*/
public function test(string $testedFile, string $expectedFile): void
{
$refactoredFileContent = $this->fileProcessor->processFileWithRectorsToString(
new SplFileInfo($testedFile),
[NamespaceReplacerRector::class]
);
$this->assertStringEqualsFile($expectedFile, $refactoredFileContent);
}
/**
* @return string[][]
*/
public function provideTestFiles(): array
{
return [
[__DIR__ . '/wrong/wrong.php.inc', __DIR__ . '/correct/correct.php.inc'],
[__DIR__ . '/wrong/wrong2.php.inc', __DIR__ . '/correct/correct2.php.inc'],
[__DIR__ . '/wrong/wrong3.php.inc', __DIR__ . '/correct/correct3.php.inc'],
];
}
}

View File

@ -0,0 +1,5 @@
rectors:
Rector\Rector\Dynamic\NamespaceReplacerRector:
# todo: add autosort to make complex first; allows reverse order
'OldNamespaceWith\OldSplitNamespace': 'NewNamespaceWith\NewSplitNamespace'
'OldNamespace': 'NewNamespace'

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace NewNamespace\SubNamespace;
use NewNamespace;
use NewNamespace\AnotherSubNamespace;
class SomeClass
{
public function someClass()
{
$keepThis = new \OldNamespace;
return new \NewNamespace\SomeClass;
}
}

View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace NewNamespace\SubNamespace;
use NewNamespace;
class SomeClass
{
public function someClass(NewNamespace\SubNamespace $argument)
{
}
}

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace NewNamespace\SubNamespace;
use NewNamespaceWith\NewSplitNamespace;
class SomeClass
{
public function someClass()
{
return new NewSplitNamespace\SomeClass;
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace OldNamespace\SubNamespace;
use OldNamespace;
use OldNamespace\AnotherSubNamespace;
class SomeClass
{
public function someClass()
{
$keepThis = new \OldNamespace;
return new \OldNamespace\SomeClass;
}
}

View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace OldNamespace\SubNamespace;
use OldNamespace;
class SomeClass
{
public function someClass(OldNamespace\SubNamespace $argument)
{
}
}

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace NewNamespace\SubNamespace;
use OldNamespaceWith\OldSplitNamespace;
class SomeClass
{
public function someClass()
{
return new OldSplitNamespace\SomeClass;
}
}