ClassConstFetchAnalyzer::analyze()   F
last analyzed

Complexity

Conditions 72
Paths 6563

Size

Total Lines 360

Duplication

Lines 75
Ratio 20.83 %

Importance

Changes 0
Metric Value
cc 72
nc 6563
nop 3
dl 75
loc 360
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\StatementsAnalyzer;
8
use Psalm\Internal\Analyzer\TraitAnalyzer;
9
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
10
use Psalm\CodeLocation;
11
use Psalm\Context;
12
use Psalm\Issue\CircularReference;
13
use Psalm\Issue\DeprecatedClass;
14
use Psalm\Issue\DeprecatedConstant;
15
use Psalm\Issue\InaccessibleClassConstant;
16
use Psalm\Issue\NonStaticSelfCall;
17
use Psalm\Issue\ParentNotFound;
18
use Psalm\Issue\UndefinedConstant;
19
use Psalm\IssueBuffer;
20
use Psalm\Type;
21
use function strtolower;
22
use function explode;
23
24
/**
25
 * @internal
26
 */
27
class ClassConstFetchAnalyzer
28
{
29
    public static function analyze(
30
        StatementsAnalyzer $statements_analyzer,
31
        PhpParser\Node\Expr\ClassConstFetch $stmt,
32
        Context $context
33
    ) : bool {
34
        $codebase = $statements_analyzer->getCodebase();
35
36
        if ($stmt->class instanceof PhpParser\Node\Name) {
37
            $first_part_lc = strtolower($stmt->class->parts[0]);
38
39
            if ($first_part_lc === 'self' || $first_part_lc === 'static') {
40
                if (!$context->self) {
41
                    if (IssueBuffer::accepts(
42
                        new NonStaticSelfCall(
43
                            'Cannot use ' . $first_part_lc . ' outside class context',
44
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
45
                        ),
46
                        $statements_analyzer->getSuppressedIssues()
47
                    )) {
48
                        return false;
49
                    }
50
51
                    return true;
52
                }
53
54
                $fq_class_name = $context->self;
55
            } elseif ($first_part_lc === 'parent') {
56
                $fq_class_name = $statements_analyzer->getParentFQCLN();
57
58
                if ($fq_class_name === null) {
59
                    if (IssueBuffer::accepts(
60
                        new ParentNotFound(
61
                            'Cannot check property fetch on parent as this class does not extend another',
62
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
63
                        ),
64
                        $statements_analyzer->getSuppressedIssues()
65
                    )) {
66
                        return false;
67
                    }
68
69
                    return true;
70
                }
71
            } else {
72
                $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
73
                    $stmt->class,
74
                    $statements_analyzer->getAliases()
75
                );
76
77
                if ($stmt->name instanceof PhpParser\Node\Identifier) {
78
                    if ((!$context->inside_class_exists || $stmt->name->name !== 'class')
79
                        && !isset($context->phantom_classes[strtolower($fq_class_name)])
80
                    ) {
81
                        if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
82
                            $statements_analyzer,
83
                            $fq_class_name,
84
                            new CodeLocation($statements_analyzer->getSource(), $stmt->class),
85
                            $context->self,
86
                            $context->calling_method_id,
87
                            $statements_analyzer->getSuppressedIssues(),
88
                            false,
89
                            true
90
                        ) === false) {
91
                            return true;
92
                        }
93
                    }
94
                }
95
            }
96
97
            $moved_class = false;
98
99
            if ($codebase->alter_code
100
                && !\in_array($stmt->class->parts[0], ['parent', 'static'])
101
            ) {
102
                $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
103
                    $codebase,
104
                    $statements_analyzer,
105
                    $stmt->class,
106
                    $fq_class_name,
107
                    $context->calling_method_id,
108
                    false,
109
                    $stmt->class->parts[0] === 'self'
110
                );
111
            }
112
113
            if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
114
                if ($codebase->classlikes->classExists($fq_class_name)) {
115
                    $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name);
116
                    $class_const_storage = $codebase->classlike_storage_provider->get($fq_class_name);
117
                    $fq_class_name = $class_const_storage->name;
118
119 View Code Duplication
                    if ($class_const_storage->deprecated && $fq_class_name !== $context->self) {
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...
120
                        if (IssueBuffer::accepts(
121
                            new DeprecatedClass(
122
                                'Class ' . $fq_class_name . ' is deprecated',
123
                                new CodeLocation($statements_analyzer->getSource(), $stmt),
124
                                $fq_class_name
125
                            ),
126
                            $statements_analyzer->getSuppressedIssues()
127
                        )) {
128
                            // fall through
129
                        }
130
                    }
131
                }
