Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-8224: Added rector rule to remove interface & methods implementations #4

Merged
merged 5 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/lib/Rule/Internal/RemoveInterfaceWithMethodsRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Rector\Rule\Internal;

use Ibexa\Rector\Visitor\DependentMethodCallRemovingVisitor;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeTraverser;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Rector\AbstractRector;
use Rector\Removing\Rector\Class_\RemoveInterfacesRector;
use ReflectionClass;
use ReflectionMethod;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class RemoveInterfaceWithMethodsRector extends AbstractRector implements ConfigurableRectorInterface
{
private RemoveInterfacesRector $removeInterfacesRector;

/** @var class-string[] */
private array $interfacesToRemove;

public function __construct(RemoveInterfacesRector $removeInterfacesRector)
{
$this->removeInterfacesRector = $removeInterfacesRector;
}

/**
* @throws \Exception
*/
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Remove Interface implementation with all its methods', [new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeClassWithInterface implements InterfaceToRemove
{
public function interfaceMethod(): array
{
return ['smth'];
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClassWithInterface
{
}
CODE_SAMPLE
,
['var_dump']
)]);
}

/**
* @return array<class-string<\PhpParser\Node>>
*/
public function getNodeTypes(): array
{
return $this->removeInterfacesRector->getNodeTypes();
}

/**
* @param \PhpParser\Node\Stmt\Class_ $node
*/
public function refactor(Node $node): ?int
{
if ($node->implements === []) {
return null;
}

foreach ($node->implements as $implement) {
if ($this->isNames($implement, $this->interfacesToRemove)) {
foreach ($this->interfacesToRemove as $interface) {
$ref = new ReflectionClass($interface);
$methods = array_map(static fn (ReflectionMethod $reflectionMethod): string => $reflectionMethod->getName(), $ref->getMethods());

// Remove method definition
foreach ($node->stmts as $key => $stmt) {
if ($stmt instanceof ClassMethod && $this->isNames($stmt, $methods)) {
unset($node->stmts[$key]);
}
}

// Remove method calls, if one of the arguments was removed method
foreach ($node->getMethods() as $classMethod) {
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor(
new DependentMethodCallRemovingVisitor(
$this->nodeNameResolver,
$methods
)
);

if ($classMethod->stmts !== null) {
/** @var array<\PhpParser\Node\Stmt>|null $traversedStmts */
$traversedStmts = $nodeTraverser->traverse($classMethod->stmts);
$classMethod->stmts = $traversedStmts;
}
}
}
}
}

// Remove interface
$this->removeInterfacesRector->refactor($node);

return null;
}

/**
* @param class-string[] $configuration
*/
public function configure(array $configuration): void
{
$this->interfacesToRemove = $configuration;

$this->removeInterfacesRector->configure($configuration);
}
}
48 changes: 48 additions & 0 deletions src/lib/Visitor/DependentMethodCallRemovingVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Rector\Visitor;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\NodeVisitorAbstract;
use Rector\NodeNameResolver\NodeNameResolver;

/**
* Removes method call, when one of the methods is used as argument to that method.
*/
final class DependentMethodCallRemovingVisitor extends NodeVisitorAbstract
{
private NodeNameResolver $nodeNameResolver;

/** @var string[] */
private array $methods;

/**
* @param string[] $methods
*/
public function __construct(NodeNameResolver $nodeNameResolver, array $methods)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->methods = $methods;
}

public function leaveNode(Node $node)
{
if ($node instanceof MethodCall) {
foreach ($node->getArgs() as $arg) {
$argValue = $arg->value;
if ($argValue instanceof MethodCall && $this->nodeNameResolver->isNames($argValue->name, $this->methods)) {
return $node->var;
}
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture;

interface SomeInterface
{
/**
* @return string[]
*/
public function someMethod(): array;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use Symfony\Component\Console\Command\Command;

class SomeClass implements SomeInterface
{
public function firstMethod(): void
{

}

public function someMethod(): array
{
return ['ezplatform:some_old:command_with_interface'];
}

public function thirdMethod(): void
{

}
}

?>
-----
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use Symfony\Component\Console\Command\Command;

class SomeClass
{
public function firstMethod(): void
{

}
public function thirdMethod(): void
{

}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;

class SomeClass implements SomeInterface
{
public function someMethod(): array
{
return ['ezplatform:some_old:command_with_interface'];
}
}

?>
-----
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;

class SomeClass
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use Symfony\Component\Console\Command\Command;

class SomeClass implements SomeInterface
{
public function firstMethod(): void
{

}

public function configure()
{
$this->firstMethod()->firstMethod($this->someMethod())->thirdMethod();
}

public function someMethod(): array
{
return ['ezplatform:some_old:command_with_interface'];
}

public function thirdMethod(): void
{

}
}

?>
-----
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use Symfony\Component\Console\Command\Command;

class SomeClass
{
public function firstMethod(): void
{

}
public function configure()
konradoboza marked this conversation as resolved.
Show resolved Hide resolved
{
$this->firstMethod()->thirdMethod();
}
public function thirdMethod(): void
ViniTou marked this conversation as resolved.
Show resolved Hide resolved
{

}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use OtherInterface;

class SomeClass implements SomeInterface, OtherInterface
{
public function firstMethod()
{

}

public function someMethod(): array
{
return ['ezplatform:some_old:command_with_interface'];
}
}

?>
-----
<?php /** @noinspection ALL */

use Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods\Fixture\SomeInterface;
use OtherInterface;

class SomeClass implements OtherInterface
{
public function firstMethod()
{

}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Rector\Tests\Rule\Internal\RemoveInterfaceWithMethods;

use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

/**
* @covers \Ibexa\Rector\Rule\Internal\RemoveLegacyClassAliasRector
*/
final class RemoveInterfaceWithMethodsRectorTest extends AbstractRectorTestCase
{
/**
* @throws \Rector\Exception\ShouldNotHappenException
*/
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): \Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Loading
Loading