| Conditions | 121 |
| Paths | > 20000 |
| Total Lines | 456 |
| Lines | 266 |
| Ratio | 58.33 % |
| 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 |
||
| 38 | public static function analyze( |
||
| 39 | StatementsAnalyzer $statements_analyzer, |
||
| 40 | PhpParser\Node\Expr $left, |
||
| 41 | PhpParser\Node\Expr $right, |
||
| 42 | Context $context, |
||
| 43 | Type\Union &$result_type = null |
||
| 44 | ) { |
||
| 45 | $codebase = $statements_analyzer->getCodebase(); |
||
| 46 | |||
| 47 | $left_type = $statements_analyzer->node_data->getType($left); |
||
| 48 | $right_type = $statements_analyzer->node_data->getType($right); |
||
| 49 | $config = Config::getInstance(); |
||
| 50 | |||
| 51 | if ($left_type && $right_type) { |
||
| 52 | $result_type = Type::getString(); |
||
| 53 | |||
| 54 | if ($left_type->hasMixed() || $right_type->hasMixed()) { |
||
| 55 | View Code Duplication | if (!$context->collect_initializations |
|
|
|
|||
| 56 | && !$context->collect_mutations |
||
| 57 | && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() |
||
| 58 | && (!(($parent_source = $statements_analyzer->getSource()) |
||
| 59 | instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) |
||
| 60 | || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) |
||
| 61 | ) { |
||
| 62 | $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); |
||
| 63 | } |
||
| 64 | |||
| 65 | if ($left_type->hasMixed()) { |
||
| 66 | if (IssueBuffer::accepts( |
||
| 67 | new MixedOperand( |
||
| 68 | 'Left operand cannot be mixed', |
||
| 69 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 70 | ), |
||
| 71 | $statements_analyzer->getSuppressedIssues() |
||
| 72 | )) { |
||
| 73 | // fall through |
||
| 74 | } |
||
| 75 | } else { |
||
| 76 | if (IssueBuffer::accepts( |
||
| 77 | new MixedOperand( |
||
| 78 | 'Right operand cannot be mixed', |
||
| 79 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 80 | ), |
||
| 81 | $statements_analyzer->getSuppressedIssues() |
||
| 82 | )) { |
||
| 83 | // fall through |
||
| 84 | } |
||
| 85 | } |
||
| 86 | |||
| 87 | return; |
||
| 88 | } |
||
| 89 | |||
| 90 | View Code Duplication | if (!$context->collect_initializations |
|
| 91 | && !$context->collect_mutations |
||
| 92 | && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() |
||
| 93 | && (!(($parent_source = $statements_analyzer->getSource()) |
||
| 94 | instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) |
||
| 95 | || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) |
||
| 96 | ) { |
||
| 97 | $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); |
||
| 98 | } |
||
| 99 | |||
| 100 | if ($left_type->isNull()) { |
||
| 101 | if (IssueBuffer::accepts( |
||
| 102 | new NullOperand( |
||
| 103 | 'Cannot concatenate with a ' . $left_type, |
||
| 104 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 105 | ), |
||
| 106 | $statements_analyzer->getSuppressedIssues() |
||
| 107 | )) { |
||
| 108 | // fall through |
||
| 109 | } |
||
| 110 | |||
| 111 | return; |
||
| 112 | } |
||
| 113 | |||
| 114 | if ($right_type->isNull()) { |
||
| 115 | if (IssueBuffer::accepts( |
||
| 116 | new NullOperand( |
||
| 117 | 'Cannot concatenate with a ' . $right_type, |
||
| 118 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 119 | ), |
||
| 120 | $statements_analyzer->getSuppressedIssues() |
||
| 121 | )) { |
||
| 122 | // fall through |
||
| 123 | } |
||
| 124 | |||
| 125 | return; |
||
| 126 | } |
||
| 127 | |||
| 128 | View Code Duplication | if ($left_type->isFalse()) { |
|
| 129 | if (IssueBuffer::accepts( |
||
| 130 | new FalseOperand( |
||
| 131 | 'Cannot concatenate with a ' . $left_type, |
||
| 132 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 133 | ), |
||
| 134 | $statements_analyzer->getSuppressedIssues() |
||
| 135 | )) { |
||
| 136 | // fall through |
||
| 137 | } |
||
| 138 | |||
| 139 | return; |
||
| 140 | } |
||
| 141 | |||
| 142 | View Code Duplication | if ($right_type->isFalse()) { |
|
| 143 | if (IssueBuffer::accepts( |
||
| 144 | new FalseOperand( |
||
| 145 | 'Cannot concatenate with a ' . $right_type, |
||
| 146 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 147 | ), |
||
| 148 | $statements_analyzer->getSuppressedIssues() |
||
| 149 | )) { |
||
| 150 | // fall through |
||
| 151 | } |
||
| 152 | |||
| 153 | return; |
||
| 154 | } |
||
| 155 | |||
| 156 | if ($left_type->isNullable() && !$left_type->ignore_nullable_issues) { |
||
| 157 | if (IssueBuffer::accepts( |
||
| 158 | new PossiblyNullOperand( |
||
| 159 | 'Cannot concatenate with a possibly null ' . $left_type, |
||
| 160 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 161 | ), |
||
| 162 | $statements_analyzer->getSuppressedIssues() |
||
| 163 | )) { |
||
| 164 | // fall through |
||
| 165 | } |
||
| 166 | } |
||
| 167 | |||
| 168 | if ($right_type->isNullable() && !$right_type->ignore_nullable_issues) { |
||
| 169 | if (IssueBuffer::accepts( |
||
| 170 | new PossiblyNullOperand( |
||
| 171 | 'Cannot concatenate with a possibly null ' . $right_type, |
||
| 172 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 173 | ), |
||
| 174 | $statements_analyzer->getSuppressedIssues() |
||
| 175 | )) { |
||
| 176 | // fall through |
||
| 177 | } |
||
| 178 | } |
||
| 179 | |||
| 180 | if ($left_type->isFalsable() && !$left_type->ignore_falsable_issues) { |
||
| 181 | if (IssueBuffer::accepts( |
||
| 182 | new PossiblyFalseOperand( |
||
| 183 | 'Cannot concatenate with a possibly false ' . $left_type, |
||
| 184 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 185 | ), |
||
| 186 | $statements_analyzer->getSuppressedIssues() |
||
| 187 | )) { |
||
| 188 | // fall through |
||
| 189 | } |
||
| 190 | } |
||
| 191 | |||
| 192 | if ($right_type->isFalsable() && !$right_type->ignore_falsable_issues) { |
||
| 193 | if (IssueBuffer::accepts( |
||
| 194 | new PossiblyFalseOperand( |
||
| 195 | 'Cannot concatenate with a possibly false ' . $right_type, |
||
| 196 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 197 | ), |
||
| 198 | $statements_analyzer->getSuppressedIssues() |
||
| 199 | )) { |
||
| 200 | // fall through |
||
| 201 | } |
||
| 202 | } |
||
| 203 | |||
| 204 | $left_type_match = true; |
||
| 205 | $right_type_match = true; |
||
| 206 | |||
| 207 | $has_valid_left_operand = false; |
||
| 208 | $has_valid_right_operand = false; |
||
| 209 | |||
| 210 | $left_comparison_result = new \Psalm\Internal\Analyzer\TypeComparisonResult(); |
||
| 211 | $right_comparison_result = new \Psalm\Internal\Analyzer\TypeComparisonResult(); |
||
| 212 | |||
| 213 | View Code Duplication | foreach ($left_type->getAtomicTypes() as $left_type_part) { |
|
| 214 | if ($left_type_part instanceof Type\Atomic\TTemplateParam) { |
||
| 215 | if (IssueBuffer::accepts( |
||
| 216 | new MixedOperand( |
||
| 217 | 'Left operand cannot be mixed', |
||
| 218 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 219 | ), |
||
| 220 | $statements_analyzer->getSuppressedIssues() |
||
| 221 | )) { |
||
| 222 | // fall through |
||
| 223 | } |
||
| 224 | |||
| 225 | return; |
||
| 226 | } |
||
| 227 | |||
| 228 | if ($left_type_part instanceof Type\Atomic\TNull || $left_type_part instanceof Type\Atomic\TFalse) { |
||
| 229 | continue; |
||
| 230 | } |
||
| 231 | |||
| 232 | $left_type_part_match = TypeAnalyzer::isAtomicContainedBy( |
||
| 233 | $codebase, |
||
| 234 | $left_type_part, |
||
| 235 | new Type\Atomic\TString, |
||
| 236 | false, |
||
| 237 | false, |
||
| 238 | $left_comparison_result |
||
| 239 | ); |
||
| 240 | |||
| 241 | $left_type_match = $left_type_match && $left_type_part_match; |
||
| 242 | |||
| 243 | $has_valid_left_operand = $has_valid_left_operand || $left_type_part_match; |
||
| 244 | |||
| 245 | if ($left_comparison_result->to_string_cast && $config->strict_binary_operands) { |
||
| 246 | if (IssueBuffer::accepts( |
||
| 247 | new ImplicitToStringCast( |
||
| 248 | 'Left side of concat op expects string, ' |
||
| 249 | . '\'' . $left_type . '\' provided with a __toString method', |
||
| 250 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 251 | ), |
||
| 252 | $statements_analyzer->getSuppressedIssues() |
||
| 253 | )) { |
||
| 254 | // fall through |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | foreach ($left_type->getAtomicTypes() as $atomic_type) { |
||
| 259 | if ($atomic_type instanceof TNamedObject) { |
||
| 260 | $to_string_method_id = new \Psalm\Internal\MethodIdentifier( |
||
| 261 | $atomic_type->value, |
||
| 262 | '__tostring' |
||
| 263 | ); |
||
| 264 | |||
| 265 | if ($codebase->methods->methodExists( |
||
| 266 | $to_string_method_id, |
||
| 267 | $context->calling_method_id, |
||
| 268 | $codebase->collect_locations |
||
| 269 | ? new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 270 | : null, |
||
| 271 | !$context->collect_initializations |
||
| 272 | && !$context->collect_mutations |
||
| 273 | ? $statements_analyzer |
||
| 274 | : null, |
||
| 275 | $statements_analyzer->getFilePath() |
||
| 276 | )) { |
||
| 277 | try { |
||
| 278 | $storage = $codebase->methods->getStorage($to_string_method_id); |
||
| 279 | } catch (\UnexpectedValueException $e) { |
||
| 280 | continue; |
||
| 281 | } |
||
| 282 | |||
| 283 | if ($context->mutation_free && !$storage->mutation_free) { |
||
| 284 | if (IssueBuffer::accepts( |
||
| 285 | new ImpureMethodCall( |
||
| 286 | 'Cannot call a possibly-mutating method ' |
||
| 287 | . $atomic_type->value . '::__toString from a pure context', |
||
| 288 | new CodeLocation($statements_analyzer, $left) |
||
| 289 | ), |
||
| 290 | $statements_analyzer->getSuppressedIssues() |
||
| 291 | )) { |
||
| 292 | // fall through |
||
| 293 | } |
||
| 294 | } |
||
| 295 | } |
||
| 296 | } |
||
| 297 | } |
||
| 298 | } |
||
| 299 | |||
| 300 | View Code Duplication | foreach ($right_type->getAtomicTypes() as $right_type_part) { |
|
| 301 | if ($right_type_part instanceof Type\Atomic\TTemplateParam) { |
||
| 302 | if (IssueBuffer::accepts( |
||
| 303 | new MixedOperand( |
||
| 304 | 'Right operand cannot be a template param', |
||
| 305 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 306 | ), |
||
| 307 | $statements_analyzer->getSuppressedIssues() |
||
| 308 | )) { |
||
| 309 | // fall through |
||
| 310 | } |
||
| 311 | |||
| 312 | return; |
||
| 313 | } |
||
| 314 | |||
| 315 | if ($right_type_part instanceof Type\Atomic\TNull || $right_type_part instanceof Type\Atomic\TFalse) { |
||
| 316 | continue; |
||
| 317 | } |
||
| 318 | |||
| 319 | $right_type_part_match = TypeAnalyzer::isAtomicContainedBy( |
||
| 320 | $codebase, |
||
| 321 | $right_type_part, |
||
| 322 | new Type\Atomic\TString, |
||
| 323 | false, |
||
| 324 | false, |
||
| 325 | $right_comparison_result |
||
| 326 | ); |
||
| 327 | |||
| 328 | $right_type_match = $right_type_match && $right_type_part_match; |
||
| 329 | |||
| 330 | $has_valid_right_operand = $has_valid_right_operand || $right_type_part_match; |
||
| 331 | |||
| 332 | if ($right_comparison_result->to_string_cast && $config->strict_binary_operands) { |
||
| 333 | if (IssueBuffer::accepts( |
||
| 334 | new ImplicitToStringCast( |
||
| 335 | 'Right side of concat op expects string, ' |
||
| 336 | . '\'' . $right_type . '\' provided with a __toString method', |
||
| 337 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 338 | ), |
||
| 339 | $statements_analyzer->getSuppressedIssues() |
||
| 340 | )) { |
||
| 341 | // fall through |
||
| 342 | } |
||
| 343 | } |
||
| 344 | |||
| 345 | foreach ($right_type->getAtomicTypes() as $atomic_type) { |
||
| 346 | if ($atomic_type instanceof TNamedObject) { |
||
| 347 | $to_string_method_id = new \Psalm\Internal\MethodIdentifier( |
||
| 348 | $atomic_type->value, |
||
| 349 | '__tostring' |
||
| 350 | ); |
||
| 351 | |||
| 352 | if ($codebase->methods->methodExists( |
||
| 353 | $to_string_method_id, |
||
| 354 | $context->calling_method_id, |
||
| 355 | $codebase->collect_locations |
||
| 356 | ? new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 357 | : null, |
||
| 358 | !$context->collect_initializations |
||
| 359 | && !$context->collect_mutations |
||
| 360 | ? $statements_analyzer |
||
| 361 | : null, |
||
| 362 | $statements_analyzer->getFilePath() |
||
| 363 | )) { |
||
| 364 | try { |
||
| 365 | $storage = $codebase->methods->getStorage($to_string_method_id); |
||
| 366 | } catch (\UnexpectedValueException $e) { |
||
| 367 | continue; |
||
| 368 | } |
||
| 369 | |||
| 370 | if ($context->mutation_free && !$storage->mutation_free) { |
||
| 371 | if (IssueBuffer::accepts( |
||
| 372 | new ImpureMethodCall( |
||
| 373 | 'Cannot call a possibly-mutating method ' |
||
| 374 | . $atomic_type->value . '::__toString from a pure context', |
||
| 375 | new CodeLocation($statements_analyzer, $right) |
||
| 376 | ), |
||
| 377 | $statements_analyzer->getSuppressedIssues() |
||
| 378 | )) { |
||
| 379 | // fall through |
||
| 380 | } |
||
| 381 | } |
||
| 382 | } |
||
| 383 | } |
||
| 384 | } |
||
| 385 | } |
||
| 386 | |||
| 387 | View Code Duplication | if (!$left_type_match |
|
| 388 | && (!$left_comparison_result->scalar_type_match_found || $config->strict_binary_operands) |
||
| 389 | ) { |
||
| 390 | if ($has_valid_left_operand) { |
||
| 391 | if (IssueBuffer::accepts( |
||
| 392 | new PossiblyInvalidOperand( |
||
| 393 | 'Cannot concatenate with a ' . $left_type, |
||
| 394 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 395 | ), |
||
| 396 | $statements_analyzer->getSuppressedIssues() |
||
| 397 | )) { |
||
| 398 | // fall through |
||
| 399 | } |
||
| 400 | } else { |
||
| 401 | if (IssueBuffer::accepts( |
||
| 402 | new InvalidOperand( |
||
| 403 | 'Cannot concatenate with a ' . $left_type, |
||
| 404 | new CodeLocation($statements_analyzer->getSource(), $left) |
||
| 405 | ), |
||
| 406 | $statements_analyzer->getSuppressedIssues() |
||
| 407 | )) { |
||
| 408 | // fall through |
||
| 409 | } |
||
| 410 | } |
||
| 411 | } |
||
| 412 | |||
| 413 | View Code Duplication | if (!$right_type_match |
|
| 414 | && (!$right_comparison_result->scalar_type_match_found || $config->strict_binary_operands) |
||
| 415 | ) { |
||
| 416 | if ($has_valid_right_operand) { |
||
| 417 | if (IssueBuffer::accepts( |
||
| 418 | new PossiblyInvalidOperand( |
||
| 419 | 'Cannot concatenate with a ' . $right_type, |
||
| 420 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 421 | ), |
||
| 422 | $statements_analyzer->getSuppressedIssues() |
||
| 423 | )) { |
||
| 424 | // fall through |
||
| 425 | } |
||
| 426 | } else { |
||
| 427 | if (IssueBuffer::accepts( |
||
| 428 | new InvalidOperand( |
||
| 429 | 'Cannot concatenate with a ' . $right_type, |
||
| 430 | new CodeLocation($statements_analyzer->getSource(), $right) |
||
| 431 | ), |
||
| 432 | $statements_analyzer->getSuppressedIssues() |
||
| 433 | )) { |
||
| 434 | // fall through |
||
| 435 | } |
||
| 436 | } |
||
| 437 | } |
||
| 438 | } |
||
| 439 | |||
| 440 | // When concatenating two known string literals (with only one possibility), |
||
| 441 | // put the concatenated string into $result_type |
||
| 442 | if ($left_type && $right_type && $left_type->isSingleStringLiteral() && $right_type->isSingleStringLiteral()) { |
||
| 443 | $literal = $left_type->getSingleStringLiteral()->value . $right_type->getSingleStringLiteral()->value; |
||
| 444 | if (strlen($literal) <= 1000) { |
||
| 445 | // Limit these to 10000 bytes to avoid extremely large union types from repeated concatenations, etc |
||
| 446 | $result_type = Type::getString($literal); |
||
| 447 | } |
||
| 448 | } else { |
||
| 449 | if ($left_type |
||
| 450 | && $right_type |
||
| 451 | ) { |
||
| 452 | $left_type_literal_value = $left_type->isSingleStringLiteral() |
||
| 453 | ? $left_type->getSingleStringLiteral()->value |
||
| 454 | : null; |
||
| 455 | |||
| 456 | $right_type_literal_value = $right_type->isSingleStringLiteral() |
||
| 457 | ? $right_type->getSingleStringLiteral()->value |
||
| 458 | : null; |
||
| 459 | |||
| 460 | if (($left_type->getId() === 'lowercase-string' |
||
| 461 | || $left_type->getId() === 'non-empty-lowercase-string' |
||
| 462 | || $left_type->isInt() |
||
| 463 | || ($left_type_literal_value !== null |
||
| 464 | && strtolower($left_type_literal_value) === $left_type_literal_value)) |
||
| 465 | && ($right_type->getId() === 'lowercase-string' |
||
| 466 | || $right_type->getId() === 'non-empty-lowercase-string' |
||
| 467 | || $right_type->isInt() |
||
| 468 | || ($right_type_literal_value !== null |
||
| 469 | && strtolower($right_type_literal_value) === $right_type_literal_value)) |
||
| 470 | ) { |
||
| 471 | if ($left_type->getId() === 'non-empty-lowercase-string' |
||
| 472 | || $left_type->isInt() |
||
| 473 | || ($left_type_literal_value !== null |
||
| 474 | && strtolower($left_type_literal_value) === $left_type_literal_value) |
||
| 475 | || $right_type->getId() === 'non-empty-lowercase-string' |
||
| 476 | || $right_type->isInt() |
||
| 477 | || ($right_type_literal_value !== null |
||
| 478 | && strtolower($right_type_literal_value) === $right_type_literal_value) |
||
| 479 | ) { |
||
| 480 | $result_type = new Type\Union([new Type\Atomic\TNonEmptyLowercaseString()]); |
||
| 481 | } else { |
||
| 482 | $result_type = new Type\Union([new Type\Atomic\TLowercaseString()]); |
||
| 483 | } |
||
| 484 | } elseif ($left_type->getId() === 'non-empty-string' |
||
| 485 | || $right_type->getId() === 'non-empty-string' |
||
| 486 | || $left_type_literal_value |
||
| 487 | || $right_type_literal_value |
||
| 488 | ) { |
||
| 489 | $result_type = new Type\Union([new Type\Atomic\TNonEmptyString()]); |
||
| 490 | } |
||
| 491 | } |
||
| 492 | } |
||
| 493 | } |
||
| 494 | } |
||
| 495 |
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.