diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/skip_fluent_no_return_type.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/skip_fluent_no_return_type.php.inc new file mode 100644 index 00000000000..d8e63e39a2e --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/skip_fluent_no_return_type.php.inc @@ -0,0 +1,31 @@ +bar() + ->baz() + ->qux(); + } + + private function bar() + { + return $this; + } + + private function baz() + { + return $this; + } + + public function qux() + { + return $this; + } +} \ No newline at end of file diff --git a/rules/DeadCode/NodeAnalyzer/CallCollectionAnalyzer.php b/rules/DeadCode/NodeAnalyzer/CallCollectionAnalyzer.php index 8f575cd9334..8d7b10520f7 100644 --- a/rules/DeadCode/NodeAnalyzer/CallCollectionAnalyzer.php +++ b/rules/DeadCode/NodeAnalyzer/CallCollectionAnalyzer.php @@ -9,6 +9,8 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PHPStan\Type\MixedType; +use PHPStan\Type\ThisType; use PHPStan\Type\TypeWithClassName; use Rector\Enum\ObjectReference; use Rector\NodeNameResolver\NodeNameResolver; @@ -32,6 +34,20 @@ public function isExists(array $calls, string $classMethodName, string $classNam $callerType = $this->nodeTypeResolver->getType($callerRoot); if (! $callerType instanceof TypeWithClassName) { + // handle fluent by $this->bar()->baz()->qux() + // that methods don't have return type + if ($callerType instanceof MixedType && ! $callerType->isExplicitMixed()) { + $cloneCallerRoot = clone $callerRoot; + while ($cloneCallerRoot instanceof MethodCall && $cloneCallerRoot->var instanceof MethodCall) { + $callerType = $this->nodeTypeResolver->getType($cloneCallerRoot->var->var); + $cloneCallerRoot = $cloneCallerRoot->var; + + if ($callerType instanceof ThisType && $callerType->getStaticObjectType()->getClassName() === $className) { + return true; + } + } + } + continue; }