[Restoratoin] Add RemoveUselessJustForSakeInterfaceRector

This commit is contained in:
TomasVotruba 2020-06-08 10:05:07 +02:00
parent 690e2e9546
commit 0774576a86
5 changed files with 291 additions and 3 deletions

View File

@ -1718,7 +1718,7 @@ catch (CatchedType $catchedVariable) {
#### Public Properties
* `$types` - `/** @var Node\Name[] Types of exceptions to catch */`
* `$var` - `/** @var Expr\Variable Variable for exception */`
* `$var` - `/** @var Expr\Variable|null Variable for exception */`
* `$stmts` - `/** @var Node\Stmt[] Statements */`
<br>

View File

@ -1,4 +1,4 @@
# All 506 Rectors Overview
# All 507 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -56,7 +56,7 @@
- [Refactoring](#refactoring) (2)
- [RemovingStatic](#removingstatic) (4)
- [Renaming](#renaming) (10)
- [Restoration](#restoration) (5)
- [Restoration](#restoration) (6)
- [SOLID](#solid) (12)
- [Sensio](#sensio) (3)
- [StrictCodeQuality](#strictcodequality) (1)
@ -9319,6 +9319,33 @@ Remove final from Doctrine entities
<br>
### `RemoveUselessJustForSakeInterfaceRector`
- class: [`Rector\Restoration\Rector\Class_\RemoveUselessJustForSakeInterfaceRector`](/../master/rules/restoration/src/Rector/Class_/RemoveUselessJustForSakeInterfaceRector.php)
Remove interface, that are added just for its sake, but nowhere useful
```diff
-class SomeClass implements OnlyHereUsedInterface
+class SomeClass
{
}
-interface OnlyHereUsedInterface
-{
-}
-
class SomePresenter
{
- public function __construct(OnlyHereUsedInterface $onlyHereUsed)
+ public function __construct(SomeClass $onlyHereUsed)
{
}
}
```
<br>
## SOLID
### `AddFalseDefaultToBoolPropertyRector`

View File

@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
namespace Rector\Restoration\Rector\Class_;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Core\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Rector\NodeCollector\NodeFinder\ClassLikeParsedNodesFinder;
use Rector\PSR4\Collector\RenamedClassesCollector;
use ReflectionClass;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @sponsor Thanks https://amateri.com for sponsoring this rule
*
* @see \Rector\Restoration\Tests\Rector\Class_\RemoveUselessJustForSakeInterfaceRector\RemoveUselessJustForSakeInterfaceRectorTest
*/
final class RemoveUselessJustForSakeInterfaceRector extends AbstractRector
{
/**
* @var string
*/
private $interfacePattern;
/**
* @var RenamedClassesCollector
*/
private $renamedClassesCollector;
public function __construct(
RenamedClassesCollector $renamedClassesCollector,
ClassLikeParsedNodesFinder $classLikeParsedNodesFinder,
string $interfacePattern = '#(.*?)#'
) {
$this->interfacePattern = $interfacePattern;
$this->renamedClassesCollector = $renamedClassesCollector;
$this->classLikeParsedNodesFinder = $classLikeParsedNodesFinder;
}
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (count((array) $node->implements) === 0) {
return null;
}
foreach ($node->implements as $key => $implement) {
$implementedInterfaceName = $this->getName($implement);
if ($implementedInterfaceName === null) {
return null;
}
if (! Strings::match($implementedInterfaceName, $this->interfacePattern)) {
continue;
}
// is interface in /vendor? probably useful
$classFileLocation = $this->resolveClassFileLocation($implementedInterfaceName);
if (Strings::contains($classFileLocation, 'vendor')) {
continue;
}
$interfaceImplementers = $this->getInterfaceImplementers($implementedInterfaceName);
// makes sense
if (count($interfaceImplementers) > 1) {
continue;
}
// 1. replace current interface with one more parent or remove it
$this->removeOrReplaceImlementedInterface($implementedInterfaceName, $node, $key);
// 2. remove file if not in /vendor
$this->removeInterfaceFile($implementedInterfaceName, $classFileLocation);
// 3. replace interface with explicit current class
$this->replaceName($node, $implementedInterfaceName);
}
return null;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Remove interface, that are added just for its sake, but nowhere useful', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass implements OnlyHereUsedInterface
{
}
interface OnlyHereUsedInterface
{
}
class SomePresenter
{
public function __construct(OnlyHereUsedInterface $onlyHereUsed)
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
}
class SomePresenter
{
public function __construct(SomeClass $onlyHereUsed)
{
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
private function getInterfaceImplementers(string $interfaceName): array
{
return array_filter(
get_declared_classes(),
function (string $className) use ($interfaceName): bool {
return in_array($interfaceName, class_implements($className), true);
}
);
}
private function getParentInterfaceIfFound(string $implementedInterfaceName): ?string
{
$interfaceReflection = new ReflectionClass($implementedInterfaceName);
// get first parent interface
return $interfaceReflection->getInterfaceNames()[0] ?? null;
}
private function removeInterfaceFile(string $interfaceName, string $classFileLocation): void
{
if (StaticPHPUnitEnvironment::isPHPUnitRun()) {
$interface = $this->classLikeParsedNodesFinder->findInterface($interfaceName);
if ($interface instanceof Interface_) {
$this->removeNode($interface);
}
} else {
$smartFileInfo = new SmartFileInfo($classFileLocation);
$this->removeFile($smartFileInfo);
}
}
private function replaceName(Class_ $class, string $implementedInterfaceName): void
{
$className = $this->getName($class);
if ($className === null) {
return;
}
$this->renamedClassesCollector->addClassRename($implementedInterfaceName, $className);
}
private function resolveClassFileLocation(string $implementedInterfaceName)
{
$deadInterfaceReflection = new ReflectionClass($implementedInterfaceName);
return $deadInterfaceReflection->getFileName();
}
private function removeOrReplaceImlementedInterface(string $implementedInterfaceName, Class_ $class, int $key): void
{
$parentInterface = $this->getParentInterfaceIfFound($implementedInterfaceName);
if ($parentInterface !== null) {
$class->implements[$key] = new FullyQualified($parentInterface);
} else {
unset($class->implements[$key]);
}
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Restoration\Tests\Rector\Class_\RemoveUselessJustForSakeInterfaceRector\Fixture;
class SomeClass implements OnlyHereUsedInterface
{
}
interface OnlyHereUsedInterface
{
}
class SomePresenter
{
public function __construct(OnlyHereUsedInterface $onlyHereUsed)
{
}
}
?>
-----
<?php
namespace Rector\Restoration\Tests\Rector\Class_\RemoveUselessJustForSakeInterfaceRector\Fixture;
class SomeClass
{
}
class SomePresenter
{
public function __construct(\Rector\Restoration\Tests\Rector\Class_\RemoveUselessJustForSakeInterfaceRector\Fixture\SomeClass $onlyHereUsed)
{
}
}
?>

View File

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