use std::collections::HashMap;
use crate::suggest::MEMBER_KINDS;
use super::index::{DefSite, GraphIndex};
pub(crate) fn split_member_seed(seed: &str) -> (&str, Option<&str>) {
match seed.rfind('.') {
Some(pos) => (&seed[pos + 1..], Some(&seed[..pos])),
None => (seed, None),
}
}
fn owner_matches_qualifier(owner_id: &str, qualifier: &str) -> bool {
owner_id == qualifier || owner_id.ends_with(&format!(".{qualifier}"))
}
pub fn qualified_member_owners(
index: &GraphIndex,
name: &str,
qualifier: Option<&str>,
) -> Vec<(String, Vec<DefSite>)> {
let mut out: Vec<(String, Vec<DefSite>)> = Vec::new();
let mut at: HashMap<&str, usize> = HashMap::new();
for n in &index.graph.names {
if n.name != name
|| n.owner.is_empty()
|| !index.by_id.contains_key(&n.owner)
|| !MEMBER_KINDS.contains(&n.kind.as_str())
{
continue;
}
if let Some(q) = qualifier {
if !owner_matches_qualifier(&n.owner, q) {
continue;
}
}
let site = DefSite {
file: n.file.clone(),
line: n.line,
};
match at.get(n.owner.as_str()) {
Some(&i) => out[i].1.push(site),
None => {
at.insert(n.owner.as_str(), out.len());
out.push((n.owner.clone(), vec![site]));
}
}
}
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct MemberCandidate {
pub owner: String,
pub name: String,
pub file: String,
pub line: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MemberSeedResolution {
Resolved(String),
Ambiguous(Vec<MemberCandidate>),
NotFound,
}
pub fn resolve_member_seed(index: &GraphIndex, seed: &str) -> MemberSeedResolution {
let (name, qualifier) = split_member_seed(seed);
let owners = qualified_member_owners(index, name, qualifier);
match owners.len() {
0 => MemberSeedResolution::NotFound,
1 => MemberSeedResolution::Resolved(owners[0].0.clone()),
_ => MemberSeedResolution::Ambiguous(member_candidates(&owners, name)),
}
}
pub fn member_candidates(owners: &[(String, Vec<DefSite>)], name: &str) -> Vec<MemberCandidate> {
owners
.iter()
.map(|(owner, sites)| MemberCandidate {
owner: owner.clone(),
name: name.to_string(),
file: sites[0].file.clone(),
line: sites[0].line,
})
.collect()
}
pub fn qualified_seed(candidate: &MemberCandidate) -> String {
format!("{}.{}", candidate.owner, candidate.name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_member_seed_separates_the_trailing_name_from_a_dotted_qualifier() {
assert_eq!(split_member_seed("Stow"), ("Stow", None));
assert_eq!(split_member_seed("Bin.Stow"), ("Stow", Some("Bin")));
assert_eq!(
split_member_seed("Acme.Storage.Bin.Stow"),
("Stow", Some("Acme.Storage.Bin"))
);
}
#[test]
fn owner_matches_qualifier_accepts_an_exact_id_or_a_dotted_suffix() {
assert!(owner_matches_qualifier("Acme.Storage.Bin", "Bin"));
assert!(owner_matches_qualifier("Acme.Storage.Bin", "Storage.Bin"));
assert!(owner_matches_qualifier(
"Acme.Storage.Bin",
"Acme.Storage.Bin"
));
assert!(!owner_matches_qualifier("Acme.Storage.Bin", "Vault"));
assert!(
!owner_matches_qualifier("Acme.Storage.Bin", "torage.Bin"),
"a suffix match starts on a dot boundary, not mid-segment"
);
}
}