[TriggerExtractor] add ClassMethodDeprecation and ConfigurableChangeMethodNameRector

This commit is contained in:
TomasVotruba 2017-09-07 05:55:28 +02:00
parent 1d68188503
commit a532b5306f
9 changed files with 263 additions and 13 deletions

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor\Contract\Deprecation;
interface DeprecationInterface
{
}

View File

@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor\Deprecation;
use Rector\TriggerExtractor\Contract\Deprecation\DeprecationInterface;
final class ClassMethodDeprecation implements DeprecationInterface
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $oldMethod;
/**
* @var string
*/
private $newMethod;
public function __construct(string $class, string $oldMethod, string $newMethod)
{
$this->class = $class;
$this->oldMethod = $oldMethod;
$this->newMethod = $newMethod;
}
public function getClass(): string
{
return $this->class;
}
public function getOldMethod(): string
{
return $this->oldMethod;
}
public function getNewMethod(): string
{
return $this->newMethod;
}
}

View File

@ -2,20 +2,22 @@
namespace Rector\TriggerExtractor\Deprecation;
use Rector\TriggerExtractor\Contract\Deprecation\DeprecationInterface;
final class DeprecationCollector
{
/**
* @var mixed[]
* @var DeprecationInterface[]
*/
private $deprecations = [];
public function addDeprecation(string $deprecation): void
public function addDeprecation(DeprecationInterface $deprecation): void
{
$this->deprecations[] = $deprecation;
}
/**
* @return mixed[]
* @return DeprecationInterface[]
*/
public function getDeprecations(): array
{

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor;
namespace Rector\TriggerExtractor\Deprecation;
use Nette\Utils\Strings;
use PhpParser\Node;
@ -10,14 +10,31 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Exception\NotImplementedException;
use Rector\Node\Attribute;
use Rector\TriggerExtractor\Contract\Deprecation\DeprecationInterface;
final class TriggerMessageResolver
final class DeprecationFactory
{
/**
* @var string
*/
private const CLASS_REGEX = '([A-Za-z]+(\\\\[A-Za-z]+)+)';
/**
* @var string
*/
private const CLASS_PATTERN = '#^' . self::CLASS_REGEX . '#s';
/**
* @var string
* @see https://regex101.com/r/WdGoyd/1
*/
private const CLASS_WITH_METHOD_PATTER = '#^' . self::CLASS_REGEX . '::[A-Za-z]+\(\)#s';
/**
* Probably resolve by recursion, similar too
* @see \Rector\NodeTypeResolver\NodeVisitor\TypeResolver::__construct()
*/
public function resolve(Node $node): string
public function createFromNode(Node $node): DeprecationInterface
{
$message = '';
if ($node instanceof Concat) {
@ -25,7 +42,7 @@ final class TriggerMessageResolver
$message .= $this->processConcatNode($node->right);
}
return $message;
return $this->createFromMesssage($message);
}
private function processConcatNode(Node $node): string
@ -92,4 +109,46 @@ final class TriggerMessageResolver
return $word;
}
private function createFromMesssage(string $message): DeprecationInterface
{
// format: don't use this, use that
if (Strings::contains($message, ' use ')) {
[$old, $new] = explode(' use ', $message);
// try to find SomeClass::methodCall()
$matches = Strings::matchAll($old, self::CLASS_WITH_METHOD_PATTER);
if (isset($matches[0][0])) {
$oldClassWithMethod = $matches[0][0];
}
// try to find SomeClass::methodCall()
$matches = Strings::matchAll($new, self::CLASS_WITH_METHOD_PATTER);
if (isset($matches[0][0])) {
$newClassWithMethod = $matches[0][0];
}
if (isset($oldClassWithMethod) && isset($newClassWithMethod)) {
[$oldClass, $oldMethod] = explode('::', $oldClassWithMethod);
[$newClass, $newMethod] = explode('::', $newClassWithMethod);
if ($oldClass === $newClass) {
// simple method replacement
return new ClassMethodDeprecation(
$oldClass,
rtrim($oldMethod, '()'),
rtrim($newMethod, '()')
);
}
}
}
throw new NotImplementedException(sprintf(
'%s::%s() did not resolve %s messsage, so %s was not created. Implement it.',
self::class,
__METHOD__,
$message,
DeprecationInterface::class
));
}
}

View File

@ -9,7 +9,7 @@ use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\NodeVisitorAbstract;
use Rector\TriggerExtractor\Deprecation\DeprecationCollector;
use Rector\TriggerExtractor\TriggerMessageResolver;
use Rector\TriggerExtractor\Deprecation\DeprecationFactory;
final class DeprecationDetector extends NodeVisitorAbstract
{
@ -19,26 +19,28 @@ final class DeprecationDetector extends NodeVisitorAbstract
private $deprecationCollector;
/**
* @var TriggerMessageResolver
* @var DeprecationFactory
*/
private $triggerMessageResolver;
private $deprecationFactory;
public function __construct(
DeprecationCollector $deprecationCollector,
TriggerMessageResolver $triggerMessageResolver
DeprecationFactory $triggerMessageResolver
) {
$this->deprecationCollector = $deprecationCollector;
$this->triggerMessageResolver = $triggerMessageResolver;
$this->deprecationFactory = $triggerMessageResolver;
}
public function enterNode(Node $node): void
{
// @todo: add @deprecate annotations as well,
// @see https://github.com/sensiolabs-de/deprecation-detector/blob/master/src/Visitor/Deprecation/FindDeprecatedTagsVisitor.php
if (! $this->isTriggerErrorUserDeprecated($node)) {
return;
}
/** @var FuncCall $node */
$deprecation = $this->triggerMessageResolver->resolve($node->args[0]->value);
$deprecation = $this->deprecationFactory->createFromNode($node->args[0]->value);
$this->deprecationCollector->addDeprecation($deprecation);
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor\Rector;
use Rector\Rector\AbstractChangeMethodNameRector;
final class ConfigurableChangeMethodNameRector extends AbstractChangeMethodNameRector
{
/**
* @var string[][]
*/
private $perClassOldToNewMethod;
public function setPerClassOldToNewMethods(array $perClassOldToNewMethod): void
{
$this->perClassOldToNewMethod = $perClassOldToNewMethod;
}
public function getSetName(): string
{
return 'dynamic';
}
public function sinceVersion(): float
{
return 0.0;
}
/**
* @return string[][] { class => [ oldMethod => newMethod ] }
*/
protected function getPerClassOldToNewMethods(): array
{
return $this->perClassOldToNewMethod;
}
}

View File

@ -0,0 +1,68 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor\Rector;
use Rector\Contract\Rector\RectorInterface;
use Rector\Exception\NotImplementedException;
use Rector\TriggerExtractor\Contract\Deprecation\DeprecationInterface;
use Rector\TriggerExtractor\Deprecation\ClassMethodDeprecation;
use Rector\TriggerExtractor\Deprecation\DeprecationCollector;
/**
* Creates rectors with propper setup based on found deprecations.
*/
final class RectorFactory
{
/**
* @var DeprecationCollector
*/
private $deprecationCollector;
/**
* @var ConfigurableChangeMethodNameRector
*/
private $configurableChangeMethodNameRector;
public function __construct(
DeprecationCollector $deprecationCollector,
ConfigurableChangeMethodNameRector $configurableChangeMethodNameRector
) {
$this->deprecationCollector = $deprecationCollector;
$this->configurableChangeMethodNameRector = $configurableChangeMethodNameRector;
}
/**
* @return RectorInterface[]
*/
public function createRectors(): array
{
$rectors = [];
foreach ($this->deprecationCollector->getDeprecations() as $deprecation) {
$rectors[] = $this->createRectorFromDeprecation($deprecation);
}
return $rectors;
}
public function createRectorFromDeprecation(DeprecationInterface $deprecation): RectorInterface
{
if ($deprecation instanceof ClassMethodDeprecation) {
$configurableChangeMethodNameRector = clone $this->configurableChangeMethodNameRector;
$configurableChangeMethodNameRector->setPerClassOldToNewMethods([
$deprecation->getClass() => [
$deprecation->getOldMethod() => $deprecation->getNewMethod()
]
]);
return $configurableChangeMethodNameRector;
}
throw new NotImplementedException(sprintf(
'%s::%s() was unable to create a Rector based on "%s" Deprecation. Create a new method there.',
self::class,
__METHOD__,
get_class($deprecation)
));
}
}

View File

@ -5,3 +5,4 @@ services:
# PSR-4 autodiscovery
Rector\TriggerExtractor\:
resource: '../../src'
exclude: '../../src/{Deprecation/ClassMethodDeprecation.php}'

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace Rector\TriggerExtractor\Tests\Rector;
use Rector\Tests\AbstractContainerAwareTestCase;
use Rector\TriggerExtractor\Rector\RectorFactory;
use Rector\TriggerExtractor\TriggerExtractor;
final class RectorFactoryTest extends AbstractContainerAwareTestCase
{
/**
* @var RectorFactory
*/
private $rectorFactory;
protected function setUp(): void
{
$this->rectorFactory = $this->container->get(RectorFactory::class);
$triggerExtractor = $this->container->get(TriggerExtractor::class);
$triggerExtractor->scanDirectories([__DIR__ . '/../TriggerExtractorSource']);
}
public function test(): void
{
$rectors = $this->rectorFactory->createRectors();
$this->assertCount(1, $rectors);
}
}