const CONCEPT_CLUSTERS: &[&[&str]] = &[
&["bug", "error", "failure", "fault", "defect"],
&["perf", "performance", "latency", "throughput", "speed"],
&["crash", "panic", "abort", "segfault"],
&["config", "configuration", "settings", "setup"],
&["auth", "authentication", "login", "signin"],
&["delete", "remove", "purge", "erase"],
&["fix", "patch", "repair", "resolve"],
&["docs", "documentation", "readme"],
&["test", "tests", "spec", "specs"],
&["dependency", "dependencies", "deps", "package", "packages"],
];
pub(crate) fn expand_query_terms(terms: &[String]) -> Vec<String> {
let mut expanded: Vec<String> = Vec::with_capacity(terms.len());
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut push_unique = |value: &str, sink: &mut Vec<String>| {
let key = value.to_ascii_lowercase();
if seen.insert(key) {
sink.push(value.to_string());
}
};
for term in terms {
push_unique(term, &mut expanded);
}
for term in terms {
let lowered = term.to_ascii_lowercase();
for cluster in CONCEPT_CLUSTERS {
if cluster.contains(&lowered.as_str()) {
for synonym in *cluster {
push_unique(synonym, &mut expanded);
}
}
}
}
expanded
}
#[cfg(test)]
mod tests {
use super::*;
fn owned(values: &[&str]) -> Vec<String> {
values.iter().map(|value| value.to_string()).collect()
}
#[test]
fn original_terms_lead_and_synonyms_follow() {
let expanded = expand_query_terms(&owned(&["bug"]));
assert_eq!(expanded[0], "bug", "original term keeps front position");
for synonym in ["error", "failure", "fault", "defect"] {
assert!(
expanded.iter().any(|term| term == synonym),
"missing synonym {synonym}"
);
}
}
#[test]
fn non_concept_terms_are_unchanged() {
let input = owned(&["nabu", "raw_offset", "harness"]);
assert_eq!(expand_query_terms(&input), input);
}
#[test]
fn expansion_is_case_insensitive_and_deduplicated() {
let expanded = expand_query_terms(&owned(&["Bug", "ERROR"]));
assert_eq!(expanded[0], "Bug");
assert_eq!(expanded[1], "ERROR");
let error_count = expanded
.iter()
.filter(|term| term.eq_ignore_ascii_case("error"))
.count();
assert_eq!(error_count, 1, "error must not be duplicated");
let bug_count = expanded
.iter()
.filter(|term| term.eq_ignore_ascii_case("bug"))
.count();
assert_eq!(bug_count, 1, "bug must not be duplicated");
}
#[test]
fn multiple_clusters_each_expand() {
let expanded = expand_query_terms(&owned(&["perf", "crash"]));
assert!(expanded.iter().any(|term| term == "latency"));
assert!(expanded.iter().any(|term| term == "panic"));
}
}