132
133
                if ($first_part_lc === 'static') {
134
                    $static_named_object = new Type\Atomic\TNamedObject($fq_class_name);
135
                    $static_named_object->was_static = true;
136
137
                    $statements_analyzer->node_data->setType(
138
                        $stmt,
139
                        new Type\Union([
140
                            new Type\Atomic\TClassString($fq_class_name, $static_named_object)
141
                        ])
142
                    );
143
                } else {
144
                    $statements_analyzer->node_data->setType($stmt, Type::getLiteralClassString($fq_class_name));
145
                }
146
147 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...
148
                    && !$context->collect_initializations
149
                    && !$context->collect_mutations
150
                ) {
151
                    $codebase->analyzer->addNodeReference(
152
                        $statements_analyzer->getFilePath(),
153
                        $stmt->class,
154
                        $fq_class_name
155
                    );
156
                }
157
158
                return true;
159
            }
160
161
            // if we're ignoring that the class doesn't exist, exit anyway
162
            if (!$codebase->classlikes->classOrInterfaceExists($fq_class_name)) {
163
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
164
165
                return true;
166
            }
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->class,
175
                    $fq_class_name
176
                );
177
            }
178
179
            if (!$stmt->name instanceof PhpParser\Node\Identifier) {
180
                return true;
181
            }
182
183
            $const_id = $fq_class_name . '::' . $stmt->name;
184
185 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...
186
                && !$context->collect_initializations
187
                && !$context->collect_mutations
188
            ) {
189
                $codebase->analyzer->addNodeReference(
190
                    $statements_analyzer->getFilePath(),
191
                    $stmt->name,
192
                    $const_id
193
                );
194
            }
195
196
            if ($fq_class_name === $context->self
197
                || (
198
                    $statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer &&
199
                    $fq_class_name === $statements_analyzer->getSource()->getFQCLN()
200
                )
201
            ) {
202
                $class_visibility = \ReflectionProperty::IS_PRIVATE;
203
            } elseif ($context->self &&
204
                ($codebase->classlikes->classExtends($context->self, $fq_class_name)
205
                    || $codebase->classlikes->classExtends($fq_class_name, $context->self))
206
            ) {
207
                $class_visibility = \ReflectionProperty::IS_PROTECTED;
208
            } else {
209
                $class_visibility = \ReflectionProperty::IS_PUBLIC;
210
            }
211
212
            try {
213
                $class_constant_type = $codebase->classlikes->getConstantForClass(
214
                    $fq_class_name,
215
                    $stmt->name->name,
216
                    $class_visibility,
217
                    $statements_analyzer
218
                );
219
            } catch (\InvalidArgumentException $_) {
220
                return true;
221
            } catch (\Psalm\Exception\CircularReferenceException $e) {
222
                if (IssueBuffer::accepts(
223
                    new CircularReference(
224
                        'Constant ' . $const_id . ' contains a circular reference',
225
                        new CodeLocation($statements_analyzer->getSource(), $stmt)
226
                    ),
227
                    $statements_analyzer->getSuppressedIssues()
228
                )) {
229
                    // fall through
230
                }
231
232
                return true;
233
            }
234
235
            if (!$class_constant_type) {
236
                if ($fq_class_name !== $context->self) {
237
                    $class_constant_type = $codebase->classlikes->getConstantForClass(
238
                        $fq_class_name,
239
                        $stmt->name->name,
240
                        \ReflectionProperty::IS_PRIVATE,
241
                        $statements_analyzer
242
                    );
243
                }
244
245 View Code Duplication
                if ($class_constant_type) {
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...
246
                    if (IssueBuffer::accepts(
247
                        new InaccessibleClassConstant(
248
                            'Constant ' . $const_id . ' is not visible in this context',
249
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
250
                        ),
251
                        $statements_analyzer->getSuppressedIssues()
252
                    )) {
253
                        // fall through
254
                    }
255
                } elseif ($context->check_consts) {
256
                    if (IssueBuffer::accepts(
257
                        new UndefinedConstant(
258
                            'Constant ' . $const_id . ' is not defined',
259
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
260
                        ),
261
                        $statements_analyzer->getSuppressedIssues()
262
                    )) {
263
                        // fall through
264
                    }
265
                }
266
267
                return true;
268
            }
269
270
            if ($context->calling_method_id) {
271
                $codebase->file_reference_provider->addMethodReferenceToClassMember(
272
                    $context->calling_method_id,
273
                    strtolower($fq_class_name) . '::' . $stmt->name->name
274
                );
275
            }
276
277
            $declaring_const_id = strtolower($fq_class_name) . '::' . $stmt->name->name;
