use crate::{
models::{
self,
parsed::{Language, SymbolKind},
},
resolver::constant::{self, DEFAULT_SCORE},
};
pub const CASE_SENSITIVE_MATCHING_CAPITALISATION_BONUS: u16 = 8;
pub const CASE_SENSITIVE_MATCHING_CASE_BONUS: u16 = 4;
pub const CLEAR_QUERY_INTENT_SYMBOL_KINDS_SCORE_BONUS: i64 = (constant::DEFAULT_SCORE * 35) / 1000;
pub const COMMON_SYMBOL_KINDS_SCORE_BONUS: i64 = (constant::DEFAULT_SCORE * 35) / 1000;
pub const INFREQUENT_SYMBOL_KINDS_SCORE_BONUS: i64 = (constant::DEFAULT_SCORE * 15) / 1000;
pub const UNCOMMON_SYMBOL_KINDS_SCORE_PENALTY: i64 = -((constant::DEFAULT_SCORE * 15) / 1000);
pub const TEST_HARNESS_SCORE_PENALTY: i64 = -((constant::DEFAULT_SCORE * 10) / 1000);
pub const ENTRYPOINT_FILE_SCORE_PENALTY: i64 = -((constant::DEFAULT_SCORE * 25) / 10000);
pub const AUTOGENERATED_CODE_SCORE_PENALTY: i64 = -((constant::DEFAULT_SCORE * 50) / 1000);
pub const SAME_FILE_SCORE_PENALTY: i64 = -((constant::DEFAULT_SCORE * 10) / 1000);
pub const JS_TS_CAMEL_CASE_CONSTANT_PENALTY: i64 = -((constant::DEFAULT_SCORE * 45) / 1000);
pub fn calculate_distance_score_penalty(distance: usize) -> i64 {
const MAX_DISTANCE: i64 = 6;
if distance == 0 {
return 0;
}
let distance = i64::try_from(distance)
.unwrap_or(MAX_DISTANCE)
.min(MAX_DISTANCE);
-((constant::DEFAULT_SCORE * (distance * 2)) / 1000)
}
pub fn calculate_fuzzy_match_bonus(fuzzy_match: frizbee::Match) -> i64 {
match fuzzy_match {
fuzzy_match if fuzzy_match.exact => {
let score = i64::from(fuzzy_match.score);
((score / 4) * 2).min((DEFAULT_SCORE * 65) / 1000)
}
fuzzy_match => {
let score = i64::from(fuzzy_match.score);
(score / 4).min((DEFAULT_SCORE * 50) / 1000)
}
}
}
pub fn calculate_camel_case_constant_penalty(symbol: &models::resolved::ResolvedSymbol) -> i64 {
if symbol.language != Language::TypeScript
&& symbol.language != Language::TypeScriptJsx
&& symbol.language != Language::Javascript
&& symbol.language != Language::JavascriptJsx
{
return 0;
}
let mut characters = symbol.name.chars();
let is_first_character_lowercase = characters.next().is_some_and(char::is_lowercase);
let is_all_uppercase = !is_first_character_lowercase && characters.all(char::is_uppercase);
if symbol.kind != SymbolKind::Constant || is_all_uppercase || !is_first_character_lowercase {
return 0;
}
JS_TS_CAMEL_CASE_CONSTANT_PENALTY
}
pub fn calculate_clear_intent_bonus(query: &str, symbol: &models::resolved::ResolvedSymbol) -> i64 {
let has_uppercase = query.chars().any(char::is_uppercase);
let has_lowercase = query.chars().any(char::is_lowercase);
let has_underscores = query.chars().any(|c| c == '_');
let is_length_for_clear_intent =
query.len() >= constant::MIN_CLEAR_INTENT_QUERY_LENGTH as usize;
let is_upper_and_lower_mix = has_uppercase && has_lowercase && !has_underscores;
let is_snake_case = has_underscores && !has_uppercase;
let is_screaming_case = has_uppercase && !has_lowercase;
match symbol.kind {
models::parsed::SymbolKind::Constant
| models::parsed::SymbolKind::StaticField
| models::parsed::SymbolKind::StaticVariable
| models::parsed::SymbolKind::StaticDataMember
if is_length_for_clear_intent && is_screaming_case =>
{
CLEAR_QUERY_INTENT_SYMBOL_KINDS_SCORE_BONUS
}
models::parsed::SymbolKind::Struct
| models::parsed::SymbolKind::Type
| models::parsed::SymbolKind::TypeAlias
| models::parsed::SymbolKind::Class
| models::parsed::SymbolKind::Enum
| models::parsed::SymbolKind::EnumMember
| models::parsed::SymbolKind::Interface
| models::parsed::SymbolKind::Trait
| models::parsed::SymbolKind::Protocol
| models::parsed::SymbolKind::Union
| models::parsed::SymbolKind::Variable if is_length_for_clear_intent && is_upper_and_lower_mix =>
{
CLEAR_QUERY_INTENT_SYMBOL_KINDS_SCORE_BONUS
}
models::parsed::SymbolKind::Function
| models::parsed::SymbolKind::Method
| models::parsed::SymbolKind::Predicate
| models::parsed::SymbolKind::TraitMethod
| models::parsed::SymbolKind::ProtocolMethod
| models::parsed::SymbolKind::AbstractMethod
| models::parsed::SymbolKind::Getter
if is_length_for_clear_intent
&& (is_snake_case || is_upper_and_lower_mix ) =>
{
CLEAR_QUERY_INTENT_SYMBOL_KINDS_SCORE_BONUS
}
_ => 0,
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use rstest::rstest;
use crate::models::{
parsed::{Language, SymbolKind},
resolved::{ResolvedSymbol, Score},
};
#[rstest]
#[case(0, 0)]
#[case(1, -2)]
#[case(2, -4)]
#[case(3, -6)]
#[case(4, -8)]
#[case(5, -10)]
#[case(6, -12)]
#[case(7, -12)]
#[case(8, -12)]
#[case(9, -12)]
#[case(10, -12)]
pub fn test_distance_weighting(#[case] distance: usize, #[case] expected_penalty: i64) {
assert_eq!(
expected_penalty,
super::calculate_distance_score_penalty(distance)
);
}
#[rstest]
#[case("x", SymbolKind::Constant, Language::TypeScript, -45)]
#[case("someVar", SymbolKind::Constant, Language::TypeScript, -45)]
#[case("MAX_RETRIES", SymbolKind::Constant, Language::TypeScript, 0)]
#[case("Vehicle", SymbolKind::Constant, Language::TypeScript, 0)]
#[case("CakeMixer", SymbolKind::Constant, Language::TypeScript, 0)]
#[case("batter", SymbolKind::Constant, Language::Javascript, -45)]
#[case("latte", SymbolKind::Constant, Language::JavascriptJsx, -45)]
#[case("MAX_ATTEMPTS", SymbolKind::Constant, Language::TypeScriptJsx, 0)]
#[case("Vehicle", SymbolKind::Constant, Language::TypeScriptJsx, 0)]
#[case("CakeMixer", SymbolKind::Constant, Language::JavascriptJsx, 0)]
#[case("somethingSpecial", SymbolKind::Constant, Language::Rust, 0)]
#[case("y", SymbolKind::Constant, Language::Lua, 0)]
#[case("someFunc", SymbolKind::Function, Language::TypeScriptJsx, 0)]
pub fn test_camel_case_const_penalty(
#[case] name: &str,
#[case] kind: SymbolKind,
#[case] language: Language,
#[case] expected_penalty: i64,
) {
assert_eq!(
expected_penalty,
super::calculate_camel_case_constant_penalty(&ResolvedSymbol {
id: 0,
name: name.to_string(),
kind,
language,
path: PathBuf::new(),
score: Score::default(),
start_line: 0,
end_line: 0,
start_column: 0,
end_column: 0,
})
);
}
}