44
55namespace Rector \PHPUnit \CodeQuality \NodeAnalyser ;
66
7+ use PhpParser \Node \Arg ;
78use PhpParser \Node \Expr ;
89use PhpParser \Node \Expr \MethodCall ;
910use PhpParser \Node \Expr \PropertyFetch ;
1011use PhpParser \Node \Expr \Variable ;
1112use PhpParser \Node \Stmt \Class_ ;
1213use PhpParser \Node \Stmt \ClassMethod ;
14+ use PHPStan \Reflection \MethodReflection ;
15+ use PHPStan \Type \ObjectType ;
1316use Rector \NodeNameResolver \NodeNameResolver ;
1417use Rector \PhpParser \Node \BetterNodeFinder ;
1518use Rector \PHPUnit \CodeQuality \NodeFinder \VariableFinder ;
19+ use Rector \PHPUnit \Enum \PHPUnitClassName ;
20+ use Rector \Reflection \ReflectionResolver ;
1621
1722final readonly class MockObjectExprDetector
1823{
1924 public function __construct (
2025 private BetterNodeFinder $ betterNodeFinder ,
2126 private NodeNameResolver $ nodeNameResolver ,
2227 private VariableFinder $ variableFinder ,
28+ private ReflectionResolver $ reflectionResolver ,
2329 ) {
2430 }
2531
@@ -67,6 +73,8 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
6773 /** @var array<Expr\MethodCall> $methodCalls */
6874 $ methodCalls = $ this ->betterNodeFinder ->findInstancesOfScoped ((array ) $ classMethod ->stmts , [MethodCall::class]);
6975
76+ $ mockObjectType = new ObjectType (PHPUnitClassName::MOCK_OBJECT );
77+
7078 foreach ($ methodCalls as $ methodCall ) {
7179 if (! $ methodCall ->var instanceof Variable) {
7280 continue ;
@@ -76,6 +84,41 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
7684 // variable is being called on, most like mocking, lets skip
7785 return true ;
7886 }
87+
88+ if ($ methodCall ->isFirstClassCallable ()) {
89+ continue ;
90+ }
91+
92+ // check if variable is passed as arg to a method that declares MockObject type parameter
93+ foreach ($ methodCall ->getArgs () as $ position => $ arg ) {
94+ if (! $ arg instanceof Arg) {
95+ continue ;
96+ }
97+
98+ if (! $ arg ->value instanceof Variable) {
99+ continue ;
100+ }
101+
102+ if (! $ this ->nodeNameResolver ->isName ($ arg ->value , $ variableName )) {
103+ continue ;
104+ }
105+
106+ $ methodReflection = $ this ->reflectionResolver ->resolveMethodReflectionFromMethodCall ($ methodCall );
107+ if (! $ methodReflection instanceof MethodReflection) {
108+ continue ;
109+ }
110+
111+ $ parameters = $ methodReflection ->getVariants ()[0 ]
112+ ->getParameters ();
113+ if (! isset ($ parameters [$ position ])) {
114+ continue ;
115+ }
116+
117+ $ paramType = $ parameters [$ position ]->getType ();
118+ if ($ mockObjectType ->isSuperTypeOf ($ paramType )->yes ()) {
119+ return true ;
120+ }
121+ }
79122 }
80123
81124 return false ;
0 commit comments