StaticPropertyFetchAnalyzer::analyze()   F
last analyzed

Complexity

Conditions 58
Paths 10593

Size

Total Lines 321

Duplication

Lines 100
Ratio 31.15 %

Importance

Changes 0
Metric Value
cc 58
nc 10593
nop 3
dl 100
loc 321
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\Expression\Fetch;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
6
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
7
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
8
use Psalm\Internal\Analyzer\StatementsAnalyzer;
9
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
10
use Psalm\CodeLocation;
11
use Psalm\Context;
12
use Psalm\Issue\ParentNotFound;
13
use Psalm\Issue\UndefinedPropertyFetch;
14
use Psalm\IssueBuffer;
15
use Psalm\Type;
16
use Psalm\Type\Atomic\TNamedObject;
17
use function strtolower;
18
use function in_array;
19
use function count;
20
use function explode;
21
22
/**
23
 * @internal
24
 */
25
class StaticPropertyFetchAnalyzer
26
{
27
    /**
28
     * @param   StatementsAnalyzer                       $statements_analyzer
29
     * @param   PhpParser\Node\Expr\StaticPropertyFetch $stmt
30
     * @param   Context                                 $context
31
     */
32
    public static function analyze(
33
        StatementsAnalyzer $statements_analyzer,
34
        PhpParser\Node\Expr\StaticPropertyFetch $stmt,
35
        Context $context
36
    ) : bool {
37
        if (!$stmt->class instanceof PhpParser\Node\Name) {
38
            self::analyzeVariableStaticPropertyFetch($statements_analyzer, $stmt->class, $stmt, $context);
39
            return true;
40
        }
41
42
        $fq_class_name = null;
0 ignored issues
show
Unused Code introduced by
$fq_class_name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
43
44
        $codebase = $statements_analyzer->getCodebase();
45
46
        if (count($stmt->class->parts) === 1
47
            && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
48
        ) {
49
            if ($stmt->class->parts[0] === 'parent') {
50
                $fq_class_name = $statements_analyzer->getParentFQCLN();
51
52
                if ($fq_class_name === null) {
53
                    if (IssueBuffer::accepts(
54
                        new ParentNotFound(
55
                            'Cannot check property fetch on parent as this class does not extend another',
56
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
57
                        ),
58
                        $statements_analyzer->getSuppressedIssues()
59
                    )) {
60
                        return false;
61
                    }
62
63
                    return true;
64
                }
65
            } else {
66
                $fq_class_name = (string)$context->self;
67
            }
68
69
            if ($context->isPhantomClass($fq_class_name)) {
70
                return true;
71
            }
72
        } else {
73
            $aliases = $statements_analyzer->getAliases();
74
75
            if ($context->calling_method_id
76
                && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
77
            ) {
78
                $codebase->file_reference_provider->addMethodReferenceToClassMember(
79
                    $context->calling_method_id,
80
                    'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
81
                );
82
            }
83
84
            $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
85
                $stmt->class,
86
                $aliases
87
            );
88
89
            if ($context->isPhantomClass($fq_class_name)) {
90
                return true;
91
            }
92
93
            if ($context->check_classes) {
94
                if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
95
                    $statements_analyzer,
96
                    $fq_class_name,
97
                    new CodeLocation($statements_analyzer->getSource(), $stmt->class),
98
                    $context->self,
99
                    $context->calling_method_id,
100
                    $statements_analyzer->getSuppressedIssues(),
101
                    false
102
                ) !== true) {
103
                    return false;
104
                }
105
            }
106
        }
107
108
        if ($fq_class_name
109
            && $codebase->methods_to_move
110
            && $context->calling_method_id
111
            && isset($codebase->methods_to_move[$context->calling_method_id])
112
        ) {
113
            $destination_method_id = $codebase->methods_to_move[$context->calling_method_id];
114
115
            $codebase->classlikes->airliftClassLikeReference(
116
                $fq_class_name,
117
                explode('::', $destination_method_id)[0],
118
                $statements_analyzer->getFilePath(),
119
                (int) $stmt->class->getAttribute('startFilePos'),
120
                (int) $stmt->class->getAttribute('endFilePos') + 1
121
            );
122
        }
123
124
        if ($fq_class_name) {
125
            $statements_analyzer->node_data->setType(
126
                $stmt->class,
127
                new Type\Union([new TNamedObject($fq_class_name)])
128
            );
129
        }
130
131 View Code Duplication
        if ($stmt->name instanceof PhpParser\Node\VarLikeIdentifier) {
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...
132
            $prop_name = $stmt->name->name;
133
        } elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name))
134
            && $stmt_name_type->isSingleStringLiteral()
135
        ) {
136
            $prop_name = $stmt_name_type->getSingleStringLiteral()->value;
137
        } else {
138
            $prop_name = null;
139
        }
