use std::sync::Arc;
use php_ast::{NamespaceBody, Stmt, StmtKind};
use tower_lsp::lsp_types::{Location, Url};
use crate::document::ast::{ParsedDoc, SourceView};
#[inline]
fn name_matches(repr: &str, word: &str, fqn: Option<&str>) -> bool {
repr == word
|| fqn.is_some_and(|f| repr.trim_start_matches('\\') == f)
|| (fqn.is_none() && !word.contains('\\') && repr.trim_start_matches('\\') == word)
}
pub fn find_implementations(
word: &str,
fqn: Option<&str>,
all_docs: &[(Url, Arc<ParsedDoc>)],
) -> Vec<Location> {
let mut locations = Vec::new();
for (uri, doc) in all_docs {
let sv = doc.view();
collect_implementations(&doc.program().stmts, word, fqn, sv, uri, &mut locations);
}
locations
}
pub fn find_method_implementations_from_workspace(
method_name: &str,
declaring_class: &str,
wi: &crate::db::workspace_index::WorkspaceIndexData,
) -> Vec<tower_lsp::lsp_types::Location> {
let mut locations = Vec::new();
if let Some(refs) = wi.subtypes_of.get(declaring_class) {
for &class_ref in refs {
if let Some((uri, cls)) = wi.at(class_ref)
&& let Some(method) = cls
.methods
.iter()
.find(|m| m.name.as_ref() == method_name && !m.is_abstract)
{
locations.push(tower_lsp::lsp_types::Location {
uri: uri.clone(),
range: crate::text::zero_width_range(method.start_line),
});
}
}
}
locations.sort_by(|a, b| {
a.uri
.as_str()
.cmp(b.uri.as_str())
.then(a.range.start.line.cmp(&b.range.start.line))
});
locations.dedup_by(|a, b| a.uri == b.uri && a.range.start.line == b.range.start.line);
locations
}
pub fn find_implementations_from_workspace(
word: &str,
fqn: Option<&str>,
wi: &crate::db::workspace_index::WorkspaceIndexData,
) -> Vec<Location> {
let mut locations = Vec::new();
let mut push_refs = |key: &str| {
if let Some(refs) = wi.subtypes_of.get(key) {
for r in refs {
if let Some((uri, cls)) = wi.at(*r) {
let extends_match = cls
.parent
.as_deref()
.map(|p| name_matches(p, word, fqn))
.unwrap_or(false);
let implements_match = cls.implements.iter().any(|iface| {
if name_matches(iface.as_ref(), word, fqn) {
return true;
}
if let Some((_, file_idx)) = wi.files.get(r.file as usize) {
file_idx.use_imports.iter().any(|(alias, resolved_fqn)| {
alias.as_ref() == iface.as_ref()
&& crate::text::fqn_short_name(resolved_fqn) == word
})
} else {
false
}
});
if extends_match || implements_match {
let pos = tower_lsp::lsp_types::Position {
line: cls.start_line,
character: 0,
};
locations.push(Location {
uri: uri.clone(),
range: tower_lsp::lsp_types::Range {
start: pos,
end: pos,
},
});
}
}
}
}
};
push_refs(word);
if let Some(f) = fqn
&& f != word
{
push_refs(f);
let trimmed = f.trim_start_matches('\\');
if trimmed != f {
push_refs(trimmed);
}
}
locations.sort_by(|a, b| {
a.uri
.as_str()
.cmp(b.uri.as_str())
.then(a.range.start.line.cmp(&b.range.start.line))
});
locations.dedup_by(|a, b| a.uri == b.uri && a.range.start.line == b.range.start.line);
locations
}
fn collect_implementations(
stmts: &[Stmt<'_, '_>],
word: &str,
fqn: Option<&str>,
sv: SourceView<'_>,
uri: &Url,
out: &mut Vec<Location>,
) {
for stmt in stmts {
match &stmt.kind {
StmtKind::Class(c) => {
let extends_match = c
.extends
.as_ref()
.map(|e| name_matches(e.to_string_repr().as_ref(), word, fqn))
.unwrap_or(false);
let implements_match = c
.implements
.iter()
.any(|iface| name_matches(iface.to_string_repr().as_ref(), word, fqn));
if (extends_match || implements_match)
&& let Some(class_name) = c.name
{
out.push(Location {
uri: uri.clone(),
range: sv.name_range_in_span(class_name.or_error(), stmt.span),
});
}
}
StmtKind::Enum(e) => {
let implements_match = e
.implements
.iter()
.any(|iface| name_matches(iface.to_string_repr().as_ref(), word, fqn));
if implements_match {
out.push(Location {
uri: uri.clone(),
range: sv.name_range_in_span(e.name.or_error(), stmt.span),
});
}
}
StmtKind::Interface(i) => {
let extends_match = i
.extends
.iter()
.any(|base| name_matches(base.to_string_repr().as_ref(), word, fqn));
if extends_match {
out.push(Location {
uri: uri.clone(),
range: sv.name_range_in_span(i.name.or_error(), stmt.span),
});
}
}
StmtKind::Namespace(ns) => {
if let NamespaceBody::Braced(inner) = &ns.body {
collect_implementations(&inner.stmts, word, fqn, sv, uri, out);
}
}
_ => {}
}
}
}