use crate::ingest::{ScopeSeparator, ScopeStack, SymbolKind};
use crate::validation::normalize_path;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct FqnBuilder {
pub(crate) crate_name: String,
pub(crate) file_path: String,
pub(crate) scope_separator: ScopeSeparator,
}
impl FqnBuilder {
pub fn new(crate_name: String, file_path: String, scope_separator: ScopeSeparator) -> Self {
Self {
crate_name,
file_path,
scope_separator,
}
}
pub fn canonical(
&self,
_scope_stack: &ScopeStack,
symbol_kind: SymbolKind,
symbol_name: &str,
) -> String {
let kind_str = self.kind_string(symbol_kind);
format!(
"{}::{}::{} {}",
self.crate_name, self.file_path, kind_str, symbol_name
)
}
pub fn display(
&self,
scope_stack: &ScopeStack,
_symbol_kind: SymbolKind,
symbol_name: &str,
) -> String {
let sep = self.scope_separator.as_str();
let mut parts = Vec::new();
if !self.crate_name.is_empty() {
parts.push(self.crate_name.clone());
}
for scope in scope_stack.scopes() {
let clean_scope = scope.strip_prefix("impl ").unwrap_or(scope);
parts.push(clean_scope.to_string());
}
parts.push(symbol_name.to_string());
parts.join(sep)
}
pub fn normalized_file_path(&self) -> Result<String, String> {
normalize_path(Path::new(&self.file_path))
.map_err(|e| format!("Path normalization failed: {}", e))
}
fn kind_string(&self, kind: SymbolKind) -> String {
match kind {
SymbolKind::Function => "Function",
SymbolKind::Method => "Method",
SymbolKind::Class => "Struct",
SymbolKind::Interface => "Trait",
SymbolKind::Enum => "Enum",
SymbolKind::Module => "Module",
SymbolKind::Union => "Union",
SymbolKind::Namespace => "Namespace",
SymbolKind::TypeAlias => "TypeAlias",
SymbolKind::Unknown => "Unknown",
}
.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_builder() -> FqnBuilder {
FqnBuilder::new(
"test_crate".to_string(),
"src/test.rs".to_string(),
ScopeSeparator::DoubleColon,
)
}
#[test]
fn test_canonical_fqn_format() {
let builder = create_test_builder();
let scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
let canonical = builder.canonical(&scope_stack, SymbolKind::Function, "test_fn");
assert_eq!(canonical, "test_crate::src/test.rs::Function test_fn");
}
#[test]
fn test_display_fqn_format() {
let builder = create_test_builder();
let scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
let display = builder.display(&scope_stack, SymbolKind::Function, "test_fn");
assert_eq!(display, "test_crate::test_fn");
}
#[test]
fn test_display_fqn_with_scope() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("my_module");
let display = builder.display(&scope_stack, SymbolKind::Function, "test_fn");
assert_eq!(display, "test_crate::my_module::test_fn");
}
#[test]
fn test_impl_block_handling() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("MyStruct");
let display = builder.display(&scope_stack, SymbolKind::Method, "my_method");
assert_eq!(display, "test_crate::MyStruct::my_method");
}
#[test]
fn test_impl_block_with_prefix() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("impl MyStruct");
let display = builder.display(&scope_stack, SymbolKind::Method, "my_method");
assert_eq!(display, "test_crate::MyStruct::my_method");
}
#[test]
fn test_nested_scope() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("outer_module");
scope_stack.push("inner_module");
scope_stack.push("MyStruct");
let canonical = builder.canonical(&scope_stack, SymbolKind::Method, "my_method");
let display = builder.display(&scope_stack, SymbolKind::Method, "my_method");
assert_eq!(canonical, "test_crate::src/test.rs::Method my_method");
assert_eq!(
display,
"test_crate::outer_module::inner_module::MyStruct::my_method"
);
}
#[test]
fn test_trait_impl_scope() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("MyTrait");
scope_stack.push("impl MyStruct");
let display = builder.display(&scope_stack, SymbolKind::Method, "trait_method");
assert_eq!(display, "test_crate::MyTrait::MyStruct::trait_method");
}
#[test]
fn test_normalized_file_path() {
let builder = FqnBuilder::new(
"test_crate".to_string(),
"./src/lib.rs".to_string(),
ScopeSeparator::DoubleColon,
);
let normalized = builder.normalized_file_path().unwrap();
assert!(!normalized.starts_with("./"));
assert!(normalized.contains("src/lib.rs"));
}
#[test]
fn test_normalized_file_path_absolute() {
let builder = FqnBuilder::new(
"test_crate".to_string(),
"/absolute/path/to/file.rs".to_string(),
ScopeSeparator::DoubleColon,
);
let normalized = builder.normalized_file_path().unwrap();
assert!(normalized.contains("file.rs"));
}
#[test]
fn test_empty_scope_stack() {
let builder = create_test_builder();
let scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
let canonical = builder.canonical(&scope_stack, SymbolKind::Function, "top_level");
let display = builder.display(&scope_stack, SymbolKind::Function, "top_level");
assert_eq!(canonical, "test_crate::src/test.rs::Function top_level");
assert_eq!(display, "test_crate::top_level");
}
#[test]
fn test_dot_separator() {
let builder = FqnBuilder::new(
"com.example".to_string(),
"src/Example.java".to_string(),
ScopeSeparator::Dot,
);
let mut scope_stack = ScopeStack::new(ScopeSeparator::Dot);
scope_stack.push("mypackage");
scope_stack.push("MyClass");
let display = builder.display(&scope_stack, SymbolKind::Method, "myMethod");
assert_eq!(display, "com.example.mypackage.MyClass.myMethod");
let canonical = builder.canonical(&scope_stack, SymbolKind::Method, "myMethod");
assert_eq!(canonical, "com.example::src/Example.java::Method myMethod");
}
#[test]
fn test_deeply_nested_scope() {
let builder = create_test_builder();
let mut scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
scope_stack.push("a");
scope_stack.push("b");
scope_stack.push("c");
scope_stack.push("MyStruct");
scope_stack.push("impl MyStruct");
let display = builder.display(&scope_stack, SymbolKind::Method, "nested_method");
assert_eq!(
display,
"test_crate::a::b::c::MyStruct::MyStruct::nested_method"
);
}
#[test]
fn test_kind_strings() {
let builder = create_test_builder();
let scope_stack = ScopeStack::new(ScopeSeparator::DoubleColon);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Function, "f"),
"test_crate::src/test.rs::Function f"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Method, "m"),
"test_crate::src/test.rs::Method m"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Class, "S"),
"test_crate::src/test.rs::Struct S"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Interface, "T"),
"test_crate::src/test.rs::Trait T"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Enum, "E"),
"test_crate::src/test.rs::Enum E"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Module, "mod"),
"test_crate::src/test.rs::Module mod"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Union, "U"),
"test_crate::src/test.rs::Union U"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::Namespace, "ns"),
"test_crate::src/test.rs::Namespace ns"
);
assert_eq!(
builder.canonical(&scope_stack, SymbolKind::TypeAlias, "TA"),
"test_crate::src/test.rs::TypeAlias TA"
);
}
}