[PHP] Add ReservedObjectRector

This commit is contained in:
Tomas Votruba 2018-10-05 23:34:25 +08:00
parent 0c65b0e09a
commit 2ffb04a229
6 changed files with 205 additions and 0 deletions

View File

@ -1,2 +1,5 @@
services: services:
Rector\Php\Rector\BinaryOp\IsIterableRector: ~ Rector\Php\Rector\BinaryOp\IsIterableRector: ~
Rector\Php\Rector\Name\ReservedObjectRector:
$reservedKeywordsToReplacements:
Object: 'BaseObject'

View File

@ -0,0 +1,110 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector\Name;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Namespace_;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://wiki.php.net/rfc/object-typehint
*
* @see https://github.com/cebe/yii2/commit/9548a212ecf6e50fcdb0e5ba6daad88019cfc544
*/
final class ReservedObjectRector extends AbstractRector
{
/**
* @var string[]
*/
private $reservedKeywordsToReplacements = [];
/**
* @param string[] $reservedKeywordsToReplacements
*/
public function __construct(array $reservedKeywordsToReplacements)
{
$this->reservedKeywordsToReplacements = $reservedKeywordsToReplacements;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Changes reserved "Object" name to "<Smart>Object" where <Smart> can be configured',
[
new CodeSample(
<<<'CODE_SAMPLE'
class Object
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SmartObject
{
}
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Identifier::class, Name::class];
}
/**
* @param Identifier|Name $node
*/
public function refactor(Node $node): ?Node
{
if ($node instanceof Identifier) {
return $this->processIdentifier($node);
}
if ($node instanceof Name) {
return $this->processName($node);
}
return $node;
}
private function processIdentifier(Identifier $identifierNode): Identifier
{
foreach ($this->reservedKeywordsToReplacements as $reservedKeyword => $replacement) {
if (strtolower($identifierNode->name) === strtolower($reservedKeyword)) {
$identifierNode->name = $replacement;
}
}
return $identifierNode;
}
private function processName(Name $nameNode): Name
{
// we look for "extends <Name>"
$parentNode = $nameNode->getAttribute(Attribute::PARENT_NODE);
// "Object" can part of namespace name
if ($parentNode instanceof Namespace_) {
return $nameNode;
}
// process lass part
foreach ($this->reservedKeywordsToReplacements as $reservedKeyword => $replacement) {
if (strtolower($nameNode->getLast()) === strtolower($reservedKeyword)) {
$nameNode->parts[count($nameNode->parts) - 1] = $replacement;
$nameNode->setAttribute(Attribute::ORIGINAL_NODE, null);
}
}
return $nameNode;
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong;
class SmartObject
{
}
class ChildObject extends \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject
{
}
$some = new ChildObject();
if ($some instanceof \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject) {
return;
}
is_subclass_of($some, \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject::class);
is_a($some, \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject::class);
function someFunction(\Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject $reservedObject): \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject {
return $reservedObject;
}
$someObject = new \Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong\SmartObject;

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\Name\ReservedObjectRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\Name\ReservedObjectRector
*/
final class ReservedObjectRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideWrongToFixedFiles()
*/
public function test(string $wrong, string $fixed): void
{
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
}
public function provideWrongToFixedFiles(): Iterator
{
// it needs to have different name, since in PHP 7.1 it already reserved
yield [__DIR__ . '/Wrong/ReservedObject.php', __DIR__ . '/Correct/correct.php.inc'];
}
protected function provideConfig(): string
{
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\Name\ReservedObjectRector\Wrong;
class ReservedObject
{
}
class ChildObject extends ReservedObject
{
}
$some = new ChildObject();
if ($some instanceof ReservedObject) {
return;
}
is_subclass_of($some, ReservedObject::class);
is_a($some, ReservedObject::class);
function someFunction(ReservedObject $reservedObject): ReservedObject {
return $reservedObject;
}
$someObject = new ReservedObject;

View File

@ -0,0 +1,5 @@
services:
Rector\Php\Rector\Name\ReservedObjectRector:
# todo make work even without these explicit variables
$reservedKeywordsToReplacements:
'ReservedObject': 'SmartObject'