Merge pull request #77 from RectorPHP/deprecation-extract

[DeprecationExtractor] improvements, guess only - more reliable
This commit is contained in:
Tomáš Votruba 2017-10-11 20:44:47 +02:00 committed by GitHub
commit 5916eb2a36
32 changed files with 450 additions and 816 deletions

View File

@ -14,7 +14,7 @@
"roave/better-reflection": "^2.0",
"symfony/console": "^3.3",
"symfony/dependency-injection": "^3.3",
"symplify/package-builder": "^2.4"
"symplify/package-builder": "^2.5"
},
"require-dev": {
"phpstan/phpstan": "^0.8",
@ -23,7 +23,7 @@
"symfony/expression-language": "^3.3",
"symfony/dependency-injection": "^3.3",
"symfony/form": "^3.3",
"symplify/easy-coding-standard": "^2.4",
"symplify/easy-coding-standard": "^2.5",
"tracy/tracy": "^2.4"
},
"autoload": {

View File

@ -54,11 +54,7 @@ parameters:
Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer:
# classes might not exist
- */src/Rector/Contrib/*/*Rector.php
- packages/DeprecationExtractor/tests/Rector/RectorFactoryTest.php
- packages/NodeTypeResolver/src/NodeVisitor/TypeResolver.php
Symplify\CodingStandard\Sniffs\Classes\EqualInterfaceImplementationSniff:
# empty parent interface, disable for now
- packages/DeprecationExtractor/src/Deprecation/ClassMethodDeprecation.php
Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff:
# false positive - @todo fix
- packages/NodeTypeResolver/src/NodeVisitor/TypeResolver.php

View File

@ -10,10 +10,16 @@ It helps to generated automate rectors and identify BC changes purely from the c
## How it works?
1. Just read the message
2. Detect what code should be change into what code
3. Create `Deprecation` class that holds information about the change - e.g. `ClassMethodRepcation`
4. Create dynamic rectors that will do the work
1. Just read the message or node
2. Guess what rector might change the code
3. Create `RectorGuess` class that holds rector suggetion
- suggested rector class
- certainity of the guess (from 0 to 100)
- related node
- related message (only for `@deprecate`)
4. Show it in console table
## How it helps you?
@ -25,5 +31,3 @@ vendor/bin/rector extract-deprecations vendor/nette/application/src
```
It will show you what changed and how.
Moreover, it will change the code for you instead of writing manuall rectors.

View File

@ -4,14 +4,20 @@ namespace Rector\DeprecationExtractor\Console\Command;
use Rector\DeprecationExtractor\Deprecation\DeprecationCollector;
use Rector\DeprecationExtractor\DeprecationExtractor;
use Rector\DeprecationExtractor\Rector\RectorGuesser;
use Rector\Naming\CommandNaming;
use Rector\Node\Attribute;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
// @todo match the name...
/**
* This commands shows suggested rector to use to resolve deprecation.
*
* We need this manually to analyze other versions and do not actually create any rector.
*/
final class ExtractDeprecationsCommand extends Command
{
/**
@ -34,13 +40,20 @@ final class ExtractDeprecationsCommand extends Command
*/
private $symfonyStyle;
/**
* @var RectorGuesser
*/
private $rectorGuesser;
public function __construct(
DeprecationExtractor $deprecationExtractor,
DeprecationCollector $deprecationCollector,
RectorGuesser $rectorGuesser,
SymfonyStyle $symfonyStyle
) {
$this->deprecationExtractor = $deprecationExtractor;
$this->deprecationCollector = $deprecationCollector;
$this->rectorGuesser = $rectorGuesser;
$this->symfonyStyle = $symfonyStyle;
parent::__construct();
@ -59,16 +72,35 @@ final class ExtractDeprecationsCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output): int
{
$source = $input->getArgument(self::ARGUMENT_SOURCE_NAME);
$this->deprecationExtractor->scanDirectories($source);
$this->deprecationExtractor->scanDirectories(
$input->getArgument(self::ARGUMENT_SOURCE_NAME)
);
$this->symfonyStyle->note(sprintf(
'Found %d deprecations.',
$this->symfonyStyle->title(sprintf(
'Found %d deprecations',
count($this->deprecationCollector->getDeprecations())
));
foreach ($this->deprecationCollector->getDeprecations() as $deprecation) {
$output->writeln($deprecation);
$guessedRectors = $this->rectorGuesser->guessForDeprecations($this->deprecationCollector->getDeprecations());
foreach ($guessedRectors as $guessedRector) {
if ($guessedRector->isUseful() === false) {
continue;
}
$this->symfonyStyle->success($guessedRector->getGuessedRectorClass());
$this->symfonyStyle->writeln(' ' . $guessedRector->getMessage());
$this->symfonyStyle->newLine();
$node = $guessedRector->getNode();
$this->symfonyStyle->writeln(' Namespace: ' . $node->getAttribute(Attribute::NAMESPACE));
$this->symfonyStyle->writeln(' Class: ' . $node->getAttribute(Attribute::CLASS_NAME));
$this->symfonyStyle->writeln(' Scope: ' . $node->getAttribute(Attribute::SCOPE));
$this->symfonyStyle->writeln(' Related node: ' . get_class($node));
$this->symfonyStyle->newLine(2);
}
return 0;

View File

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

View File

@ -1,34 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Deprecation;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
final class ClassDeprecation implements DeprecationInterface
{
/**
* @var string
*/
private $oldClass;
/**
* @var string
*/
private $newClass;
public function __construct(string $oldClass, string $newClass)
{
$this->oldClass = $oldClass;
$this->newClass = $newClass;
}
public function getOldClass(): string
{
return $this->oldClass;
}
public function getNewClass(): string
{
return $this->newClass;
}
}

View File

@ -1,105 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Deprecation;
use Nette\Utils\Strings;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
final class ClassMethodDeprecation implements DeprecationInterface
{
/**
* @var string
*/
private $oldMethod;
/**
* @var string
*/
private $newMethod;
/**
* @var string
*/
private $oldClass;
/**
* @var string
*/
private $newClass;
/**
* @var mixed[]
*/
private $oldArguments = [];
/**
* @var mixed[]
*/
private $newArguments = [];
/**
* @param mixed[] $oldArguments
* @param mixed[] $newArguments
*/
public function __construct(
string $oldMethod,
string $newMethod,
array $oldArguments = [],
array $newArguments = []
) {
if (Strings::contains($oldMethod, '::')) {
[$this->oldClass, $this->oldMethod] = explode('::', $oldMethod);
} else {
$this->oldMethod = $oldMethod;
}
$this->oldMethod = rtrim($this->oldMethod, '()');
if (Strings::contains($newMethod, '::')) {
[$this->newClass, $this->newMethod] = explode('::', $newMethod);
} else {
$this->newMethod = $newMethod;
}
$this->newMethod = rtrim($this->newMethod, '()');
$this->oldArguments = $oldArguments;
$this->newArguments = $newArguments;
}
public function getOldClass(): string
{
return $this->oldClass;
}
public function getNewClass(): string
{
return $this->newClass;
}
public function getOldMethod(): string
{
return $this->oldMethod;
}
public function getNewMethod(): string
{
return $this->newMethod;
}
/**
* @return mixed[]
*/
public function getOldArguments(): array
{
return $this->oldArguments;
}
/**
* @return mixed[]
*/
public function getNewArguments(): array
{
return $this->newArguments;
}
}

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Deprecation;
use PhpParser\Node;
final class Deprecation
{
/**
* @var string
*/
private $message;
/**
* @var Node
*/
private $node;
private function __construct(string $message, Node $node)
{
$this->message = $message;
$this->node = $node;
}
public static function createFromMessageAndNode(string $message, Node $node): self
{
return new self($message, $node);
}
public function getMessage(): string
{
return $this->message;
}
public function getNode(): Node
{
return $this->node;
}
}

View File

@ -3,125 +3,28 @@
namespace Rector\DeprecationExtractor\Deprecation;
use PhpParser\Node;
use PhpParser\Node\Arg;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
use Rector\DeprecationExtractor\Transformer\ArgumentToDeprecationTransformer;
use Rector\DeprecationExtractor\Transformer\MessageToDeprecationTransformer;
/**
* Collected from "deprecated" annotations and
* from trigger_error(*, E_USER_DEPRECATED) function calls
*/
final class DeprecationCollector
{
/**
* Collected from "deprecated" annotations
*
* @var string[]|Node[]
*/
private $deprecationMessages = [];
/**
* Collected from trigger_error(*, E_USER_DEPRECATED) function calls
*
* @var Arg[]
*/
private $deprecationArgNodes = [];
/**
* @var DeprecationInterface[]
* @var Deprecation[]
*/
private $deprecations = [];
/**
* @var bool
*/
private $areDeprecationsTransformed = false;
/**
* @var MessageToDeprecationTransformer
*/
private $messageToDeprecationTransformer;
/**
* @var ArgumentToDeprecationTransformer
*/
private $argumentToDeprecationTransformer;
public function __construct(
MessageToDeprecationTransformer $messageToDeprecationTransformer,
ArgumentToDeprecationTransformer $argumentToDeprecationTransformer
) {
$this->messageToDeprecationTransformer = $messageToDeprecationTransformer;
$this->argumentToDeprecationTransformer = $argumentToDeprecationTransformer;
}
public function addDeprecationMessage(string $deprecationMessage, Node $node): void
public function addDeprecation(string $message, Node $node): void
{
$this->deprecationMessages[] = [
'message' => $deprecationMessage,
'node' => $node,
];
}
public function addDeprecationArgNode(Arg $argNode): void
{
$this->deprecationArgNodes[] = $argNode;
$this->deprecations[] = Deprecation::createFromMessageAndNode($message, $node);
}
/**
* @return string[]|Node[]
*/
public function getDeprecationMessages(): array
{
return $this->deprecationMessages;
}
/**
* @return Arg[]
*/
public function getDeprecationArgNodes(): array
{
return $this->deprecationArgNodes;
}
public function addDeprecation(DeprecationInterface $deprecation): void
{
$this->deprecations[] = $deprecation;
}
/**
* @return DeprecationInterface[]
* @return Deprecation[]
*/
public function getDeprecations(): array
{
if (! $this->areDeprecationsTransformed) {
$this->transformDeprecations();
}
$this->areDeprecationsTransformed = true;
return $this->deprecations;
}
/**
* perform on getDeprecationArgNodes/addDeprecationMessage?
*/
private function transformDeprecations(): void
{
foreach ($this->deprecationMessages as $deprecationMessage) {
$deprecation = $this->messageToDeprecationTransformer->transform(
$deprecationMessage['message'],
$deprecationMessage['node']
);
if ($deprecation) {
$this->addDeprecation($deprecation);
}
}
foreach ($this->deprecationArgNodes as $deprecationArgNode) {
$deprecation = $this->argumentToDeprecationTransformer->transform($deprecationArgNode);
if ($deprecation) {
$this->addDeprecation($deprecation);
}
}
}
}

View File

@ -1,34 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Deprecation;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
final class RemovedClassMethodDeprecation implements DeprecationInterface
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $method;
public function __construct(string $class, string $method)
{
$this->class = $class;
$this->method = $method;
}
public function getClass(): string
{
return $this->class;
}
public function getMethod(): string
{
return $this->method;
}
}

View File

@ -1,23 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Deprecation;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
final class RemovedFunctionalityDeprecation implements DeprecationInterface
{
/**
* @var string
*/
private $message;
public function __construct(string $message)
{
$this->message = $message;
}
public function getMessage(): string
{
return $this->message;
}
}

View File

@ -5,9 +5,9 @@ namespace Rector\DeprecationExtractor;
use PhpParser\NodeTraverser;
use Rector\Contract\Parser\ParserInterface;
use Rector\DeprecationExtractor\NodeVisitor\DeprecationDetector;
use Rector\FileSystem\PhpFilesFinder;
use Rector\NodeTraverser\NodeTraverserFactory;
use Rector\NodeTraverser\StandaloneTraverseNodeTraverser;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
final class DeprecationExtractor
{
@ -21,21 +21,27 @@ final class DeprecationExtractor
*/
private $standaloneTraverseNodeTraverser;
/**
* @var PhpFilesFinder
*/
private $phpFilesFinder;
/**
* @var NodeTraverser
*/
private $mainNodeTraverser;
private $deprecationDetectorNodeTraverser;
public function __construct(
ParserInterface $parser,
DeprecationDetector $deprecationDetector,
StandaloneTraverseNodeTraverser $standaloneTraverseNodeTraverser
StandaloneTraverseNodeTraverser $standaloneTraverseNodeTraverser,
PhpFilesFinder $phpFilesFinder,
NodeTraverserFactory $nodeTraverserFactory
) {
$this->parser = $parser;
$this->standaloneTraverseNodeTraverser = $standaloneTraverseNodeTraverser;
$this->mainNodeTraverser = new NodeTraverser;
$this->mainNodeTraverser->addVisitor($deprecationDetector);
$this->deprecationDetectorNodeTraverser = $nodeTraverserFactory->createWithNodeVisitor($deprecationDetector);
$this->phpFilesFinder = $phpFilesFinder;
}
/**
@ -43,28 +49,14 @@ final class DeprecationExtractor
*/
public function scanDirectories(array $directories): void
{
$files = $this->findPhpFilesInDirectories($directories);
$files = $this->phpFilesFinder->findInDirectories($directories);
foreach ($files as $file) {
$nodes = $this->parser->parseFile($file->getRealPath());
// this completes parent & child nodes, types and classses
$this->standaloneTraverseNodeTraverser->traverse($nodes);
$this->mainNodeTraverser->traverse($nodes);
$this->deprecationDetectorNodeTraverser->traverse($nodes);
}
}
/**
* @param string[] $directories
* @return SplFileInfo[] array
*/
private function findPhpFilesInDirectories(array $directories): array
{
$finder = Finder::create()
->files()
->name('*.php')
->exclude(['tests', 'Tests']) // deprecations won't be in tests
->in($directories);
return iterator_to_array($finder->getIterator());
}
}

View File

@ -46,10 +46,10 @@ final class TriggerErrorAnalyzer
return $constFetchNode->name->toString() === 'E_USER_DEPRECATED';
}
public function messageNodeForNode(FuncCall $triggerErrorFuncCallNode): Arg
{
return $this->messageNodePerTriggerErrorNode[$triggerErrorFuncCallNode];
}
// public function messageNodeForNode(FuncCall $triggerErrorFuncCallNode): Arg
// {
// return $this->messageNodePerTriggerErrorNode[$triggerErrorFuncCallNode];
// }
private function isFunctionWithName(Node $node, string $name): bool
{

View File

@ -8,6 +8,7 @@ use PhpParser\NodeVisitorAbstract;
use Rector\DeprecationExtractor\Deprecation\DeprecationCollector;
use Rector\DeprecationExtractor\NodeAnalyzer\TriggerErrorAnalyzer;
use Rector\NodeAnalyzer\DocBlockAnalyzer;
use Rector\NodeValueResolver\NodeValueResolver;
/**
* Inspired by https://github.com/sensiolabs-de/deprecation-detector/blob/master/src/Visitor/Deprecation/FindDeprecatedTagsVisitor.php
@ -29,14 +30,21 @@ final class DeprecationDetector extends NodeVisitorAbstract
*/
private $triggerErrorAnalyzer;
/**
* @var NodeValueResolver
*/
private $nodeValueResolver;
public function __construct(
DeprecationCollector $deprecationCollector,
DocBlockAnalyzer $docBlockAnalyzer,
TriggerErrorAnalyzer $triggerErrorAnalyzer
TriggerErrorAnalyzer $triggerErrorAnalyzer,
NodeValueResolver $nodeValueResolver
) {
$this->deprecationCollector = $deprecationCollector;
$this->docBlockAnalyzer = $docBlockAnalyzer;
$this->triggerErrorAnalyzer = $triggerErrorAnalyzer;
$this->nodeValueResolver = $nodeValueResolver;
}
public function enterNode(Node $node): void
@ -49,8 +57,14 @@ final class DeprecationDetector extends NodeVisitorAbstract
if ($this->triggerErrorAnalyzer->isUserDeprecation($node)) {
/** @var FuncCall $node */
$argNode = $this->triggerErrorAnalyzer->messageNodeForNode($node);
$this->deprecationCollector->addDeprecationArgNode($argNode);
$argNode = $node->args[0];
$message = $this->nodeValueResolver->resolve($argNode);
if ($message === null) {
return;
}
$this->deprecationCollector->addDeprecation($message, $node);
return;
}
@ -63,6 +77,6 @@ final class DeprecationDetector extends NodeVisitorAbstract
return;
}
$this->deprecationCollector->addDeprecationMessage($deprecation, $node);
$this->deprecationCollector->addDeprecation($deprecation, $node);
}
}

View File

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

View File

@ -0,0 +1,81 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Rector;
use Nette\Utils\Strings;
use Rector\DeprecationExtractor\Deprecation\Deprecation;
use Rector\DeprecationExtractor\RectorGuess\RectorGuess;
use Rector\DeprecationExtractor\RectorGuess\RectorGuessFactory;
/**
* This class tries to guess, which Rector could be used to create refactoring
* based on deprecation message and related options.
*/
final class RectorGuesser
{
/**
* @var RectorGuessFactory
*/
private $rectorGuessFactory;
/**
* @var UnsupportedDeprecationFilter
*/
private $unsupportedDeprecationFilter;
public function __construct(
RectorGuessFactory $rectorGuessFactory,
UnsupportedDeprecationFilter $unsupportedDeprecationFilter
) {
$this->rectorGuessFactory = $rectorGuessFactory;
$this->unsupportedDeprecationFilter = $unsupportedDeprecationFilter;
}
/**
* @param Deprecation[] $deprecations
* @return RectorGuess[]
*/
public function guessForDeprecations(array $deprecations): array
{
$guessedRectors = [];
foreach ($deprecations as $deprecation) {
$guessedRectors[] = $this->guessForDeprecation($deprecation);
}
return $guessedRectors;
}
private function guessForDeprecation(Deprecation $deprecation): ?RectorGuess
{
$message = $deprecation->getMessage();
if ($this->unsupportedDeprecationFilter->matches($deprecation)) {
return $this->rectorGuessFactory->createUnsupported($message, $deprecation->getNode());
}
if (Strings::contains($message, 'It will be made mandatory in') ||
Strings::contains($message, 'Not defining it is deprecated since')
) {
return $this->rectorGuessFactory->createNewArgument($message, $deprecation->getNode());
}
$result = Strings::split($message, '#use |Use#');
if (count($result) === 2) {
if (Strings::contains($message, 'class is deprecated')) {
return $this->rectorGuessFactory->createClassReplacer(
$message,
$deprecation->getNode()
);
}
return $this->rectorGuessFactory->createMethodNameReplacerGuess(
$message,
$deprecation->getNode()
);
}
return $this->rectorGuessFactory->createRemoval($message, $deprecation->getNode());
}
}

View File

@ -0,0 +1,52 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Rector;
use Nette\Utils\Strings;
use Rector\DeprecationExtractor\Deprecation\Deprecation;
final class UnsupportedDeprecationFilter
{
/**
* @var string[]
*/
private $yamlDeprecationMessages = [
'Autowiring-types are deprecated',
'The "=" suffix that used to disable strict references',
'The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported',
'The "strict" attribute used when referencing the "" service is deprecated',
'Service names that start with an underscore are deprecated',
'configuration key',
];
/**
* @var string[]
*/
private $serviceDeprecationMessages = [
'It should either be deprecated or its implementation upgraded.',
'It should either be deprecated or its factory upgraded.',
'Service identifiers will be made case sensitive',
'Generating a dumped container without populating the method map is deprecated',
'Dumping an uncompiled ContainerBuilder is deprecated',
'service is private, ',
'service is already initialized, ',
'Relying on its factory\'s return-type to define the class of service',
];
public function matches(Deprecation $deprecation): bool
{
foreach ($this->yamlDeprecationMessages as $yamlDeprecationMessage) {
if (Strings::contains($deprecation->getMessage(), $yamlDeprecationMessage)) {
return true;
}
}
foreach ($this->serviceDeprecationMessages as $serviceDeprecationMessage) {
if (Strings::contains($deprecation->getMessage(), $serviceDeprecationMessage)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\RectorGuess;
use PhpParser\Node;
final class RectorGuess
{
/**
* @var string
*/
public const TYPE_REMOVAL = 'REMOVAL';
/**
* @var string
*/
public const TYPE_UNSUPPORTED = 'UNSUPPORTED';
/**
* @var string
*/
private $guessedRectorClass;
/**
* @var Node
*/
private $node;
/**
* @var string
*/
private $message;
public function __construct(
string $rectorClassOrType,
Node $node,
string $message
) {
$this->guessedRectorClass = $rectorClassOrType;
$this->node = $node;
$this->message = $message;
}
public function getGuessedRectorClass(): string
{
return $this->guessedRectorClass;
}
public function getNode(): Node
{
return $this->node;
}
public function getMessage(): string
{
return $this->message;
}
public function isUseful(): bool
{
return class_exists($this->guessedRectorClass);
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\RectorGuess;
use PhpParser\Node;
use Rector\Rector\Dynamic\ClassReplacerRector;
use Rector\Rector\Dynamic\MethodArgumentChangerRector;
use Rector\Rector\Dynamic\MethodNameReplacerRector;
final class RectorGuessFactory
{
public function createClassReplacer(string $message, Node $node): RectorGuess
{
return new RectorGuess(ClassReplacerRector::class, $node, $message);
}
public function createMethodNameReplacerGuess(string $message, Node $node): RectorGuess
{
return new RectorGuess(MethodNameReplacerRector::class, $node, $message);
}
public function createNewArgument(string $message, Node $node): RectorGuess
{
return new RectorGuess(MethodArgumentChangerRector::class, $node, $message);
}
public function createRemoval(string $message, Node $node): RectorGuess
{
return new RectorGuess(RectorGuess::TYPE_REMOVAL, $node, $message);
}
public function createUnsupported(string $message, Node $node): RectorGuess
{
return new RectorGuess(RectorGuess::TYPE_UNSUPPORTED, $node, $message);
}
}

View File

@ -1,143 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\RegExp;
use Nette\Utils\Strings;
final class ClassAndMethodMatcher
{
/**
* Matches:
* - SomeClass
* - SomeNamespace\AnotherClass
*
* @var string
*/
private const CLASS_PATTERN = '[A-Za-z\\\\]+';
/**
* Matches:
* - isMethod()
* - isMethod('arg')
*
* @var string
*/
private const METHOD_PATTERN = '[A-Za-z]+\([A-Za-z\']*\)';
/**
* Same as @see self::METHOD_PATTERN
*
* Just ignores (arguments), so:
* - isMethod('arg')
* will match and return:
* - isMethod()
*
* @var string
*/
private const METHOD_WITHOUT_ARGUMENTS_PATTERN = '[A-Za-z]+';
/**
* Matches:
* - Use <class> instead
* - Use the <class> instead
* - Use the <class> class instead
* - use the <class> class instead
*
* @var string
*/
private const CLASS_METHOD_INSTEAD_PATTERN = '#use( the)? (?<classMethod>' .
self::CLASS_PATTERN .
')( class)? instead#i';
/**
* @var string
*/
private const CLASS_METHOD_PATTERN = '#^(?<classMethod>' .
self::CLASS_PATTERN .
'::' .
self::METHOD_PATTERN .
')#s';
/**
* @var string
*/
private const CLASS_METHOD_WITHOUT_ARGUMENTS_PATTERN = '#^(?<classMethod>' .
self::CLASS_PATTERN .
'::' .
self::METHOD_WITHOUT_ARGUMENTS_PATTERN .
')#s';
public function matchClassWithMethod(string $content): string
{
$result = Strings::match($content, self::CLASS_METHOD_PATTERN);
return $result['classMethod'] ?? '';
}
public function matchClassWithMethodWithoutArguments(string $content): string
{
$result = Strings::match($content, self::CLASS_METHOD_WITHOUT_ARGUMENTS_PATTERN);
return $result['classMethod'] ?? '';
}
public function matchClassWithMethodInstead(string $content): string
{
$matches = Strings::match($content, self::CLASS_METHOD_INSTEAD_PATTERN);
return $matches['classMethod'] ?? '';
}
public function matchLocalMethod(string $content): string
{
$matches = Strings::match($content, '#(?<method>' . self::METHOD_PATTERN . ')#');
return $matches['classMethod'] ?? '';
}
/**
* Only local namespaced class like:
* - "use ContainerBuilder::getReflectionClass() instead"
*/
public function matchNamespacedClassWithMethod(string $content): string
{
$matches = Strings::match($content, '#(?<classMethod>[A-Za-z]+::' . self::METHOD_PATTERN . ')#');
return $matches['classMethod'] ?? '';
}
/**
* @return mixed[]
*/
public function matchMethodArguments(string $method): array
{
$matches = Strings::match($method, '#\((?<arguments>[^\)]*)\)#');
if (! isset($matches['arguments']) || empty($matches['arguments'])) {
return [];
}
$arguments = explode(', ', $matches['arguments']);
return $this->normalizeMethodArguments($arguments);
}
private function isStringSurroundedBy(string $content, string $needle): bool
{
return Strings::startsWith($content, $needle) && Strings::endsWith($content, $needle);
}
/**
* @param mixed[] $arguments
* @return mixed[]
*/
private function normalizeMethodArguments(array $arguments): array
{
foreach ($arguments as $key => $argument) {
if ($this->isStringSurroundedBy($argument, '\'')) {
$arguments[$key] = Strings::trim($argument, '\'');
}
}
return $arguments;
}
}

View File

@ -1,92 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Transformer;
use Nette\Utils\Strings;
use PhpParser\Node\Arg;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
use Rector\DeprecationExtractor\Deprecation\ClassMethodDeprecation;
use Rector\DeprecationExtractor\Deprecation\RemovedFunctionalityDeprecation;
use Rector\DeprecationExtractor\RegExp\ClassAndMethodMatcher;
use Rector\Exception\NotImplementedException;
use Rector\Node\Attribute;
use Rector\NodeValueResolver\Message\ClassPrepender;
use Rector\NodeValueResolver\NodeValueResolver;
final class ArgumentToDeprecationTransformer
{
/**
* @var ClassAndMethodMatcher
*/
private $classAndMethodMatcher;
/**
* @var NodeValueResolver
*/
private $nodeValueResolver;
public function __construct(
ClassAndMethodMatcher $classAndMethodMatcher,
NodeValueResolver $nodeValueResolver,
ClassPrepender $classPrepender
) {
$this->classAndMethodMatcher = $classAndMethodMatcher;
$this->nodeValueResolver = $nodeValueResolver;
$this->classPrepender = $classPrepender;
}
public function transform(Arg $argNode): ?DeprecationInterface
{
$message = $this->nodeValueResolver->resolve($argNode->value);
if ($message === null) {
return $message;
}
$message = $this->classPrepender->completeClassToLocalMethods(
$message,
(string) $argNode->getAttribute(Attribute::CLASS_NAME)
);
if ($message === '') {
throw new NotImplementedException(sprintf(
'Not implemented yet. Go to "%s()" and add check for "%s" node.',
__METHOD__,
get_class($argNode->value)
));
}
return $this->createFromMessage($message);
}
public function tryToCreateClassMethodDeprecation(string $oldMessage, string $newMessage): ?DeprecationInterface
{
$oldMethod = $this->classAndMethodMatcher->matchClassWithMethodWithoutArguments($oldMessage);
$newMethod = $this->classAndMethodMatcher->matchClassWithMethodWithoutArguments($newMessage);
$oldArguments = $this->classAndMethodMatcher->matchMethodArguments($oldMessage);
$newArguments = $this->classAndMethodMatcher->matchMethodArguments($newMessage);
return new ClassMethodDeprecation(
$oldMethod,
$newMethod,
$oldArguments,
$newArguments
);
}
private function createFromMessage(string $message): DeprecationInterface
{
$result = Strings::split($message, '#use |Use#');
if (count($result) === 2) {
[$oldMessage, $newMessage] = $result;
$deprecation = $this->tryToCreateClassMethodDeprecation($oldMessage, $newMessage);
if ($deprecation) {
return $deprecation;
}
}
return new RemovedFunctionalityDeprecation($message);
}
}

View File

@ -1,85 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Transformer;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\DeprecationExtractor\Contract\Deprecation\DeprecationInterface;
use Rector\DeprecationExtractor\Deprecation\ClassDeprecation;
use Rector\DeprecationExtractor\Deprecation\ClassMethodDeprecation;
use Rector\DeprecationExtractor\Deprecation\RemovedClassMethodDeprecation;
use Rector\DeprecationExtractor\RegExp\ClassAndMethodMatcher;
use Rector\Exception\NotImplementedException;
use Rector\Node\Attribute;
final class MessageToDeprecationTransformer
{
/**
* @var ClassAndMethodMatcher
*/
private $classAndMethodMatcher;
public function __construct(ClassAndMethodMatcher $classAndMethodMatcher)
{
$this->classAndMethodMatcher = $classAndMethodMatcher;
}
public function transform(string $message, Node $node): DeprecationInterface
{
if ($node instanceof Class_) {
return new ClassDeprecation(
$node->namespacedName->toString(),
$this->classAndMethodMatcher->matchClassWithMethodInstead($message)
);
}
if ($node instanceof ClassMethod) {
$classWithMethod = $this->classAndMethodMatcher->matchClassWithMethod($message);
$localMethod = $this->classAndMethodMatcher->matchLocalMethod($message);
$className = $node->getAttribute(Attribute::CLASS_NODE)->namespacedName->toString();
$methodName = (string) $node->name . '()';
$fqnMethodName = $className . '::' . $methodName;
if ($classWithMethod === '' && $localMethod === '') {
return new RemovedClassMethodDeprecation($className, $methodName);
}
if ($localMethod) {
return new ClassMethodDeprecation($fqnMethodName, $className . '::' . $localMethod . '()');
}
$namespacedClassWithMethod = $this->classAndMethodMatcher->matchNamespacedClassWithMethod($message);
/** @var string[] $useStatements */
$useStatements = $node->getAttribute(Attribute::USE_STATEMENTS);
$fqnClassWithMethod = $this->completeNamespace($useStatements, $namespacedClassWithMethod);
return new ClassMethodDeprecation($fqnMethodName, $fqnClassWithMethod);
}
throw new NotImplementedException(sprintf(
'%s() was unable to create a Deprecation based on "%s" string and "%s" Node. Create a new method there.',
__METHOD__,
$message,
get_class($node)
));
}
/**
* @param string[] $useStatements
*/
private function completeNamespace(array $useStatements, string $namespacedClassWithMethod): string
{
[$class, $method] = explode('::', $namespacedClassWithMethod);
foreach ($useStatements as $useStatement) {
if (Strings::endsWith($useStatement, $class)) {
return $useStatement . '::' . $method;
}
}
return $namespacedClassWithMethod;
}
}

View File

@ -4,4 +4,4 @@ services:
Rector\DeprecationExtractor\:
resource: '../../src'
exclude: '../../src/{Deprecation/*Deprecation.php,Rector/ConfigurableChangeMethodNameRector.php}'
exclude: '../../src/{Deprecation/*Deprecation.php,RectorGuess/RectorGuess.php}'

View File

@ -30,8 +30,6 @@ final class DeprecationCollectorTest extends AbstractContainerAwareTestCase
__DIR__ . '/../../../../vendor/symfony/dependency-injection',
]);
$deprecations = $this->deprecationCollector->getDeprecations();
$this->assertGreaterThanOrEqual(35, $deprecations);
$this->assertGreaterThanOrEqual(35, $this->deprecationCollector->getDeprecations());
}
}

View File

@ -2,7 +2,6 @@
namespace Rector\DeprecationExtractor\Tests;
use PhpParser\Node\Arg;
use Rector\DeprecationExtractor\Deprecation\DeprecationCollector;
use Rector\DeprecationExtractor\DeprecationExtractor;
use Rector\Tests\AbstractContainerAwareTestCase;
@ -25,16 +24,7 @@ final class DeprecationExtractorTest extends AbstractContainerAwareTestCase
public function testDeprectaionMessages(): void
{
$deprecationMessages = $this->deprecationCollector->getDeprecationMessages();
$this->assertCount(0, $deprecationMessages);
}
public function testDeprecationNodes(): void
{
$deprecationArgNodes = $this->deprecationCollector->getDeprecationArgNodes();
$this->assertCount(2, $deprecationArgNodes);
$deprecationArgNode = $deprecationArgNodes[0];
$this->assertInstanceOf(Arg::class, $deprecationArgNode);
$deprecationMessages = $this->deprecationCollector->getDeprecations();
$this->assertCount(2, $deprecationMessages);
}
}

View File

@ -1,52 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\DeprecationExtractor\Tests\Rector;
use PHPUnit\Framework\Assert;
use Rector\DeprecationExtractor\DeprecationExtractor;
use Rector\DeprecationExtractor\Rector\RectorFactory;
use Rector\Rector\Dynamic\MethodNameReplacerRector;
use Rector\Tests\AbstractContainerAwareTestCase;
final class RectorFactoryTest extends AbstractContainerAwareTestCase
{
/**
* @var RectorFactory
*/
private $rectorFactory;
protected function setUp(): void
{
$this->rectorFactory = $this->container->get(RectorFactory::class);
/** @var DeprecationExtractor $deprecationExtractor */
$deprecationExtractor = $this->container->get(DeprecationExtractor::class);
$deprecationExtractor->scanDirectories([__DIR__ . '/../DeprecationExtractorSource']);
}
public function test(): void
{
$rectors = $this->rectorFactory->createRectors();
$this->assertCount(2, $rectors);
/** @var MethodNameReplacerRector $secondRector */
$secondRector = $rectors[0];
$this->assertInstanceOf(MethodNameReplacerRector::class, $secondRector);
$this->assertSame([
'Nette\DI\ServiceDefinition' => [
'setInject' => 'addTag',
],
], Assert::getObjectAttribute($secondRector, 'perClassOldToNewMethods'));
/** @var MethodNameReplacerRector $firstRector */
$firstRector = $rectors[1];
$this->assertInstanceOf(MethodNameReplacerRector::class, $firstRector);
$this->assertSame([
'Nette\DI\ServiceDefinition' => [
'setClass' => 'setFactory',
],
], Assert::getObjectAttribute($firstRector, 'perClassOldToNewMethods'));
}
}

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace Rector\NodeValueResolver\PerNodeValueResolver;
use PhpParser\Node;
use PhpParser\Node\Arg;
use Rector\NodeValueResolver\Contract\NodeValueResolverAwareInterface;
use Rector\NodeValueResolver\Contract\PerNodeValueResolver\PerNodeValueResolverInterface;
use Rector\NodeValueResolver\NodeValueResolver;
final class ArgValueResolver implements PerNodeValueResolverInterface, NodeValueResolverAwareInterface
{
/**
* @var NodeValueResolver
*/
private $nodeValueResolver;
public function getNodeClass(): string
{
return Arg::class;
}
/**
* @param Arg $argNode
*/
public function resolve(Node $argNode): ?string
{
return $this->nodeValueResolver->resolve($argNode->value);
}
public function setNodeValueResolver(NodeValueResolver $nodeValueResolver): void
{
$this->nodeValueResolver = $nodeValueResolver;
}
}

View File

@ -4,6 +4,10 @@ namespace Rector\FileSystem;
use SplFileInfo;
/**
* Consider using NodeTraverser, same as for namespace to add to node.
* Would allow united API.
*/
final class CurrentFileProvider
{
/**

View File

@ -7,7 +7,7 @@ use PhpParser\NodeVisitor;
final class NodeTraverserFactory
{
public function createWithNoeVisitor(NodeVisitor $nodeVisitor): NodeTraverser
public function createWithNodeVisitor(NodeVisitor $nodeVisitor): NodeTraverser
{
$nodeTraverser = new NodeTraverser;
$nodeTraverser->addVisitor($nodeVisitor);

View File

@ -38,7 +38,7 @@ final class StandaloneTraverseNodeTraverser
public function traverse(array $nodes): array
{
foreach ($this->nodeVisitors as $nodeVisitor) {
$nodeTraverser = $this->nodeTraverserFactory->createWithNoeVisitor($nodeVisitor);
$nodeTraverser = $this->nodeTraverserFactory->createWithNodeVisitor($nodeVisitor);
$nodes = $nodeTraverser->traverse($nodes);
}

View File

@ -18,6 +18,11 @@ use Rector\Regex\MagicMethodMatcher;
*/
final class MagicMethodRector extends AbstractRector
{
/**
* @var string
*/
private const NETTE_OBJECT_CLASS = 'Nette\Object';
/**
* @var mixed[]
*/
@ -36,7 +41,7 @@ final class MagicMethodRector extends AbstractRector
/**
* @var ClassReflector
*/
private $currentFileAwareClassReflector;
private $classReflector;
/**
* @var MagicMethodMatcher
@ -46,12 +51,12 @@ final class MagicMethodRector extends AbstractRector
public function __construct(
MethodBuilder $methodBuilder,
DocBlockAnalyzer $docBlockAnalyzer,
ClassReflector $currentFileAwareClassReflector,
ClassReflector $classReflector,
MagicMethodMatcher $magicMethodMatcher
) {
$this->methodBuilder = $methodBuilder;
$this->docBlockAnalyzer = $docBlockAnalyzer;
$this->currentFileAwareClassReflector = $currentFileAwareClassReflector;
$this->classReflector = $classReflector;
$this->magicMethodMatcher = $magicMethodMatcher;
}
@ -77,7 +82,7 @@ final class MagicMethodRector extends AbstractRector
$className = $node->getAttribute(Attribute::CLASS_NAME);
$this->magicMethods = $this->magicMethodMatcher->matchInContent(
$this->currentFileAwareClassReflector->reflect($className),
$this->classReflector->reflect($className),
$docComments[0]->getText()
);
@ -116,6 +121,6 @@ final class MagicMethodRector extends AbstractRector
$parentClassName = (string) $classNode->extends->getAttribute(Attribute::RESOLVED_NAME);
return $parentClassName === 'Nette\Object';
return $parentClassName === self::NETTE_OBJECT_CLASS;
}
}

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace Rector\Rector\Dynamic;
use PhpParser\Node;
use Rector\Rector\AbstractRector;
/**
* @todo collect cases and prepare tests for them
*
* Possible options so far:
* - new argument
* - argument removed
* - new default value for argument
*/
final class MethodArgumentChangerRector extends AbstractRector
{
public function isCandidate(Node $node): bool
{
return true;
}
public function refactor(Node $node): ?Node
{
return null;
}
}