WhileAnalyzer::analyze()   F
last analyzed

Complexity

Conditions 37
Paths > 20000

Size

Total Lines 155

Duplication

Lines 27
Ratio 17.42 %

Importance

Changes 0
Metric Value
cc 37
nc 70260
nop 3
dl 27
loc 155
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Block;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\ScopeAnalyzer;
6
use Psalm\Internal\Analyzer\StatementsAnalyzer;
7
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
8
use Psalm\Context;
9
use Psalm\Internal\Scope\LoopScope;
10
use Psalm\Type;
11
use function in_array;
12
use function array_merge;
13
14
/**
15
 * @internal
16
 */
17
class WhileAnalyzer
18
{
19
    /**
20
     * @param   StatementsAnalyzer           $statements_analyzer
21
     * @param   PhpParser\Node\Stmt\While_  $stmt
22
     * @param   Context                     $context
23
     *
24
     * @return  false|null
25
     */
26
    public static function analyze(
27
        StatementsAnalyzer $statements_analyzer,
28
        PhpParser\Node\Stmt\While_ $stmt,
29
        Context $context
30
    ) {
31
        $while_true = ($stmt->cond instanceof PhpParser\Node\Expr\ConstFetch && $stmt->cond->name->parts === ['true'])
32
            || ($stmt->cond instanceof PhpParser\Node\Scalar\LNumber && $stmt->cond->value > 0);
33
34
        $pre_context = null;
35
36
        if ($while_true) {
37
            $pre_context = clone $context;
38
        }
39
40
        $while_context = clone $context;
41
42
        $while_context->inside_loop = true;
43
        $while_context->break_types[] = 'loop';
44
45
        $codebase = $statements_analyzer->getCodebase();
46
47
        if ($codebase->alter_code) {
48
            $while_context->branch_point = $while_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
49
        }
50
51
        $loop_scope = new LoopScope($while_context, $context);
52
        $loop_scope->protected_var_ids = $context->protected_var_ids;
53
54
        if (LoopAnalyzer::analyze(
55
            $statements_analyzer,
56
            $stmt->stmts,
57
            self::getAndExpressions($stmt->cond),
58
            [],
59
            $loop_scope,
60
            $inner_loop_context
61
        ) === false) {
62
            return false;
63
        }
64
65
        if (!$inner_loop_context) {
66
            throw new \UnexpectedValueException('Should always enter loop');
67
        }
68
69
        $always_enters_loop = false;
70
71
        if ($stmt_cond_type = $statements_analyzer->node_data->getType($stmt->cond)) {
72
            $always_enters_loop = true;
73
74
            foreach ($stmt_cond_type->getAtomicTypes() as $iterator_type) {
75
                if ($iterator_type instanceof Type\Atomic\TArray
76
                    || $iterator_type instanceof Type\Atomic\ObjectLike
77
                ) {
78
                    if ($iterator_type instanceof Type\Atomic\ObjectLike) {
79
                        if (!$iterator_type->sealed) {
80
                            $always_enters_loop = false;
81
                        }
82
                    } elseif (!$iterator_type instanceof Type\Atomic\TNonEmptyArray) {
83
                        $always_enters_loop = false;
84
                    }
85
86
                    continue;
87
                }
88
89
                if ($iterator_type instanceof Type\Atomic\TTrue) {
90
                    continue;
91
                }
92
93
                if ($iterator_type instanceof Type\Atomic\TLiteralString
94
                    && $iterator_type->value
95
                ) {
96
                    continue;
97
                }
98
99
                if ($iterator_type instanceof Type\Atomic\TLiteralInt
100
                    && $iterator_type->value
101
                ) {
102
                    continue;
103
                }
104
105
                $always_enters_loop = false;
106
                break;
107
            }
108
        }
109
110
        $can_leave_loop = !$while_true
111
            || in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true);
112
113 View Code Duplication
        if ($always_enters_loop && $can_leave_loop) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
            foreach ($inner_loop_context->vars_in_scope as $var_id => $type) {
115
                // if there are break statements in the loop it's not certain
116
                // that the loop has finished executing, so the assertions at the end
117
                // the loop in the while conditional may not hold
118
                if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true)
119
                    || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true)
120
                ) {
121
                    if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) {
122
                        $context->vars_in_scope[$var_id] = Type::combineUnionTypes(
123
                            $type,
124
                            $loop_scope->possibly_defined_loop_parent_vars[$var_id]
125
                        );
126
                    }
127
                } else {
128
                    $context->vars_in_scope[$var_id] = $type;
129
                }
130
            }
131
        }
132
133
        $while_context->loop_scope = null;
134
135 View Code Duplication
        if ($can_leave_loop) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
            $context->vars_possibly_in_scope = array_merge(
137
                $context->vars_possibly_in_scope,
138
                $while_context->vars_possibly_in_scope
139
            );
140
        } elseif ($pre_context) {
141
            $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope;
142
        }
143
144
        $context->referenced_var_ids = array_merge(
145
            $context->referenced_var_ids,
146
            $while_context->referenced_var_ids
147
        );
148
149
        if ($codebase->find_unused_variables) {
150
            $suppressed_issues = $statements_analyzer->getSuppressedIssues();
151
152
            if (!in_array('RedundantCondition', $suppressed_issues, true)) {
153
                $statements_analyzer->addSuppressedIssues(['RedundantCondition']);
154
            }
155
            if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
156
                $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']);
157
            }
158
            if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) {
159
                $statements_analyzer->addSuppressedIssues(['TypeDoesNotContainType']);
160
            }
161
162
            $while_context->inside_conditional = true;
163
            ExpressionAnalyzer::analyze($statements_analyzer, $stmt->cond, $while_context);
164
            $while_context->inside_conditional = false;
165
166
            if (!in_array('RedundantCondition', $suppressed_issues, true)) {
167
                $statements_analyzer->removeSuppressedIssues(['RedundantCondition']);
168
            }
169
            if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
170
                $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']);
171
            }
172
            if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) {
173
                $statements_analyzer->removeSuppressedIssues(['TypeDoesNotContainType']);
174
            }
175
176
            $context->unreferenced_vars = $while_context->unreferenced_vars;
177
        }
178
179
        return null;
180
    }
181
182
    /**
183
     * @return list<PhpParser\Node\Expr>
0 ignored issues
show
Documentation introduced by
The doc-type list<PhpParser\Node\Expr> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
184
     */
185
    private static function getAndExpressions(
186
        PhpParser\Node\Expr $expr
187
    ) : array {
188
        if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) {
189
            return array_merge(
190
                self::getAndExpressions($expr->left),
191
                self::getAndExpressions($expr->right)
192
            );
193
        }
194
195
        return [$expr];
196
    }
197
}
198