use crate::error::{Result, SpliceError};
use crate::ingest::imports::ImportKind;
use std::path::Path;
pub fn extract_rust_imports(path: &Path, source: &[u8]) -> Result<Vec<super::ImportFact>> {
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 mut imports = Vec::new();
extract_use_statements(tree.root_node(), source, &mut imports);
Ok(imports)
}
fn extract_use_statements(
node: tree_sitter::Node,
source: &[u8],
imports: &mut Vec<super::ImportFact>,
) {
if node.kind() == "use_declaration" {
if let Some(import) = extract_use_declaration(node, source) {
imports.push(import);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
extract_use_statements(child, source, imports);
}
}
fn extract_use_declaration(node: tree_sitter::Node, source: &[u8]) -> Option<super::ImportFact> {
let byte_start = node.start_byte();
let byte_end = node.end_byte();
let is_reexport = check_is_reexport(node, source);
let argument = node.child_by_field_name("argument")?;
let kind = argument.kind();
match kind {
"scoped_identifier" => {
extract_scoped_identifier(argument, source, byte_start, byte_end, is_reexport)
}
"use_wildcard" => extract_use_wildcard(argument, source, byte_start, byte_end, is_reexport),
"scoped_use_list" => {
extract_scoped_use_list(argument, source, byte_start, byte_end, is_reexport)
}
"use_as_clause" => {
extract_use_as_clause(argument, source, byte_start, byte_end, is_reexport)
}
_ => None,
}
}
fn check_is_reexport(node: tree_sitter::Node, source: &[u8]) -> bool {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"visibility_modifier" => return true,
_ => {
if let Ok(text) = child.utf8_text(source) {
if text == "pub" {
return true;
}
}
}
}
}
false
}
fn extract_scoped_path(node: tree_sitter::Node, source: &[u8]) -> Option<(Vec<String>, String)> {
let mut segments = Vec::new();
let mut current = Some(node);
while let Some(n) = current {
if n.kind() == "scoped_identifier" {
if let Some(name_field) = n.child_by_field_name("name") {
if let Ok(text) = name_field.utf8_text(source) {
segments.push(text.to_string());
}
}
current = n.child_by_field_name("path");
} else if n.kind() == "identifier"
|| n.kind() == "crate"
|| n.kind() == "super"
|| n.kind() == "self"
{
if let Ok(text) = n.utf8_text(source) {
segments.push(text.to_string());
}
break;
} else {
break;
}
}
segments.reverse();
if segments.is_empty() {
return None;
}
let imported_name = segments.pop().unwrap_or_default();
Some((segments, imported_name))
}
fn import_kind_from_path(path: &[String]) -> ImportKind {
if path.first().map(|s| s.as_str()) == Some("crate") {
ImportKind::UseCrate
} else if path.first().map(|s| s.as_str()) == Some("super") {
ImportKind::UseSuper
} else if path.first().map(|s| s.as_str()) == Some("self") {
ImportKind::UseSelf
} else {
ImportKind::PlainUse
}
}
fn extract_scoped_identifier(
node: tree_sitter::Node,
source: &[u8],
byte_start: usize,
byte_end: usize,
is_reexport: bool,
) -> Option<super::ImportFact> {
let (path, imported_name) = extract_scoped_path(node, source)?;
let import_kind = import_kind_from_path(&path);
Some(super::ImportFact {
file_path: std::path::PathBuf::new(),
import_kind,
path,
imported_names: vec![imported_name],
is_glob: false,
is_reexport,
byte_span: (byte_start, byte_end),
})
}
fn extract_use_wildcard(
node: tree_sitter::Node,
source: &[u8],
byte_start: usize,
byte_end: usize,
is_reexport: bool,
) -> Option<super::ImportFact> {
let mut cursor = node.walk();
let scoped = node
.children(&mut cursor)
.find(|n| n.kind() == "scoped_identifier")?;
let (mut path, imported_name) = extract_scoped_path(scoped, source)?;
path.push(imported_name);
let import_kind = import_kind_from_path(&path);
Some(super::ImportFact {
file_path: std::path::PathBuf::new(),
import_kind,
path,
imported_names: vec!["*".to_string()],
is_glob: true,
is_reexport,
byte_span: (byte_start, byte_end),
})
}
fn extract_scoped_use_list(
node: tree_sitter::Node,
source: &[u8],
byte_start: usize,
byte_end: usize,
is_reexport: bool,
) -> Option<super::ImportFact> {
let path_node = node.child_by_field_name("path")?;
let list_node = node.child_by_field_name("list")?;
let (mut path, imported_name) = extract_scoped_path(path_node, source)?;
path.push(imported_name);
let import_kind = import_kind_from_path(&path);
let mut imported_names = Vec::new();
let mut cursor = list_node.walk();
for child in list_node.children(&mut cursor) {
match child.kind() {
"identifier" => {
if let Ok(text) = child.utf8_text(source) {
imported_names.push(text.to_string());
}
}
"use_as_clause" => {
if let Some(alias) = child.child_by_field_name("alias") {
if let Ok(text) = alias.utf8_text(source) {
imported_names.push(text.to_string());
}
}
}
"," | "{" | "}" => continue,
_ => {}
}
}
Some(super::ImportFact {
file_path: std::path::PathBuf::new(),
import_kind,
path,
imported_names,
is_glob: false,
is_reexport,
byte_span: (byte_start, byte_end),
})
}
fn extract_use_as_clause(
node: tree_sitter::Node,
source: &[u8],
byte_start: usize,
byte_end: usize,
is_reexport: bool,
) -> Option<super::ImportFact> {
let path_node = node.child_by_field_name("path")?;
let alias_node = node.child_by_field_name("alias")?;
let alias = alias_node.utf8_text(source).ok()?.to_string();
let (path, _imported_name) = extract_scoped_path(path_node, source)?;
let import_kind = import_kind_from_path(&path);
Some(super::ImportFact {
file_path: std::path::PathBuf::new(),
import_kind,
path,
imported_names: vec![alias],
is_glob: false,
is_reexport,
byte_span: (byte_start, byte_end),
})
}