Detect when the body is a single method call or function call statement (no assignment, no return). Returns a name derived from the called method/function. Examples: - `$this->execute($fn)` → `"execute"` - `self::validate($x)` → `"validate"` - `doSomething($x)` → `"doSomething"`
(body: &str)
| 686 | /// - `self::validate($x)` → `"validate"` |
| 687 | /// - `doSomething($x)` → `"doSomething"` |
| 688 | fn detect_single_call(body: &str) -> Option<String> { |
| 689 | let trimmed = body.trim(); |
| 690 | |
| 691 | // Must be a single non-comment line. |
| 692 | let lines: Vec<&str> = trimmed |
| 693 | .lines() |
| 694 | .map(|l| l.trim()) |
| 695 | .filter(|l| !l.is_empty() && !l.starts_with("//") && !l.starts_with('#')) |
| 696 | .collect(); |
| 697 | if lines.len() != 1 { |
| 698 | return None; |
| 699 | } |
| 700 | |
| 701 | let line = lines[0].strip_suffix(';').unwrap_or(lines[0]).trim(); |
| 702 | |
| 703 | // Must not be an assignment. |
| 704 | if line.contains('=') { |
| 705 | // Allow `==`, `!=`, `===`, `!==`, `>=`, `<=` inside expressions, |
| 706 | // but reject bare `$var = ...` assignments. |
| 707 | if let Some(eq_pos) = line.find('=') { |
| 708 | let before = &line[..eq_pos]; |
| 709 | let after = &line[eq_pos + 1..]; |
| 710 | if before.trim().starts_with('$') |
| 711 | && !after.starts_with('=') |
| 712 | && !before.ends_with('!') |
| 713 | && !before.ends_with('<') |
| 714 | && !before.ends_with('>') |
| 715 | { |
| 716 | return None; |
| 717 | } |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | // Extract the method/function name from the call. |
| 722 | // `$this->foo(...)` or `$var->foo(...)` |
| 723 | if let Some(arrow_pos) = line.rfind("->") { |
| 724 | let after = &line[arrow_pos + 2..]; |
| 725 | let name: String = after |
| 726 | .chars() |
| 727 | .take_while(|c| c.is_alphanumeric() || *c == '_') |
| 728 | .collect(); |
| 729 | if !name.is_empty() && after[name.len()..].starts_with('(') { |
| 730 | return Some(name); |
| 731 | } |
| 732 | } |
| 733 | // `self::foo(...)` or `static::foo(...)` or `ClassName::foo(...)` |
| 734 | if let Some(colon_pos) = line.rfind("::") { |
| 735 | let after = &line[colon_pos + 2..]; |
| 736 | let name: String = after |
| 737 | .chars() |
| 738 | .take_while(|c| c.is_alphanumeric() || *c == '_') |
| 739 | .collect(); |
| 740 | if !name.is_empty() && after[name.len()..].starts_with('(') { |
| 741 | return Some(name); |
| 742 | } |
| 743 | } |
| 744 | // `functionName(...)` — bare function call |
| 745 | let name: String = line |