add SimplifyTestsRector

Signed-off-by: Tomas Votruba <tomas.vot@gmail.com>
This commit is contained in:
Tomas Votruba 2018-12-03 22:42:57 +01:00
parent 14580c07e7
commit 14c6178f57
5 changed files with 229 additions and 27 deletions

View File

@ -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",

View File

@ -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

View File

@ -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;
}

View 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;
}
}

View File

@ -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
{
/**