mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 13:28:18 +01:00
add SimplifyTestsRector
Signed-off-by: Tomas Votruba <tomas.vot@gmail.com>
This commit is contained in:
parent
14580c07e7
commit
14c6178f57
@ -84,6 +84,7 @@
|
||||
"Rector\\FileSystemRector\\Tests\\": "packages/FileSystemRector/tests"
|
||||
},
|
||||
"classmap": [
|
||||
"examples",
|
||||
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",
|
||||
"packages/Symfony/tests/Rector/FrameworkBundle/ContainerGetToConstructorInjectionRector/Source",
|
||||
"packages/NodeTypeResolver/tests/PerNodeTypeResolver/ParamTypeResolver/Source",
|
||||
|
1
ecs.yml
1
ecs.yml
@ -103,6 +103,7 @@ parameters:
|
||||
- 'packages/Php/src/EregToPcreTransformer.php'
|
||||
# dev
|
||||
- 'packages/Php/src/Rector/FunctionLike/*ScalarTypehintRector.php'
|
||||
- 'examples/*'
|
||||
|
||||
SlevomatCodingStandard\Sniffs\Functions\UnusedParameterSniff.UnusedParameter:
|
||||
# enforced by interface
|
||||
|
@ -29,18 +29,17 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\Nop;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeToStringResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
|
||||
final class MergeIsCandidateRector extends AbstractRector
|
||||
{
|
||||
@ -54,26 +53,19 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
*/
|
||||
private $docBlockAnalyzer;
|
||||
|
||||
/**
|
||||
* @var TypeToStringResolver
|
||||
*/
|
||||
private $typeToStringResolver;
|
||||
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callbackNodeTraverser;
|
||||
private $callableNodeTraverser;
|
||||
|
||||
public function __construct(
|
||||
BuilderFactory $builderFactory,
|
||||
DocBlockAnalyzer $docBlockAnalyzer,
|
||||
TypeToStringResolver $typeToStringResolver,
|
||||
CallableNodeTraverser $callbackNodeTraverser
|
||||
CallableNodeTraverser $callableNodeTraverser
|
||||
) {
|
||||
$this->builderFactory = $builderFactory;
|
||||
$this->docBlockAnalyzer = $docBlockAnalyzer;
|
||||
$this->typeToStringResolver = $typeToStringResolver;
|
||||
$this->callbackNodeTraverser = $callbackNodeTraverser;
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -97,7 +89,7 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (! $this->isType($node, 'Rector\Rector\AbstractRector')) {
|
||||
if (! $this->isType($node, AbstractRector::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -179,10 +171,8 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
}
|
||||
} elseif ($paramTagValueNode->type instanceof IdentifierTypeNode) {
|
||||
$types[] = $paramTagValueNode->type->name;
|
||||
} else {
|
||||
dump($paramTagValueNode->type);
|
||||
// todo: resolve
|
||||
}
|
||||
// todo: resolve
|
||||
|
||||
return $types;
|
||||
}
|
||||
@ -198,11 +188,10 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
$classConstFetchNode = $this->createClassConstFetchFromClassName($paramType);
|
||||
$nodeToBeReturned->items[] = new ArrayItem($classConstFetchNode);
|
||||
}
|
||||
|
||||
} elseif (count($paramTypes) === 1) {
|
||||
$nodeToBeReturned->items[] = $this->createClassConstFetchFromClassName($paramTypes[0]);
|
||||
} else { // fallback to basic node
|
||||
$nodeToBeReturned->items[] = $this->createClassConstFetchFromClassName('PhpParser\\Node');
|
||||
$nodeToBeReturned->items[] = $this->createClassConstFetchFromClassName(Node::class);
|
||||
}
|
||||
|
||||
return $this->builderFactory->method('getNodeTypes')
|
||||
@ -237,7 +226,7 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
|
||||
private function replaceReturnFalseWithReturnNull(ClassMethod $classMethod): void
|
||||
{
|
||||
$this->callbackNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
if (! $node instanceof Return_ || ! $node->expr instanceof ConstFetch) {
|
||||
return null;
|
||||
}
|
||||
@ -252,7 +241,9 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
|
||||
private function renameNodeToParamNode(ClassMethod $classMethod, string $nodeName): void
|
||||
{
|
||||
$this->callbackNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node) use ($nodeName): ?Node {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node) use (
|
||||
$nodeName
|
||||
): ?Node {
|
||||
if (! $node instanceof Variable || ! $this->isName($node, 'node')) {
|
||||
return null;
|
||||
}
|
||||
@ -265,7 +256,7 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
|
||||
private function replaceLastReturnWithIf(ClassMethod $classMethod): void
|
||||
{
|
||||
$this->callbackNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
if (! $node instanceof Return_) {
|
||||
return null;
|
||||
}
|
||||
@ -274,18 +265,16 @@ final class MergeIsCandidateRector extends AbstractRector
|
||||
return null;
|
||||
}
|
||||
|
||||
$identicalCondition = new Identical($node->expr, new ConstFetch(new Name('false')));
|
||||
$identicalCondition = new Identical($node->expr, new ConstFetch(new Name('false')));
|
||||
return new If_($identicalCondition, [
|
||||
'stmts' => [
|
||||
new Return_(new ConstFetch(new Name('null')))
|
||||
]
|
||||
'stmts' => [new Return_(new ConstFetch(new Name('null')))],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
private function removeReturnTrue(ClassMethod $classMethod): void
|
||||
{
|
||||
$this->callbackNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$classMethod], function (Node $node): ?Node {
|
||||
if (! $node instanceof Return_ || ! $node->expr instanceof ConstFetch || ! $this->isTrue($node->expr)) {
|
||||
return null;
|
||||
}
|
||||
|
208
examples/SimplifyTestsRector.php
Normal file
208
examples/SimplifyTestsRector.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Examples;
|
||||
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\ConstExprEvaluator;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Expr\Yield_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\NodeTypeResolver\Node\Attribute;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
final class SimplifyTestsRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var ConstExprEvaluator
|
||||
*/
|
||||
private $constExprEvaluator;
|
||||
|
||||
/**
|
||||
* @var DocBlockAnalyzer
|
||||
*/
|
||||
private $docBlockAnalyzer;
|
||||
|
||||
/**
|
||||
* @var BuilderFactory
|
||||
*/
|
||||
private $builderFactory;
|
||||
|
||||
public function __construct(
|
||||
ConstExprEvaluator $constExprEvaluator,
|
||||
DocBlockAnalyzer $docBlockAnalyzer,
|
||||
BuilderFactory $builderFactory
|
||||
) {
|
||||
$this->constExprEvaluator = $constExprEvaluator;
|
||||
$this->docBlockAnalyzer = $docBlockAnalyzer;
|
||||
$this->builderFactory = $builderFactory;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Simplify tests - see PR #@todo', [new CodeSample('', '')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (! $this->isType($node, AbstractRectorTestCase::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($node->isAbstract()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classMethodsByName = $this->getClassMethodByName($node);
|
||||
if (isset($classMethodsByName['test']) && empty($classMethodsByName['test']->params)) {
|
||||
// test()... method without any params, no provider → skip
|
||||
return null;
|
||||
}
|
||||
|
||||
$rectorClass = null;
|
||||
|
||||
if (! isset($classMethodsByName['provideConfig'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$stmts = $classMethodsByName['provideConfig']->stmts;
|
||||
$configPath = null;
|
||||
if ($stmts[0] instanceof Return_) {
|
||||
/** @var SplFileInfo $fileInfo */
|
||||
$fileInfo = $node->getAttribute(Attribute::FILE_INFO);
|
||||
$configPath = $fileInfo->getPath() . $this->constExprEvaluator->evaluateDirectly($stmts[0]->expr);
|
||||
|
||||
$rectorClass = $this->matchSingleServiceWithoutConfigInFile($configPath);
|
||||
}
|
||||
|
||||
// not just single rector config
|
||||
if ($configPath === null || ! is_string($rectorClass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add "getRectorClass" method
|
||||
|
||||
$returnNode = new Return_(new ClassConstFetch(new FullyQualified($rectorClass), 'class'));
|
||||
|
||||
$classMethod = $this->builderFactory->method('getRectorClass')
|
||||
->makePublic()
|
||||
->setReturnType('string')
|
||||
->addStmt($returnNode)
|
||||
->getNode();
|
||||
|
||||
$node->stmts[] = $classMethod;
|
||||
|
||||
// remove "provideConfig" method
|
||||
$this->removeNode($classMethodsByName['provideConfig']);
|
||||
|
||||
// remove @covers annotation
|
||||
$this->docBlockAnalyzer->removeTagFromNode($node, 'covers');
|
||||
|
||||
// merge "test" + "provideFiles"
|
||||
if (isset($classMethodsByName['test']) && isset($classMethodsByName['provideFiles'])) {
|
||||
$this->removeNode($classMethodsByName['test']);
|
||||
|
||||
$provideFilesClassMethod = $classMethodsByName['provideFiles'];
|
||||
$provideFilesClassMethod->returnType = new Identifier('void');
|
||||
$provideFilesClassMethod->name = new Name('test');
|
||||
|
||||
// collect yields to array + wrap with method call
|
||||
|
||||
$arrayItems = [];
|
||||
|
||||
foreach ($provideFilesClassMethod->stmts as $stmt) {
|
||||
if ($stmt instanceof Expression) {
|
||||
if ($stmt->expr instanceof Yield_) {
|
||||
$arrayItems[] = $stmt->expr->value;
|
||||
$this->removeNode($stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$doTestFilesMethodCall = new MethodCall(new Variable('this'), 'doTestFiles');
|
||||
$doTestFilesMethodCall->args[] = new Arg(new Array_($arrayItems));
|
||||
|
||||
$provideFilesClassMethod->stmts = [new Expression($doTestFilesMethodCall)];
|
||||
}
|
||||
|
||||
if ($configPath) {
|
||||
// remove config file, not needed anymore
|
||||
unlink($configPath);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
private function getClassMethodByName(Class_ $classNode): array
|
||||
{
|
||||
$classMethodsByName = [];
|
||||
foreach ($classNode->stmts as $stmt) {
|
||||
if (! $stmt instanceof ClassMethod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classMethodsByName[$this->getName($stmt)] = $stmt;
|
||||
}
|
||||
|
||||
return $classMethodsByName;
|
||||
}
|
||||
|
||||
private function matchSingleServiceWithoutConfigInFile(string $configPath): ?string
|
||||
{
|
||||
if (! file_exists($configPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$yaml = Yaml::parseFile($configPath);
|
||||
|
||||
if (count($yaml) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! isset($yaml['services'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count($yaml['services']) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$serviceName = key($yaml['services']);
|
||||
if ($yaml['services'][$serviceName] === null) {
|
||||
return $serviceName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ namespace Rector\Tests\Issues\Issue594;
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Rector\Symfony\Rector\HttpKernel\GetRequestRector
|
||||
*/
|
||||
final class Issue594Test extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user