betterNodeFinder = $betterNodeFinder; $this->reflectionResolver = $reflectionResolver; } /** * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Expr\Closure|\PhpParser\Node\Stmt\Function_ $functionLike */ public function hasExclusiveVoid($functionLike) : bool { $classReflection = $this->reflectionResolver->resolveClassReflection($functionLike); if ($classReflection instanceof ClassReflection && $classReflection->isInterface()) { return \false; } if ($this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($functionLike, [Yield_::class, YieldFrom::class])) { return \false; } $return = $this->betterNodeFinder->findFirstInFunctionLikeScoped($functionLike, static function (Node $node) : bool { return $node instanceof Return_ && $node->expr instanceof Expr; }); return !$return instanceof Return_; } public function hasSilentVoid(FunctionLike $functionLike) : bool { if ($functionLike instanceof ArrowFunction) { return \false; } $stmts = (array) $functionLike->getStmts(); return !$this->hasStmtsAlwaysReturnOrExit($stmts); } /** * @param Stmt[]|Expression[] $stmts */ private function hasStmtsAlwaysReturnOrExit(array $stmts) : bool { foreach ($stmts as $stmt) { if ($stmt instanceof Expression) { $stmt = $stmt->expr; } if ($this->isStopped($stmt)) { return \true; } // has switch with always return if ($stmt instanceof Switch_ && $this->isSwitchWithAlwaysReturnOrExit($stmt)) { return \true; } if ($stmt instanceof TryCatch && $this->isTryCatchAlwaysReturnOrExit($stmt)) { return \true; } if ($this->isIfReturn($stmt)) { return \true; } if ($stmt instanceof Do_ && $this->isDoWithAlwaysReturnOrExit($stmt)) { return \true; } } return \false; } private function isDoWithAlwaysReturnOrExit(Do_ $do) : bool { if (!$this->hasStmtsAlwaysReturnOrExit($do->stmts)) { return \false; } return !(bool) $this->betterNodeFinder->findFirst($do->stmts, static function (Node $node) : bool { return $node instanceof Break_ || $node instanceof Continue_ || $node instanceof Goto_; }); } /** * @param \PhpParser\Node\Stmt|\PhpParser\Node\Expr $stmt */ private function isIfReturn($stmt) : bool { if (!$stmt instanceof If_) { return \false; } foreach ($stmt->elseifs as $elseIf) { if (!$this->hasStmtsAlwaysReturnOrExit($elseIf->stmts)) { return \false; } } if (!$stmt->else instanceof Else_) { return \false; } if (!$this->hasStmtsAlwaysReturnOrExit($stmt->stmts)) { return \false; } return $this->hasStmtsAlwaysReturnOrExit($stmt->else->stmts); } /** * @param \PhpParser\Node\Stmt|\PhpParser\Node\Expr $stmt */ private function isStopped($stmt) : bool { return $stmt instanceof Throw_ || $stmt instanceof Exit_ || $stmt instanceof Return_ && $stmt->expr instanceof Expr || $stmt instanceof Yield_ || $stmt instanceof YieldFrom; } private function isSwitchWithAlwaysReturnOrExit(Switch_ $switch) : bool { $hasDefault = \false; foreach ($switch->cases as $case) { if (!$case->cond instanceof Expr) { $hasDefault = \true; break; } } if (!$hasDefault) { return \false; } $casesWithReturnOrExitCount = $this->resolveReturnOrExitCount($switch); // has same amount of first return or exit nodes as switches return \count($switch->cases) === $casesWithReturnOrExitCount; } private function isTryCatchAlwaysReturnOrExit(TryCatch $tryCatch) : bool { if (!$this->hasStmtsAlwaysReturnOrExit($tryCatch->stmts)) { return \false; } foreach ($tryCatch->catches as $catch) { if (!$this->hasStmtsAlwaysReturnOrExit($catch->stmts)) { return \false; } } return !($tryCatch->finally instanceof Finally_ && !$this->hasStmtsAlwaysReturnOrExit($tryCatch->finally->stmts)); } private function resolveReturnOrExitCount(Switch_ $switch) : int { $casesWithReturnCount = 0; foreach ($switch->cases as $case) { if ($this->hasStmtsAlwaysReturnOrExit($case->stmts)) { ++$casesWithReturnCount; } } return $casesWithReturnCount; } }