Skip to content

Commit 48bf940

Browse files
committed
Updated Rector to commit ca9da16d1620daf8ca4a12912dee1aa8fbbb5b71
rectorphp/rector-src@ca9da16 Fix deep ArrayItem scope filling on Foreach_ value (#7873)
1 parent abf7991 commit 48bf940

File tree

10 files changed

+160
-71
lines changed

10 files changed

+160
-71
lines changed

vendor/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,7 @@
19361936
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\AssignedMocksCollector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/AssignedMocksCollector.php',
19371937
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\ClosureUsesResolver' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/ClosureUsesResolver.php',
19381938
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\DoctrineEntityDocumentAnalyser' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/DoctrineEntityDocumentAnalyser.php',
1939+
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\MockObjectExprDetector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php',
19391940
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\MockObjectPropertyDetector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php',
19401941
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\NullableObjectAssignCollector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/NullableObjectAssignCollector.php',
19411942
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\ParentCallDetector' => $vendorDir . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/ParentCallDetector.php',

vendor/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,6 +2196,7 @@ class ComposerStaticInit43920ef660f86182970f9c36338f7965
21962196
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\AssignedMocksCollector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/AssignedMocksCollector.php',
21972197
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\ClosureUsesResolver' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/ClosureUsesResolver.php',
21982198
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\DoctrineEntityDocumentAnalyser' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/DoctrineEntityDocumentAnalyser.php',
2199+
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\MockObjectExprDetector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php',
21992200
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\MockObjectPropertyDetector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php',
22002201
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\NullableObjectAssignCollector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/NullableObjectAssignCollector.php',
22012202
'Rector\\PHPUnit\\CodeQuality\\NodeAnalyser\\ParentCallDetector' => __DIR__ . '/..' . '/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/ParentCallDetector.php',

vendor/composer/installed.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,12 +1818,12 @@
18181818
"source": {
18191819
"type": "git",
18201820
"url": "https:\/\/github.com\/rectorphp\/rector-phpunit.git",
1821-
"reference": "4608de8da300aed18e761830469d0ee159a0c6c8"
1821+
"reference": "9f5f19d7af2a45fa701ff1f7b98765719c0629a2"
18221822
},
18231823
"dist": {
18241824
"type": "zip",
1825-
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-phpunit\/zipball\/4608de8da300aed18e761830469d0ee159a0c6c8",
1826-
"reference": "4608de8da300aed18e761830469d0ee159a0c6c8",
1825+
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-phpunit\/zipball\/9f5f19d7af2a45fa701ff1f7b98765719c0629a2",
1826+
"reference": "9f5f19d7af2a45fa701ff1f7b98765719c0629a2",
18271827
"shasum": ""
18281828
},
18291829
"require": {
@@ -1850,7 +1850,7 @@
18501850
"tomasvotruba\/unused-public": "^2.2",
18511851
"tracy\/tracy": "^2.11"
18521852
},
1853-
"time": "2026-02-02T18:58:21+00:00",
1853+
"time": "2026-02-03T10:58:22+00:00",
18541854
"default-branch": true,
18551855
"type": "rector-extension",
18561856
"extra": {

vendor/composer/installed.php

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

vendor/rector/extension-installer/src/GeneratedConfig.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
final class GeneratedConfig
1111
{
12-
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 46e4f77'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main a110e2f'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 4608de8'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 582fc7c'));
12+
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 46e4f77'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main a110e2f'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 9f5f19d'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 582fc7c'));
1313
private function __construct()
1414
{
1515
}

vendor/rector/rector-phpunit/config/sets/phpunit-code-quality.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\UseSpecificWithMethodRector;
5252
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector;
5353
use Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector;
54+
use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector;
55+
use Rector\PHPUnit\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector;
5456
use Rector\PHPUnit\PHPUnit60\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector;
5557
use Rector\PHPUnit\PHPUnit90\Rector\MethodCall\ReplaceAtMethodWithDesiredMatcherRector;
5658
use Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector;
@@ -113,6 +115,9 @@
113115
SingleMockPropertyTypeRector::class,
114116
SimplerWithIsInstanceOfRector::class,
115117
DirectInstanceOverMockArgRector::class,
118+
// stub over mock
119+
CreateStubOverCreateMockArgRector::class,
120+
ExpressionCreateMockToCreateStubRector::class,
116121
// @test first, enable later
117122
// \Rector\PHPUnit\CodeQuality\Rector\Expression\ConfiguredMockEntityToSetterObjectRector::class,
118123
FinalizeTestCaseClassRector::class,