278
279
            if ($codebase->alter_code && !$moved_class) {
280
                foreach ($codebase->class_constant_transforms as $original_pattern => $transformation) {
281
                    if ($declaring_const_id === $original_pattern) {
282
                        list($new_fq_class_name, $new_const_name) = explode('::', $transformation);
283
284
                        $file_manipulations = [];
285
286
                        if (strtolower($new_fq_class_name) !== strtolower($fq_class_name)) {
287
                            $file_manipulations[] = new \Psalm\FileManipulation(
288
                                (int) $stmt->class->getAttribute('startFilePos'),
289
                                (int) $stmt->class->getAttribute('endFilePos') + 1,
290
                                Type::getStringFromFQCLN(
291
                                    $new_fq_class_name,
292
                                    $statements_analyzer->getNamespace(),
293
                                    $statements_analyzer->getAliasedClassesFlipped(),
294
                                    null
295
                                )
296
                            );
297
                        }
298
299
                        $file_manipulations[] = new \Psalm\FileManipulation(
300
                            (int) $stmt->name->getAttribute('startFilePos'),
301
                            (int) $stmt->name->getAttribute('endFilePos') + 1,
302
                            $new_const_name
303
                        );
304
305
                        FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
306
                    }
307
                }
308
            }
309
310
            $class_const_storage = $codebase->classlike_storage_provider->get($fq_class_name);
311
312
            if ($class_const_storage->deprecated && $fq_class_name !== $context->self) {
313
                if (IssueBuffer::accepts(
314
                    new DeprecatedClass(
315
                        'Class ' . $fq_class_name . ' is deprecated',
316
                        new CodeLocation($statements_analyzer->getSource(), $stmt),
317
                        $fq_class_name
318
                    ),
319
                    $statements_analyzer->getSuppressedIssues()
320
                )) {
321
                    // fall through
322
                }
323
            } elseif (isset($class_const_storage->deprecated_constants[$stmt->name->name])) {
324
                if (IssueBuffer::accepts(
325
                    new DeprecatedConstant(
326
                        'Constant ' . $const_id . ' is deprecated',
327
                        new CodeLocation($statements_analyzer->getSource(), $stmt)
328
                    ),
329
                    $statements_analyzer->getSuppressedIssues()
330
                )) {
331
                    // fall through
332
                }
333
            }
334
335
            if ($first_part_lc !== 'static' || $class_const_storage->final) {
336
                $stmt_type = clone $class_constant_type;
337
338
                $statements_analyzer->node_data->setType($stmt, $stmt_type);
339
                $context->vars_in_scope[$const_id] = $stmt_type;
340
            } else {
341
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
342
            }
343
344
            return true;
345
        }
346
347
        if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
348
            ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context);
349
            $lhs_type = $statements_analyzer->node_data->getType($stmt->class);
350
351
            $class_string_types = [];
352
353
            $has_mixed_or_object = false;
354
355
            if ($lhs_type) {
356 View Code Duplication
                foreach ($lhs_type->getAtomicTypes() as $lhs_atomic_type) {
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...
357
                    if ($lhs_atomic_type instanceof Type\Atomic\TNamedObject) {
358
                        $class_string_types[] = new Type\Atomic\TClassString(
359
                            $lhs_atomic_type->value,
360
                            clone $lhs_atomic_type
361
                        );
362
                    } elseif ($lhs_atomic_type instanceof Type\Atomic\TObject
363
                        || $lhs_atomic_type instanceof Type\Atomic\TMixed
364
                    ) {
365
                        $has_mixed_or_object = true;
366
                    }
367
                }
368
            }
369
370
            if ($has_mixed_or_object) {
371
                $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TClassString()]));
372
            } elseif ($class_string_types) {
373
                $statements_analyzer->node_data->setType($stmt, new Type\Union($class_string_types));
374
            } else {
375
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
376
            }
377
378
            return true;
379
        }
380
381
        $statements_analyzer->node_data->setType($stmt, Type::getMixed());
382
383
        if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context) === false) {
384
            return false;
385
        }
386
387
        return true;
388
    }
389
390
    public static function analyzeClassConstAssignment(
391
        StatementsAnalyzer $statements_analyzer,
392
        PhpParser\Node\Stmt\ClassConst $stmt,
393
        Context $context
394
    ): void {
395
        $const_visibility = \ReflectionProperty::IS_PUBLIC;
396
397
        if ($stmt->isProtected()) {
398
            $const_visibility = \ReflectionProperty::IS_PROTECTED;
399
        }
400
401
        if ($stmt->isPrivate()) {
402
            $const_visibility = \ReflectionProperty::IS_PRIVATE;
403
        }
404
405
        $codebase = $statements_analyzer->getCodebase();
406
407
        foreach ($stmt->consts as $const) {
408
            ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context);
409
410
            if (($const_type = $statements_analyzer->node_data->getType($const->value))
411
                && !$const_type->hasMixed()
412
            ) {
413
                $codebase->classlikes->setConstantType(
414
                    (string)$statements_analyzer->getFQCLN(),
415
                    $const->name->name,
416
                    $const_type,
417
                    $const_visibility
418
                );
419
            }
420
        }
421
    }
422
}
423