140
141 View Code Duplication
        if (!$prop_name) {
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...
142
            if ($fq_class_name) {
143
                $codebase->analyzer->addMixedMemberName(
144
                    strtolower($fq_class_name) . '::$',
145
                    $context->calling_method_id ?: $statements_analyzer->getFileName()
146
                );
147
            }
148
149
            return true;
150
        }
151
152
        if (!$fq_class_name
153
            || !$context->check_classes
154
            || !$context->check_variables
155
            || ExpressionAnalyzer::isMock($fq_class_name)
156
        ) {
157
            return true;
158
        }
159
160
        $var_id = ExpressionIdentifier::getVarId(
161
            $stmt,
162
            $context->self ?: $statements_analyzer->getFQCLN(),
163
            $statements_analyzer
164
        );
165
166
        $property_id = $fq_class_name . '::$' . $prop_name;
167
168 View Code Duplication
        if ($codebase->store_node_types
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...
169
            && !$context->collect_initializations
170
            && !$context->collect_mutations
171
        ) {
172
            $codebase->analyzer->addNodeReference(
173
                $statements_analyzer->getFilePath(),
174
                $stmt->name,
175
                $property_id
176
            );
177
        }
178
179
        if ($context->mutation_free) {
180
            if (IssueBuffer::accepts(
181
                new \Psalm\Issue\ImpureStaticProperty(
182
                    'Cannot use a static property in a mutation-free context',
183
                    new CodeLocation($statements_analyzer, $stmt)
184
                ),
185
                $statements_analyzer->getSuppressedIssues()
186
            )) {
187
                // fall through
188
            }
189
        }
190
191
        if ($var_id && $context->hasVariable($var_id, $statements_analyzer)) {
192
            $stmt_type = $context->vars_in_scope[$var_id];
193
194
            // we don't need to check anything
195
            $statements_analyzer->node_data->setType($stmt, $stmt_type);
196
197
            if ($codebase->collect_references) {
198
                // log the appearance
199
                $codebase->properties->propertyExists(
200
                    $property_id,
201
                    true,
202
                    $statements_analyzer,
203
                    $context,
204
                    $codebase->collect_locations
205
                        ? new CodeLocation($statements_analyzer->getSource(), $stmt)
206
                        : null
207
                );
208
            }
209
210 View Code Duplication
            if ($codebase->store_node_types
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...
211
                && !$context->collect_initializations
212
                && !$context->collect_mutations
213
                && ($stmt_type = $statements_analyzer->node_data->getType($stmt))
214
            ) {
215
                $codebase->analyzer->addNodeType(
216
                    $statements_analyzer->getFilePath(),
217
                    $stmt->name,
218
                    $stmt_type->getId()
219
                );
220
            }
221
222
            return true;
223
        }
224
225
        if (!$codebase->properties->propertyExists(
226
            $property_id,
227
            true,
228
            $statements_analyzer,
229
            $context,
230
            $codebase->collect_locations
231
                ? new CodeLocation($statements_analyzer->getSource(), $stmt)
232
                : null
233
        )
234
        ) {
235
            if ($context->inside_isset) {
236
                return true;
237
            }
238
239
            if (IssueBuffer::accepts(
240
                new UndefinedPropertyFetch(
241
                    'Static property ' . $property_id . ' is not defined',
242
                    new CodeLocation($statements_analyzer->getSource(), $stmt),
243
                    $property_id
244
                ),
245
                $statements_analyzer->getSuppressedIssues()
246
            )) {
247
                // fall through
248
            }
249
250
            return true;
251
        }
252
253 View Code Duplication
        if (ClassLikeAnalyzer::checkPropertyVisibility(
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...
254
            $property_id,
255
            $context,
256
            $statements_analyzer,
257
            new CodeLocation($statements_analyzer->getSource(), $stmt),
258
            $statements_analyzer->getSuppressedIssues()
259
        ) === false) {
260
            return false;
261
        }
262
263
        $declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
264
            $fq_class_name . '::$' . $prop_name,
265
            true,
266
            $statements_analyzer
267
        );
268
269
        if ($declaring_property_class === null) {
270
            return false;
271
        }
272
273
        $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name;
274
275 View Code Duplication
        if ($codebase->alter_code) {
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...
276
            $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
277
                $codebase,
278
                $statements_analyzer,
279
                $stmt->class,
280
                $fq_class_name,
281
                $context->calling_method_id
282
            );