vendor/rector/rector-phpunit/rules/CodeQuality/NodeAnalyser/AssertMethodAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function __construct(NodeNameResolver $nodeNameResolver, ReflectionResolv
3939
public function detectTestCaseCall($call): bool
4040
{
4141
$objectCaller = $call instanceof MethodCall ? $call->var : $call->class;
42-
if (!$this->nodeTypeResolver->isObjectType($objectCaller, new ObjectType('PHPUnit\Framework\TestCase'))) {
42+
if (!$this->nodeTypeResolver->isObjectType($objectCaller, new ObjectType(PHPUnitClassName::TEST_CASE))) {
4343
return \false;
4444
}
4545
$methodName = $this->nodeNameResolver->getName($call->name);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare (strict_types=1);
4+
namespace Rector\PHPUnit\CodeQuality\NodeAnalyser;
5+
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PhpParser\Node\Expr\Variable;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use Rector\NodeNameResolver\NodeNameResolver;
11+
use Rector\PhpParser\Node\BetterNodeFinder;
12+
use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder;
13+
final class MockObjectExprDetector
14+
{
15+
/**
16+
* @readonly
17+
*/
18+
private BetterNodeFinder $betterNodeFinder;
19+
/**
20+
* @readonly
21+
*/
22+
private NodeNameResolver $nodeNameResolver;
23+
/**
24+
* @readonly
25+
*/
26+
private VariableFinder $variableFinder;
27+
public function __construct(BetterNodeFinder $betterNodeFinder, NodeNameResolver $nodeNameResolver, VariableFinder $variableFinder)
28+
{
29+
$this->betterNodeFinder = $betterNodeFinder;
30+
$this->nodeNameResolver = $nodeNameResolver;
31+
$this->variableFinder = $variableFinder;
32+
}
33+
public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
34+
{
35+
if (!$expr instanceof Variable) {
36+
return \false;
37+
}
38+
$variableName = $this->nodeNameResolver->getName($expr);
39+
// to be safe
40+
if ($variableName === null) {
41+
return \true;
42+
}
43+
$relatedVariables = $this->variableFinder->find($classMethod, $variableName);
44+
// only self variable found, nothing to mock
45+
if (count($relatedVariables) === 1) {
46+
return \false;
47+
}
48+
// find out, how many are used in call likes as args
49+
/** @var array<Expr\MethodCall> $methodCalls */
50+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, [MethodCall::class]);
51+
foreach ($methodCalls as $methodCall) {
52+
if (!$methodCall->var instanceof Variable) {
53+
continue;
54+
}
55+
if ($this->nodeNameResolver->isName($methodCall->var, $variableName)) {
56+
// variable is being called on, most like mocking, lets skip
57+
return \true;
58+
}
59+
}
60+
return \false;
61+
}
62+
}

vendor/rector/rector-phpunit/rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55

66
use PhpParser\Node;
77
use PhpParser\Node\ArrayItem;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Assign;
810
use PhpParser\Node\Expr\MethodCall;
911
use PhpParser\Node\Expr\New_;
1012
use PhpParser\Node\Expr\StaticCall;
1113
use PhpParser\Node\Identifier;
14+
use PhpParser\Node\Stmt\ClassMethod;
15+
use PhpParser\Node\Stmt\Expression;
1216
use Rector\PHPStan\ScopeFetcher;
17+
use Rector\PHPUnit\CodeQuality\NodeAnalyser\MockObjectExprDetector;
1318
use Rector\PHPUnit\Enum\PHPUnitClassName;
19+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
1420
use Rector\Rector\AbstractRector;
1521
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1622
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -21,6 +27,19 @@
2127
*/
2228
final class CreateStubOverCreateMockArgRector extends AbstractRector
2329
{
30+
/**
31+
* @readonly
32+
*/
33+
private TestsNodeAnalyzer $testsNodeAnalyzer;
34+
/**
35+
* @readonly
36+
*/
37+
private MockObjectExprDetector $mockObjectExprDetector;
38+
public function __construct(TestsNodeAnalyzer $testsNodeAnalyzer, MockObjectExprDetector $mockObjectExprDetector)
39+
{
40+
$this->testsNodeAnalyzer = $testsNodeAnalyzer;
41+
$this->mockObjectExprDetector = $mockObjectExprDetector;
42+
}
2443
public function getRuleDefinition(): RuleDefinition
2544
{
2645
return new RuleDefinition('Use createStub() over createMock() when used as argument or array value and does not add any mock requirements', [new CodeSample(<<<'CODE_SAMPLE'
@@ -59,11 +78,11 @@ private function someMethod($someClass)
5978
*/
6079
public function getNodeTypes(): array
6180
{
62-
return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class];
81+
return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class, ClassMethod::class];
6382
}
6483
/**
65-
* @param MethodCall|StaticCall|New_|ArrayItem $node
66-
* @return \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\New_|\PhpParser\Node\ArrayItem|null
84+
* @param MethodCall|StaticCall|New_|ArrayItem|ClassMethod $node
85+
* @return \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\New_|\PhpParser\Node\ArrayItem|\PhpParser\Node\Stmt\ClassMethod|null
6786
*/
6887
public function refactor(Node $node)
6988
{
@@ -75,16 +94,11 @@ public function refactor(Node $node)
7594
if (!$classReflection->is(PHPUnitClassName::TEST_CASE)) {
7695
return null;
7796
}
97+
if ($node instanceof ClassMethod) {
98+
return $this->refactorClassMethod($node);
99+
}
78100
if ($node instanceof ArrayItem) {
79-
if (!$node->value instanceof MethodCall) {
80-
return null;
81-
}
82-
$methodCall = $node->value;
83-
if (!$this->isName($methodCall->name, 'createMock')) {
84-
return null;
85-
}
86-
$methodCall->name = new Identifier('createStub');
87-
return $node;
101+
return $this->refactorArrayItem($node);
88102
}
89103
$hasChanges = \false;
90104
if ($node->isFirstClassCallable()) {
@@ -106,4 +120,56 @@ public function refactor(Node $node)
106120
}
107121
return null;
108122
}
123+
private function matchCreateMockMethodCall(Expr $expr): ?\PhpParser\Node\Expr\MethodCall
124+
{
125+
if (!$expr instanceof MethodCall) {
126+
return null;
127+
}
128+
if (!$this->isName($expr->name, 'createMock')) {
129+
return null;
130+
}
131+
return $expr;
132+
}
133+
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
134+
{
135+
if (!$this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
136+
return null;
137+
}
138+
$hasChanged = \false;
139+
foreach ((array) $classMethod->stmts as $stmt) {
140+
if (!$stmt instanceof Expression) {
141+
continue;
142+
}
143+
if (!$stmt->expr instanceof Assign) {
144+
continue;
145+
}
146+
$assign = $stmt->expr;
147+
$createMockMethodCall = $this->matchCreateMockMethodCall($assign->expr);
148+
if (!$createMockMethodCall instanceof MethodCall) {
149+
continue;
150+
}
151+
// no change, as we use the variable for mocking later
152+
if ($this->mockObjectExprDetector->isUsedForMocking($assign->var, $classMethod)) {
153+
continue;
154+
}
155+
$createMockMethodCall->name = new Identifier('createStub');
156+
$hasChanged = \true;
157+
}
158+
if ($hasChanged) {
159+
return $classMethod;
160+
}
161+
return null;
162+
}
163+
private function refactorArrayItem(ArrayItem $arrayItem): ?ArrayItem
164+
{
165+
if (!$arrayItem->value instanceof MethodCall) {
166+
return null;
167+
}
168+
$methodCall = $arrayItem->value;
169+
if (!$this->isName($methodCall->name, 'createMock')) {
170+
return null;
171+
}
172+
$methodCall->name = new Identifier('createStub');
173+
return $arrayItem;
174+
}
109175
}

vendor/rector/rector-phpunit/rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@
66
use PhpParser\Node;
77
use PhpParser\Node\Arg;
88
use PhpParser\Node\Expr\Assign;
9-
use PhpParser\Node\Expr\CallLike;
109
use PhpParser\Node\Expr\MethodCall;
11-
use PhpParser\Node\Expr\New_;
12-
use PhpParser\Node\Expr\StaticCall;
1310
use PhpParser\Node\Expr\Variable;
1411
use PhpParser\Node\Identifier;
1512
use PhpParser\Node\Stmt\ClassMethod;
1613
use PhpParser\Node\Stmt\Expression;
17-
use Rector\PhpParser\Node\BetterNodeFinder;
1814
use Rector\PHPUnit\CodeQuality\NodeAnalyser\AssignedMocksCollector;
19-
use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder;
15+
use Rector\PHPUnit\CodeQuality\NodeAnalyser\MockObjectExprDetector;
2016
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
2117
use Rector\Rector\AbstractRector;
2218
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
@@ -37,17 +33,12 @@ final class ExpressionCreateMockToCreateStubRector extends AbstractRector
3733
/**
3834
* @readonly
3935
*/
40-
private VariableFinder $variableFinder;
41-
/**
42-
* @readonly
43-
*/
44-
private BetterNodeFinder $betterNodeFinder;
45-
public function __construct(AssignedMocksCollector $assignedMocksCollector, TestsNodeAnalyzer $testsNodeAnalyzer, VariableFinder $variableFinder, BetterNodeFinder $betterNodeFinder)
36+
private MockObjectExprDetector $mockObjectExprDetector;
37+
public function __construct(AssignedMocksCollector $assignedMocksCollector, TestsNodeAnalyzer $testsNodeAnalyzer, MockObjectExprDetector $mockObjectExprDetector)
4638
{
4739
$this->assignedMocksCollector = $assignedMocksCollector;
4840
$this->testsNodeAnalyzer = $testsNodeAnalyzer;
49-
$this->variableFinder = $variableFinder;
50-
$this->betterNodeFinder = $betterNodeFinder;
41+
$this->mockObjectExprDetector = $mockObjectExprDetector;
5142
}
5243
public function getRuleDefinition(): RuleDefinition
5344
{
@@ -113,24 +104,10 @@ public function refactor(Node $node): ?ClassMethod
113104
if (!$assign->var instanceof Variable) {
114105
continue;
115106
}
116-
$assignedVariable = $assign->var;
117-
$variableName = $this->getName($assignedVariable);
118-
if ($variableName === null) {
119-
continue;
120-
}
121-
// find variable usages outside call like and inside it
122-
$usedVariables = $this->variableFinder->find($node, $variableName);
123-
// used variable in calls
124-
/** @var array<StaticCall|MethodCall|New_> $callLikes */
125-
$callLikes = $this->betterNodeFinder->findInstancesOfScoped($node->stmts, [CallLike::class]);
126-
$callLikeUsedVariables = $this->collectVariableInCallLikeArg($callLikes, $variableName);
127-
if (count($usedVariables) - 1 !== count($callLikeUsedVariables)) {
128-
continue;
129-
}
130-
// here we can flip the createMock() to createStub()
131-
if (!$assign->expr instanceof MethodCall) {
107+
if ($this->mockObjectExprDetector->isUsedForMocking($assign->var, $node)) {
132108
continue;
133109
}
110+
/** @var MethodCall $methodCall */
134111
$methodCall = $assign->expr;
135112
$methodCall->name = new Identifier('createStub');
136113
$hasChanged = \true;
@@ -140,27 +117,4 @@ public function refactor(Node $node): ?ClassMethod
140117
}
141118
return null;
142119
}
143-
/**
144-
* @param CallLike[] $callLikes
145-
* @return Variable[]
146-
*/
147-
private function collectVariableInCallLikeArg(array $callLikes, string $variableName): array
148-
{
149-
$callLikeUsedVariables = [];
150-
foreach ($callLikes as $callLike) {
151-
if ($callLike->isFirstClassCallable()) {
152-
continue;
153-
}
154-
foreach ($callLike->getArgs() as $arg) {
155-
if (!$arg->value instanceof Variable) {
156-
continue;
157-
}
158-
if (!$this->isName($arg->value, $variableName)) {
159-
continue;
160-
}
161-
$callLikeUsedVariables[] = $arg->value;
162-
}
163-
}
164-
return $callLikeUsedVariables;
165-
}
166120
}

0 commit comments

Comments
 (0)