use crate::ir::{Language, Symbol, SymbolId, SymbolKind};
const UNRESOLVED_IMPORT_PREFIX: &str = "unresolved_import";
const UNRESOLVED_LOCAL_TYPE_PREFIX: &str = "unresolved_local_type";
const EXTERNAL_PREFIX: &str = "ext";
pub fn kind_suffix(kind: &SymbolKind) -> &'static str {
match kind {
SymbolKind::Class => "class",
SymbolKind::Interface => "interface",
SymbolKind::TypeAlias => "type_alias",
SymbolKind::Function => "function",
SymbolKind::Method => "method",
SymbolKind::Property => "property",
SymbolKind::Variable => "variable",
SymbolKind::Module => "module",
SymbolKind::Enum => "enum",
SymbolKind::EnumVariant => "enum_variant",
SymbolKind::ExternalPackage => "package",
}
}
pub fn make_symbol_id(file: &str, name: &str, kind: &SymbolKind) -> SymbolId {
format!("{file}::{name}::{}", kind_suffix(kind))
}
pub fn module_symbol_id(file: &str) -> SymbolId {
make_symbol_id(file, "module", &SymbolKind::Module)
}
pub fn module_symbol(file: &str, language: Language) -> Symbol {
Symbol {
id: module_symbol_id(file),
name: "module".to_string(),
kind: SymbolKind::Module,
language,
file: file.to_string(),
line_start: 1,
line_end: 1,
signature: None,
}
}
pub fn parse_symbol_id(id: &str) -> Option<(&str, &str, &str)> {
let first = id.find("::")?;
let last = id.rfind("::")?;
if last <= first {
return None;
}
Some((&id[..first], &id[first + 2..last], &id[last + 2..]))
}
pub fn symbol_name_of(id: &str) -> Option<&str> {
parse_symbol_id(id).map(|(_, name, _)| name)
}
pub fn unresolved_import_id(module: &str, symbol: &str) -> SymbolId {
format!("{UNRESOLVED_IMPORT_PREFIX}|{module}|{symbol}")
}
pub const IMPORT_ALL: &str = "*";
pub fn parse_unresolved_import_id(raw: &str) -> Option<(&str, &str)> {
let rest = raw.strip_prefix(UNRESOLVED_IMPORT_PREFIX)?.strip_prefix('|')?;
let cut = rest.rfind('|')?;
Some((&rest[..cut], &rest[cut + 1..]))
}
pub fn unresolved_local_type_id(type_name: &str) -> SymbolId {
format!("{UNRESOLVED_LOCAL_TYPE_PREFIX}|{type_name}")
}
pub fn parse_unresolved_local_type_id(raw: &str) -> Option<&str> {
raw.strip_prefix(UNRESOLVED_LOCAL_TYPE_PREFIX)?
.strip_prefix('|')
}
pub fn is_placeholder(id: &str) -> bool {
id.starts_with(UNRESOLVED_IMPORT_PREFIX) || id.starts_with(UNRESOLVED_LOCAL_TYPE_PREFIX)
}
pub fn external_package_id(package: &str) -> SymbolId {
format!("{EXTERNAL_PREFIX}::{package}::package")
}
pub fn is_external_package(id: &str) -> bool {
id.starts_with("ext::") && id.ends_with("::package")
}
pub fn parse_external_package_id(id: &str) -> Option<&str> {
id.strip_prefix("ext::")?.strip_suffix("::package")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolved_ids_round_trip() {
let id = make_symbol_id("src/models/user.rs", "UserPayload", &SymbolKind::Class);
assert_eq!(id, "src/models/user.rs::UserPayload::class");
assert_eq!(
parse_symbol_id(&id),
Some(("src/models/user.rs", "UserPayload", "class"))
);
}
#[test]
fn symbol_names_containing_the_separator_survive_a_round_trip() {
let id = make_symbol_id("src/lib.rs", "Display::fmt", &SymbolKind::Method);
assert_eq!(
parse_symbol_id(&id),
Some(("src/lib.rs", "Display::fmt", "method"))
);
}
#[test]
fn import_placeholders_round_trip_paths_containing_colons() {
let id = unresolved_import_id("crate::models::user_payload", "UserPayload");
assert_eq!(
parse_unresolved_import_id(&id),
Some(("crate::models::user_payload", "UserPayload"))
);
}
#[test]
fn import_placeholders_round_trip_go_style_module_paths() {
let id = unresolved_import_id("github.com/test/app/models", IMPORT_ALL);
assert_eq!(
parse_unresolved_import_id(&id),
Some(("github.com/test/app/models", "*"))
);
}
#[test]
fn local_type_placeholders_round_trip() {
let id = unresolved_local_type_id("ResponseModel");
assert_eq!(parse_unresolved_local_type_id(&id), Some("ResponseModel"));
assert!(is_placeholder(&id));
}
#[test]
fn resolved_ids_are_not_placeholders() {
let id = make_symbol_id("src/a.rs", "Alpha", &SymbolKind::Class);
assert!(!is_placeholder(&id));
assert!(!is_placeholder(&external_package_id("serde")));
}
#[test]
fn external_package_ids_round_trip() {
let id = external_package_id("serde");
assert!(is_external_package(&id));
assert_eq!(parse_external_package_id(&id), Some("serde"));
}
}