1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-04 18:48:03 +01:00
psalm/src/CodeInspector/ScopeChecker.php

251 lines
7.4 KiB
PHP
Raw Normal View History

<?php
namespace CodeInspector;
use PhpParser;
class ScopeChecker
{
/**
* Do all code paths in this list of statements exit the block (return/throw)
*
* @param array<PhpParser\Node\Stmt> $stmts
* @param bool $check_continue - also looks for a continue
* @return bool
*/
public static function doesLeaveBlock(array $stmts, $check_continue = true, $check_break = true)
{
2016-06-13 07:48:29 +02:00
if (empty($stmts)) {
return false;
}
for ($i = count($stmts) - 1; $i >= 0; $i--) {
$stmt = $stmts[$i];
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
2016-06-21 00:10:24 +02:00
$stmt instanceof PhpParser\Node\Expr\Exit_ ||
($check_continue && $stmt instanceof PhpParser\Node\Stmt\Continue_) ||
($check_break && $stmt instanceof PhpParser\Node\Stmt\Break_)) {
return true;
}
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
2016-06-20 22:18:31 +02:00
if ($stmt->else &&
self::doesLeaveBlock($stmt->stmts, $check_continue, $check_break) &&
self::doesLeaveBlock($stmt->else->stmts, $check_continue, $check_break)) {
if (empty($stmt->elseifs)) {
return true;
}
foreach ($stmt->elseifs as $elseif) {
if (!self::doesLeaveBlock($elseif->stmts, $check_continue, $check_break)) {
return false;
}
}
return true;
}
}
2016-06-28 19:56:44 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
$has_left = false;
2016-06-28 19:56:44 +02:00
$has_default_leaver = false;
// iterate backwards in a case statement
for ($i = count($stmt->cases) - 1; $i >= 0; $i--) {
$case = $stmt->cases[$i];
$case_does_leave = self::doesLeaveBlock($case->stmts, false);
if ($case_does_leave) {
$has_left = true;
}
if (!$case_does_leave && !$has_left) {
return false;
}
2016-06-28 19:56:44 +02:00
if (!$case->cond && $case_does_leave) {
$has_default_leaver = true;
}
}
2016-06-28 19:56:44 +02:00
return $has_default_leaver;
}
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
continue;
}
return false;
}
return false;
}
2016-06-28 19:56:44 +02:00
public static function doesEverBreakOrContinue(array $stmts, $ignore_break = false)
2016-06-20 22:18:31 +02:00
{
if (empty($stmts)) {
return false;
}
for ($i = count($stmts) - 1; $i >= 0; $i--) {
$stmt = $stmts[$i];
2016-06-28 19:56:44 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Continue_ || (!$ignore_break && $stmt instanceof PhpParser\Node\Stmt\Break_)) {
2016-06-20 22:18:31 +02:00
return true;
}
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
2016-06-28 19:56:44 +02:00
if (self::doesEverBreakOrContinue($stmt->stmts, $ignore_break)) {
return true;
}
2016-06-20 22:18:31 +02:00
2016-06-28 19:56:44 +02:00
if ($stmt->else && self::doesEverBreakOrContinue($stmt->else->stmts, $ignore_break)) {
2016-06-20 22:18:31 +02:00
return true;
}
2016-06-28 19:56:44 +02:00
foreach ($stmt->elseifs as $elseif) {
if (self::doesEverBreakOrContinue($elseif->stmts, $ignore_break)) {
return true;
}
}
2016-06-20 22:18:31 +02:00
}
2016-06-28 19:56:44 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
// iterate backwards
// in switch statements we only care here about continue
for ($i = count($stmt->cases) - 1; $i >= 0; $i--) {
$case = $stmt->cases[$i];
2016-06-20 22:18:31 +02:00
2016-06-28 19:56:44 +02:00
if (self::doesEverBreakOrContinue($case->stmts, true)) {
return true;
2016-06-20 22:18:31 +02:00
}
}
}
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
continue;
}
}
return false;
}
public static function doesReturnOrThrow(array $stmts)
{
if (empty($stmts)) {
return false;
}
for ($i = count($stmts) - 1; $i >= 0; $i--) {
$stmt = $stmts[$i];
2016-06-21 00:10:24 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
$stmt instanceof PhpParser\Node\Expr\Exit_
) {
2016-06-20 22:18:31 +02:00
return true;
}
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
if ($stmt->else && self::doesReturnOrThrow($stmt->stmts) && self::doesReturnOrThrow($stmt->else->stmts)) {
if (empty($stmt->elseifs)) {
return true;
}
foreach ($stmt->elseifs as $elseif) {
if (!self::doesReturnOrThrow($elseif->stmts)) {
return false;
}
}
return true;
}
}
2016-06-28 19:56:44 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
$has_returned = false;
// iterate backwards in a case statement
for ($i = count($stmt->cases) - 1; $i >= 0; $i--) {
$case = $stmt->cases[$i];
2016-06-20 22:18:31 +02:00
2016-06-28 19:56:44 +02:00
if (self::doesEverBreakOrContinue($case->stmts)) {
2016-06-20 22:18:31 +02:00
return false;
}
2016-06-28 19:56:44 +02:00
$case_does_return = self::doesReturnOrThrow($case->stmts);
if ($case_does_return) {
$has_returned = true;
2016-06-20 22:18:31 +02:00
}
2016-06-28 19:56:44 +02:00
if (!$case_does_return && !$has_returned) {
return false;
2016-06-20 22:18:31 +02:00
}
2016-06-28 19:56:44 +02:00
if (!$case->cond && $case_does_return) {
$has_default_terminator = true;
}
2016-06-20 22:18:31 +02:00
}
2016-06-28 19:56:44 +02:00
return $has_default_terminator;
2016-06-20 22:18:31 +02:00
}
2016-06-21 00:10:24 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\While_) {
if (self::doesReturnOrThrow($stmt->stmts)) {
return true;
}
}
if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
if (self::doesReturnOrThrow($stmt->stmts)) {
foreach ($stmt->catches as $catch) {
if (!self::doesReturnOrThrow($catch->stmts)) {
return false;
}
}
return true;
}
}
2016-06-20 22:18:31 +02:00
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
continue;
}
return false;
}
return false;
}
public static function onlyThrows(array $stmts)
{
if (empty($stmts)) {
return false;
}
for ($i = count($stmts) - 1; $i >= 0; $i--) {
$stmt = $stmts[$i];
if ($stmt instanceof PhpParser\Node\Stmt\Throw_) {
return true;
}
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
continue;
}
return false;
}
return false;
}
}