improve chain method call resolutuin

This commit is contained in:
TomasVotruba 2020-03-23 17:13:04 +01:00
parent 0c3359e217
commit eb88378488
7 changed files with 114 additions and 123 deletions

View File

@ -1,4 +1,4 @@
# All 465 Rectors Overview
# All 466 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -5333,6 +5333,38 @@ Turns true/false comparisons to their method name alternatives in PHPUnit TestCa
<br>
### `CreateMockToCreateStubRector`
- class: [`Rector\PHPUnit\Rector\MethodCall\CreateMockToCreateStubRector`](/../master/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php)
- [test fixtures](/../master/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture)
Replaces createMock() with createStub() when relevant
```diff
use PHPUnit\Framework\TestCase
class MyTest extends TestCase
{
public function testItBehavesAsExpected(): void
{
- $stub = $this->createMock(\Exception::class);
+ $stub = $this->createStub(\Exception::class);
$stub->method('getMessage')
->willReturn('a message');
$mock = $this->createMock(\Exception::class);
$mock->expects($this->once())
->method('getMessage')
->willReturn('a message');
self::assertSame('a message', $stub->getMessage());
self::assertSame('a message', $mock->getMessage());
}
}
```
<br>
### `DelegateExceptionArgumentsRector`
- class: [`Rector\PHPUnit\Rector\DelegateExceptionArgumentsRector`](/../master/rules/phpunit/src/Rector/DelegateExceptionArgumentsRector.php)

View File

@ -140,5 +140,5 @@ final class AttributeKey
/**
* @var string
*/
public const METHOD_CALL_NODE_VARIABLE = 'method_call_variable';
public const METHOD_CALL_NODE_CALLER_NAME = 'method_call_variable_name';
}

View File

@ -5,16 +5,30 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PhpParser\NodeVisitorAbstract;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class MethodCallNodeVisitor extends NodeVisitorAbstract
{
/**
* @var Node\Expr
* @var Expr|Name
*/
private $currentCaller;
private $callerNode;
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}
/**
* @return int|Node|void|null
@ -32,12 +46,22 @@ final class MethodCallNodeVisitor extends NodeVisitorAbstract
return;
}
if (! $node->var instanceof MethodCall) {
$this->currentCaller = $node->var;
$callerNode = $node->var;
if ($callerNode instanceof MethodCall) {
while ($callerNode instanceof MethodCall) {
$callerNode = $callerNode->var;
}
}
$node->setAttribute(AttributeKey::METHOD_CALL_NODE_VARIABLE, $this->currentCaller);
if ($callerNode instanceof StaticCall) {
while ($callerNode instanceof StaticCall) {
$callerNode = $callerNode->class;
}
}
$this->callerNode = $callerNode;
$currentCallerName = $this->nodeNameResolver->getName($this->callerNode);
$node->setAttribute(AttributeKey::METHOD_CALL_NODE_CALLER_NAME, $currentCallerName);
}
}

View File

@ -8,7 +8,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use Rector\Core\PhpParser\Node\Manipulator\AssignManipulator;
use PhpParser\Node\Identifier;
use Rector\Core\PhpParser\Node\Manipulator\MethodCallManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
@ -38,16 +38,20 @@ final class CreateMockToCreateStubRector extends AbstractRector
new CodeSample(
<<<'PHP'
use PHPUnit\Framework\TestCase
class MyTest extends TestCase {
class MyTest extends TestCase
{
public function testItBehavesAsExpected(): void
{
$stub = $this->createMock(\Exception::class);
$stub->method('getMessage')
->willReturn('a message');
$mock = $this->createMock(\Exception::class);
$mock->expects($this->once())
->method('getMessage')
->willReturn('a message');
self::assertSame('a message', $stub->getMessage());
self::assertSame('a message', $mock->getMessage());
}
@ -56,16 +60,20 @@ PHP
,
<<<'PHP'
use PHPUnit\Framework\TestCase
class MyTest extends TestCase {
class MyTest extends TestCase
{
public function testItBehavesAsExpected(): void
{
$stub = $this->createStub(\Exception::class);
$stub->method('getMessage')
->willReturn('a message');
$mock = $this->createMock(\Exception::class);
$mock->expects($this->once())
->method('getMessage')
->willReturn('a message');
self::assertSame('a message', $stub->getMessage());
self::assertSame('a message', $mock->getMessage());
}
@ -91,6 +99,7 @@ PHP
if (! $this->isName($node->name, 'createMock')) {
return null;
}
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! $parentNode instanceof Assign) {
return null;
@ -101,7 +110,12 @@ PHP
return null;
}
dump($this->methodCallManipulator->findMethodCallNamesOnVariable($mockVariable));
$methodCallNamesOnVariable = $this->methodCallManipulator->findMethodCallNamesOnVariable($mockVariable);
if (in_array('expects', $methodCallNamesOnVariable, true)) {
return null;
}
$node->name = new Identifier('createStub');
return $node;
}

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Rector\phpunit\Tests\Rector\MethodCall\CreateMockToCreateStubRector;
namespace Rector\PHPUnit\Tests\Rector\MethodCall\CreateMockToCreateStubRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\phpunit\Rector\MethodCall\CreateMockToCreateStubRector;
use Rector\PHPUnit\Rector\MethodCall\CreateMockToCreateStubRector;
final class CreateMockToCreateStubRectorTest extends AbstractRectorTestCase
{

View File

@ -175,6 +175,24 @@ final class BetterNodeFinder
return $this->findFirstPrevious($previousStatement, $filter);
}
/**
* @param class-string[] $types
*/
public function findFirstPreviousOfTypes(Node $mainNode, array $types): ?Node
{
return $this->findFirstPrevious($mainNode, function (Node $node) use ($types) {
foreach ($types as $type) {
if (! is_a($node, $type, true)) {
continue;
}
return true;
}
return false;
});
}
/**
* @param Node|Node[] $nodes
*/

