Conditions | 194 |
Paths | > 20000 |
Total Lines | 1023 |
Lines | 242 |
Ratio | 23.66 % |
Changes | 0 |
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:
If many parameters/temporary variables are present:
1 | <?php |
||
67 | public static function analyze( |
||
68 | StatementsAnalyzer $statements_analyzer, |
||
69 | $stmt, |
||
70 | $prop_name, |
||
71 | $assignment_value, |
||
72 | Type\Union $assignment_value_type, |
||
73 | Context $context, |
||
74 | $direct_assignment = true |
||
75 | ) { |
||
76 | $class_property_types = []; |
||
77 | |||
78 | $codebase = $statements_analyzer->getCodebase(); |
||
79 | |||
80 | $property_exists = false; |
||
81 | |||
82 | $property_ids = []; |
||
83 | |||
84 | if ($stmt instanceof PropertyProperty) { |
||
85 | if (!$context->self || !$stmt->default) { |
||
86 | return null; |
||
87 | } |
||
88 | |||
89 | $property_id = $context->self . '::$' . $prop_name; |
||
90 | $property_ids[] = $property_id; |
||
91 | |||
92 | $property_exists = true; |
||
93 | |||
94 | try { |
||
95 | $class_property_type = $codebase->properties->getPropertyType( |
||
96 | $property_id, |
||
97 | true, |
||
98 | $statements_analyzer, |
||
99 | $context |
||
100 | ); |
||
101 | } catch (\UnexpectedValueException $e) { |
||
102 | return false; |
||
103 | } |
||
104 | |||
105 | if ($class_property_type) { |
||
106 | $class_storage = $codebase->classlike_storage_provider->get($context->self); |
||
107 | |||
108 | $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( |
||
109 | $codebase, |
||
110 | clone $class_property_type, |
||
111 | $class_storage->name, |
||
112 | $class_storage->name, |
||
113 | $class_storage->parent_class |
||
114 | ); |
||
115 | } |
||
116 | |||
117 | $class_property_types[] = $class_property_type ?: Type::getMixed(); |
||
118 | |||
119 | $var_id = '$this->' . $prop_name; |
||
120 | } else { |
||
121 | if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { |
||
122 | return false; |
||
123 | } |
||
124 | |||
125 | $lhs_type = $statements_analyzer->node_data->getType($stmt->var); |
||
126 | |||
127 | if ($lhs_type === null) { |
||
128 | return null; |
||
129 | } |
||
130 | |||
131 | $lhs_var_id = ExpressionIdentifier::getVarId( |
||
132 | $stmt->var, |
||
133 | $statements_analyzer->getFQCLN(), |
||
134 | $statements_analyzer |
||
135 | ); |
||
136 | |||
137 | $var_id = ExpressionIdentifier::getVarId( |
||
138 | $stmt, |
||
139 | $statements_analyzer->getFQCLN(), |
||
140 | $statements_analyzer |
||
141 | ); |
||
142 | |||
143 | if ($var_id) { |
||
144 | $context->assigned_var_ids[$var_id] = true; |
||
145 | |||
146 | if ($direct_assignment && isset($context->protected_var_ids[$var_id])) { |
||
147 | if (IssueBuffer::accepts( |
||
148 | new LoopInvalidation( |
||
149 | 'Variable ' . $var_id . ' has already been assigned in a for/foreach loop', |
||
150 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
151 | ), |
||
152 | $statements_analyzer->getSuppressedIssues() |
||
153 | )) { |
||
154 | // fall through |
||
155 | } |
||
156 | } |
||
157 | } |
||
158 | |||
159 | if ($lhs_type->hasMixed()) { |
||
160 | View Code Duplication | if (!$context->collect_initializations |
|
|
|||
161 | && !$context->collect_mutations |
||
162 | && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() |
||
163 | && (!(($parent_source = $statements_analyzer->getSource()) |
||
164 | instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) |
||
165 | || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) |
||
166 | ) { |
||
167 | $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); |
||
168 | } |
||
169 | |||
170 | View Code Duplication | if ($stmt->name instanceof PhpParser\Node\Identifier) { |
|
171 | $codebase->analyzer->addMixedMemberName( |
||
172 | '$' . $stmt->name->name, |
||
173 | $context->calling_method_id ?: $statements_analyzer->getFileName() |
||
174 | ); |
||
175 | } |
||
176 | |||
177 | if (IssueBuffer::accepts( |
||
178 | new MixedPropertyAssignment( |
||
179 | $lhs_var_id . ' of type mixed cannot be assigned to', |
||
180 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
181 | ), |
||
182 | $statements_analyzer->getSuppressedIssues() |
||
183 | )) { |
||
184 | return false; |
||
185 | } |
||
186 | |||
187 | return null; |
||
188 | } |
||
189 | |||
190 | View Code Duplication | if (!$context->collect_initializations |
|
191 | && !$context->collect_mutations |
||
192 | && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() |
||
193 | && (!(($parent_source = $statements_analyzer->getSource()) |
||
194 | instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) |
||
195 | || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) |
||
196 | ) { |
||
197 | $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); |
||
198 | } |
||
199 | |||
200 | if ($lhs_type->isNull()) { |
||
201 | if (IssueBuffer::accepts( |
||
202 | new NullPropertyAssignment( |
||
203 | $lhs_var_id . ' of type null cannot be assigned to', |
||
204 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
205 | ), |
||
206 | $statements_analyzer->getSuppressedIssues() |
||
207 | )) { |
||
208 | return false; |
||
209 | } |
||
210 | |||
211 | return null; |
||
212 | } |
||
213 | |||
214 | if ($lhs_type->isNullable() && !$lhs_type->ignore_nullable_issues) { |
||
215 | if (IssueBuffer::accepts( |
||
216 | new PossiblyNullPropertyAssignment( |
||
217 | $lhs_var_id . ' with possibly null type \'' . $lhs_type . '\' cannot be assigned to', |
||
218 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
219 | ), |
||
220 | $statements_analyzer->getSuppressedIssues() |
||
221 | )) { |
||
222 | return false; |
||
223 | } |
||
224 | } |
||
225 | |||
226 | $has_regular_setter = false; |
||
227 | |||
228 | $invalid_assignment_types = []; |
||
229 | |||
230 | $has_valid_assignment_type = false; |
||
231 | |||
232 | $lhs_atomic_types = $lhs_type->getAtomicTypes(); |
||
233 | |||
234 | while ($lhs_atomic_types) { |
||
235 | $lhs_type_part = \array_pop($lhs_atomic_types); |
||
236 | |||
237 | if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) { |
||
238 | $lhs_atomic_types = \array_merge( |
||
239 | $lhs_atomic_types, |
||
240 | $lhs_type_part->as->getAtomicTypes() |
||
241 | ); |
||
242 | |||
243 | continue; |
||
244 | } |
||
245 | |||
246 | if ($lhs_type_part instanceof TNull) { |
||
247 | continue; |
||
248 | } |
||
249 | |||
250 | if ($lhs_type_part instanceof Type\Atomic\TFalse |
||
251 | && $lhs_type->ignore_falsable_issues |
||
252 | && count($lhs_type->getAtomicTypes()) > 1 |
||
253 | ) { |
||
254 | continue; |
||
255 | } |
||
256 | |||
257 | if (!$lhs_type_part instanceof TObject && !$lhs_type_part instanceof TNamedObject) { |
||
258 | $invalid_assignment_types[] = (string)$lhs_type_part; |
||
259 | |||
260 | continue; |
||
261 | } |
||
262 | |||
263 | $has_valid_assignment_type = true; |
||
264 | |||
265 | // stdClass and SimpleXMLElement are special cases where we cannot infer the return types |
||
266 | // but we don't want to throw an error |
||
267 | // Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164 |
||
268 | if ($lhs_type_part instanceof TObject || |
||
269 | ( |
||
270 | in_array( |
||
271 | strtolower($lhs_type_part->value), |
||
272 | ['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode'], |
||
273 | true |
||
274 | ) |
||
275 | ) |
||
276 | ) { |
||
277 | if ($var_id) { |
||
278 | if ($lhs_type_part instanceof TNamedObject && |
||
279 | strtolower($lhs_type_part->value) === 'stdclass' |
||
280 | ) { |
||
281 | $context->vars_in_scope[$var_id] = $assignment_value_type; |
||
282 | } else { |
||
283 | $context->vars_in_scope[$var_id] = Type::getMixed(); |
||
284 | } |
||
285 | } |
||
286 | |||
287 | return null; |
||
288 | } |
||
289 | |||
290 | if (ExpressionAnalyzer::isMock($lhs_type_part->value)) { |
||
291 | if ($var_id) { |
||
292 | $context->vars_in_scope[$var_id] = Type::getMixed(); |
||
293 | } |
||
294 | |||
295 | return null; |
||
296 | } |
||
297 | |||
298 | $intersection_types = $lhs_type_part->getIntersectionTypes() ?: []; |
||
299 | |||
300 | $fq_class_name = $lhs_type_part->value; |
||
301 | |||
302 | $override_property_visibility = false; |
||
303 | |||
304 | $class_exists = false; |
||
305 | $interface_exists = false; |
||
306 | |||
307 | if (!$codebase->classExists($lhs_type_part->value)) { |
||
308 | View Code Duplication | if ($codebase->interfaceExists($lhs_type_part->value)) { |
|
309 | $interface_exists = true; |
||
310 | $interface_storage = $codebase->classlike_storage_provider->get( |
||
311 | strtolower($lhs_type_part->value) |
||
312 | ); |
||
313 | |||
314 | $override_property_visibility = $interface_storage->override_property_visibility; |
||
315 | |||
316 | foreach ($intersection_types as $intersection_type) { |
||
317 | if ($intersection_type instanceof TNamedObject |
||
318 | && $codebase->classExists($intersection_type->value) |
||
319 | ) { |
||
320 | $fq_class_name = $intersection_type->value; |
||
321 | $class_exists = true; |
||
322 | break; |
||
323 | } |
||
324 | } |
||
325 | |||
326 | if (!$class_exists) { |
||
327 | if (IssueBuffer::accepts( |
||
328 | new NoInterfaceProperties( |
||
329 | 'Interfaces cannot have properties', |
||
330 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
331 | $lhs_type_part->value |
||
332 | ), |
||
333 | $statements_analyzer->getSuppressedIssues() |
||
334 | )) { |
||
335 | return null; |
||
336 | } |
||
337 | |||
338 | if (!$codebase->methods->methodExists( |
||
339 | new \Psalm\Internal\MethodIdentifier( |
||
340 | $fq_class_name, |
||
341 | '__set' |
||
342 | ) |
||
343 | )) { |
||
344 | return null; |
||
345 | } |
||
346 | } |
||
347 | } |
||
348 | |||
349 | if (!$class_exists && !$interface_exists) { |
||
350 | if (IssueBuffer::accepts( |
||
351 | new UndefinedClass( |
||
352 | 'Cannot set properties of undefined class ' . $lhs_type_part->value, |
||
353 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
354 | $lhs_type_part->value |
||
355 | ), |
||
356 | $statements_analyzer->getSuppressedIssues() |
||
357 | )) { |
||
358 | // fall through |
||
359 | } |
||
360 | |||
361 | return null; |
||
362 | } |
||
363 | } else { |
||
364 | $class_exists = true; |
||
365 | } |
||
366 | |||
367 | $property_id = $fq_class_name . '::$' . $prop_name; |
||
368 | $property_ids[] = $property_id; |
||
369 | |||
370 | $has_magic_setter = false; |
||
371 | |||
372 | $set_method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, '__set'); |
||
373 | |||
374 | if ((!$codebase->properties->propertyExists($property_id, false, $statements_analyzer, $context) |
||
375 | || ($lhs_var_id !== '$this' |
||
376 | && $fq_class_name !== $context->self |
||
377 | && ClassLikeAnalyzer::checkPropertyVisibility( |
||
378 | $property_id, |
||
379 | $context, |
||
380 | $statements_analyzer, |
||
381 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
382 | $statements_analyzer->getSuppressedIssues(), |
||
383 | false |
||
384 | ) !== true) |
||
385 | ) |
||
386 | && $codebase->methods->methodExists( |
||
387 | $set_method_id, |
||
388 | $context->calling_method_id, |
||
389 | $codebase->collect_locations |
||
390 | ? new CodeLocation($statements_analyzer->getSource(), $stmt) |
||
391 | : null, |
||
392 | !$context->collect_initializations |
||
393 | && !$context->collect_mutations |
||
394 | ? $statements_analyzer |
||
395 | : null, |
||
396 | $statements_analyzer->getFilePath() |
||
397 | ) |
||
398 | ) { |
||
399 | $has_magic_setter = true; |
||
400 | $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); |
||
401 | |||
402 | if ($var_id) { |
||
403 | if (isset($class_storage->pseudo_property_set_types['$' . $prop_name])) { |
||
404 | $class_property_types[] = |
||
405 | clone $class_storage->pseudo_property_set_types['$' . $prop_name]; |
||
406 | |||
407 | $has_regular_setter = true; |
||
408 | $property_exists = true; |
||
409 | |||
410 | if (!$context->collect_initializations) { |
||
411 | self::taintProperty( |
||
412 | $statements_analyzer, |
||
413 | $stmt, |
||
414 | $property_id, |
||
415 | $class_storage, |
||
416 | $assignment_value_type, |
||
417 | $context |
||
418 | ); |
||
419 | } |
||
420 | |||
421 | continue; |
||
422 | } |
||
423 | } |
||
424 | |||
425 | if ($assignment_value) { |
||
426 | if ($var_id) { |
||
427 | $context->removeVarFromConflictingClauses( |
||
428 | $var_id, |
||
429 | Type::getMixed(), |
||
430 | $statements_analyzer |
||
431 | ); |
||
432 | |||
433 | unset($context->vars_in_scope[$var_id]); |
||
434 | } |
||
435 | |||
436 | $old_data_provider = $statements_analyzer->node_data; |
||
437 | |||
438 | $statements_analyzer->node_data = clone $statements_analyzer->node_data; |
||
439 | |||
440 | $fake_method_call = new PhpParser\Node\Expr\MethodCall( |
||
441 | $stmt->var, |
||
442 | new PhpParser\Node\Identifier('__set', $stmt->name->getAttributes()), |
||
443 | [ |
||
444 | new PhpParser\Node\Arg( |
||
445 | new PhpParser\Node\Scalar\String_( |
||
446 | $prop_name, |
||
447 | $stmt->name->getAttributes() |
||
448 | ) |
||
449 | ), |
||
450 | new PhpParser\Node\Arg( |
||
451 | $assignment_value |
||
452 | ) |
||
453 | ] |
||
454 | ); |
||
455 | |||
456 | $suppressed_issues = $statements_analyzer->getSuppressedIssues(); |
||
457 | |||
458 | if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { |
||
459 | $statements_analyzer->addSuppressedIssues(['PossiblyNullReference']); |
||
460 | } |
||
461 | |||
462 | \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( |
||
463 | $statements_analyzer, |
||
464 | $fake_method_call, |
||
465 | $context, |
||
466 | false |
||
467 | ); |
||
468 | |||
469 | if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { |
||
470 | $statements_analyzer->removeSuppressedIssues(['PossiblyNullReference']); |
||
471 | } |
||
472 | |||
473 | $statements_analyzer->node_data = $old_data_provider; |
||
474 | } |
||
475 | |||
476 | /* |
||
477 | * If we have an explicit list of all allowed magic properties on the class, and we're |
||
478 | * not in that list, fall through |
||
479 | */ |
||
480 | if (!$var_id || !$class_storage->sealed_properties) { |
||
481 | if (!$context->collect_initializations) { |
||
482 | self::taintProperty( |
||
483 | $statements_analyzer, |
||
484 | $stmt, |
||
485 | $property_id, |
||
486 | $class_storage, |
||
487 | $assignment_value_type, |
||
488 | $context |
||
489 | ); |
||
490 | } |
||
491 | |||
492 | continue; |
||
493 | } |
||
494 | |||
495 | if (!$class_exists) { |
||
496 | if (IssueBuffer::accepts( |
||
497 | new UndefinedMagicPropertyAssignment( |
||
498 | 'Magic instance property ' . $property_id . ' is not defined', |
||
499 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
500 | $property_id |
||
501 | ), |
||
502 | $statements_analyzer->getSuppressedIssues() |
||
503 | )) { |
||
504 | // fall through |
||
505 | } |
||
506 | } |
||
507 | } |
||
508 | |||
509 | if (!$class_exists) { |
||
510 | continue; |
||
511 | } |
||
512 | |||
513 | $has_regular_setter = true; |
||
514 | |||
515 | if ($stmt->var instanceof PhpParser\Node\Expr\Variable |
||
516 | && $stmt->var->name === 'this' |
||
517 | && $context->self |
||
518 | ) { |
||
519 | $self_property_id = $context->self . '::$' . $prop_name; |
||
520 | |||
521 | if ($self_property_id !== $property_id |
||
522 | && $codebase->properties->propertyExists( |
||
523 | $self_property_id, |
||
524 | false, |
||
525 | $statements_analyzer, |
||
526 | $context |
||
527 | ) |
||
528 | ) { |
||
529 | $property_id = $self_property_id; |
||
530 | } |
||
531 | } |
||
532 | |||
533 | if ($codebase->taint && !$context->collect_initializations) { |
||
534 | $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); |
||
535 | |||
536 | self::taintProperty( |
||
537 | $statements_analyzer, |
||
538 | $stmt, |
||
539 | $property_id, |
||
540 | $class_storage, |
||
541 | $assignment_value_type, |
||
542 | $context |
||
543 | ); |
||
544 | } |
||
545 | |||
546 | if (!$codebase->properties->propertyExists( |
||
547 | $property_id, |
||
548 | false, |
||
549 | $statements_analyzer, |
||
550 | $context, |
||
551 | new CodeLocation($statements_analyzer->getSource(), $stmt) |
||
552 | )) { |
||
553 | if ($stmt->var instanceof PhpParser\Node\Expr\Variable && $stmt->var->name === 'this') { |
||
554 | // if this is a proper error, we'll see it on the first pass |
||
555 | if ($context->collect_mutations) { |
||
556 | continue; |
||
557 | } |
||
558 | |||
559 | if (IssueBuffer::accepts( |
||
560 | new UndefinedThisPropertyAssignment( |
||
561 | 'Instance property ' . $property_id . ' is not defined', |
||
562 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
563 | $property_id |
||
564 | ), |
||
565 | $statements_analyzer->getSuppressedIssues() |
||
566 | )) { |
||
567 | // fall through |
||
568 | } |
||
569 | View Code Duplication | } else { |
|
570 | if ($has_magic_setter) { |
||
571 | if (IssueBuffer::accepts( |
||
572 | new UndefinedMagicPropertyAssignment( |
||
573 | 'Magic instance property ' . $property_id . ' is not defined', |
||
574 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
575 | $property_id |
||
576 | ), |
||
577 | $statements_analyzer->getSuppressedIssues() |
||
578 | )) { |
||
579 | // fall through |
||
580 | } |
||
581 | } else { |
||
582 | if (IssueBuffer::accepts( |
||
583 | new UndefinedPropertyAssignment( |
||
584 | 'Instance property ' . $property_id . ' is not defined', |
||
585 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
586 | $property_id |
||
587 | ), |
||
588 | $statements_analyzer->getSuppressedIssues() |
||
589 | )) { |
||
590 | // fall through |
||
591 | } |
||
592 | } |
||
593 | } |
||
594 | |||
595 | continue; |
||
596 | } |
||
597 | |||
598 | View Code Duplication | if ($codebase->store_node_types |
|
599 | && !$context->collect_initializations |
||
600 | && !$context->collect_mutations |
||
601 | ) { |
||
602 | $codebase->analyzer->addNodeReference( |
||
603 | $statements_analyzer->getFilePath(), |
||
604 | $stmt->name, |
||
605 | $property_id |
||
606 | ); |
||
607 | } |
||
608 | |||
609 | $property_exists = true; |
||
610 | |||
611 | if (!$override_property_visibility) { |
||
612 | if (!$context->collect_mutations) { |
||
613 | View Code Duplication | if (ClassLikeAnalyzer::checkPropertyVisibility( |
|
614 | $property_id, |
||
615 | $context, |
||
616 | $statements_analyzer, |
||
617 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
618 | $statements_analyzer->getSuppressedIssues() |
||
619 | ) === false) { |
||
620 | return false; |
||
621 | } |
||
622 | View Code Duplication | } else { |
|
623 | if (ClassLikeAnalyzer::checkPropertyVisibility( |
||
624 | $property_id, |
||
625 | $context, |
||
626 | $statements_analyzer, |
||
627 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
628 | $statements_analyzer->getSuppressedIssues(), |
||
629 | false |
||
630 | ) !== true) { |
||
631 | continue; |
||
632 | } |
||
633 | } |
||
634 | } |
||
635 | |||
636 | $declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty( |
||
637 | $property_id, |
||
638 | false |
||
639 | ); |
||
640 | |||
641 | View Code Duplication | if ($codebase->properties_to_rename) { |
|
642 | $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name; |
||
643 | |||
644 | foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) { |
||
645 | if ($declaring_property_id === $original_property_id) { |
||
646 | $file_manipulations = [ |
||
647 | new \Psalm\FileManipulation( |
||
648 | (int) $stmt->name->getAttribute('startFilePos'), |
||
649 | (int) $stmt->name->getAttribute('endFilePos') + 1, |
||
650 | $new_property_name |
||
651 | ) |
||
652 | ]; |
||
653 | |||
654 | \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( |
||
655 | $statements_analyzer->getFilePath(), |
||
656 | $file_manipulations |
||
657 | ); |
||
658 | } |
||
659 | } |
||
660 | } |
||
661 | |||
662 | $declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); |
||
663 | |||
664 | if (isset($declaring_class_storage->properties[$prop_name])) { |
||
665 | $property_storage = $declaring_class_storage->properties[$prop_name]; |
||
666 | |||
667 | if ($property_storage->deprecated) { |
||
668 | if (IssueBuffer::accepts( |
||
669 | new DeprecatedProperty( |
||
670 | $property_id . ' is marked deprecated', |
||
671 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
672 | $property_id |
||
673 | ), |
||
674 | $statements_analyzer->getSuppressedIssues() |
||
675 | )) { |
||
676 | // fall through |
||
677 | } |
||
678 | } |
||
679 | |||
680 | View Code Duplication | if ($property_storage->psalm_internal && $context->self) { |
|
681 | if (! NamespaceAnalyzer::isWithin($context->self, $property_storage->psalm_internal)) { |
||
682 | if (IssueBuffer::accepts( |
||
683 | new InternalProperty( |
||
684 | $property_id . ' is marked internal to ' . $property_storage->psalm_internal, |
||
685 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
686 | $property_id |
||
687 | ), |
||
688 | $statements_analyzer->getSuppressedIssues() |
||
689 | )) { |
||
690 | // fall through |
||
691 | } |
||
692 | } |
||
693 | } |
||
694 | |||
695 | if ($property_storage->internal && $context->self) { |
||
696 | if (! NamespaceAnalyzer::nameSpaceRootsMatch($context->self, $declaring_property_class)) { |
||
697 | if (IssueBuffer::accepts( |
||
698 | new InternalProperty( |
||
699 | $property_id . ' is marked internal', |
||
700 | new CodeLocation($statements_analyzer->getSource(), $stmt), |
||
701 | $property_id |
||
702 | ), |
||
703 | $statements_analyzer->getSuppressedIssues() |
||
704 | )) { |
||
705 | // fall through |
||
706 | } |
||
707 | } |
||
708 | } |
||
709 | |||
710 | // prevents writing to readonly properties |
||
711 | if ($property_storage->readonly) { |
||
712 | $appearing_property_class = $codebase->properties->getAppearingClassForProperty( |
||
713 | $property_id, |
||
714 | true |
||
715 | ); |
||
716 | |||
717 | $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); |
||
718 | |||
719 | $property_var_pure_compatible = $stmt_var_type |
||
720 | && $stmt_var_type->reference_free |
||
721 | && $stmt_var_type->allow_mutations; |
||
722 | |||
723 | if ($appearing_property_class) { |
||
724 | $can_set_property = $context->self |
||
725 | && $context->calling_method_id |
||
726 | && ($appearing_property_class === $context->self |
||
727 | || $codebase->classExtends($context->self, $appearing_property_class)) |
||
728 | && (\strpos($context->calling_method_id, '::__construct') |
||
729 | || \strpos($context->calling_method_id, '::unserialize') |
||
730 | || \strpos($context->calling_method_id, '::__unserialize') |
||
731 | || $property_storage->allow_private_mutation |
||
732 | || $property_var_pure_compatible); |
||
733 | |||
734 | if (!$can_set_property) { |
||
735 | if (IssueBuffer::accepts( |
||
736 | new InaccessibleProperty( |
||
737 | $property_id . ' is marked readonly', |
||
738 | new CodeLocation($statements_analyzer->getSource(), $stmt) |
||
739 | ), |
||
740 | $statements_analyzer->getSuppressedIssues() |
||
741 | )) { |
||
742 | // fall through |
||
743 | } |
||
744 | } elseif ($declaring_class_storage->mutation_free) { |
||
745 | $visitor = new \Psalm\Internal\TypeVisitor\ImmutablePropertyAssignmentVisitor( |
||
746 | $statements_analyzer, |
||
747 | $stmt |
||
748 | ); |
||
749 | |||
750 | $visitor->traverse($assignment_value_type); |
||
751 | } |
||
752 | } |
||
753 | } |
||
754 | |||
755 | if ($property_storage->getter_method) { |
||
756 | $getter_id = $lhs_var_id . '->' . $property_storage->getter_method . '()'; |
||
757 | |||
758 | unset($context->vars_in_scope[$getter_id]); |
||
759 | } |
||
760 | } |
||
761 | |||
762 | $class_property_type = $codebase->properties->getPropertyType( |
||
763 | $property_id, |
||
764 | true, |
||
765 | $statements_analyzer, |
||
766 | $context |
||
767 | ); |
||
768 | |||
769 | if (!$class_property_type |
||
770 | || (isset($declaring_class_storage->properties[$prop_name]) |
||
771 | && !$declaring_class_storage->properties[$prop_name]->type_location) |
||
772 | ) { |
||
773 | if (!$class_property_type) { |
||
774 | $class_property_type = Type::getMixed(); |
||
775 | } |
||
776 | |||
777 | $source_analyzer = $statements_analyzer->getSource()->getSource(); |
||
778 | |||
779 | View Code Duplication | if ($lhs_var_id === '$this' |
|
780 | && $source_analyzer instanceof ClassAnalyzer |
||
781 | ) { |
||
782 | if (isset($source_analyzer->inferred_property_types[$prop_name])) { |
||
783 | $source_analyzer->inferred_property_types[$prop_name] = Type::combineUnionTypes( |
||
784 | $assignment_value_type, |
||
785 | $source_analyzer->inferred_property_types[$prop_name] |
||
786 | ); |
||
787 | } else { |
||
788 | $source_analyzer->inferred_property_types[$prop_name] = $assignment_value_type; |
||
789 | } |
||
790 | } |
||
791 | } |
||
792 | |||
793 | if (!$class_property_type->isMixed()) { |
||
794 | $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( |
||
795 | $codebase, |
||
796 | clone $class_property_type, |
||
797 | $fq_class_name, |
||
798 | $lhs_type_part, |
||
799 | $declaring_class_storage->parent_class |
||
800 | ); |
||
801 | |||
802 | $class_property_type = \Psalm\Internal\Codebase\Methods::localizeType( |
||
803 | $codebase, |
||
804 | $class_property_type, |
||
805 | $fq_class_name, |
||
806 | $declaring_property_class |
||
807 | ); |
||
808 | |||
809 | if ($lhs_type_part instanceof Type\Atomic\TGenericObject) { |
||
810 | $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); |
||
811 | |||
812 | $class_property_type = InstancePropertyFetchAnalyzer::localizePropertyType( |
||
813 | $codebase, |
||
814 | $class_property_type, |
||
815 | $lhs_type_part, |
||
816 | $class_storage, |
||
817 | $declaring_class_storage |
||
818 | ); |
||
819 | } |
||
820 | |||
821 | $assignment_value_type = \Psalm\Internal\Codebase\Methods::localizeType( |
||
822 | $codebase, |
||
823 | $assignment_value_type, |
||
824 | $fq_class_name, |
||
825 | $declaring_property_class |
||
826 | ); |
||
827 | |||
828 | if (!$class_property_type->hasMixed() && $assignment_value_type->hasMixed()) { |
||
829 | if (IssueBuffer::accepts( |
||
830 | new MixedAssignment( |
||
831 | 'Cannot assign' . ($var_id ? ' ' . $var_id . ' ' : ' ') . 'to a mixed type', |
||
832 | new CodeLocation($statements_analyzer->getSource(), $stmt) |
||
833 | ), |
||
834 | $statements_analyzer->getSuppressedIssues() |
||
835 | )) { |
||
836 | // fall through |
||
837 | } |
||
838 | } |
||
839 | } |
||
840 | |||
841 | $class_property_types[] = $class_property_type; |
||
842 | } |
||
843 | |||
844 | if ($invalid_assignment_types) { |
||
845 | $invalid_assignment_type = $invalid_assignment_types[0]; |
||
846 | |||
847 | if (!$has_valid_assignment_type) { |
||
848 | if (IssueBuffer::accepts( |
||
849 | new InvalidPropertyAssignment( |
||
850 | $lhs_var_id . ' with non-object type \'' . $invalid_assignment_type . |
||
851 | '\' cannot treated as an object', |
||
852 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
853 | ), |
||
854 | $statements_analyzer->getSuppressedIssues() |
||
855 | )) { |
||
856 | return false; |
||
857 | } |
||
858 | } else { |
||
859 | if (IssueBuffer::accepts( |
||
860 | new PossiblyInvalidPropertyAssignment( |
||
861 | $lhs_var_id . ' with possible non-object type \'' . $invalid_assignment_type . |
||
862 | '\' cannot treated as an object', |
||
863 | new CodeLocation($statements_analyzer->getSource(), $stmt->var) |
||
864 | ), |
||
865 | $statements_analyzer->getSuppressedIssues() |
||
866 | )) { |
||
867 | return false; |
||
868 | } |
||
869 | } |
||
870 | } |
||
871 | |||
872 | if (!$has_regular_setter) { |
||
873 | return null; |
||
874 | } |
||
875 | |||
876 | if ($var_id) { |
||
877 | if ($context->collect_initializations |
||
878 | && $lhs_var_id === '$this' |
||
879 | ) { |
||
880 | $assignment_value_type->initialized_class = $context->self; |
||
881 | } |
||
882 | |||
883 | // because we don't want to be assigning for property declarations |
||
884 | $context->vars_in_scope[$var_id] = $assignment_value_type; |
||
885 | } |
||
886 | } |
||
887 | |||
888 | if (!$property_exists) { |
||
889 | return null; |
||
890 | } |
||
891 | |||
892 | if ($assignment_value_type->hasMixed()) { |
||
893 | return null; |
||
894 | } |
||
895 | |||
896 | $invalid_assignment_value_types = []; |
||
897 | |||
898 | $has_valid_assignment_value_type = false; |
||
899 | |||
900 | if ($codebase->store_node_types |
||
901 | && !$context->collect_initializations |
||
902 | && !$context->collect_mutations |
||
903 | && count($class_property_types) === 1 |
||
904 | ) { |
||
905 | $codebase->analyzer->addNodeType( |
||
906 | $statements_analyzer->getFilePath(), |
||
907 | $stmt->name, |
||
908 | $class_property_types[0]->getId() |
||
909 | ); |
||
910 | } |
||
911 | |||
912 | foreach ($class_property_types as $class_property_type) { |
||
913 | if ($class_property_type->hasMixed()) { |
||
914 | continue; |
||
915 | } |
||
916 | |||
917 | $union_comparison_results = new \Psalm\Internal\Analyzer\TypeComparisonResult(); |
||
918 | |||
919 | $type_match_found = TypeAnalyzer::isContainedBy( |
||
920 | $codebase, |
||
921 | $assignment_value_type, |
||
922 | $class_property_type, |
||
923 | true, |
||
924 | true, |
||
925 | $union_comparison_results |
||
926 | ); |
||
927 | |||
928 | if ($type_match_found && $union_comparison_results->replacement_union_type) { |
||
929 | if ($var_id) { |
||
930 | $context->vars_in_scope[$var_id] = $union_comparison_results->replacement_union_type; |
||
931 | } |
||
932 | } |
||
933 | |||
934 | View Code Duplication | if ($union_comparison_results->type_coerced) { |
|
935 | if ($union_comparison_results->type_coerced_from_mixed) { |
||
936 | if (IssueBuffer::accepts( |
||
937 | new MixedPropertyTypeCoercion( |
||
938 | $var_id . ' expects \'' . $class_property_type->getId() . '\', ' |
||
939 | . ' parent type `' . $assignment_value_type->getId() . '` provided', |
||
940 | new CodeLocation( |
||
941 | $statements_analyzer->getSource(), |
||
942 | $assignment_value ?: $stmt, |
||
943 | $context->include_location |
||
944 | ), |
||
945 | $property_ids[0] |
||
946 | ), |
||
947 | $statements_analyzer->getSuppressedIssues() |
||
948 | )) { |
||
949 | // keep soldiering on |
||
950 | } |
||
951 | } else { |
||
952 | if (IssueBuffer::accepts( |
||
953 | new PropertyTypeCoercion( |
||
954 | $var_id . ' expects \'' . $class_property_type->getId() . '\', ' |
||
955 | . ' parent type \'' . $assignment_value_type->getId() . '\' provided', |
||
956 | new CodeLocation( |
||
957 | $statements_analyzer->getSource(), |
||
958 | $assignment_value ?: $stmt, |
||
959 | $context->include_location |
||
960 | ), |
||
961 | $property_ids[0] |
||
962 | ), |
||
963 | $statements_analyzer->getSuppressedIssues() |
||
964 | )) { |
||
965 | // keep soldiering on |
||
966 | } |
||
967 | } |
||
968 | } |
||
969 | |||
970 | if ($union_comparison_results->to_string_cast) { |
||
971 | if (IssueBuffer::accepts( |
||
972 | new ImplicitToStringCast( |
||
973 | $var_id . ' expects \'' . $class_property_type . '\', ' |
||
974 | . '\'' . $assignment_value_type . '\' provided with a __toString method', |
||
975 | new CodeLocation( |
||
976 | $statements_analyzer->getSource(), |
||
977 | $assignment_value ?: $stmt, |
||
978 | $context->include_location |
||
979 | ) |
||
980 | ), |
||
981 | $statements_analyzer->getSuppressedIssues() |
||
982 | )) { |
||
983 | // fall through |
||
984 | } |
||
985 | } |
||
986 | |||
987 | if (!$type_match_found && !$union_comparison_results->type_coerced) { |
||
988 | if (TypeAnalyzer::canBeContainedBy( |
||
989 | $codebase, |
||
990 | $assignment_value_type, |
||
991 | $class_property_type, |
||
992 | true, |
||
993 | true |
||
994 | )) { |
||
995 | $has_valid_assignment_value_type = true; |
||
996 | } |
||
997 | |||
998 | $invalid_assignment_value_types[] = $class_property_type->getId(); |
||
999 | } else { |
||
1000 | $has_valid_assignment_value_type = true; |
||
1001 | } |
||
1002 | |||
1003 | if ($type_match_found) { |
||
1004 | View Code Duplication | if (!$assignment_value_type->ignore_nullable_issues |
|
1005 | && $assignment_value_type->isNullable() |
||
1006 | && !$class_property_type->isNullable() |
||
1007 | ) { |
||
1008 | if (IssueBuffer::accepts( |
||
1009 | new PossiblyNullPropertyAssignmentValue( |
||
1010 | $var_id . ' with non-nullable declared type \'' . $class_property_type . |
||
1011 | '\' cannot be assigned nullable type \'' . $assignment_value_type . '\'', |
||
1012 | new CodeLocation( |
||
1013 | $statements_analyzer->getSource(), |
||
1014 | $assignment_value ?: $stmt, |
||
1015 | $context->include_location |
||
1016 | ), |
||
1017 | $property_ids[0] |
||
1018 | ), |
||
1019 | $statements_analyzer->getSuppressedIssues() |
||
1020 | )) { |
||
1021 | return false; |
||
1022 | } |
||
1023 | } |
||
1024 | |||
1025 | View Code Duplication | if (!$assignment_value_type->ignore_falsable_issues |
|
1026 | && $assignment_value_type->isFalsable() |
||
1027 | && !$class_property_type->hasBool() |
||
1028 | && !$class_property_type->hasScalar() |
||
1029 | ) { |
||
1030 | if (IssueBuffer::accepts( |
||
1031 | new PossiblyFalsePropertyAssignmentValue( |
||
1032 | $var_id . ' with non-falsable declared type \'' . $class_property_type . |
||
1033 | '\' cannot be assigned possibly false type \'' . $assignment_value_type . '\'', |
||
1034 | new CodeLocation( |
||
1035 | $statements_analyzer->getSource(), |
||
1036 | $assignment_value ?: $stmt, |
||
1037 | $context->include_location |
||
1038 | ), |
||
1039 | $property_ids[0] |
||
1040 | ), |
||
1041 | $statements_analyzer->getSuppressedIssues() |
||
1042 | )) { |
||
1043 | return false; |
||
1044 | } |
||
1045 | } |
||
1046 | } |
||
1047 | } |
||
1048 | |||
1049 | if ($invalid_assignment_value_types) { |
||
1050 | $invalid_class_property_type = $invalid_assignment_value_types[0]; |
||
1051 | |||
1052 | if (!$has_valid_assignment_value_type) { |
||
1053 | if (IssueBuffer::accepts( |
||
1054 | new InvalidPropertyAssignmentValue( |
||
1055 | $var_id . ' with declared type \'' . $invalid_class_property_type . |
||
1056 | '\' cannot be assigned type \'' . $assignment_value_type->getId() . '\'', |
||
1057 | new CodeLocation( |
||
1058 | $statements_analyzer->getSource(), |
||
1059 | $assignment_value ?: $stmt, |
||
1060 | $context->include_location |
||
1061 | ), |
||
1062 | $property_ids[0] |
||
1063 | ), |
||
1064 | $statements_analyzer->getSuppressedIssues() |
||
1065 | )) { |
||
1066 | return false; |
||
1067 | } |
||
1068 | } else { |
||
1069 | if (IssueBuffer::accepts( |
||
1070 | new PossiblyInvalidPropertyAssignmentValue( |
||
1071 | $var_id . ' with declared type \'' . $invalid_class_property_type . |
||
1072 | '\' cannot be assigned possibly different type \'' . |
||
1073 | $assignment_value_type->getId() . '\'', |
||
1074 | new CodeLocation( |
||
1075 | $statements_analyzer->getSource(), |
||
1076 | $assignment_value ?: $stmt, |
||
1077 | $context->include_location |
||
1078 | ), |
||
1079 | $property_ids[0] |
||
1080 | ), |
||
1081 | $statements_analyzer->getSuppressedIssues() |
||
1082 | )) { |
||
1083 | return false; |
||
1084 | } |
||
1085 | } |
||
1086 | } |
||
1087 | |||
1088 | return null; |
||
1089 | } |
||
1090 | |||
1230 |
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.