283
284
            if (!$moved_class) {
285
                foreach ($codebase->property_transforms as $original_pattern => $transformation) {
286
                    if ($declaring_property_id === $original_pattern) {
287
                        list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
288
                        list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
289
290
                        $file_manipulations = [];
291
292
                        if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
293
                            $file_manipulations[] = new \Psalm\FileManipulation(
294
                                (int) $stmt->class->getAttribute('startFilePos'),
295
                                (int) $stmt->class->getAttribute('endFilePos') + 1,
296
                                Type::getStringFromFQCLN(
297
                                    $new_fq_class_name,
298
                                    $statements_analyzer->getNamespace(),
299
                                    $statements_analyzer->getAliasedClassesFlipped(),
300
                                    null
301
                                )
302
                            );
303
                        }
304
305
                        $file_manipulations[] = new \Psalm\FileManipulation(
306
                            (int) $stmt->name->getAttribute('startFilePos'),
307
                            (int) $stmt->name->getAttribute('endFilePos') + 1,
308
                            '$' . $new_property_name
309
                        );
310
311
                        FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
312
                    }
313
                }
314
            }
315
        }
316
317
        $class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
318
        $property = $class_storage->properties[$prop_name];
319
320
        if ($var_id) {
321
            if ($property->type) {
322
                $context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion(
323
                    $codebase,
324
                    clone $property->type,
325
                    $class_storage->name,
326
                    $class_storage->name,
327
                    $class_storage->parent_class
328
                );
329
            } else {
330
                $context->vars_in_scope[$var_id] = Type::getMixed();
331
            }
332
333
            $stmt_type = clone $context->vars_in_scope[$var_id];
334
335
            $statements_analyzer->node_data->setType($stmt, $stmt_type);
336
337 View Code Duplication
            if ($codebase->store_node_types
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...
338
                && !$context->collect_initializations
339
                && !$context->collect_mutations
340
            ) {
341
                $codebase->analyzer->addNodeType(
342
                    $statements_analyzer->getFilePath(),
343
                    $stmt->name,
344
                    $stmt_type->getId()
345
                );
346
            }
347
        } else {
348
            $statements_analyzer->node_data->setType($stmt, Type::getMixed());
349
        }
350
351
        return true;
352
    }
353
354
    private static function analyzeVariableStaticPropertyFetch(
355
        StatementsAnalyzer $statements_analyzer,
356
        PhpParser\Node\Expr $stmt_class,
357
        PhpParser\Node\Expr\StaticPropertyFetch $stmt,
358
        Context $context
359
    ) : void {
360
        ExpressionAnalyzer::analyze(
361
            $statements_analyzer,
362
            $stmt_class,
363
            $context
364
        );
365
366
        $stmt_class_type = $statements_analyzer->node_data->getType($stmt_class) ?: Type::getMixed();
367
368
        $old_data_provider = $statements_analyzer->node_data;
369
370
        $stmt_type = null;
371
372
        $codebase = $statements_analyzer->getCodebase();
373
374
        foreach ($stmt_class_type->getAtomicTypes() as $class_atomic_type) {
375
            $statements_analyzer->node_data = clone $statements_analyzer->node_data;
376
377
            $string_type = ($class_atomic_type instanceof Type\Atomic\TClassString
378
                    && $class_atomic_type->as_type !== null)
379
                ? $class_atomic_type->as_type->value
380
                : ($class_atomic_type instanceof Type\Atomic\TLiteralString
381
                    ? $class_atomic_type->value
382
                    : null);
383
384
            if ($string_type) {
385
                $new_stmt_name = new PhpParser\Node\Name\FullyQualified(
386
                    $string_type,
387
                    $stmt_class->getAttributes()
388
                );
389
390
                $fake_static_property = new PhpParser\Node\Expr\StaticPropertyFetch(
391
                    $new_stmt_name,
392
                    $stmt->name,
393
                    $stmt->getAttributes()
394
                );
395
396
                self::analyze($statements_analyzer, $fake_static_property, $context);
397
398
                $fake_stmt_type = $statements_analyzer->node_data->getType($fake_static_property)
399
                    ?: Type::getMixed();
400
            } else {
401
                $fake_var_name = '__fake_var_' . (string) $stmt->getAttribute('startFilePos');
402
403
                $fake_var = new PhpParser\Node\Expr\Variable(
404
                    $fake_var_name,
405
                    $stmt_class->getAttributes()
406
                );
407
408
                $context->vars_in_scope['$' . $fake_var_name] = new Type\Union([$class_atomic_type]);
409
410
                $fake_instance_property = new PhpParser\Node\Expr\PropertyFetch(
411
                    $fake_var,
412
                    $stmt->name,
413
                    $stmt->getAttributes()
414
                );
415
416
                InstancePropertyFetchAnalyzer::analyze(
417
                    $statements_analyzer,
418
                    $fake_instance_property,
419
                    $context
420
                );
421
422
                $fake_stmt_type = $statements_analyzer->node_data->getType($fake_instance_property)
423
                    ?: Type::getMixed();
424
            }
425
426
            $stmt_type = $stmt_type
427
                ? Type::combineUnionTypes($stmt_type, $fake_stmt_type, $codebase)
428
                : $fake_stmt_type;
429
430
            $statements_analyzer->node_data = $old_data_provider;
431
        }
432
433
        $statements_analyzer->node_data->setType($stmt, $stmt_type);
434
    }
435
}
436