use php_ast::{NamespaceBody, Stmt, StmtKind, UseKind};
pub fn extract_receiver_var_before_cursor(line: &str, cursor_col_utf16: usize) -> Option<String> {
let chars: Vec<char> = line.chars().collect();
let mut utf16 = 0usize;
let mut char_idx = 0usize;
for ch in &chars {
if utf16 >= cursor_col_utf16 {
break;
}
utf16 += ch.len_utf16();
char_idx += 1;
}
let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
let mut word_start = char_idx;
while word_start > 0 && is_word_char(chars[word_start - 1]) {
word_start -= 1;
}
let (is_arrow, arrow_end) = if word_start >= 3
&& chars[word_start - 3] == '?'
&& chars[word_start - 2] == '-'
&& chars[word_start - 1] == '>'
{
(true, word_start - 3)
} else if word_start >= 2 && chars[word_start - 2] == '-' && chars[word_start - 1] == '>' {
(true, word_start - 2)
} else {
(false, 0)
};
if !is_arrow {
return None;
}
extract_name_from_chars_end(&chars[..arrow_end])
}
pub(crate) fn extract_static_class_before_cursor(
line: &str,
cursor_col_utf16: usize,
) -> Option<String> {
let chars: Vec<char> = line.chars().collect();
let mut utf16 = 0usize;
let mut char_idx = 0usize;
for ch in &chars {
if utf16 >= cursor_col_utf16 {
break;
}
utf16 += ch.len_utf16();
char_idx += 1;
}
let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
let mut word_start = char_idx;
while word_start > 0 && is_word_char(chars[word_start - 1]) {
word_start -= 1;
}
if word_start > 0 && chars[word_start - 1] == '$' {
word_start -= 1;
}
if word_start < 2 || chars[word_start - 2] != ':' || chars[word_start - 1] != ':' {
return None;
}
let before_colons = &chars[..word_start - 2];
let is_name_char = |c: char| c.is_alphanumeric() || c == '_' || c == '\\';
let end = before_colons.len().saturating_sub(
before_colons
.iter()
.rev()
.take_while(|&&c| c == ' ' || c == '\t')
.count(),
);
let mut start = end;
while start > 0 && is_name_char(before_colons[start - 1]) {
start -= 1;
}
if start == end {
return None;
}
let full: String = before_colons[start..end].iter().collect();
Some(full.rsplit('\\').next().unwrap_or(&full).to_owned())
}
pub(crate) fn extract_name_from_chars_end(chars: &[char]) -> Option<String> {
let is_var_char = |c: char| c.is_alphanumeric() || c == '_' || c == '$';
let end = chars.len()
- chars
.iter()
.rev()
.take_while(|&&c| c == ' ' || c == '\t')
.count();
if end == 0 {
return None;
}
let mut start = end;
while start > 0 && is_var_char(chars[start - 1]) {
start -= 1;
}
if start == end {
return None;
}
let name: String = chars[start..end].iter().collect();
if name.starts_with('$') && name.len() > 1 {
Some(name)
} else if !name.is_empty() && !name.starts_with('$') {
Some(format!("${}", name))
} else {
None
}
}
pub fn resolve_use_alias(stmts: &[Stmt<'_, '_>], word: &str) -> Option<String> {
for stmt in stmts {
match &stmt.kind {
StmtKind::Use(u) if u.kind == UseKind::Normal => {
for item in u.uses.iter() {
if let Some(alias) = item.alias
&& alias == word
{
let fqn = item.name.to_string_repr();
let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref()).to_owned();
return Some(short);
}
}
}
StmtKind::Namespace(ns) => {
if let NamespaceBody::Braced(inner) = &ns.body
&& let Some(s) = resolve_use_alias(inner, word)
{
return Some(s);
}
}
_ => {}
}
}
None
}