use crate::graph::{FragUsing, Fragment};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::sync::Arc;
pub(super) fn namespace_encloses(outer: &str, inner: &str) -> bool {
inner
.strip_prefix(outer)
.is_some_and(|rest| rest.starts_with('.'))
}
pub(super) fn score_candidate(
def_namespace: &str,
ref_namespace: &str,
usings: &HashSet<String>,
) -> u8 {
if def_namespace == ref_namespace {
return 3;
}
if usings.contains(def_namespace) {
return 2;
}
1
}
pub(super) struct FileContext {
pub(super) usings: FileUsings,
pub(super) aliases: HashMap<String, String>,
}
#[derive(Default)]
pub(super) struct FileUsings {
all: HashSet<String>,
by_level: HashMap<String, BTreeSet<String>>,
reach: Option<Arc<[bool]>>,
}
impl FileUsings {
pub(super) fn insert(&mut self, level: &str, text: &str) {
self.all.insert(text.to_string());
self.by_level
.entry(level.to_string())
.or_default()
.insert(text.to_string());
}
pub(super) fn at_level(&self, level: &str) -> impl Iterator<Item = &str> {
self.by_level
.get(level)
.into_iter()
.flatten()
.map(String::as_str)
}
pub(super) fn set_reach(&mut self, reach: Arc<[bool]>) {
self.reach = Some(reach);
}
pub(super) fn reaches(&self, def: usize) -> bool {
self.reach
.as_ref()
.is_none_or(|reach| reach.get(def).copied().unwrap_or(true))
}
}
impl std::ops::Deref for FileUsings {
type Target = HashSet<String>;
fn deref(&self) -> &HashSet<String> {
&self.all
}
}
pub(super) fn build_file_contexts(
fragments_by_file: &[(String, Fragment)],
repo_wide: &GlobalUsings,
by_unit: Option<UnitGlobals<'_>>,
) -> HashMap<String, FileContext> {
let mut contexts = HashMap::new();
let empty: GlobalUsings = (HashSet::new(), HashMap::new());
for (file, frag) in fragments_by_file {
let seed = match by_unit {
Some((unit_of_file, by_unit)) => match unit_of_file.get(file).copied().flatten() {
Some(u) => by_unit.get(&u).unwrap_or(&empty),
None => repo_wide,
},
None => repo_wide,
};
let mut usings = FileUsings::default();
for text in &seed.0 {
usings.insert("", text);
}
let mut aliases = seed.1.clone();
for u in &frag.usings {
match u {
FragUsing::Alias { alias, target, .. } => {
aliases.insert(alias.clone(), target.clone());
}
FragUsing::Plain {
text, namespace, ..
} => {
usings.insert(namespace, text);
}
}
}
contexts.insert(file.clone(), FileContext { usings, aliases });
}
contexts
}
type UnitGlobals<'a> = (
&'a HashMap<String, Option<usize>>,
&'a HashMap<usize, GlobalUsings>,
);
type GlobalUsings = (HashSet<String>, HashMap<String, String>);
pub(super) fn collect_global_usings_by_unit(
fragments_by_file: &[(String, Fragment)],
unit_of_file: &HashMap<String, Option<usize>>,
) -> (GlobalUsings, HashMap<usize, GlobalUsings>) {
let mut repo_wide: GlobalUsings = (HashSet::new(), HashMap::new());
let mut by_unit: HashMap<usize, GlobalUsings> = HashMap::new();
for (file, frag) in fragments_by_file {
let unit = unit_of_file.get(file).copied().flatten();
for u in &frag.usings {
match u {
FragUsing::Alias {
alias,
target,
global,
} => {
if *global {
repo_wide
.1
.entry(alias.clone())
.or_insert_with(|| target.clone());
if let Some(idx) = unit {
by_unit
.entry(idx)
.or_default()
.1
.entry(alias.clone())
.or_insert_with(|| target.clone());
}
}
}
FragUsing::Plain { text, global, .. } => {
if *global {
repo_wide.0.insert(text.clone());
if let Some(idx) = unit {
by_unit.entry(idx).or_default().0.insert(text.clone());
}
}
}
}
}
}
(repo_wide, by_unit)
}