use std::collections::HashMap;
use crate::graph::types::{FileFacts, Symbol};
use crate::symbol::SymbolId;
pub(crate) fn dedup_files_last_wins(files: &[FileFacts]) -> Vec<&FileFacts> {
let mut last: HashMap<&str, usize> = HashMap::new();
for (i, f) in files.iter().enumerate() {
last.insert(f.file.as_str(), i);
}
files
.iter()
.enumerate()
.filter(|(i, f)| last[f.file.as_str()] == *i)
.map(|(_, f)| f)
.collect()
}
pub(crate) fn enclosing_symbol_index(
symbols: &[Symbol],
file_indices: &[usize],
byte: usize,
) -> Option<usize> {
file_indices
.iter()
.copied()
.filter(|&i| symbols[i].span.contains(byte))
.min_by_key(|&i| symbols[i].span.len())
}
pub(crate) fn normalize_from_path(path: &str) -> Vec<&str> {
path.split(['.', '/', ':'])
.filter(|s| !s.is_empty() && !matches!(*s, "." | ".." | "crate" | "self" | "super"))
.collect()
}
pub(crate) fn namespaces_end_with<S: AsRef<str>>(candidate: &SymbolId, segs: &[S]) -> bool {
if segs.is_empty() {
return false;
}
let n = candidate.namespaces_iter().count();
if segs.len() > n {
return false;
}
candidate
.namespaces_iter()
.skip(n - segs.len())
.zip(segs.iter())
.all(|(a, b)| a == b.as_ref())
}
pub(crate) fn enclosing_path_ends_with<S: AsRef<str>>(candidate: &SymbolId, segs: &[S]) -> bool {
if segs.is_empty() {
return false;
}
let names: Vec<&str> = candidate.descriptor_names_iter().collect();
let containers = match names.split_last() {
Some((_leaf, rest)) if !rest.is_empty() => rest,
_ => return false,
};
if segs.len() > containers.len() {
return false;
}
containers[containers.len() - segs.len()..]
.iter()
.zip(segs.iter())
.all(|(a, b)| *a == b.as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extract::{Extractor, RustExtractor};
#[test]
fn dedup_keeps_last_occurrence_in_slice_order() {
let a_v1 = RustExtractor
.extract("pub fn one() {}", "src/a.rs")
.unwrap();
let b = RustExtractor
.extract("pub fn two() {}", "src/b.rs")
.unwrap();
let a_v2 = RustExtractor
.extract("pub fn three() {}", "src/a.rs")
.unwrap();
let files = vec![a_v1, b, a_v2];
let kept = dedup_files_last_wins(&files);
let kept_keys: Vec<&str> = kept.iter().map(|f| f.file.as_str()).collect();
assert_eq!(kept_keys, vec!["src/b.rs", "src/a.rs"]);
let a_kept = kept
.iter()
.find(|f| f.file == "src/a.rs")
.expect("src/a.rs must survive");
assert!(
a_kept
.symbols
.iter()
.any(|s| s.id.to_scip_string().ends_with("three().")),
"last-wins must keep v2 (defines `three`), got: {:?}",
a_kept
.symbols
.iter()
.map(|s| s.id.to_scip_string())
.collect::<Vec<_>>()
);
}
}