use crate::store::SymbolRow;
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub(crate) struct Feature {
pub name: &'static str,
pub value: f64,
}
impl Feature {
pub(crate) fn reported(&self) -> f64 {
self.value.round() + 0.0 }
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Scored {
pub total: f64,
pub features: Vec<Feature>,
}
pub(crate) fn match_quality(features: &[Feature]) -> f64 {
if features.iter().any(|f| f.name == "scope_typo") {
return 0.25;
}
if features.iter().any(|f| f.name == "constructor_owner") {
return 0.75;
}
for f in features {
match f.name {
"exact" | "constructor" => return 1.0,
"prefix" => return 0.9,
"wildcard" => return 0.7,
"fuzzy" | "typo" => return (0.30 + 0.35 * (f.value / 600.0)).clamp(0.30, 0.65),
_ => {}
}
}
0.25 }
pub(crate) fn is_literal(features: &[Feature]) -> bool {
features
.iter()
.any(|f| matches!(f.name, "exact" | "prefix" | "wildcard" | "constructor"))
}
pub(crate) fn name_evidence(features: &[Feature]) -> f64 {
features
.iter()
.find(|f| matches!(f.name, "fuzzy" | "typo"))
.map_or(0.0, |f| f.value)
}
pub(crate) fn confidence(score: f64, quality: f64, best_other: Option<f64>) -> f64 {
let lead = match best_other {
None => 1.0,
Some(_) if score <= 0.0 => 0.5,
Some(other) => (0.5 + 3.0 * (score - other) / score).clamp(0.0, 1.0),
};
((quality * lead) * 100.0).round() / 100.0
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub(crate) struct Boosts {
pub recency: f64,
pub branch: f64,
pub enclosing: f64,
pub proximity: f64,
pub anchor_file: bool,
}
pub(crate) fn score(
query: &str,
cand: &SymbolRow,
current_repo_id: Option<i64>,
boosts: Boosts,
near_miss: bool,
) -> Option<Scored> {
let (leaf, qualifier) = parse_qualified(query);
let q = lower(leaf);
let name_lower = lower(&cand.name);
let mut features = Vec::new();
let wildcard = has_wildcard(&q);
let name_matched = if qualifier.is_some()
&& q == "new"
&& crate::lang::is_constructor(&cand.language, &cand.name)
{
features.push(Feature {
name: "constructor",
value: 1000.0,
});
true
} else if wildcard {
if let Some(s) = wildcard_score(&q, &cand.name) {
features.push(Feature {
name: "wildcard",
value: s.min(600.0),
});
true
} else {
false
}
} else if name_lower == q {
features.push(Feature {
name: "exact",
value: 1000.0,
});
if leaf != q && cand.name == leaf {
features.push(Feature {
name: "case",
value: CASE_MATCH,
});
}
true
} else if joiners_eq(&name_lower, &q) {
features.push(Feature {
name: "exact",
value: 1000.0,
});
features.push(Feature {
name: "separators",
value: -SEPARATOR_PENALTY,
});
true
} else if name_lower.starts_with(q.as_ref()) {
let tail = cand.name.chars().count().saturating_sub(q.chars().count());
features.push(Feature {
name: "prefix",
value: 700.0 - (tail as f64).min(100.0),
});
true
} else if let Some(value) = fuzzy_value(&q, &cand.name) {
features.push(Feature {
name: "fuzzy",
value,
});
true
} else if let Some(d) = near_miss
.then(|| near_miss_distance(&q, &name_lower))
.flatten()
{
features.push(Feature {
name: "typo",
value: near_miss_score(&q, &cand.name, d),
});
true
} else {
false
};
let quality = match_quality(&features);
if leaf != q {
let matched = match features.first().map(|f| f.name) {
Some("typo") => Some(common_subsequence(leaf, &cand.name)),
Some("prefix" | "fuzzy") => Some(leaf.to_string()),
_ => None,
};
if let Some(share) = matched.and_then(|m| case_agreement(&m, &cand.name)) {
features.push(Feature {
name: "case",
value: CASE_MATCH * share * quality,
});
}
}
let stem = path_stem(&cand.file);
let path_match = if wildcard {
wildcard_score(&q, stem)
} else {
subsequence_score(&q, stem)
};
if name_matched {
if let Some(ps) = path_match {
features.push(Feature {
name: "path",
value: (ps * 0.2).min(50.0) * quality,
});
}
} else {
match path_match {
Some(ps) if is_primary_kind(&cand.kind) => {
features.push(Feature {
name: "path",
value: (ps * 0.6).min(300.0),
});
}
_ => return None,
}
}
let visibility = match cand.visibility.as_deref() {
Some("private" | "protected") => Some(("private", -15.0)),
Some("local") => Some(("local", -LOCAL_PENALTY)),
_ => None,
};
if let Some((name, value)) = visibility {
features.push(Feature {
name,
value: value * quality,
});
}
if cand.stub {
features.push(Feature {
name: "stub",
value: -STUB_PENALTY * quality,
});
}
let secondary = if in_test_path(&cand.file) {
Some("test_path")
} else if in_test_scope(cand.parent.as_deref(), &cand.name, &cand.kind) {
Some("test_scope")
} else if cand.generated {
Some("generated")
} else if in_example_path(&cand.file) {
Some("example_path")
} else {
None
};
if let Some(name) = secondary.filter(|_| !boosts.anchor_file) {
let value = if features.iter().any(|f| matches!(f.name, "fuzzy" | "typo")) {
(TEST_PATH_SHARE * name_evidence(&features).max(0.0)).min(TEST_PATH_PENALTY)
} else {
TEST_PATH_PENALTY
};
features.push(Feature {
name,
value: -value,
});
}
if let Some(end) = cand.end_line {
let span = (end - cand.line + 1).max(1) as f64;
if span > 1.0 {
features.push(Feature {
name: "extent",
value: (span.ln() * BODY_WEIGHT).min(MAX_BODY_BONUS) * quality,
});
}
}
let depth = cand
.parent
.as_deref()
.map_or(0, segment_count)
.saturating_sub(FREE_DEPTH);
if depth > 0 {
features.push(Feature {
name: "depth",
value: -(DEPTH_PENALTY * depth as f64).min(MAX_DEPTH_PENALTY) * quality,
});
}
let kind = match cand.kind.as_str() {
"class" | "struct" | "trait" => 15.0,
"module" | "enum" | "type" => 12.0,
_ => 0.0,
};
if kind != 0.0 {
features.push(Feature {
name: "kind",
value: kind * quality,
});
}
if let Some(qual) = qualifier {
if let Some((b, edits)) = parent_boost(qual, cand.parent.as_deref(), near_miss) {
features.push(Feature {
name: "parent",
value: b,
});
if edits > 0 {
features.push(Feature {
name: "scope_typo",
value: -NEAR_MISS_STEP * edits as f64,
});
}
} else {
let (owned, pathed, depth) = path_scope(qual, cand)?;
if owned > 0 {
features.push(Feature {
name: "parent",
value: parent_value(owned),
});
}
features.push(Feature {
name: "path_scope",
value: PATH_SCOPE_STEP * pathed as f64 * 0.5_f64.powi(depth as i32),
});
}
}
if let Some(cur) = current_repo_id
&& cur == cand.repository_id
{
features.push(Feature {
name: "current_repo",
value: 200.0,
});
}
if boosts.recency > 0.0 {
features.push(Feature {
name: "recency",
value: boosts.recency,
});
}
if boosts.branch > 0.0 {
features.push(Feature {
name: "branch",
value: boosts.branch,
});
}
if boosts.enclosing > 0.0 {
features.push(Feature {
name: "enclosing",
value: boosts.enclosing,
});
}
if boosts.proximity > 0.0 {
features.push(Feature {
name: "proximity",
value: boosts.proximity,
});
}
let total = features.iter().map(|f| f.value).sum();
Some(Scored { total, features })
}
const MAX_NONBOUNDARY_GAP: usize = 2;
fn droppable(c: char) -> bool {
matches!(c, 'a' | 'e' | 'i' | 'o' | 'u')
}
const CASE_MATCH: f64 = 150.0;
const PATH_SCOPE_STEP: f64 = 30.0;
const NEAR_MISS_STEP: f64 = 40.0;
const MAX_NEAR_MISS: usize = 2;
const SEPARATOR_PENALTY: f64 = 150.0;
const BODY_WEIGHT: f64 = 10.0;
const MAX_BODY_BONUS: f64 = 50.0;
const FREE_DEPTH: usize = 2;
const LOCAL_PENALTY: f64 = 150.0;
const STUB_PENALTY: f64 = 150.0;
const DEPTH_PENALTY: f64 = 15.0;
const MAX_DEPTH_PENALTY: f64 = 60.0;
const TEST_PATH_PENALTY: f64 = 400.0;
const TEST_PATH_SHARE: f64 = 0.4;
const SIGIL_CREDIT: f64 = 10.0;
const WORD_SKIP_PENALTY: f64 = 40.0;
const GAP_PENALTY: f64 = 3.0;
struct Alignment {
score: f64,
positions: Vec<usize>,
}
fn align(query: &str, name: &str) -> Option<Alignment> {
let q: Vec<char> = query
.chars()
.filter(|c| c.is_alphanumeric())
.map(fold)
.collect();
if q.is_empty() {
return None;
}
let mut qi = 0;
for c in name.chars() {
if qi < q.len() && fold(c) == q[qi] {
qi += 1;
}
}
if qi < q.len() {
return None;
}
let chars: Vec<char> = name.chars().collect();
let n = chars.len();
let lower: Vec<char> = chars.iter().copied().map(fold).collect();
let boundary = boundaries(&chars);
let mut bnd_prefix = vec![0usize; n + 1];
for i in 0..n {
bnd_prefix[i + 1] = bnd_prefix[i] + boundary[i] as usize;
}
let credit = |anchored: bool, i: usize| {
if anchored {
10.0 + if boundary[i] { 15.0 } else { 0.0 }
} else {
0.0
}
};
type Cell = Option<(f64, (usize, usize))>;
let mut table: Vec<Vec<[Cell; 2]>> = vec![vec![[None; 2]; n]; q.len()];
for (i, &c) in lower.iter().enumerate() {
if c == q[0] {
let a = usize::from(boundary[i]);
let s = credit(a == 1, i) + if i == 0 { 20.0 } else { 0.0 };
table[0][i][a] = Some((s, (i, a)));
}
}
for qi in 1..q.len() {
for i in qi..n {
if lower[i] != q[qi] {
continue;
}
let past_vowel = i >= 2 && !boundary[i] && boundary[i - 1] && droppable(lower[i - 1]);
let j_start = if boundary[i] || past_vowel {
qi - 1
} else {
(qi - 1).max(i.saturating_sub(MAX_NONBOUNDARY_GAP + 1))
};
let mut best: [Cell; 2] = [None; 2];
let prev_row = &table[qi - 1];
for (j, cells) in prev_row.iter().enumerate().take(i).skip(j_start) {
let trans = if j + 1 == i {
10.0 } else {
let gap = i - j - 1;
let crossed = bnd_prefix[i] - bnd_prefix[j + 1];
let crossed_word = crossed > 0;
let skipped = if boundary[i] {
crossed
} else if past_vowel && crossed_word {
crossed - 1
} else if gap > MAX_NONBOUNDARY_GAP || crossed_word {
continue;
} else {
0
};
-(gap as f64) * GAP_PENALTY - skipped as f64 * WORD_SKIP_PENALTY
};
for (pa, cell) in cells.iter().enumerate() {
let Some((pscore, _)) = cell else {
continue;
};
let a = usize::from(pa == 1 || boundary[i]);
let cand = pscore + trans;
if best[a].is_none_or(|(b, _)| cand > b) {
best[a] = Some((cand, (j, pa)));
}
}
}
for (a, cell) in best.into_iter().enumerate() {
if let Some((bscore, back)) = cell {
table[qi][i][a] = Some((bscore + credit(a == 1, i), back));
}
}
}
}
let last = q.len() - 1;
let (mut pos, mut state, score) = (0..n)
.flat_map(|i| (0..2).map(move |a| (i, a)))
.filter_map(|(i, a)| table[last][i][a].map(|(s, _)| (i, a, s)))
.max_by(|x, y| x.2.total_cmp(&y.2))?;
let mut positions = Vec::with_capacity(q.len());
for qi in (0..q.len()).rev() {
positions.push(pos);
(pos, state) = table[qi][pos][state]
.expect("backtrack hits a filled cell")
.1;
}
positions.reverse();
Some(Alignment {
score: score.max(0.0),
positions,
})
}
const PAIRS_VERSION: u32 = 3;
pub(crate) const NAME_INDEX_FORMAT: u32 = PAIRS_VERSION * 100 + MAX_NONBOUNDARY_GAP as u32;
pub(super) const PAIR_CODES: usize = 37;
pub(super) fn pair_code(c: char) -> Option<u8> {
match c.to_ascii_lowercase() {
c @ 'a'..='z' => Some(c as u8 - b'a'),
c @ '0'..='9' => Some(26 + c as u8 - b'0'),
c if c.is_alphanumeric() => Some(36),
_ => None,
}
}
pub(super) fn transition_pairs(chars: &[char], boundary: &[bool], out: &mut Vec<u16>) {
let pair = |a: u8, b: u8| u16::from(a) * PAIR_CODES as u16 + u16::from(b);
let codes: Vec<Option<u8>> = chars.iter().map(|&c| pair_code(c)).collect();
let mut word = 0;
let mut last_alnum: Option<u8> = None;
for i in 0..chars.len() {
if boundary[i] {
word = i;
}
let Some(b) = codes[i] else { continue };
let past_vowel = i >= 2 && word == i - 1 && droppable(fold(chars[i - 1]));
let from = if (boundary[i] && i > 0) || past_vowel {
0
} else {
i.saturating_sub(MAX_NONBOUNDARY_GAP + 1).max(word)
};
out.extend(codes[from..i].iter().flatten().map(|&a| pair(a, b)));
out.extend(last_alnum.map(|a| pair(a, b)));
last_alnum = Some(b);
}
}
pub(super) fn aligns(query: &[u8], name: &[u8], boundary: u128) -> bool {
let n = name.len();
if n == 0 || n > 128 || query.is_empty() || query.len() > n {
return false;
}
let mut slot = [u8::MAX; 128];
let mut masks = [0u128; PAIR_CODES];
let mut distinct = 0;
for &c in query {
match slot.get_mut(usize::from(c)) {
Some(s) if *s == u8::MAX && distinct < PAIR_CODES => {
*s = distinct as u8;
distinct += 1;
}
Some(_) => {}
None => return false, }
}
for (i, &c) in name.iter().enumerate() {
if let Some(&k) = slot.get(usize::from(c))
&& k != u8::MAX
{
masks[usize::from(k)] |= 1 << i;
}
}
let at = |c: u8| {
let k = slot.get(usize::from(c)).copied().unwrap_or(u8::MAX);
masks.get(usize::from(k)).copied().unwrap_or(0)
};
let live = if n == 128 { !0 } else { (1u128 << n) - 1 };
let inner = !boundary & live;
let vowels = name
.iter()
.enumerate()
.filter(|&(_, &c)| droppable(char::from(c)))
.fold(0u128, |m, (i, _)| m | 1 << i);
let b = boundary;
let mut s = at(query[0]);
for &c in &query[1..] {
if s == 0 {
return false;
}
let m = at(c);
let near = (s << 1) | ((s << 2) & !(b << 1)) | ((s << 3) & !(b << 1) & !(b << 2));
let earliest = s.isolate_lowest_one();
let starts = b & !(earliest | (earliest - 1));
let past_vowel = ((starts & vowels) << 1) & inner;
s = (near & inner & m) | (starts & m) | (past_vowel & m);
}
s != 0
}
pub(crate) fn match_positions(query: &str, name: &str) -> Vec<usize> {
let (leaf, _) = parse_qualified(query);
if has_wildcard(leaf) {
return glob_positions(leaf, name).unwrap_or_default();
}
let positions = align(leaf, name).map(|a| a.positions).unwrap_or_default();
contiguous_highlight(positions, name)
}
pub(crate) fn parse_qualified(query: &str) -> (&str, Option<&str>) {
let sep = query
.rmatch_indices("::")
.map(|(i, _)| (i, 2usize))
.chain(query.rmatch_indices(['#', '.']).map(|(i, _)| (i, 1usize)))
.max_by_key(|&(i, _)| i);
match sep {
Some((i, len)) if i > 0 && i + len < query.len() => (&query[i + len..], Some(&query[..i])),
_ => (query, None),
}
}
fn segment_count(s: &str) -> usize {
s.split("::")
.flat_map(|p| p.split(['#', '.']))
.filter(|p| !p.is_empty())
.count()
}
pub(crate) fn segments(s: &str) -> Vec<String> {
s.split("::")
.flat_map(|p| p.split(['#', '.']))
.map(|p| p.trim_matches(|c: char| !c.is_alphanumeric() && c != '_'))
.filter(|p| !p.is_empty())
.map(|p| p.to_ascii_lowercase())
.collect()
}
fn parent_boost(qualifier: &str, parent: Option<&str>, near_miss: bool) -> Option<(f64, usize)> {
let p = segments(parent?);
let q = segments(qualifier);
if q.is_empty() || q.len() > p.len() {
return None;
}
let off = p.len() - q.len();
let mut edits = 0;
for (qs, ps) in q.iter().zip(&p[off..]) {
if qs != ps {
edits += near_miss.then(|| near_miss_distance(qs, ps)).flatten()?;
}
}
(edits <= MAX_NEAR_MISS).then(|| (parent_value(q.len()), edits))
}
fn parent_value(segments: usize) -> f64 {
(120.0 + 60.0 * segments as f64).min(300.0)
}
fn path_scope(qualifier: &str, cand: &SymbolRow) -> Option<(usize, usize, usize)> {
let q = segments(qualifier);
if q.is_empty() {
return None;
}
let p = cand.parent.as_deref().map(segments).unwrap_or_default();
let owned = q
.iter()
.rev()
.zip(p.iter().rev())
.take_while(|(a, b)| a == b)
.count();
let (last, outer) = q[..q.len() - owned].split_last()?;
let repo = cand.repo_identity.rsplit(['/', ':']).next();
let (dirs, _) = cand.file.rsplit_once('/').unwrap_or_default();
let chain: Vec<String> = repo
.into_iter()
.chain(dirs.split('/').filter(|d| !d.is_empty()))
.chain([path_stem(&cand.file)])
.map(str::to_lowercase)
.collect();
let mut at = 0;
for seg in outer {
at += chain[at..].iter().position(|c| joiners_eq(c, seg))? + 1;
}
let inner = at + chain[at..].iter().rposition(|c| joiners_eq(c, last))?;
let own_dir = chain.len() - 2;
Some((owned, outer.len() + 1, own_dir.saturating_sub(inner)))
}
fn contiguous_highlight(positions: Vec<usize>, name: &str) -> Vec<usize> {
if positions.is_empty() {
return positions;
}
let boundary = boundaries(&name.chars().collect::<Vec<_>>());
let mut out = Vec::with_capacity(positions.len());
let mut i = 0;
while i < positions.len() {
let mut j = i;
while j + 1 < positions.len() && positions[j + 1] == positions[j] + 1 {
j += 1;
}
if j > i {
out.extend_from_slice(&positions[i..=j]); } else if boundary[positions[i]] {
out.push(positions[i]); }
i = j + 1;
}
out
}
pub(crate) const PRIMARY_KINDS: [&str; 5] = ["class", "module", "struct", "enum", "trait"];
fn is_primary_kind(kind: &str) -> bool {
PRIMARY_KINDS.contains(&kind)
}
pub(super) fn fuzzy_value(q: &str, name: &str) -> Option<f64> {
let sigil = q.len() - q.trim_start_matches(|c: char| !c.is_alphanumeric()).len();
if sigil > 0 && name.get(..sigil) == Some(&q[..sigil]) {
return fuzzy_value(&q[sigil..], &name[sigil..]).map(|v| v + SIGIL_CREDIT * sigil as f64);
}
let s = subsequence_score(q, name)?;
let tail = name.chars().count().saturating_sub(q.chars().count());
Some(s.min(600.0) - (tail as f64).min(100.0))
}
fn case_agreement(query: &str, name: &str) -> Option<f64> {
let positions = align(query, name)?.positions;
let chars: Vec<char> = name.chars().collect();
let (mut agree, mut cased) = (0usize, 0usize);
for (c, &i) in query
.chars()
.filter(|c| c.is_alphanumeric())
.zip(&positions)
{
if c.is_uppercase() || c.is_lowercase() {
cased += 1;
agree += usize::from(c.is_uppercase() == chars[i].is_uppercase());
}
}
(cased > 0).then(|| agree as f64 / cased as f64)
}
pub(super) fn subsequence_score(query: &str, name: &str) -> Option<f64> {
align(query, name).map(|a| a.score)
}
pub(crate) fn has_wildcard(query: &str) -> bool {
match query.strip_suffix('?') {
Some(body) if !body.contains(['*', '?']) => false,
_ => query.contains(['*', '?']),
}
}
pub(crate) fn strip_wildcards(query: &str) -> String {
query.chars().filter(|c| !matches!(c, '*' | '?')).collect()
}
enum Glob {
Lit(char), Any, Star, }
fn compile_glob(query: &str) -> Vec<Glob> {
query
.chars()
.filter_map(|c| match c {
'*' => Some(Glob::Star),
'?' => Some(Glob::Any),
c if c.is_alphanumeric() => Some(Glob::Lit(fold(c))),
_ => None,
})
.collect()
}
fn glob_positions(query: &str, name: &str) -> Option<Vec<usize>> {
let mut toks = vec![Glob::Star];
toks.extend(compile_glob(query));
toks.push(Glob::Star);
let lower: Vec<char> = name.chars().map(fold).collect();
let mut ti = 0;
let mut ni = 0;
let mut positions: Vec<usize> = Vec::new();
let mut star: Option<(usize, usize, usize)> = None;
while ni < lower.len() {
match toks.get(ti) {
Some(Glob::Lit(c)) if lower[ni] == *c => {
positions.push(ni);
ti += 1;
ni += 1;
}
Some(Glob::Lit(_)) if !lower[ni].is_alphanumeric() => {
ni += 1;
}
Some(Glob::Any) => {
ti += 1;
ni += 1;
}
Some(Glob::Star) => {
star = Some((ti + 1, ni, positions.len()));
ti += 1;
}
_ => match star {
Some((sti, sni, plen)) => {
ti = sti;
ni = sni + 1;
star = Some((sti, sni + 1, plen));
positions.truncate(plen);
}
None => return None,
},
}
}
while matches!(toks.get(ti), Some(Glob::Star)) {
ti += 1;
}
(ti == toks.len()).then_some(positions)
}
pub(super) fn wildcard_score(query: &str, name: &str) -> Option<f64> {
let positions = glob_positions(query, name)?;
if positions.is_empty() {
return None;
}
let chars: Vec<char> = name.chars().collect();
let boundary = boundaries(&chars);
let mut score = 0.0;
let mut prev: Option<usize> = None;
for &i in &positions {
score += 10.0;
if boundary[i] {
score += 15.0;
}
match prev {
Some(p) if p + 1 == i => score += 10.0, None if i == 0 => score += 20.0, _ => {}
}
prev = Some(i);
}
Some(score)
}
pub(crate) fn path_stem(path: &str) -> &str {
let base = path.rsplit(['/', '\\']).next().unwrap_or(path);
match base.rfind('.') {
Some(i) if i > 0 => &base[..i],
_ => base,
}
}
pub(super) fn boundaries(chars: &[char]) -> Vec<bool> {
let mut out = vec![false; chars.len()];
for i in 0..chars.len() {
let c = chars[i];
out[i] = if i == 0 {
true
} else {
let prev = chars[i - 1];
!prev.is_alphanumeric()
|| (c.is_uppercase() && prev.is_lowercase())
|| (c.is_uppercase()
&& prev.is_uppercase()
&& chars.get(i + 1).is_some_and(|n| n.is_lowercase()))
};
}
out
}
pub(crate) fn near_miss_possible(query: &str, name: &str) -> bool {
let leaf = parse_qualified(query).0;
let (qlen, nlen) = (leaf.chars().count(), name.chars().count());
if qlen < 4 || qlen.abs_diff(nlen) > MAX_NEAR_MISS {
return false;
}
let mut qc = leaf.chars().map(fold);
let mut nc = name.chars().map(fold);
match (qc.next(), qc.next(), nc.next(), nc.next()) {
(Some(q0), Some(q1), Some(n0), Some(n1)) => q0 == n0 || (q0 == n1 && q1 == n0),
_ => false,
}
}
pub(super) fn near_miss_distance(q: &str, name: &str) -> Option<usize> {
let (qlen, nlen) = (q.chars().count(), name.chars().count());
if qlen < 4 || qlen.abs_diff(nlen) > MAX_NEAR_MISS {
return None;
}
let mut qc = q.chars();
let mut nc = name.chars();
let (q0, q1) = (qc.next()?, qc.next()?);
let (n0, n1) = (nc.next()?, nc.next()?);
if q0 != n0 && !(q0 == n1 && q1 == n0) {
return None;
}
let (a, b): (Vec<char>, Vec<char>) = (q.chars().collect(), name.chars().collect());
let mut prev2: Vec<usize> = vec![0; b.len() + 1];
let mut prev: Vec<usize> = (0..=b.len()).collect();
let mut cur: Vec<usize> = vec![0; b.len() + 1];
for i in 1..=a.len() {
cur[0] = i;
let mut best = cur[0];
for j in 1..=b.len() {
let cost = usize::from(a[i - 1] != b[j - 1]);
cur[j] = (prev[j] + 1).min(cur[j - 1] + 1).min(prev[j - 1] + cost);
if i > 1 && j > 1 && a[i - 1] == b[j - 2] && a[i - 2] == b[j - 1] {
cur[j] = cur[j].min(prev2[j - 2] + 1);
}
best = best.min(cur[j]);
}
if best > MAX_NEAR_MISS {
return None;
}
std::mem::swap(&mut prev2, &mut prev);
std::mem::swap(&mut prev, &mut cur);
}
let d = prev[b.len()];
(d > 0 && d <= MAX_NEAR_MISS).then_some(d)
}
fn near_miss_score(query: &str, name: &str, edits: usize) -> f64 {
let right = common_subsequence(query, name);
let len = right.chars().count();
let aligned = align(&right, name).map_or(0.0, |a| a.score.min(600.0));
let kept = len.saturating_sub(edits) as f64 / len.max(1) as f64;
let tail = name.chars().count().saturating_sub(len);
aligned * kept - (tail as f64).min(100.0)
}
fn common_subsequence(a: &str, b: &str) -> String {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().map(fold).collect();
let eq = |i: usize, j: usize| fold(a[i]) == b[j];
let mut len = vec![vec![0usize; b.len() + 1]; a.len() + 1];
for i in (0..a.len()).rev() {
for j in (0..b.len()).rev() {
len[i][j] = if eq(i, j) {
len[i + 1][j + 1] + 1
} else {
len[i + 1][j].max(len[i][j + 1])
};
}
}
let (mut i, mut j, mut out) = (0, 0, String::new());
while i < a.len() && j < b.len() {
if eq(i, j) {
out.push(a[i]);
(i, j) = (i + 1, j + 1);
} else if len[i + 1][j] >= len[i][j + 1] {
i += 1;
} else {
j += 1;
}
}
out
}
pub(super) fn fold(c: char) -> char {
if c.is_ascii() {
return c.to_ascii_lowercase();
}
match c.to_lowercase().next() {
Some('ς') => 'σ',
Some(l) if !l.is_ascii() => l,
_ => c,
}
}
pub(super) fn lower(s: &str) -> std::borrow::Cow<'_, str> {
if s.is_ascii() && !s.bytes().any(|b| b.is_ascii_uppercase()) {
std::borrow::Cow::Borrowed(s)
} else {
std::borrow::Cow::Owned(s.to_lowercase())
}
}
pub(crate) fn joiners_eq(a: &str, b: &str) -> bool {
let word = |c: &char| !matches!(c, '_' | '-' | '.');
let mut sa = a.chars().filter(word);
let mut sb = b.chars().filter(word);
let mut any = false;
loop {
match (sa.next(), sb.next()) {
(None, None) => return any,
(Some(x), Some(y)) if x == y => any = true,
_ => return false,
}
}
}
fn in_example_path(file: &str) -> bool {
let dirs = file.rsplit_once('/').map_or("", |(d, _)| d);
dirs.split('/').any(|seg| {
matches!(
seg,
"example" | "examples" | "_examples" | "demo" | "demos" | "docs" | "dev-docs"
)
})
}
fn in_test_path(file: &str) -> bool {
let (dirs, name) = match file.rsplit_once('/') {
Some((d, n)) => (d, n),
None => ("", file),
};
if dirs.split('/').any(|seg| {
matches!(
seg,
"test"
| "tests"
| "spec"
| "specs"
| "__tests__"
| "__mocks__"
| "testdata"
| "fixtures"
)
}) {
return true;
}
if name == "conftest.py" {
return true;
}
let stem = name.rsplit_once('.').map_or(name, |(s, _)| s);
stem.ends_with("_test")
|| stem.ends_with("_spec")
|| stem.ends_with(".test")
|| stem.ends_with(".spec")
}
fn in_test_scope(parent: Option<&str>, name: &str, kind: &str) -> bool {
let test = |seg: &str| matches!(seg, "tests" | "test") || seg.ends_with("_tests");
parent.is_some_and(|p| p.split("::").flat_map(|s| s.split(['#', '.'])).any(test))
|| (kind == "module" && test(name))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recognizes_test_paths_without_catching_libraries_about_testing() {
for p in [
"actionpack/test/lib/controller/fake_models.rb",
"spec/models/widget_spec.rb",
"pkg/thing/thing_test.go",
"src/__tests__/widget.ts",
"src/widget.test.tsx",
"tests/conftest.py",
"internal/testdata/sample.go",
] {
assert!(in_test_path(p), "should be a test path: {p}");
}
for p in [
"activesupport/lib/active_support/test_case.rb",
"activesupport/lib/active_support/testing/assertions.rb",
"activejob/lib/active_job/test_helper.rb",
"src/search/score.rs",
"lib/latest.rb",
] {
assert!(!in_test_path(p), "should not be a test path: {p}");
}
}
#[test]
fn recognizes_test_scopes_by_their_lowercase_name() {
for (parent, name, kind) in [
(Some("tests"), "helper", "function"),
(Some("store::tests"), "fixture", "function"),
(Some("resolve::surface_tests"), "case", "function"),
(None, "tests", "module"),
] {
assert!(in_test_scope(parent, name, kind), "{parent:?} {name}");
}
for (parent, name, kind) in [
(Some("Minitest::Test"), "assert", "method"),
(Some("ActiveSupport::Testing"), "travel", "method"),
(Some("testing"), "helper", "function"),
(None, "tests", "function"),
(None, "latest", "module"),
] {
assert!(!in_test_scope(parent, name, kind), "{parent:?} {name}");
}
}
fn row(name: &str, kind: &str, repo: i64) -> SymbolRow {
SymbolRow {
name: name.into(),
kind: kind.into(),
language: "ruby".into(),
file: "f.rb".into(),
line: 1,
end_line: Some(1),
parent: None,
repository_id: repo,
repo_identity: "r".into(),
mtime: None,
git_ts: None,
visibility: None,
stub: false,
generated: false,
}
}
fn total(query: &str, name: &str) -> Option<f64> {
score(
query,
&row(name, "class", 1),
None,
Boosts::default(),
false,
)
.map(|s| s.total)
}
#[test]
fn a_typo_prefers_the_tight_match_over_a_longer_superstring() {
let tight = total("Validaton", "Validations").unwrap();
let longer = total("Validaton", "ValidationError").unwrap();
assert!(tight > longer, "{tight} > {longer}");
assert!(total("apc", "ApplicationController").is_some());
}
#[test]
fn a_near_miss_catches_the_typos_a_subsequence_cannot() {
let d = |q: &str, name: &str| near_miss_distance(q, name);
assert_eq!(d("connectoin_pool", "connection_pool"), Some(1)); assert_eq!(d("connection_poool", "connection_pool"), Some(1)); assert_eq!(d("activerecrod", "activerecord"), Some(1));
assert_eq!(d("connection_pool", "widget_factory"), None);
assert_eq!(d("cat", "car"), None);
assert_eq!(d("widget", "widget"), None);
}
#[test]
fn a_real_body_outranks_a_stub_of_the_same_name() {
let span = |lines: i64| {
let mut r = row("where", "method", 1);
r.end_line = Some(r.line + lines - 1);
score("where", &r, None, Boosts::default(), false)
.unwrap()
.total
};
assert!(span(9) > span(3), "a real body should outrank a stub");
assert!(span(4000) - span(40) < CASE_MATCH);
}
#[test]
fn an_implementation_outranks_its_declaration_elsewhere() {
let declared = SymbolRow {
file: "types/widget.d.ts".into(),
end_line: Some(200),
visibility: Some("public".into()),
stub: true,
..row("Widget", "class", 1)
};
let implemented = SymbolRow {
file: "lib/index.js".into(),
end_line: Some(40),
visibility: Some("private".into()),
..row("Widget", "class", 1)
};
let total = |q: &str, c: &SymbolRow| score(q, c, None, Boosts::default(), false);
let stub = total("Widget", &declared).unwrap();
assert!(total("Widget", &implemented).unwrap().total > stub.total);
assert!(stub.features.iter().any(|f| f.name == "stub"));
let longer = row("WidgetBuilder", "class", 1);
assert!(stub.total > total("Widget", &longer).unwrap().total);
}
#[test]
fn separators_left_out_still_read_as_an_exact_match() {
let exact = total("parsefile", "parse_file").unwrap();
let plural = total("parsefile", "parse_files").unwrap();
assert!(exact > plural, "{exact} > {plural}");
assert!(total("parse_file", "parse_file").unwrap() > exact);
}
#[test]
fn a_sigil_the_query_left_off_is_not_an_exact_match() {
let literal = |query: &str, name: &str| {
score(
query,
&row(name, "method", 1),
None,
Boosts::default(),
false,
)
.is_some_and(|s| s.features.iter().any(|f| f.name == "exact"))
};
for (query, name) in [("save", "save!"), ("valid", "valid?"), ("name", "name=")] {
assert!(!literal(query, name), "{query} is not exactly {name}");
assert!(literal(name, name));
assert!(!literal(name, query), "{name} is not exactly {query}");
let gap = total(query, query).unwrap() - total(query, name).unwrap();
assert!(gap > 200.0, "{query}: {name} trails by {gap}");
}
assert!(literal("isvalid?", "is_valid?"));
assert!(literal("init", "__init__"));
}
#[test]
fn the_shallower_of_two_identical_matches_wins() {
let nested = |parent: &str| {
let mut r = row("save", "method", 1);
r.parent = Some(parent.into());
score("save", &r, None, Boosts::default(), false)
.unwrap()
.total
};
let shallow = nested("ActiveRecord::Persistence");
let deep = nested("ActiveRecord::Middleware::DatabaseSelector::Resolver::Session");
assert!(shallow > deep, "{shallow} > {deep}");
assert_eq!(nested("ActiveRecord::Persistence"), nested("Widget"));
assert!(shallow - deep < CASE_MATCH, "depth outweighs match quality");
}
#[test]
fn source_outranks_an_identical_match_in_a_test() {
let at = |file: &str| {
let mut r = row("save", "method", 1);
r.file = file.into();
score("save", &r, None, Boosts::default(), false)
.unwrap()
.total
};
let lib = at("activerecord/lib/active_record/persistence.rb");
let fixture = at("actionpack/test/lib/controller/fake_models.rb");
assert!(lib > fixture, "{lib} > {fixture}");
assert!(fixture > 0.0);
assert_eq!(fixture, at("spec/models/widget_spec.rb"));
}
#[test]
fn example_and_docs_apps_rank_below_the_library() {
let at = |file: &str| SymbolRow {
file: file.into(),
..row("Widget", "class", 1)
};
let total = |c: &SymbolRow| score("Widget", c, None, Boosts::default(), false).unwrap();
let library = total(&at("packages/widget/index.tsx")).total;
for file in [
"examples/with-next/src/pages/widget.tsx",
"dev-docs/src/theme/index.js",
"docs/_ext/widget.py",
"demo/widget.go",
] {
let example = total(&at(file));
assert!(library > example.total, "{file}");
assert!(example.features.iter().any(|f| f.name == "example_path"));
}
assert!(!in_example_path("crates/core/flags/doc/help.rs"));
assert!(!in_example_path("lib/examples_helper.rb"));
}
#[test]
fn generated_code_ranks_below_hand_written_code() {
let hand = row("String", "method", 1);
let generated = SymbolRow {
generated: true,
..row("String", "method", 1)
};
let total = |c: &SymbolRow| score("String", c, None, Boosts::default(), false).unwrap();
let g = total(&generated);
assert!(total(&hand).total > g.total);
assert!(g.features.iter().any(|f| f.name == "generated"));
}
#[test]
fn a_strong_fuzzy_match_in_a_test_outranks_a_weak_one_outside() {
let at = |name: &str, file: &str| {
let mut r = row(name, "method", 1);
r.file = file.into();
score("coclfi", &r, None, Boosts::default(), false).unwrap()
};
let strong = at("conditional_class_filter", "test/filters_test.rb");
let weak = at("remove_scoped_cable_files_if_skipped", "lib/generator.rb");
assert!(
strong.total > weak.total,
"{} > {}",
strong.total,
weak.total
);
let lib = at("conditional_class_filter", "lib/filters.rb");
assert!(lib.total > strong.total);
let exact = |file: &str| {
let mut r = row("save", "method", 1);
r.file = file.into();
score("save", &r, None, Boosts::default(), false).unwrap()
};
let lib = exact("lib/persistence.rb").total;
assert_eq!(lib - exact("test/fake_models.rb").total, TEST_PATH_PENALTY);
}
#[test]
fn a_leading_underscore_asks_for_the_underscored_name() {
let fz = |q: &str, name: &str| {
let mut r = row(name, "method", 1);
r.visibility = Some(
if name.starts_with('_') {
"private"
} else {
"public"
}
.into(),
);
score(q, &r, None, Boosts::default(), false).unwrap().total
};
assert!(fz("_frmtr", "_formatter") > fz("_frmtr", "formatter"));
assert!(fz("frmtr", "formatter") > fz("frmtr", "_formatter"));
assert!(fuzzy_value("_zq", "_formatter").is_none());
assert!(fuzzy_value("_frmtr", "formatter").is_some());
}
#[test]
fn a_typed_capital_picks_the_matching_case() {
let upper = total("Symbol", "Symbol").unwrap();
let lower = total("Symbol", "symbol").unwrap();
assert!(upper > lower, "{upper} > {lower}");
assert!(upper - lower > 120.0, "margin {} too small", upper - lower);
}
#[test]
fn a_typed_capital_counts_on_an_approximate_match_too() {
let typo = |q: &str, name: &str| {
score(q, &row(name, "function", 1), None, Boosts::default(), true)
.unwrap()
.total
};
assert!(typo("Fraem", "Frame") > typo("Fraem", "frame"));
assert!(typo("FRAEM", "FRAME") > typo("FRAEM", "Frame"));
assert!(total("Widg", "WidgetBox") > total("Widg", "widget_box"));
assert!(total("WdgBx", "WidgetBox") > total("WdgBx", "widget_box"));
assert_eq!(typo("fraem", "Frame"), typo("fraem", "frame"));
}
#[test]
fn a_lowercase_query_stays_case_agnostic() {
assert_eq!(total("symbol", "symbol"), total("symbol", "Symbol"));
assert_eq!(total("user", "User"), total("user", "user"));
}
#[test]
fn a_closer_name_outranks_a_bigger_definition() {
let method = row("find_public_node", "method", 1);
let mut class = row("RefillPushNoticeLevel", "class", 1);
class.file = "app/models/refill_push_notice_level.rb".into();
class.end_line = Some(200);
let at = |r: &SymbolRow| score("fipuno", r, None, Boosts::default(), false).unwrap();
let (m, c) = (at(&method), at(&class));
assert!(name_evidence(&m.features) > name_evidence(&c.features));
assert!(m.total > c.total, "{} > {}", m.total, c.total);
let exact = |r: &SymbolRow| score(&r.name, r, None, Boosts::default(), false).unwrap();
let extent = |s: &Scored| {
s.features
.iter()
.find(|f| f.name == "extent")
.unwrap()
.value
};
assert_eq!(extent(&exact(&class)), MAX_BODY_BONUS);
}
#[test]
fn private_ranks_below_public_on_an_equal_match() {
let mut public = row("save", "method", 1);
public.visibility = Some("public".into());
let mut private = row("save", "method", 1);
private.visibility = Some("private".into());
let unknown = row("save", "method", 1);
let pub_score = score("save", &public, None, Boosts::default(), false).unwrap();
let priv_score = score("save", &private, None, Boosts::default(), false).unwrap();
let unk_score = score("save", &unknown, None, Boosts::default(), false).unwrap();
assert!(pub_score.total > priv_score.total);
assert_eq!(
pub_score.total, unk_score.total,
"unknown carries no penalty"
);
assert!(priv_score.total > 700.0, "still comfortably above a prefix");
}
#[test]
fn exact_beats_prefix_beats_fuzzy() {
let exact = total("user", "user").unwrap();
let prefix = total("user", "users").unwrap();
let fuzzy = total("usr", "user").unwrap();
assert!(exact > prefix, "{exact} > {prefix}");
assert!(prefix > fuzzy, "{prefix} > {fuzzy}");
}
#[test]
fn abbreviations_match() {
assert!(total("refundproc", "RefundProcessor").is_some());
assert!(total("refproc", "RefundProcessor").is_some());
assert!(total("paymnt", "Payments").is_some());
assert!(total("perf", "perform").is_some());
assert!(total("usr", "User").is_some());
assert!(total("ctrl", "Controller").is_some());
}
#[test]
fn rejects_scattered_midword_matches() {
assert!(total("employeescontroller", "EmployeeXYZsController").is_none());
assert!(total("employeescontroller", "EmployeesController").is_some());
assert!(total("employescontroller", "EmployeesController").is_some());
}
#[test]
fn non_ascii_letters_match_across_case() {
assert_eq!(match_positions("ΣΑΣprs", "ΣΑΣParser"), [0, 1, 2, 3, 5, 6]);
assert_eq!(match_positions("σασprs", "ΣΑΣParser"), [0, 1, 2, 3, 5, 6]);
assert_eq!(match_positions(&lower("ΣΑΣ"), "ΣΑΣParser"), [0, 1, 2]);
assert_eq!(match_positions("grösse", "GRÖSSE"), [0, 1, 2, 3, 4, 5]);
assert!(match_positions("istrtr", "İstanbulRouter").is_empty());
}
#[test]
fn match_positions_report_what_matched() {
assert_eq!(match_positions("foo", "FooThing"), vec![0, 1, 2]);
assert_eq!(match_positions("ft", "FooThing"), vec![0, 3]); assert_eq!(match_positions("wc", "WidgetController"), vec![0, 6]); assert!(match_positions("xyz", "FooThing").is_empty());
}
#[test]
fn prefers_the_contiguous_run_over_an_earlier_scattered_match() {
assert_eq!(
match_positions("employee", "xxxe_employee"),
vec![5, 6, 7, 8, 9, 10, 11, 12]
);
assert_eq!(
match_positions("controller", "calc_controller"),
(5..15).collect::<Vec<_>>()
);
assert_eq!(
match_positions("widgetcontroller", "WidgetController"),
(0..16).collect::<Vec<_>>()
);
}
#[test]
fn a_consonant_skeleton_may_drop_a_word_initial_vowel() {
assert_eq!(
match_positions("prsnch", "parse_anchor"),
[0, 2, 3, 7, 8, 9]
);
assert!(subsequence_score("mxncls", "MAX_ENCLOSING").is_some());
assert!(subsequence_score("prsnchr", "parse_branchor").is_none());
let score = |name| subsequence_score("prsnch", name).unwrap();
assert!(score("parse_anchor") > score("parse_x_anchor"));
}
#[test]
fn matches_only_span_adjacent_words() {
assert_eq!(
match_positions("employeescontroller", "employees_controller"),
vec![
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
]
);
assert!(subsequence_score("refproc", "RefundProcessor").is_some());
assert!(subsequence_score("refprocsvc", "RefundProcessingService").is_some());
}
#[test]
fn skipping_a_word_costs_more_than_entering_it() {
assert!(subsequence_score("braboost", "BRANCH_DIR_BOOST").is_some());
let score = |q, name| subsequence_score(q, name).unwrap();
let skipped = score("paycontroller", "pay_runs_controller");
let adjacent = score("paycontroller", "pay_controller");
assert!(
adjacent - skipped >= WORD_SKIP_PENALTY,
"{adjacent} vs {skipped}"
);
assert!(score("refsvc", "RefundService") > score("refsvc", "RefundProcessingService"));
}
#[test]
fn a_contiguous_match_beats_a_farther_boundary_jump() {
assert_eq!(match_positions("car", "car_r"), vec![0, 1, 2]);
}
#[test]
fn acronyms_highlight_word_initials_across_adjacent_words() {
assert_eq!(match_positions("uc", "UserController"), vec![0, 4]);
assert_eq!(
match_positions("abc", "alpha_bravo_charlie"),
vec![0, 6, 12] );
let score = |name| subsequence_score("apc", name).unwrap();
assert!(score("alpha_charlie") > score("alpha_bravo_charlie"));
}
#[test]
fn a_gap_cannot_cross_a_word_boundary_into_a_mid_word_char() {
let scattered = "employee_before_starting_controller";
let positions = match_positions("employeescontroller", scattered);
assert!(
!positions.iter().any(|p| (9..15).contains(p)),
"{positions:?}"
);
let score = |name| subsequence_score("employeescontroller", name).unwrap();
assert!(score("employees_controller") - score(scattered) > WORD_SKIP_PENALTY);
assert!(subsequence_score("usr", "user").is_some());
assert!(subsequence_score("cfg", "config").is_some());
}
#[test]
fn a_contiguous_word_match_outranks_a_scattered_cross_word_one() {
let contiguous = total("test", "test_helper").unwrap(); let scattered = total("test", "the_settings_store");
if let Some(s) = scattered {
assert!(contiguous > s, "contiguous {contiguous} > scattered {s}");
}
}
#[test]
fn score_and_positions_come_from_the_same_alignment() {
assert!(subsequence_score("refproc", "RefundProcessor").is_some());
assert_eq!(match_positions("refproc", "RefundProcessor").len(), 7);
assert!(subsequence_score("xyz", "RefundProcessor").is_none());
assert!(match_positions("xyz", "RefundProcessor").is_empty());
}
#[test]
fn highlights_are_ordered_in_bounds_and_correct_across_varied_inputs() {
let cases = [
("usr", "UserService"),
("paymnt", "Payments"),
("wc", "WidgetController"),
("ctrl", "Controller"),
("gp", "get_post"),
("ab", "alpha_beta"),
("refproc", "RefundProcessor"),
("emp", "EmployeesController"),
("http", "HTTPParser"),
];
for (q, name) in cases {
let nchars: Vec<char> = name.chars().collect();
let qchars: Vec<char> = q.chars().filter(|c| c.is_alphanumeric()).collect();
let boundary = boundaries(&nchars);
let pos = match_positions(q, name);
assert!(
pos.windows(2).all(|w| w[0] < w[1]),
"strictly increasing: {q}/{name} {pos:?}"
);
let mut qi = 0;
for &p in &pos {
assert!(p < nchars.len(), "in bounds: {q}/{name}");
while qi < qchars.len() && fold(qchars[qi]) != fold(nchars[p]) {
qi += 1;
}
assert!(
qi < qchars.len(),
"highlight maps to a query char: {q}/{name}"
);
qi += 1;
}
for (idx, &p) in pos.iter().enumerate() {
let clumped = (idx > 0 && pos[idx - 1] + 1 == p)
|| (idx + 1 < pos.len() && p + 1 == pos[idx + 1]);
assert!(
clumped || boundary[p],
"no isolated mid-word highlight: {q}/{name} at {p} {pos:?}"
);
}
}
}
#[test]
fn highlights_avoid_isolated_single_chars() {
assert_eq!(
match_positions("paymnt", "Payments"),
vec![0, 1, 2, 3, 5, 6]
);
assert_eq!(match_positions("usr", "UserService"), vec![0, 1]);
assert_eq!(match_positions("ctrl", "Controller"), vec![0, 3, 4]);
assert!(match_positions("rp", "wrapper").is_empty());
assert_eq!(match_positions("uc", "UserController"), vec![0, 4]);
}
#[test]
fn an_acronym_at_boundaries_outranks_a_mid_word_alignment() {
let acronym = subsequence_score("wc", "WidgetController").unwrap();
let midword = subsequence_score("wc", "switchcase").unwrap();
assert!(acronym > midword, "{acronym} > {midword}");
}
#[test]
fn a_far_path_straggler_never_outranks_a_prefix_match() {
let mut straggler = row("Thing", "class", 1);
straggler.file = "app/employee_x_syy.rb".into();
let prefixed = row("EmployeesController", "class", 1);
let pre = score("employees", &prefixed, None, Boosts::default(), false)
.unwrap()
.total;
if let Some(s) = score("employees", &straggler, None, Boosts::default(), false) {
assert!(pre > s.total, "prefix {pre} > path straggler {}", s.total);
}
}
#[test]
fn snake_case_query_matches_camelcase_name() {
assert!(total("widget_controller", "WidgetsController").is_some());
assert!(total("widget_controller", "WidgetController").is_some());
assert!(total("widget_controller", "AdminController").is_none());
}
#[test]
fn wildcard_star_spans_an_explicit_gap() {
assert!(total("find*controller", "FindController").is_some());
assert!(total("find*controller", "FindUserController").is_some());
assert!(total("find*controller", "FindUserAccountController").is_some());
assert!(total("find*ctrlr", "FindController").is_none());
assert!(total("find*controller", "FindService").is_none());
}
#[test]
fn a_trailing_question_mark_is_a_predicate_name() {
assert!(!has_wildcard("empty?"));
assert!(has_wildcard("emp?y"));
assert!(has_wildcard("emp?y?"));
assert!(has_wildcard("find*?"));
}
#[test]
fn wildcard_literals_step_over_the_names_separators() {
assert!(total("only_up*s", "only_uploads").is_some());
assert!(total("onlyup*s", "only_uploads").is_some());
}
#[test]
fn wildcard_question_mark_matches_one_char() {
assert!(total("find?controller", "FindXController").is_some());
assert!(total("find?controller", "Find1Controller").is_some());
assert!(total("find?controller", "FindController").is_none());
assert!(total("find?controller", "FindXyController").is_none());
}
#[test]
fn wildcard_highlights_only_the_literals() {
assert_eq!(
match_positions("find*er", "FindController"),
vec![0, 1, 2, 3, 12, 13] );
}
#[test]
fn wildcard_prefers_boundary_aligned_matches() {
let boundary = total("a*b", "Alpha_Bravo").unwrap();
let midword = total("a*b", "Alphabet").unwrap();
assert!(boundary > midword, "{boundary} > {midword}");
}
#[test]
fn non_subsequence_does_not_match() {
assert!(total("xyz", "RefundProcessor").is_none());
assert!(total("zzz", "User").is_none());
}
#[test]
fn confidence_reflects_quality_and_dominance() {
let exact = vec![Feature {
name: "exact",
value: 1000.0,
}];
let fuzzy = vec![Feature {
name: "fuzzy",
value: 300.0,
}];
assert_eq!(confidence(1000.0, match_quality(&exact), None), 1.0);
let f = confidence(300.0, match_quality(&fuzzy), None);
assert!(f > 0.3 && f < 0.65, "fuzzy confidence {f}");
let tied = confidence(1000.0, match_quality(&exact), Some(1000.0));
assert!(tied < 0.6, "tied exact confidence {tied}");
let dominant = confidence(1000.0, match_quality(&exact), Some(300.0));
assert!(dominant > 0.9, "dominant confidence {dominant}");
}
#[test]
fn parse_qualified_splits_on_scope_separators() {
assert_eq!(parse_qualified("User"), ("User", None));
assert_eq!(parse_qualified("Foo::Bar"), ("Bar", Some("Foo")));
assert_eq!(parse_qualified("App::Foo::Bar"), ("Bar", Some("App::Foo")));
assert_eq!(parse_qualified("Foo::Bar#baz"), ("baz", Some("Foo::Bar")));
assert_eq!(parse_qualified("Foo.bar"), ("bar", Some("Foo")));
assert_eq!(parse_qualified("Foo::Bar.baz"), ("baz", Some("Foo::Bar")));
assert_eq!(parse_qualified("::Bar"), ("::Bar", None));
assert_eq!(parse_qualified("Foo::"), ("Foo::", None));
}
#[test]
fn a_receiver_in_parentheses_names_its_type() {
assert_eq!(segments("(*HugoSites)"), ["hugosites"]);
assert_eq!(segments("(*pkg.T)"), ["pkg", "t"]);
assert!(parent_boost("(*Widget)", Some("Widget"), false).is_some());
}
#[test]
fn parent_boost_matches_the_innermost_scopes() {
assert!(parent_boost("Foo", Some("Foo"), false).is_some());
assert!(parent_boost("Foo", Some("App::Foo"), false).is_some());
assert!(parent_boost("App::Foo", Some("App::Foo"), false).is_some());
let one = parent_boost("Foo", Some("App::Foo"), false).unwrap().0;
let two = parent_boost("App::Foo", Some("App::Foo"), false).unwrap().0;
assert!(two > one, "{two} > {one}");
assert!(parent_boost("App", Some("App::Foo"), false).is_none());
assert!(parent_boost("Foo", Some("Foo::Inner"), false).is_none());
assert!(parent_boost("Foo", None, false).is_none());
assert!(parent_boost("Inner", Some("Outer.Inner"), false).is_some());
assert!(parent_boost("Widgit", Some("Widget"), false).is_none());
assert_eq!(parent_boost("Widgit", Some("Widget"), true).unwrap().1, 1);
assert!(parent_boost("Gadget", Some("Widget"), true).is_none());
}
#[test]
fn a_scope_no_parent_records_is_found_in_the_path() {
let at = |file: &str, parent: Option<&str>| SymbolRow {
file: file.into(),
parent: parent.map(Into::into),
repo_identity: "github.com/org/shop".into(),
..row("Widget", "class", 1)
};
let features = |q: &str, c: &SymbolRow| {
score(q, c, None, Boosts::default(), false).map(|s| {
s.features
.iter()
.filter(|f| matches!(f.name, "parent" | "path_scope"))
.map(|f| (f.name, f.value))
.collect::<Vec<_>>()
})
};
let module = at("lib/shop/db/models/widget.py", None);
assert!(features("db.models.Widget", &module).is_some());
assert!(features("shop.models.Widget", &module).is_some());
assert!(features("models.widget.Widget", &module).is_some());
assert!(features("models.db.Widget", &module).is_none());
assert!(features("orders.Widget", &module).is_none());
let root = features("shop.Widget", &at("widget.go", None)).unwrap();
let below = features("shop.Widget", &at("orders/widget.go", None)).unwrap();
assert_eq!((root[0].1, below[0].1), (30.0, 15.0));
assert!(features("shop_util.Widget", &at("shop-util/src/a.rs", None)).is_some());
let method = at("pkg/widgets/widget.go", Some("Catalog"));
let mixed = features("widgets.Catalog.Widget", &method).unwrap();
assert_eq!(mixed, [("parent", 180.0), ("path_scope", 30.0)]);
let owned = features("Catalog.Widget", &method).unwrap();
let total = |f: &[(&str, f64)]| f.iter().map(|x| x.1).sum::<f64>();
let pathed = features("widgets.Widget", &method).unwrap();
assert!(total(&owned) > total(&pathed));
}
#[test]
fn a_named_scope_excludes_candidates_outside_it() {
let in_foo = SymbolRow {
parent: Some("Foo".into()),
..row("Bar", "class", 1)
};
let in_baz = SymbolRow {
parent: Some("Baz".into()),
..row("Bar", "class", 1)
};
assert!(score("Foo::Bar", &in_foo, None, Boosts::default(), false).is_some());
assert!(score("Foo::Bar", &in_baz, None, Boosts::default(), false).is_none());
let top_level = row("Bar", "class", 1);
assert!(score("Foo::Bar", &top_level, None, Boosts::default(), false).is_none());
assert!(score("Bar", &in_baz, None, Boosts::default(), false).is_some());
assert!(score("Bar", &top_level, None, Boosts::default(), false).is_some());
assert!(score("Foo::Zzz", &in_foo, None, Boosts::default(), false).is_none());
}
#[test]
fn boundary_alignment_outranks_scattered() {
let aligned = total("rp", "RefundProcessor").unwrap();
let scattered = total("rp", "wrapper").unwrap();
assert!(aligned > scattered, "{aligned} > {scattered}");
}
#[test]
fn path_only_match_surfaces_a_class_in_a_named_file() {
let mut cand = row("Invoice", "class", 1);
cand.file = "app/models/billing.rb".into();
let s = score("billing", &cand, None, Boosts::default(), false).expect("path match");
assert!(s.features.iter().any(|f| f.name == "path"));
let mut method = row("compute", "method", 1);
method.file = "app/models/billing.rb".into();
assert!(score("billing", &method, None, Boosts::default(), false).is_none());
}
#[test]
fn path_bonus_reinforces_a_name_match() {
let mut named = row("User", "class", 1);
named.file = "app/models/user.rb".into();
let mut elsewhere = row("User", "class", 1);
elsewhere.file = "app/lib/misc.rb".into();
let with_path = score("user", &named, None, Boosts::default(), false)
.unwrap()
.total;
let without = score("user", &elsewhere, None, Boosts::default(), false)
.unwrap()
.total;
assert!(with_path > without, "{with_path} > {without}");
}
#[test]
fn current_repo_boost_applies() {
let cand = row("User", "class", 7);
let in_repo = score("user", &cand, Some(7), Boosts::default(), false)
.unwrap()
.total;
let out_repo = score("user", &cand, Some(99), Boosts::default(), false)
.unwrap()
.total;
assert!(in_repo > out_repo);
assert_eq!(in_repo - out_repo, 200.0);
}
#[test]
fn recency_boost_adds_to_the_score() {
let cand = row("User", "class", 1);
let base = score("user", &cand, None, Boosts::default(), false)
.unwrap()
.total;
let boosted = score(
"user",
&cand,
None,
Boosts {
recency: 80.0,
..Default::default()
},
false,
)
.unwrap();
assert_eq!(boosted.total - base, 80.0);
assert!(boosted.features.iter().any(|f| f.name == "recency"));
}
#[test]
fn branch_boost_adds_to_the_score() {
let cand = row("User", "class", 1);
let base = score("user", &cand, None, Boosts::default(), false)
.unwrap()
.total;
let boosted = score(
"user",
&cand,
None,
Boosts {
branch: 180.0,
..Default::default()
},
false,
)
.unwrap();
assert_eq!(boosted.total - base, 180.0);
assert!(boosted.features.iter().any(|f| f.name == "branch"));
}
}