From 88f27689ebff670458c80b80aede12612818096d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 22 Mar 2020 15:38:35 +0100 Subject: [PATCH 1/4] WIP add createMock to createStub rector --- .../CreateMockToCreateStubRector.php | 102 ++++++++++++++++++ .../CreateMockToCreateStubRectorTest.php | 30 ++++++ .../Fixture/fixture.php.inc | 47 ++++++++ 3 files changed, 179 insertions(+) create mode 100644 rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php create mode 100644 rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php create mode 100644 rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc diff --git a/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php new file mode 100644 index 000000000000..0e7141653c42 --- /dev/null +++ b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php @@ -0,0 +1,102 @@ +methodCallManipulator = $methodCallManipulator; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Replaces createMock() with createStub() when relevant', [ + new CodeSample( + <<<'PHP' +use PHPUnit\Framework\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()); + } +} +PHP + , + <<<'PHP' +use PHPUnit\Framework\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()); + } +} +PHP + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isName($node->name, 'createMock')) { + return null; + } + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if (!$parentNode instanceof Assign) { + return null; + } + + dump($this->methodCallManipulator->findMethodCallNamesOnVariable($parentNode->var)); + + return $node; + } +} diff --git a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php new file mode 100644 index 000000000000..25c27b9ec00f --- /dev/null +++ b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return CreateMockToCreateStubRector::class; + } +} diff --git a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..0f37f693b361 --- /dev/null +++ b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc @@ -0,0 +1,47 @@ +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()); + } +} + +?> +----- +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()); + } +} + +?> From 44a1654b66e9d7a8dbef2374781e1c5c87f48d5f Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sun, 22 Mar 2020 15:55:44 +0100 Subject: [PATCH 2/4] split fixture --- .../Fixture/fixture.php.inc | 12 ------------ .../Fixture/skip_with_expects.php.inc | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/skip_with_expects.php.inc diff --git a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc index 0f37f693b361..c099e83409af 100644 --- a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc +++ b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/fixture.php.inc @@ -11,12 +11,6 @@ class MyTest extends TestCase $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()); } } @@ -35,12 +29,6 @@ class MyTest extends TestCase $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()); } } diff --git a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/skip_with_expects.php.inc b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/skip_with_expects.php.inc new file mode 100644 index 000000000000..fa7a5d95aa36 --- /dev/null +++ b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/Fixture/skip_with_expects.php.inc @@ -0,0 +1,16 @@ +createMock(\Exception::class); + $mock->expects($this->once()) + ->method('getMessage') + ->willReturn('a message'); + } +} From 0c3359e217b34bed1a64a68e1a0ecba589be52f7 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sun, 22 Mar 2020 16:30:36 +0100 Subject: [PATCH 3/4] add variable to method call traverser --- .../src/Node/AttributeKey.php | 5 ++ .../src/NodeScopeAndMetadataDecorator.php | 11 +++- .../src/NodeVisitor/MethodCallNodeVisitor.php | 43 +++++++++++++++ .../CreateMockToCreateStubRector.php | 10 +++- .../Manipulator/MethodCallManipulator.php | 55 ++++++++++++++++++- 5 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php diff --git a/packages/node-type-resolver/src/Node/AttributeKey.php b/packages/node-type-resolver/src/Node/AttributeKey.php index e78d8b222e49..f1ffc3079e76 100644 --- a/packages/node-type-resolver/src/Node/AttributeKey.php +++ b/packages/node-type-resolver/src/Node/AttributeKey.php @@ -136,4 +136,9 @@ final class AttributeKey * @var string */ public const PHP_DOC_INFO = PhpDocInfo::class; + + /** + * @var string + */ + public const METHOD_CALL_NODE_VARIABLE = 'method_call_variable'; } diff --git a/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php b/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php index 5fe6e3d30a82..0a35bb707cc1 100644 --- a/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php +++ b/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php @@ -12,6 +12,7 @@ use Rector\NodeCollector\NodeVisitor\NodeCollectorNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\FileInfoNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\FunctionMethodAndClassNodeVisitor; +use Rector\NodeTypeResolver\NodeVisitor\MethodCallNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\NamespaceNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\ParentAndNextNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\PhpDocInfoNodeVisitor; @@ -70,6 +71,11 @@ final class NodeScopeAndMetadataDecorator */ private $phpDocInfoNodeVisitor; + /** + * @var MethodCallNodeVisitor + */ + private $methodCallNodeVisitor; + public function __construct( NodeScopeResolver $nodeScopeResolver, ParentAndNextNodeVisitor $parentAndNextNodeVisitor, @@ -80,7 +86,8 @@ public function __construct( FileInfoNodeVisitor $fileInfoNodeVisitor, NodeCollectorNodeVisitor $nodeCollectorNodeVisitor, PhpDocInfoNodeVisitor $phpDocInfoNodeVisitor, - Configuration $configuration + Configuration $configuration, + MethodCallNodeVisitor $methodCallNodeVisitor ) { $this->nodeScopeResolver = $nodeScopeResolver; $this->parentAndNextNodeVisitor = $parentAndNextNodeVisitor; @@ -92,6 +99,7 @@ public function __construct( $this->nodeCollectorNodeVisitor = $nodeCollectorNodeVisitor; $this->configuration = $configuration; $this->phpDocInfoNodeVisitor = $phpDocInfoNodeVisitor; + $this->methodCallNodeVisitor = $methodCallNodeVisitor; } /** @@ -125,6 +133,7 @@ public function decorateNodesFromFile(array $nodes, string $filePath, bool $need $nodeTraverser->addVisitor($this->parentAndNextNodeVisitor); $nodeTraverser->addVisitor($this->functionMethodAndClassNodeVisitor); $nodeTraverser->addVisitor($this->namespaceNodeVisitor); + $nodeTraverser->addVisitor($this->methodCallNodeVisitor); $nodeTraverser->addVisitor($this->phpDocInfoNodeVisitor); $nodes = $nodeTraverser->traverse($nodes); diff --git a/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php b/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php new file mode 100644 index 000000000000..0b85ef349c8b --- /dev/null +++ b/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php @@ -0,0 +1,43 @@ +processMethodCall($node); + + return $node; + } + + private function processMethodCall(Node $node): void + { + if (! $node instanceof MethodCall) { + return; + } + + if (! $node->var instanceof MethodCall) { + $this->currentCaller = $node->var; + } + + $node->setAttribute(AttributeKey::METHOD_CALL_NODE_VARIABLE, $this->currentCaller); + + + } +} diff --git a/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php index 0e7141653c42..54c7f43f57da 100644 --- a/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php +++ b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php @@ -7,6 +7,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 Rector\Core\PhpParser\Node\Manipulator\MethodCallManipulator; use Rector\Core\Rector\AbstractRector; @@ -91,11 +92,16 @@ public function refactor(Node $node): ?Node return null; } $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if (!$parentNode instanceof Assign) { + if (! $parentNode instanceof Assign) { return null; } - dump($this->methodCallManipulator->findMethodCallNamesOnVariable($parentNode->var)); + $mockVariable = $parentNode->var; + if (! $mockVariable instanceof Variable) { + return null; + } + + dump($this->methodCallManipulator->findMethodCallNamesOnVariable($mockVariable)); return $node; } diff --git a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php index 24b9f09da6ef..188a612eb871 100644 --- a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php +++ b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php @@ -59,6 +59,9 @@ public function findMethodCallNamesOnVariable(Variable $variable): array $methodCallNamesOnVariable[] = $methodName; } + dump($methodCallNamesOnVariable); + die; + return array_unique($methodCallNamesOnVariable); } @@ -137,6 +140,10 @@ private function findMethodCallsOnVariable(Variable $variable): array $parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); } while ($parentNode instanceof Node && ! $parentNode instanceof FunctionLike); + dump(12345); + dump($previousMethodCalls); + die; + return $previousMethodCalls; } @@ -209,15 +216,36 @@ private function collectMethodCallsOnVariableName(Node $node, string $variableNa $variableName, &$methodCalls ) { + // @todo add variable + if (! $node instanceof MethodCall) { return null; } - if (! $node->var instanceof Variable) { + 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($node->var, $variableName)) { + if (! $this->nodeNameResolver->isName($onCalledVariable, $variableName)) { return null; } @@ -228,4 +256,27 @@ private function collectMethodCallsOnVariableName(Node $node, string $variableNa 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); + } } From eb88378488819db7168968728323e28cdf125500 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Mon, 23 Mar 2020 17:13:04 +0100 Subject: [PATCH 4/4] improve chain method call resolutuin --- docs/AllRectorsOverview.md | 34 ++++- .../src/Node/AttributeKey.php | 2 +- .../src/NodeVisitor/MethodCallNodeVisitor.php | 34 ++++- .../CreateMockToCreateStubRector.php | 22 +++- .../CreateMockToCreateStubRectorTest.php | 4 +- src/PhpParser/Node/BetterNodeFinder.php | 18 +++ .../Manipulator/MethodCallManipulator.php | 123 ++---------------- 7 files changed, 114 insertions(+), 123 deletions(-) diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index fe55f1bae18a..74ac88a4fc09 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -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
+### `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()); + } + } +``` + +
+ ### `DelegateExceptionArgumentsRector` - class: [`Rector\PHPUnit\Rector\DelegateExceptionArgumentsRector`](/../master/rules/phpunit/src/Rector/DelegateExceptionArgumentsRector.php) diff --git a/packages/node-type-resolver/src/Node/AttributeKey.php b/packages/node-type-resolver/src/Node/AttributeKey.php index f1ffc3079e76..8cef36b2e3e3 100644 --- a/packages/node-type-resolver/src/Node/AttributeKey.php +++ b/packages/node-type-resolver/src/Node/AttributeKey.php @@ -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'; } diff --git a/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php b/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php index 0b85ef349c8b..60a26bd66ffd 100644 --- a/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php +++ b/packages/node-type-resolver/src/NodeVisitor/MethodCallNodeVisitor.php @@ -5,16 +5,30 @@ 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 @@ private function processMethodCall(Node $node): void 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); } } diff --git a/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php index 54c7f43f57da..7529347e7aea 100644 --- a/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php +++ b/rules/phpunit/src/Rector/MethodCall/CreateMockToCreateStubRector.php @@ -8,7 +8,7 @@ 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 @@ public function getDefinition(): RectorDefinition 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 @@ public function testItBehavesAsExpected(): void , <<<'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 @@ public function refactor(Node $node): ?Node if (! $this->isName($node->name, 'createMock')) { return null; } + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); if (! $parentNode instanceof Assign) { return null; @@ -101,7 +110,12 @@ public function refactor(Node $node): ?Node 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; } diff --git a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php index 25c27b9ec00f..425eba70fae9 100644 --- a/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php +++ b/rules/phpunit/tests/Rector/MethodCall/CreateMockToCreateStubRector/CreateMockToCreateStubRectorTest.php @@ -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 { diff --git a/src/PhpParser/Node/BetterNodeFinder.php b/src/PhpParser/Node/BetterNodeFinder.php index d79256fa1cb4..67ae94a215bf 100644 --- a/src/PhpParser/Node/BetterNodeFinder.php +++ b/src/PhpParser/Node/BetterNodeFinder.php @@ -175,6 +175,24 @@ public function findFirstPrevious(Node $node, callable $filter): ?Node 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 */ diff --git a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php index 188a612eb871..9f214bf7dfd1 100644 --- a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php +++ b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php @@ -11,7 +11,6 @@ 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 @@ public function findMethodCallNamesOnVariable(Variable $variable): array $methodCallNamesOnVariable[] = $methodName; } - dump($methodCallNamesOnVariable); - die; - return array_unique($methodCallNamesOnVariable); } @@ -120,31 +107,22 @@ public function findAssignToVariable(Variable $variable): ?Assign */ 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 []; - } - - $previousMethodCalls = []; - - do { - $methodCalls = $this->collectMethodCallsOnVariableName($parentNode, $variableName); - $previousMethodCalls = array_merge($previousMethodCalls, $methodCalls); - - $parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); - } while ($parentNode instanceof Node && ! $parentNode instanceof FunctionLike); + return $this->betterNodeFinder->find($scopeNode, function (Node $node) use ($variable) { + if (! $node instanceof MethodCall) { + return false; + } - dump(12345); - dump($previousMethodCalls); - die; + /** @var string $methodCallVariableName */ + $methodCallVariableName = $node->getAttribute(AttributeKey::METHOD_CALL_NODE_CALLER_NAME); - return $previousMethodCalls; + return $this->nodeNameResolver->isName($variable, $methodCallVariableName); + }); } /** @@ -204,79 +182,4 @@ private function resolvePreviousNodeInSameScope(Node $parentNode): ?Node 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); - } }