ForAnalyzer   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 189
Duplicated Lines 13.76 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 26
loc 189
rs 8.5599
c 0
b 0
f 0
wmc 48
lcom 1
cbo 9

1 Method

Rating   Name   Duplication   Size   Complexity  
F analyze() 26 179 48

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ForAnalyzer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ForAnalyzer, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Block;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\ScopeAnalyzer;
6
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
7
use Psalm\Internal\Analyzer\StatementsAnalyzer;
8
use Psalm\Context;
9
use Psalm\Internal\Scope\LoopScope;
10
use Psalm\Type;
11
use function array_merge;
12
use function in_array;
13
use function array_intersect_key;
14
15
/**
16
 * @internal
17
 */
18
class ForAnalyzer
19
{
20
    /**
21
     * @param   StatementsAnalyzer           $statements_analyzer
22
     * @param   PhpParser\Node\Stmt\For_    $stmt
23
     * @param   Context                     $context
24
     *
25
     * @return  false|null
26
     */
27
    public static function analyze(
28
        StatementsAnalyzer $statements_analyzer,
29
        PhpParser\Node\Stmt\For_ $stmt,
30
        Context $context
31
    ) {
32
        $pre_assigned_var_ids = $context->assigned_var_ids;
33
        $context->assigned_var_ids = [];
34
35
        $init_var_types = [];
36
37
        foreach ($stmt->init as $init) {
38
            if (ExpressionAnalyzer::analyze($statements_analyzer, $init, $context) === false) {
39
                return false;
40
            }
41
42
            if ($init instanceof PhpParser\Node\Expr\Assign
43
                && $init->var instanceof PhpParser\Node\Expr\Variable
44
                && \is_string($init->var->name)
45
                && ($init_var_type = $statements_analyzer->node_data->getType($init->expr))
46
            ) {
47
                if ($init_var_type->isSingleIntLiteral()) {
48
                    $context->vars_in_scope['$' . $init->var->name] = Type::getInt();
49
                }
50
51
                $init_var_types[$init->var->name] = $init_var_type;
52
            }
53
        }
54
55
        $assigned_var_ids = $context->assigned_var_ids;
56
57
        $context->assigned_var_ids = array_merge(
58
            $pre_assigned_var_ids,
59
            $assigned_var_ids
60
        );
61
62
        $while_true = !$stmt->cond && !$stmt->init && !$stmt->loop;
63
64
        $pre_context = null;
65
66
        if ($while_true) {
67
            $pre_context = clone $context;
68
        }
69
70
        $for_context = clone $context;
71
72
        $for_context->inside_loop = true;
73
        $for_context->break_types[] = 'loop';
74
75
        $codebase = $statements_analyzer->getCodebase();
76
77
        if ($codebase->alter_code) {
78
            $for_context->branch_point = $for_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
79
        }
80
81
        $loop_scope = new LoopScope($for_context, $context);
82
83
        $loop_scope->protected_var_ids = array_merge(
84
            $assigned_var_ids,
85
            $context->protected_var_ids
86
        );
87
88
        LoopAnalyzer::analyze(
89
            $statements_analyzer,
90
            $stmt->stmts,
91
            $stmt->cond,
92
            $stmt->loop,
93
            $loop_scope,
94
            $inner_loop_context
95
        );
96
97
        if (!$inner_loop_context) {
98
            throw new \UnexpectedValueException('There should be an inner loop context');
99
        }
100
101
        $always_enters_loop = false;
102
103
        foreach ($stmt->cond as $cond) {
104
            if ($cond_type = $statements_analyzer->node_data->getType($cond)) {
105
                foreach ($cond_type->getAtomicTypes() as $iterator_type) {
106
                    $always_enters_loop = $iterator_type instanceof Type\Atomic\TTrue;
107
108
                    break;
109
                }
110
            }
111
112
            if (\count($stmt->init) === 1
113
                && \count($stmt->cond) === 1
114
                && $cond instanceof PhpParser\Node\Expr\BinaryOp
115
                && $cond->right instanceof PhpParser\Node\Scalar\LNumber
116
                && $cond->left instanceof PhpParser\Node\Expr\Variable
117
                && \is_string($cond->left->name)
118
                && isset($init_var_types[$cond->left->name])
119
                && $init_var_types[$cond->left->name]->isSingleIntLiteral()
120
            ) {
121
                $init_value = $init_var_types[$cond->left->name]->getSingleIntLiteral()->value;
122
                $cond_value = $cond->right->value;
123
124
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Smaller && $init_value < $cond_value) {
125
                    $always_enters_loop = true;
126
                    break;
127
                }
128
129
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual && $init_value <= $cond_value) {
130
                    $always_enters_loop = true;
131
                    break;
132
                }
133
134
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Greater && $init_value > $cond_value) {
135
                    $always_enters_loop = true;
136
                    break;
137
                }
138
139
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual && $init_value >= $cond_value) {
140
                    $always_enters_loop = true;
141
                    break;
142
                }
143
            }
144
        }
145
146
        if ($while_true) {
147
            $always_enters_loop = true;
148
        }
149
150
        $can_leave_loop = !$while_true
151
            || in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true);
152
153 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...
154
            foreach ($inner_loop_context->vars_in_scope as $var_id => $type) {
155
                // if there are break statements in the loop it's not certain
156
                // that the loop has finished executing
157
                if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true)
158
                    || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true)
159
                ) {
160
                    if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) {
161
                        $context->vars_in_scope[$var_id] = Type::combineUnionTypes(
162
                            $type,
163
                            $loop_scope->possibly_defined_loop_parent_vars[$var_id]
164
                        );
165
                    }
166
                } else {
167
                    $context->vars_in_scope[$var_id] = $type;
168
                }
169
            }
170
        }
171
172
        $for_context->loop_scope = null;
173
174 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...
175
            $context->vars_possibly_in_scope = array_merge(
176
                $context->vars_possibly_in_scope,
177
                $for_context->vars_possibly_in_scope
178
            );
179
        } elseif ($pre_context) {
180
            $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope;
181
        }
182
183
        $context->referenced_var_ids = array_intersect_key(
184
            $for_context->referenced_var_ids,
185
            $context->referenced_var_ids
186
        );
187
188
        if ($codebase->find_unused_variables) {
189
            foreach ($for_context->unreferenced_vars as $var_id => $locations) {
190
                if (isset($loop_scope->referenced_var_ids[$var_id])) {
191
                    $statements_analyzer->registerVariableUses($locations);
192
                } elseif (isset($context->unreferenced_vars[$var_id])) {
193
                    $context->unreferenced_vars[$var_id] += $locations;
194
                } else {
195
                    $context->unreferenced_vars[$var_id] = $locations;
196
                }
197
            }
198
        }
199
200
        if ($context->collect_exceptions) {
201
            $context->mergeExceptions($for_context);
202
        }
203
204
        return null;
205
    }
206
}
207