Detect if the body text is a factory pattern: the extracted code constructs an object and returns it. Returns a name suitable for `create{Name}`. When the body assigns `$var = new X(…)` and later returns `$var`, the variable name is used (e.g. `$users` → `"Users"`). This produces `createUsers` rather than `createCollection`, which matches how developers think about the domain object. When the
(body: &str)
| 505 | /// When the body does `return new ClassName(…)` directly, the class |
| 506 | /// name is used instead (there is no variable to take a hint from). |
| 507 | fn detect_factory_pattern(body: &str) -> Option<String> { |
| 508 | let mut returned_class: Option<String> = None; |
| 509 | let mut returned_var: Option<String> = None; |
| 510 | let mut assigned_var: Option<String> = None; |
| 511 | let mut assigned_class: Option<String> = None; |
| 512 | |
| 513 | for line in body.lines() { |
| 514 | let trimmed = line.trim(); |
| 515 | // Check for `return new ClassName(…)` — direct return. |
| 516 | if let Some(after_return) = trimmed.strip_prefix("return ") |
| 517 | && let Some(name) = extract_new_class_name(after_return.trim_start()) |
| 518 | { |
| 519 | returned_class = Some(name); |
| 520 | } |
| 521 | // Check for `return $var;` — returning a variable. |
| 522 | if let Some(after_return) = trimmed.strip_prefix("return ") { |
| 523 | let var = after_return.trim().trim_end_matches(';').trim(); |
| 524 | if var.starts_with('$') && var[1..].chars().all(|c| c.is_alphanumeric() || c == '_') { |
| 525 | returned_var = Some(var.to_string()); |
| 526 | } |
| 527 | } |
| 528 | // Check for `$var = new ClassName(…)` (direct assignment). |
| 529 | if let Some(eq_pos) = trimmed.find('=') { |
| 530 | // Make sure it's `=` not `==` / `===` / `!=` etc. |
| 531 | let before_eq = &trimmed[..eq_pos]; |
| 532 | let after_eq = &trimmed[eq_pos + 1..]; |
| 533 | let var_name = before_eq.trim(); |
| 534 | if var_name.starts_with('$') |
| 535 | && !after_eq.starts_with('=') |
| 536 | && !before_eq.ends_with('!') |
| 537 | && !before_eq.ends_with('<') |
| 538 | && !before_eq.ends_with('>') |
| 539 | && let Some(class_name) = extract_new_class_name(after_eq.trim_start()) |
| 540 | { |
| 541 | assigned_var = Some(var_name.to_string()); |
| 542 | assigned_class = Some(class_name); |
| 543 | } |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | // Best case: `$var = new X(…); ... return $var;` — use the |
| 548 | // variable name because it carries domain meaning (e.g. `$users` |
| 549 | // → `createUsers`). Fall back to the class name when the variable |
| 550 | // is too short to be meaningful (`$u`, `$x`, etc.). |
| 551 | if let Some(ref ret_var) = returned_var |
| 552 | && let Some(ref asgn_var) = assigned_var |
| 553 | && ret_var == asgn_var |
| 554 | { |
| 555 | let var_clean = ret_var.trim_start_matches('$'); |
| 556 | if var_clean.len() > 2 { |
| 557 | return Some(capitalise(var_clean)); |
| 558 | } |
| 559 | // Short variable — prefer the class name. |
| 560 | if let Some(ref name) = assigned_class { |
| 561 | let short = name.rsplit('\\').next().unwrap_or(name); |
| 562 | return Some(short.to_string()); |
| 563 | } |
| 564 | } |
no test coverage detected