use crate::error::{Result, SpliceError};
use ropey::Rope;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Visibility {
Public,
Restricted(String),
Private,
}
pub fn extract_rust_symbols(path: &Path, source: &[u8]) -> Result<Vec<RustSymbol>> {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_rust::language())
.map_err(|e| SpliceError::Parse {
file: path.to_path_buf(),
message: format!("Failed to set Rust language: {:?}", e),
})?;
let tree = parser
.parse(source, None)
.ok_or_else(|| SpliceError::Parse {
file: path.to_path_buf(),
message: "Parse failed - no tree returned".to_string(),
})?;
let rope = Rope::from_str(std::str::from_utf8(source)?);
let mut symbols = Vec::new();
extract_symbols(tree.root_node(), source, &rope, &mut symbols, "crate");
Ok(symbols)
}
fn extract_symbols(
node: tree_sitter::Node,
source: &[u8],
rope: &Rope,
symbols: &mut Vec<RustSymbol>,
module_path: &str,
) {
let kind = node.kind();
let symbol_kind = match kind {
"function_item" => Some(RustSymbolKind::Function),
"struct_item" => Some(RustSymbolKind::Struct),
"enum_item" => Some(RustSymbolKind::Enum),
"trait_item" => Some(RustSymbolKind::Trait),
"impl_item" => Some(RustSymbolKind::Impl),
"mod_item" => Some(RustSymbolKind::Module),
_ => None,
};
if let Some(kind) = symbol_kind {
if let Some(symbol) = extract_symbol(node, source, rope, kind, module_path) {
symbols.push(symbol);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let new_module_path = if child.kind() == "mod_item" {
if let Some(name_node) = child.child_by_field_name("name") {
if let Ok(name) = name_node.utf8_text(source) {
Some(format!("{}::{}", module_path, name))
} else {
None
}
} else {
None
}
} else {
None
};
let path_for_children = match &new_module_path {
Some(s) => s.as_str(),
None => module_path,
};
extract_symbols(child, source, rope, symbols, path_for_children);
}
}
fn extract_impl_name(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
let type_node = node.child_by_field_name("type")?;
let name_bytes = &source[type_node.start_byte() as usize..type_node.end_byte() as usize];
std::str::from_utf8(name_bytes).ok().map(|s| s.to_string())
}
fn extract_symbol(
node: tree_sitter::Node,
source: &[u8],
rope: &Rope,
kind: RustSymbolKind,
module_path: &str,
) -> Option<RustSymbol> {
let name = if kind == RustSymbolKind::Impl {
extract_impl_name(&node, source)?
} else {
let name_node = node.child_by_field_name("name")?;
name_node.utf8_text(source).ok()?.to_string()
};
let byte_start = node.start_byte();
let byte_end = node.end_byte();
let start_char = rope.byte_to_char(byte_start);
let end_char = rope.byte_to_char(byte_end);
let line_start = rope.char_to_line(start_char);
let line_end = rope.char_to_line(end_char);
let line_start_byte = rope.line_to_byte(line_start);
let line_end_byte = rope.line_to_byte(line_end);
let col_start = byte_start - line_start_byte;
let col_end = byte_end - line_end_byte;
let visibility = extract_visibility(node, source);
let fully_qualified = format!("{}::{}", module_path, name);
Some(RustSymbol {
name,
kind,
byte_start,
byte_end,
line_start: line_start + 1, line_end: line_end + 1, col_start,
col_end,
children: Vec::new(), module_path: module_path.to_string(),
fully_qualified,
visibility,
})
}
fn extract_visibility(node: tree_sitter::Node, source: &[u8]) -> Visibility {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let kind = child.kind();
if kind == "name" || kind == "type_parameters" {
continue;
}
if kind == "declaration_body" || kind == "parameters" {
break;
}
let text = child.utf8_text(source).unwrap_or("");
if text == "pub" {
return Visibility::Public;
}
if text.starts_with("pub(") {
return Visibility::Restricted(text.to_string());
}
}
Visibility::Private
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustSymbol {
pub name: String,
pub kind: RustSymbolKind,
pub byte_start: usize,
pub byte_end: usize,
pub line_start: usize,
pub line_end: usize,
pub col_start: usize,
pub col_end: usize,
pub children: Vec<RustSymbol>,
pub module_path: String,
pub fully_qualified: String,
pub visibility: Visibility,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RustSymbolKind {
Function,
Struct,
Enum,
Impl,
Module,
Trait,
TypeAlias,
Const,
Static,
}
impl RustSymbolKind {
pub fn as_str(&self) -> &'static str {
match self {
RustSymbolKind::Function => "function",
RustSymbolKind::Struct => "struct",
RustSymbolKind::Enum => "enum",
RustSymbolKind::Impl => "impl",
RustSymbolKind::Module => "module",
RustSymbolKind::Trait => "trait",
RustSymbolKind::TypeAlias => "type_alias",
RustSymbolKind::Const => "const",
RustSymbolKind::Static => "static",
}
}
}