View File

@ -11,7 +11,6 @@ use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Expression;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -22,23 +21,14 @@ final class MethodCallManipulator
*/
private $nodeNameResolver;
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(
NodeNameResolver $nodeNameResolver,
CallableNodeTraverser $callableNodeTraverser,
BetterNodeFinder $betterNodeFinder
) {
public function __construct(NodeNameResolver $nodeNameResolver, BetterNodeFinder $betterNodeFinder)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->callableNodeTraverser = $callableNodeTraverser;
$this->betterNodeFinder = $betterNodeFinder;
}
@ -59,9 +49,6 @@ final class MethodCallManipulator
$methodCallNamesOnVariable[] = $methodName;
}
dump($methodCallNamesOnVariable);
die;
return array_unique($methodCallNamesOnVariable);
}
@ -120,31 +107,22 @@ final class MethodCallManipulator
*/
private function findMethodCallsOnVariable(Variable $variable): array
{
/** @var Node|null $parentNode */
$parentNode = $variable->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode === null) {
// get scope node, e.g. parent function call, method call or anonymous function
$scopeNode = $this->betterNodeFinder->findFirstPreviousOfTypes($variable, [FunctionLike::class]);
if ($scopeNode === null) {
return [];
}
$variableName = $this->nodeNameResolver->getName($variable);
if ($variableName === null) {
return [];
}
return $this->betterNodeFinder->find($scopeNode, function (Node $node) use ($variable) {
if (! $node instanceof MethodCall) {
return false;
}
$previousMethodCalls = [];
/** @var string $methodCallVariableName */
$methodCallVariableName = $node->getAttribute(AttributeKey::METHOD_CALL_NODE_CALLER_NAME);
do {
$methodCalls = $this->collectMethodCallsOnVariableName($parentNode, $variableName);
$previousMethodCalls = array_merge($previousMethodCalls, $methodCalls);
$parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
} while ($parentNode instanceof Node && ! $parentNode instanceof FunctionLike);
dump(12345);
dump($previousMethodCalls);
die;
return $previousMethodCalls;
return $this->nodeNameResolver->isName($variable, $methodCallVariableName);
});
}
/**
@ -204,79 +182,4 @@ final class MethodCallManipulator
return $parentNode;
}
/**
* @return MethodCall[]
*/
private function collectMethodCallsOnVariableName(Node $node, string $variableName): array
{
$methodCalls = [];
$this->callableNodeTraverser->traverseNodesWithCallable($node, function (Node $node) use (
$variableName,
&$methodCalls
) {
// @todo add variable
if (! $node instanceof MethodCall) {
return null;
}
dump($node->getAttribute(AttributeKey::METHOD_CALL_NODE_VARIABLE));
die;
// include chain method calls
$nestedMethodCall = $node->var;
while ($nestedMethodCall instanceof MethodCall) {
$onCalledVariable = $this->resolveMethodCallAndChainMethodCallVariable($nestedMethodCall);
if (! $this->isVariableOfName($onCalledVariable, $variableName)) {
continue;
}
$methodCalls[] = $nestedMethodCall;
}
$onCalledVariable = $this->resolveMethodCallAndChainMethodCallVariable($node);
dump($onCalledVariable);
die;
if (! $onCalledVariable instanceof Variable) {
return null;
}
if (! $this->nodeNameResolver->isName($onCalledVariable, $variableName)) {
return null;
}
$methodCalls[] = $node;
return null;
});
return $methodCalls;
}
private function resolveMethodCallAndChainMethodCallVariable(MethodCall $methodCall): ?Variable
{
$possibleVariable = $methodCall->var;
while ($possibleVariable instanceof MethodCall) {
$possibleVariable = $possibleVariable->var;
}
if (! $possibleVariable instanceof Variable) {
return null;
}
return $possibleVariable;
}
private function isVariableOfName(Node\Expr $node, string $variableName): bool
{
if (! $node instanceof Variable) {
return false;
}
return $this->nodeNameResolver->isName($node, $variableName);
}
}