| Conditions | 74 |
| Paths | > 20000 |
| Total Lines | 463 |
| Lines | 0 |
| Ratio | 0 % |
| 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:
Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.
There are several approaches to avoid long parameter lists:
| 1 | <?php |
||
| 37 | public static function analyze( |
||
| 38 | StatementsAnalyzer $statements_analyzer, |
||
| 39 | Codebase $codebase, |
||
| 40 | PhpParser\Node\Stmt\Switch_ $stmt, |
||
| 41 | $switch_var_id, |
||
| 42 | PhpParser\Node\Stmt\Case_ $case, |
||
| 43 | Context $context, |
||
| 44 | Context $original_context, |
||
| 45 | string $case_exit_type, |
||
| 46 | array $case_actions, |
||
| 47 | bool $is_last, |
||
| 48 | SwitchScope $switch_scope |
||
| 49 | ) { |
||
| 50 | // has a return/throw at end |
||
| 51 | $has_ending_statements = $case_actions === [ScopeAnalyzer::ACTION_END]; |
||
| 52 | $has_leaving_statements = $has_ending_statements |
||
| 53 | || (count($case_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $case_actions, true)); |
||
| 54 | |||
| 55 | $case_context = clone $original_context; |
||
| 56 | |||
| 57 | if ($codebase->alter_code) { |
||
| 58 | $case_context->branch_point = $case_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); |
||
| 59 | } |
||
| 60 | |||
| 61 | $case_context->parent_context = $context; |
||
| 62 | $case_scope = $case_context->case_scope = new CaseScope($case_context); |
||
| 63 | |||
| 64 | $case_equality_expr = null; |
||
| 65 | |||
| 66 | $old_node_data = $statements_analyzer->node_data; |
||
| 67 | |||
| 68 | $fake_switch_condition = false; |
||
| 69 | |||
| 70 | if ($switch_var_id && substr($switch_var_id, 0, 15) === '$__tmp_switch__') { |
||
| 71 | $switch_condition = new PhpParser\Node\Expr\Variable( |
||
| 72 | substr($switch_var_id, 1), |
||
| 73 | $stmt->cond->getAttributes() |
||
| 74 | ); |
||
| 75 | |||
| 76 | $fake_switch_condition = true; |
||
| 77 | } else { |
||
| 78 | $switch_condition = $stmt->cond; |
||
| 79 | } |
||
| 80 | |||
| 81 | if ($case->cond) { |
||
| 82 | $was_inside_conditional = $case_context->inside_conditional; |
||
| 83 | $case_context->inside_conditional = true; |
||
| 84 | |||
| 85 | if (ExpressionAnalyzer::analyze($statements_analyzer, $case->cond, $case_context) === false) { |
||
| 86 | /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ |
||
| 87 | $case_scope->parent_context = null; |
||
| 88 | $case_context->case_scope = null; |
||
| 89 | $case_context->parent_context = null; |
||
| 90 | |||
| 91 | return false; |
||
| 92 | } |
||
| 93 | |||
| 94 | if (!$was_inside_conditional) { |
||
| 95 | $case_context->inside_conditional = false; |
||
| 96 | } |
||
| 97 | |||
| 98 | $statements_analyzer->node_data = clone $statements_analyzer->node_data; |
||
| 99 | |||
| 100 | $traverser = new PhpParser\NodeTraverser; |
||
| 101 | $traverser->addVisitor( |
||
| 102 | new \Psalm\Internal\PhpVisitor\ConditionCloningVisitor( |
||
| 103 | $statements_analyzer->node_data |
||
| 104 | ) |
||
| 105 | ); |
||
| 106 | |||
| 107 | /** @var PhpParser\Node\Expr */ |
||
| 108 | $switch_condition = $traverser->traverse([$switch_condition])[0]; |
||
| 109 | |||
| 110 | if ($fake_switch_condition) { |
||
| 111 | $statements_analyzer->node_data->setType( |
||
| 112 | $switch_condition, |
||
| 113 | $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed() |
||
| 114 | ); |
||
| 115 | } |
||
| 116 | |||
| 117 | if ($switch_condition instanceof PhpParser\Node\Expr\Variable |
||
| 118 | && is_string($switch_condition->name) |
||
| 119 | && isset($context->vars_in_scope['$' . $switch_condition->name]) |
||
| 120 | ) { |
||
| 121 | $switch_var_type = $context->vars_in_scope['$' . $switch_condition->name]; |
||
| 122 | |||
| 123 | $type_statements = []; |
||
| 124 | |||
| 125 | foreach ($switch_var_type->getAtomicTypes() as $type) { |
||
| 126 | if ($type instanceof Type\Atomic\GetClassT) { |
||
| 127 | $type_statements[] = new PhpParser\Node\Expr\FuncCall( |
||
| 128 | new PhpParser\Node\Name(['get_class']), |
||
| 129 | [ |
||
| 130 | new PhpParser\Node\Arg( |
||
| 131 | new PhpParser\Node\Expr\Variable(substr($type->typeof, 1)) |
||
| 132 | ), |
||
| 133 | ] |
||
| 134 | ); |
||
| 135 | } elseif ($type instanceof Type\Atomic\GetTypeT) { |
||
| 136 | $type_statements[] = new PhpParser\Node\Expr\FuncCall( |
||
| 137 | new PhpParser\Node\Name(['gettype']), |
||
| 138 | [ |
||
| 139 | new PhpParser\Node\Arg( |
||
| 140 | new PhpParser\Node\Expr\Variable(substr($type->typeof, 1)) |
||
| 141 | ), |
||
| 142 | ] |
||
| 143 | ); |
||
| 144 | } else { |
||
| 145 | $type_statements = null; |
||
| 146 | break; |
||
| 147 | } |
||
| 148 | } |
||
| 149 | |||
| 150 | if ($type_statements && count($type_statements) === 1) { |
||
| 151 | $switch_condition = $type_statements[0]; |
||
| 152 | |||
| 153 | if ($fake_switch_condition) { |
||
| 154 | $statements_analyzer->node_data->setType( |
||
| 155 | $switch_condition, |
||
| 156 | $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed() |
||
| 157 | ); |
||
| 158 | } |
||
| 159 | } |
||
| 160 | } |
||
| 161 | |||
| 162 | if (($switch_condition_type = $statements_analyzer->node_data->getType($switch_condition)) |
||
| 163 | && ($case_cond_type = $statements_analyzer->node_data->getType($case->cond)) |
||
| 164 | && (($switch_condition_type->isString() && $case_cond_type->isString()) |
||
| 165 | || ($switch_condition_type->isInt() && $case_cond_type->isInt()) |
||
| 166 | || ($switch_condition_type->isFloat() && $case_cond_type->isFloat()) |
||
| 167 | ) |
||
| 168 | ) { |
||
| 169 | $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Identical( |
||
| 170 | $switch_condition, |
||
| 171 | $case->cond, |
||
| 172 | $case->cond->getAttributes() |
||
| 173 | ); |
||
| 174 | } else { |
||
| 175 | $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Equal( |
||
| 176 | $switch_condition, |
||
| 177 | $case->cond, |
||
| 178 | $case->cond->getAttributes() |
||
| 179 | ); |
||
| 180 | } |
||
| 181 | } |
||
| 182 | |||
| 183 | $continue_case_equality_expr = false; |
||
| 184 | |||
| 185 | if ($case->stmts) { |
||
| 186 | $case_stmts = array_merge($switch_scope->leftover_statements, $case->stmts); |
||
| 187 | } else { |
||
| 188 | $continue_case_equality_expr = count($switch_scope->leftover_statements) === 1; |
||
| 189 | $case_stmts = $switch_scope->leftover_statements; |
||
| 190 | } |
||
| 191 | |||
| 192 | if (!$has_leaving_statements && !$is_last) { |
||
| 193 | if (!$case_equality_expr) { |
||
| 194 | $case_equality_expr = new PhpParser\Node\Expr\FuncCall( |
||
| 195 | new PhpParser\Node\Name\FullyQualified(['rand']), |
||
| 196 | [ |
||
| 197 | new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)), |
||
| 198 | new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)), |
||
| 199 | ], |
||
| 200 | $case->getAttributes() |
||
| 201 | ); |
||
| 202 | } |
||
| 203 | |||
| 204 | $switch_scope->leftover_case_equality_expr = $switch_scope->leftover_case_equality_expr |
||
| 205 | ? new PhpParser\Node\Expr\BinaryOp\BooleanOr( |
||
| 206 | $switch_scope->leftover_case_equality_expr, |
||
| 207 | $case_equality_expr, |
||
| 208 | $case->cond ? $case->cond->getAttributes() : $case->getAttributes() |
||
| 209 | ) |
||
| 210 | : $case_equality_expr; |
||
| 211 | |||
| 212 | if ($continue_case_equality_expr |
||
| 213 | && $switch_scope->leftover_statements[0] instanceof PhpParser\Node\Stmt\If_ |
||
| 214 | ) { |
||
| 215 | $case_if_stmt = $switch_scope->leftover_statements[0]; |
||
| 216 | $case_if_stmt->cond = $switch_scope->leftover_case_equality_expr; |
||
| 217 | } else { |
||
| 218 | $case_if_stmt = new PhpParser\Node\Stmt\If_( |
||
| 219 | $switch_scope->leftover_case_equality_expr, |
||
| 220 | ['stmts' => $case_stmts] |
||
| 221 | ); |
||
| 222 | |||
| 223 | $switch_scope->leftover_statements = [$case_if_stmt]; |
||
| 224 | } |
||
| 225 | |||
| 226 | /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ |
||
| 227 | $case_scope->parent_context = null; |
||
| 228 | $case_context->case_scope = null; |
||
| 229 | $case_context->parent_context = null; |
||
| 230 | |||
| 231 | $statements_analyzer->node_data = $old_node_data; |
||
| 232 | |||
| 233 | return; |
||
| 234 | } |
||
| 235 | |||
| 236 | if ($switch_scope->leftover_case_equality_expr) { |
||
| 237 | $case_or_default_equality_expr = $case_equality_expr; |
||
| 238 | |||
| 239 | if (!$case_or_default_equality_expr) { |
||
| 240 | $case_or_default_equality_expr = new PhpParser\Node\Expr\FuncCall( |
||
| 241 | new PhpParser\Node\Name\FullyQualified(['rand']), |
||
| 242 | [ |
||
| 243 | new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)), |
||
| 244 | new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)), |
||
| 245 | ], |
||
| 246 | $case->getAttributes() |
||
| 247 | ); |
||
| 248 | } |
||
| 249 | |||
| 250 | $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\BooleanOr( |
||
| 251 | $switch_scope->leftover_case_equality_expr, |
||
| 252 | $case_or_default_equality_expr, |
||
| 253 | $case_or_default_equality_expr->getAttributes() |
||
| 254 | ); |
||
| 255 | } |
||
| 256 | |||
| 257 | if ($case_equality_expr |
||
| 258 | && $switch_condition instanceof PhpParser\Node\Expr\Variable |
||
| 259 | && is_string($switch_condition->name) |
||
| 260 | && isset($context->vars_in_scope['$' . $switch_condition->name]) |
||
| 261 | ) { |
||
| 262 | $new_case_equality_expr = self::simplifyCaseEqualityExpression( |
||
| 263 | $case_equality_expr, |
||
| 264 | $switch_condition |
||
| 265 | ); |
||
| 266 | |||
| 267 | if ($new_case_equality_expr) { |
||
| 268 | ExpressionAnalyzer::analyze( |
||
| 269 | $statements_analyzer, |
||
| 270 | $new_case_equality_expr->args[1]->value, |
||
| 271 | $case_context |
||
| 272 | ); |
||
| 273 | |||
| 274 | $case_equality_expr = $new_case_equality_expr; |
||
| 275 | } |
||
| 276 | } |
||
| 277 | |||
| 278 | $case_context->break_types[] = 'switch'; |
||
| 279 | |||
| 280 | $switch_scope->leftover_statements = []; |
||
| 281 | $switch_scope->leftover_case_equality_expr = null; |
||
| 282 | |||
| 283 | $case_clauses = []; |
||
| 284 | |||
| 285 | if ($case_equality_expr) { |
||
| 286 | $case_clauses = Algebra::getFormula( |
||
| 287 | \spl_object_id($case_equality_expr), |
||
| 288 | $case_equality_expr, |
||
| 289 | $context->self, |
||
| 290 | $statements_analyzer, |
||
| 291 | $codebase, |
||
| 292 | false, |
||
| 293 | false |
||
| 294 | ); |
||
| 295 | } |
||
| 296 | |||
| 297 | if ($switch_scope->negated_clauses && count($switch_scope->negated_clauses) < 50) { |
||
| 298 | $entry_clauses = Algebra::simplifyCNF( |
||
| 299 | array_merge( |
||
| 300 | $original_context->clauses, |
||
| 301 | $switch_scope->negated_clauses |
||
| 302 | ) |
||
| 303 | ); |
||
| 304 | } else { |
||
| 305 | $entry_clauses = $original_context->clauses; |
||
| 306 | } |
||
| 307 | |||
| 308 | if ($case_clauses && $case->cond) { |
||
| 309 | // this will see whether any of the clauses in set A conflict with the clauses in set B |
||
| 310 | AlgebraAnalyzer::checkForParadox( |
||
| 311 | $entry_clauses, |
||
| 312 | $case_clauses, |
||
| 313 | $statements_analyzer, |
||
| 314 | $case->cond, |
||
| 315 | [] |
||
| 316 | ); |
||
| 317 | |||
| 318 | if (count($entry_clauses) + count($case_clauses) < 50) { |
||
| 319 | $case_context->clauses = Algebra::simplifyCNF(array_merge($entry_clauses, $case_clauses)); |
||
| 320 | } else { |
||
| 321 | $case_context->clauses = array_merge($entry_clauses, $case_clauses); |
||
| 322 | } |
||
| 323 | } else { |
||
| 324 | $case_context->clauses = $entry_clauses; |
||
| 325 | } |
||
| 326 | |||
| 327 | $reconcilable_if_types = Algebra::getTruthsFromFormula($case_context->clauses); |
||
| 328 | |||
| 329 | // if the if has an || in the conditional, we cannot easily reason about it |
||
| 330 | if ($reconcilable_if_types) { |
||
| 331 | $changed_var_ids = []; |
||
| 332 | |||
| 333 | $suppressed_issues = $statements_analyzer->getSuppressedIssues(); |
||
| 334 | |||
| 335 | if (!in_array('RedundantCondition', $suppressed_issues, true)) { |
||
| 336 | $statements_analyzer->addSuppressedIssues(['RedundantCondition']); |
||
| 337 | } |
||
| 338 | |||
| 339 | if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { |
||
| 340 | $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); |
||
| 341 | } |
||
| 342 | |||
| 343 | $case_vars_in_scope_reconciled = |
||
| 344 | Reconciler::reconcileKeyedTypes( |
||
| 345 | $reconcilable_if_types, |
||
| 346 | [], |
||
| 347 | $case_context->vars_in_scope, |
||
| 348 | $changed_var_ids, |
||
| 349 | $case->cond && $switch_var_id ? [$switch_var_id => true] : [], |
||
| 350 | $statements_analyzer, |
||
| 351 | [], |
||
| 352 | $case_context->inside_loop, |
||
| 353 | new CodeLocation( |
||
| 354 | $statements_analyzer->getSource(), |
||
| 355 | $case->cond ? $case->cond : $case, |
||
| 356 | $context->include_location |
||
| 357 | ) |
||
| 358 | ); |
||
| 359 | |||
| 360 | if (!in_array('RedundantCondition', $suppressed_issues, true)) { |
||
| 361 | $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); |
||
| 362 | } |
||
| 363 | |||
| 364 | if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { |
||
| 365 | $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); |
||
| 366 | } |
||
| 367 | |||
| 368 | $case_context->vars_in_scope = $case_vars_in_scope_reconciled; |
||
| 369 | foreach ($reconcilable_if_types as $var_id => $_) { |
||
| 370 | $case_context->vars_possibly_in_scope[$var_id] = true; |
||
| 371 | } |
||
| 372 | |||
| 373 | if ($changed_var_ids) { |
||
| 374 | $case_context->clauses = Context::removeReconciledClauses($case_context->clauses, $changed_var_ids)[0]; |
||
| 375 | } |
||
| 376 | } |
||
| 377 | |||
| 378 | if ($case_clauses) { |
||
| 379 | $switch_scope->negated_clauses = array_merge( |
||
| 380 | $switch_scope->negated_clauses, |
||
| 381 | Algebra::negateFormula($case_clauses) |
||
| 382 | ); |
||
| 383 | } |
||
| 384 | |||
| 385 | $pre_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids; |
||
| 386 | $case_context->possibly_assigned_var_ids = []; |
||
| 387 | |||
| 388 | $pre_assigned_var_ids = $case_context->assigned_var_ids; |
||
| 389 | $case_context->assigned_var_ids = []; |
||
| 390 | |||
| 391 | $statements_analyzer->analyze($case_stmts, $case_context); |
||
| 392 | |||
| 393 | $traverser = new PhpParser\NodeTraverser; |
||
| 394 | $traverser->addVisitor( |
||
| 395 | new \Psalm\Internal\PhpVisitor\TypeMappingVisitor( |
||
| 396 | $statements_analyzer->node_data, |
||
| 397 | $old_node_data |
||
| 398 | ) |
||
| 399 | ); |
||
| 400 | |||
| 401 | $traverser->traverse([$case]); |
||
| 402 | |||
| 403 | $statements_analyzer->node_data = $old_node_data; |
||
| 404 | |||
| 405 | /** @var array<string, bool> */ |
||
| 406 | $new_case_assigned_var_ids = $case_context->assigned_var_ids; |
||
| 407 | $case_context->assigned_var_ids = $pre_assigned_var_ids + $new_case_assigned_var_ids; |
||
| 408 | |||
| 409 | /** @var array<string, bool> */ |
||
| 410 | $new_case_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids; |
||
| 411 | $case_context->possibly_assigned_var_ids = |
||
| 412 | $pre_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids; |
||
| 413 | |||
| 414 | $context->referenced_var_ids = array_merge( |
||
| 415 | $context->referenced_var_ids, |
||
| 416 | $case_context->referenced_var_ids |
||
| 417 | ); |
||
| 418 | |||
| 419 | if ($case_exit_type !== 'return_throw') { |
||
| 420 | if (self::handleNonReturningCase( |
||
| 421 | $statements_analyzer, |
||
| 422 | $switch_var_id, |
||
| 423 | $case, |
||
| 424 | $context, |
||
| 425 | $case_context, |
||
| 426 | $original_context, |
||
| 427 | $new_case_assigned_var_ids, |
||
| 428 | $new_case_possibly_assigned_var_ids, |
||
| 429 | $case_exit_type, |
||
| 430 | $switch_scope, |
||
| 431 | $case_scope |
||
| 432 | ) === false) { |
||
| 433 | /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ |
||
| 434 | $case_scope->parent_context = null; |
||
| 435 | $case_context->case_scope = null; |
||
| 436 | $case_context->parent_context = null; |
||
| 437 | |||
| 438 | return false; |
||
| 439 | } |
||
| 440 | } |
||
| 441 | |||
| 442 | // augment the information with data from break statements |
||
| 443 | if ($case_scope->break_vars !== null) { |
||
| 444 | if ($switch_scope->possibly_redefined_vars === null) { |
||
| 445 | $switch_scope->possibly_redefined_vars = array_intersect_key( |
||
| 446 | $case_scope->break_vars, |
||
| 447 | $context->vars_in_scope |
||
| 448 | ); |
||
| 449 | } else { |
||
| 450 | foreach ($case_scope->break_vars as $var_id => $type) { |
||
| 451 | if (isset($context->vars_in_scope[$var_id])) { |
||
| 452 | if (!isset($switch_scope->possibly_redefined_vars[$var_id])) { |
||
| 453 | $switch_scope->possibly_redefined_vars[$var_id] = clone $type; |
||
| 454 | } else { |
||
| 455 | $switch_scope->possibly_redefined_vars[$var_id] = Type::combineUnionTypes( |
||
| 456 | clone $type, |
||
| 457 | $switch_scope->possibly_redefined_vars[$var_id] |
||
| 458 | ); |
||
| 459 | } |
||
| 460 | } |
||
| 461 | } |
||
| 462 | } |
||
| 463 | |||
| 464 | if ($switch_scope->new_vars_in_scope !== null) { |
||
| 465 | foreach ($switch_scope->new_vars_in_scope as $var_id => $type) { |
||
| 466 | if (isset($case_scope->break_vars[$var_id])) { |
||
| 467 | if (!isset($case_context->vars_in_scope[$var_id])) { |
||
| 468 | unset($switch_scope->new_vars_in_scope[$var_id]); |
||
| 469 | } else { |
||
| 470 | $switch_scope->new_vars_in_scope[$var_id] = Type::combineUnionTypes( |
||
| 471 | clone $case_scope->break_vars[$var_id], |
||
| 472 | $type |
||
| 473 | ); |
||
| 474 | } |
||
| 475 | } else { |
||
| 476 | unset($switch_scope->new_vars_in_scope[$var_id]); |
||
| 477 | } |
||
| 478 | } |
||
| 479 | } |
||
| 480 | |||
| 481 | if ($switch_scope->redefined_vars !== null) { |
||
| 482 | foreach ($switch_scope->redefined_vars as $var_id => $type) { |
||
| 483 | if (isset($case_scope->break_vars[$var_id])) { |
||
| 484 | $switch_scope->redefined_vars[$var_id] = Type::combineUnionTypes( |
||
| 485 | clone $case_scope->break_vars[$var_id], |
||
| 486 | $type |
||
| 487 | ); |
||
| 488 | } else { |
||
| 489 | unset($switch_scope->redefined_vars[$var_id]); |
||
| 490 | } |
||
| 491 | } |
||
| 492 | } |
||
| 493 | } |
||
| 494 | |||
| 495 | /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ |
||
| 496 | $case_scope->parent_context = null; |
||
| 497 | $case_context->case_scope = null; |
||
| 498 | $case_context->parent_context = null; |
||
| 499 | } |
||
| 500 | |||
| 748 |
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.