1use php_ast::{NamespaceBody, Stmt, StmtKind, UseKind};
2
3pub fn extract_receiver_var_before_cursor(line: &str, cursor_col_utf16: usize) -> Option<String> {
8 let chars: Vec<char> = line.chars().collect();
9
10 let mut utf16 = 0usize;
12 let mut char_idx = 0usize;
13 for ch in &chars {
14 if utf16 >= cursor_col_utf16 {
15 break;
16 }
17 utf16 += ch.len_utf16();
18 char_idx += 1;
19 }
20
21 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
23 let mut word_start = char_idx;
24 while word_start > 0 && is_word_char(chars[word_start - 1]) {
25 word_start -= 1;
26 }
27
28 let (is_arrow, arrow_end) = if word_start >= 3
30 && chars[word_start - 3] == '?'
31 && chars[word_start - 2] == '-'
32 && chars[word_start - 1] == '>'
33 {
34 (true, word_start - 3)
35 } else if word_start >= 2 && chars[word_start - 2] == '-' && chars[word_start - 1] == '>' {
36 (true, word_start - 2)
37 } else {
38 (false, 0)
39 };
40
41 if !is_arrow {
42 return None;
43 }
44
45 extract_name_from_chars_end(&chars[..arrow_end])
46}
47
48pub(crate) fn extract_static_class_before_cursor(
50 line: &str,
51 cursor_col_utf16: usize,
52) -> Option<String> {
53 let chars: Vec<char> = line.chars().collect();
54
55 let mut utf16 = 0usize;
56 let mut char_idx = 0usize;
57 for ch in &chars {
58 if utf16 >= cursor_col_utf16 {
59 break;
60 }
61 utf16 += ch.len_utf16();
62 char_idx += 1;
63 }
64
65 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
66 let mut word_start = char_idx;
67 while word_start > 0 && is_word_char(chars[word_start - 1]) {
68 word_start -= 1;
69 }
70
71 if word_start > 0 && chars[word_start - 1] == '$' {
73 word_start -= 1;
74 }
75
76 if word_start < 2 || chars[word_start - 2] != ':' || chars[word_start - 1] != ':' {
77 return None;
78 }
79
80 let before_colons = &chars[..word_start - 2];
81 let is_name_char = |c: char| c.is_alphanumeric() || c == '_' || c == '\\';
83 let end = before_colons.len().saturating_sub(
84 before_colons
85 .iter()
86 .rev()
87 .take_while(|&&c| c == ' ' || c == '\t')
88 .count(),
89 );
90 let mut start = end;
91 while start > 0 && is_name_char(before_colons[start - 1]) {
92 start -= 1;
93 }
94 if start == end {
95 return None;
96 }
97 let full: String = before_colons[start..end].iter().collect();
98 Some(full.rsplit('\\').next().unwrap_or(&full).to_owned())
100}
101
102pub(crate) fn extract_name_from_chars_end(chars: &[char]) -> Option<String> {
105 let is_var_char = |c: char| c.is_alphanumeric() || c == '_' || c == '$';
106 let end = chars.len()
107 - chars
108 .iter()
109 .rev()
110 .take_while(|&&c| c == ' ' || c == '\t')
111 .count();
112 if end == 0 {
113 return None;
114 }
115 let mut start = end;
116 while start > 0 && is_var_char(chars[start - 1]) {
117 start -= 1;
118 }
119 if start == end {
120 return None;
121 }
122 let name: String = chars[start..end].iter().collect();
123 if name.starts_with('$') && name.len() > 1 {
124 Some(name)
125 } else if !name.is_empty() && !name.starts_with('$') {
126 Some(format!("${}", name))
129 } else {
130 None
131 }
132}
133
134pub fn resolve_use_alias(stmts: &[Stmt<'_, '_>], word: &str) -> Option<String> {
139 for stmt in stmts {
140 match &stmt.kind {
141 StmtKind::Use(u) if u.kind == UseKind::Normal => {
142 for item in u.uses.iter() {
143 if let Some(alias) = item.alias
144 && alias == word
145 {
146 let fqn = item.name.to_string_repr();
147 let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref()).to_owned();
148 return Some(short);
149 }
150 }
151 }
152 StmtKind::Namespace(ns) => {
153 if let NamespaceBody::Braced(inner) = &ns.body
154 && let Some(s) = resolve_use_alias(inner, word)
155 {
156 return Some(s);
157 }
158 }
159 _ => {}
160 }
161 }
162 None
163}