use std::collections::{HashMap, HashSet};
use std::ops::ControlFlow;
use std::sync::Arc;
use php_ast::visitor::{Visitor, walk_stmt};
use php_ast::{Stmt, StmtKind, UseKind};
use tower_lsp::lsp_types::{Location, Position, Range, Url};
use super::walk::all_class_ref_names_in_stmts;
use crate::document::ast::ParsedDoc;
use crate::text::fqn_short_name;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
Function,
Method,
Class,
Property,
Constant,
}
pub(crate) fn session_tuple_to_location(
(file, line, col_start, col_end): (Arc<str>, u32, u32, u32),
) -> Option<Location> {
let uri = Url::parse(&file).ok()?;
Some(Location {
uri,
range: Range {
start: Position {
line,
character: col_start,
},
end: Position {
line,
character: col_end,
},
},
})
}
pub(crate) fn ref_location_key(loc: &Location) -> (String, u32, u32, u32) {
(
loc.uri.to_string(),
loc.range.start.line,
loc.range.start.character,
loc.range.end.character,
)
}
pub(crate) fn dedup_ref_locations(locations: &mut Vec<Location>) {
let mut seen = HashSet::new();
locations.retain(|loc| seen.insert(ref_location_key(loc)));
}
struct ImportsVisitor {
only_kind: Option<UseKind>,
out: HashMap<String, String>,
}
impl<'arena, 'src> Visitor<'arena, 'src> for ImportsVisitor {
fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
match &stmt.kind {
StmtKind::Use(u) if self.only_kind.is_none_or(|k| u.kind == k) => {
for item in u.uses.iter() {
let fqn = item.name.to_string_repr().into_owned();
let short = item
.alias
.map(|a| a.to_string())
.unwrap_or_else(|| fqn_short_name(&fqn).to_string());
self.out.insert(short, fqn);
}
ControlFlow::Continue(())
}
StmtKind::Namespace(_) => walk_stmt(self, stmt),
_ => ControlFlow::Continue(()),
}
}
}
pub(crate) fn collect_file_imports(doc: &ParsedDoc) -> HashMap<String, String> {
collect_imports_filtered(doc, None)
}
pub(crate) fn collect_class_imports(doc: &ParsedDoc) -> HashMap<String, String> {
collect_imports_filtered(doc, Some(UseKind::Normal))
}
fn collect_imports_filtered(
doc: &ParsedDoc,
only_kind: Option<UseKind>,
) -> HashMap<String, String> {
let mut v = ImportsVisitor {
only_kind,
out: HashMap::new(),
};
for stmt in doc.program().stmts.iter() {
let _ = v.visit_stmt(stmt);
}
v.out
}
pub(crate) fn collect_referenced_class_fqns(doc: &ParsedDoc) -> Vec<String> {
let imports = collect_class_imports(doc);
let names = all_class_ref_names_in_stmts(&doc.program().stmts);
let locals = collect_local_type_decl_fqns(doc);
let mut out: Vec<String> = names
.into_iter()
.map(|name| {
if let Some(stripped) = name.strip_prefix('\\') {
return stripped.to_string();
}
let fqn = crate::navigation::moniker::resolve_fqn(doc, &name, &imports);
fqn.trim_start_matches('\\').to_string()
})
.filter(|fqn| !locals.contains(fqn))
.collect();
out.sort_unstable();
out.dedup();
out
}
fn collect_local_type_decl_fqns(doc: &ParsedDoc) -> HashSet<String> {
use php_ast::NamespaceBody;
let mut out = HashSet::new();
fn name_of(kind: &StmtKind<'_, '_>) -> Option<String> {
match kind {
StmtKind::Class(c) => c.name.as_ref().map(|n| n.to_string()),
StmtKind::Interface(i) => Some(i.name.to_string()),
StmtKind::Trait(t) => Some(t.name.to_string()),
StmtKind::Enum(e) => Some(e.name.to_string()),
_ => None,
}
}
let mut current_ns: Option<String> = None;
for stmt in doc.program().stmts.iter() {
match &stmt.kind {
StmtKind::Namespace(ns) => {
let ns_name = ns.name.as_ref().map(|n| n.to_string_repr().to_string());
match &ns.body {
NamespaceBody::Braced(inner) => {
let prefix = ns_name
.as_deref()
.map(|n| format!("{n}\\"))
.unwrap_or_default();
for s in inner.stmts.iter() {
if let Some(n) = name_of(&s.kind) {
out.insert(format!("{prefix}{n}"));
}
}
}
NamespaceBody::Simple => {
current_ns = ns_name;
}
}
}
k => {
if let Some(n) = name_of(k) {
let fqn = match ¤t_ns {
Some(ns) => format!("{ns}\\{n}"),
None => n,
};
out.insert(fqn);
}
}
}
}
out
}
pub fn build_mir_symbol(
word: &str,
kind: Option<SymbolKind>,
target_fqn: Option<&str>,
class_constant: bool,
) -> Option<mir_analyzer::Name> {
let norm = |s: &str| -> Arc<str> { Arc::from(s.trim_start_matches('\\')) };
match kind {
Some(SymbolKind::Function) => Some(mir_analyzer::Name::Function(norm(
target_fqn.unwrap_or(word),
))),
Some(SymbolKind::Class) => {
Some(mir_analyzer::Name::Class(norm(target_fqn.unwrap_or(word))))
}
Some(SymbolKind::Method) => Some(mir_analyzer::Name::Method {
class: norm(target_fqn.unwrap_or("")),
name: Arc::from(word.to_ascii_lowercase()),
}),
Some(SymbolKind::Property) => Some(mir_analyzer::Name::Property {
class: norm(target_fqn.unwrap_or("")),
name: Arc::from(word),
}),
Some(SymbolKind::Constant) => Some(if class_constant {
mir_analyzer::Name::ClassConstant {
class: norm(target_fqn?),
name: Arc::from(word),
}
} else {
mir_analyzer::Name::GlobalConstant(norm(target_fqn.unwrap_or(word)))
}),
None => None,
}
}