use crate::indexer::languages::Language;
use tree_sitter::Node;
pub struct Rust {}
impl Language for Rust {
fn name(&self) -> &'static str {
"rust"
}
fn get_ts_language(&self) -> tree_sitter::Language {
tree_sitter_rust::LANGUAGE.into()
}
fn get_meaningful_kinds(&self) -> Vec<&'static str> {
vec![
"function_item",
"struct_item",
"enum_item",
"trait_item",
"mod_item",
"const_item",
"macro_definition",
]
}
fn extract_symbols(&self, node: Node, contents: &str) -> Vec<String> {
let mut symbols = Vec::new();
match node.kind() {
"function_item" => {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" {
if let Ok(n) = child.utf8_text(contents.as_bytes()) {
symbols.push(n.to_string());
}
break;
}
}
}
"struct_item" | "enum_item" | "trait_item" | "mod_item" | "const_item"
| "macro_definition" => {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" || child.kind().contains("name") {
if let Ok(n) = child.utf8_text(contents.as_bytes()) {
symbols.push(n.to_string());
}
break;
}
}
}
_ => self.extract_identifiers(node, contents, &mut symbols),
}
symbols.sort();
symbols.dedup();
symbols
}
fn extract_identifiers(&self, node: Node, contents: &str, symbols: &mut Vec<String>) {
let kind = node.kind();
if kind.contains("identifier") || kind.contains("name") {
if let Ok(text) = node.utf8_text(contents.as_bytes()) {
let t = text.trim();
if !t.is_empty() && !symbols.contains(&t.to_string()) {
symbols.push(t.to_string());
}
}
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
self.extract_identifiers(cursor.node(), contents, symbols);
if !cursor.goto_next_sibling() {
break;
}
}
}
}
fn are_node_types_equivalent(&self, type1: &str, type2: &str) -> bool {
if type1 == type2 {
return true;
}
let semantic_groups = [
&["mod_item", "use_declaration", "extern_crate_item"] as &[&str],
&["struct_item", "enum_item", "union_item", "type_item"],
&["function_item"],
&["const_item", "static_item"],
&["trait_item", "impl_item"],
&["macro_definition", "macro_rules"],
];
for group in &semantic_groups {
let contains_type1 = group.contains(&type1);
let contains_type2 = group.contains(&type2);
if contains_type1 && contains_type2 {
return true;
}
}
false
}
fn get_node_type_description(&self, node_type: &str) -> &'static str {
match node_type {
"mod_item" => "module declarations",
"use_declaration" | "extern_crate_item" => "import statements",
"struct_item" | "enum_item" | "union_item" => "type definitions",
"type_item" => "type declarations",
"function_item" => "function declarations",
"const_item" | "static_item" => "constant declarations",
"trait_item" => "trait declarations",
"impl_item" => "implementation blocks",
"macro_definition" | "macro_rules" => "macro definitions",
_ => "declarations",
}
}
fn extract_imports_exports(&self, node: Node, contents: &str) -> (Vec<String>, Vec<String>) {
let mut imports = Vec::new();
let mut exports = Vec::new();
match node.kind() {
"use_declaration" => {
if let Ok(use_text) = node.utf8_text(contents.as_bytes()) {
if let Some(import_path) = parse_rust_use_statement_full_path(use_text) {
imports.push(import_path);
}
}
}
"function_item" | "struct_item" | "enum_item" | "trait_item" | "mod_item"
| "const_item" | "macro_definition" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "visibility_modifier" {
if let Ok(vis_text) = child.utf8_text(contents.as_bytes()) {
if vis_text.contains("pub") {
for name_child in node.children(&mut node.walk()) {
if name_child.kind() == "identifier" {
if let Ok(name) = name_child.utf8_text(contents.as_bytes())
{
exports.push(name.to_string());
break;
}
}
}
}
}
break;
}
}
}
_ => {}
}
(imports, exports)
}
fn resolve_import(
&self,
import_path: &str,
source_file: &str,
all_files: &[String],
) -> Option<String> {
use super::resolution_utils::FileRegistry;
let registry = FileRegistry::new(all_files);
let rust_files = registry.get_files_with_extensions(&self.get_file_extensions());
if import_path.starts_with("crate::") {
let module_path = import_path.strip_prefix("crate::")?;
self.resolve_crate_import(module_path, source_file, &rust_files)
} else if import_path.starts_with("super::") {
let module_path = import_path.strip_prefix("super::")?;
self.resolve_super_import(module_path, source_file, &rust_files)
} else if import_path.starts_with("self::") {
let module_path = import_path.strip_prefix("self::")?;
self.resolve_self_import(module_path, source_file, &rust_files)
} else if import_path.contains("::") {
self.resolve_crate_import(import_path, source_file, &rust_files)
} else {
self.resolve_simple_import(import_path, source_file, &rust_files)
}
}
fn get_file_extensions(&self) -> Vec<&'static str> {
vec!["rs"]
}
}
fn parse_rust_use_statement_full_path(use_text: &str) -> Option<String> {
let cleaned = use_text
.trim()
.strip_prefix("use ")?
.trim_end_matches(';')
.trim();
Some(cleaned.to_string())
}
impl Rust {
fn resolve_crate_import(
&self,
module_path: &str,
source_file: &str,
rust_files: &[String],
) -> Option<String> {
let parts: Vec<&str> = module_path.split("::").collect();
if parts.is_empty() {
return None;
}
let crate_root = self.find_crate_root(source_file, rust_files)?;
let crate_dir = std::path::Path::new(&crate_root).parent()?;
for end_idx in (1..=parts.len()).rev() {
let module_parts = &parts[0..end_idx];
let module_path_str = module_parts.join("/");
let file_path = crate_dir.join(&module_path_str).with_extension("rs");
if let Some(resolved) = self.find_matching_file(&file_path, rust_files) {
return Some(resolved);
}
let mod_path = crate_dir.join(&module_path_str).join("mod.rs");
if let Some(resolved) = self.find_matching_file(&mod_path, rust_files) {
return Some(resolved);
}
}
None
}
fn find_matching_file(
&self,
target_path: &std::path::Path,
rust_files: &[String],
) -> Option<String> {
let target_str = target_path.to_string_lossy().to_string();
if let Some(exact_match) = rust_files.iter().find(|f| *f == &target_str) {
return Some(exact_match.clone());
}
if let Some(found) =
crate::utils::path::PathNormalizer::find_path_in_collection(&target_str, rust_files)
{
return Some(found.to_string());
}
if let Ok(canonical_target) = target_path.canonicalize() {
let canonical_str = canonical_target.to_string_lossy().to_string();
for rust_file in rust_files {
if let Ok(canonical_rust) = std::path::Path::new(rust_file).canonicalize() {
let canonical_rust_str = canonical_rust.to_string_lossy().to_string();
if canonical_str == canonical_rust_str {
return Some(rust_file.clone());
}
}
}
}
if let Some(target_file_name) = target_path.file_name() {
if let Some(target_parent) = target_path.parent() {
for rust_file in rust_files {
let rust_path = std::path::Path::new(rust_file);
if let Some(rust_file_name) = rust_path.file_name() {
if let Some(rust_parent) = rust_path.parent() {
if target_file_name == rust_file_name {
if let (Some(target_parent_str), Some(rust_parent_str)) =
(target_parent.to_str(), rust_parent.to_str())
{
if crate::utils::path::PathNormalizer::paths_equal(
target_parent_str,
rust_parent_str,
) {
return Some(rust_file.clone());
}
}
}
}
}
}
}
}
None
}
fn resolve_super_import(
&self,
module_path: &str,
source_file: &str,
rust_files: &[String],
) -> Option<String> {
let source_path = std::path::Path::new(source_file);
let source_dir = source_path.parent()?;
self.resolve_relative_import(module_path, source_dir, rust_files)
}
fn resolve_self_import(
&self,
_module_path: &str,
source_file: &str,
rust_files: &[String],
) -> Option<String> {
if rust_files.iter().any(|f| f == source_file) {
Some(source_file.to_string())
} else {
None
}
}
fn resolve_simple_import(
&self,
import_path: &str,
source_file: &str,
rust_files: &[String],
) -> Option<String> {
let source_path = std::path::Path::new(source_file);
let source_dir = source_path.parent()?;
let target_file = source_dir.join(format!("{}.rs", import_path));
self.find_matching_file(&target_file, rust_files)
}
fn resolve_relative_import(
&self,
module_path: &str,
base_dir: &std::path::Path,
rust_files: &[String],
) -> Option<String> {
let parts: Vec<&str> = module_path.split("::").collect();
if parts.is_empty() {
return None;
}
for end_idx in (1..=parts.len()).rev() {
let module_parts = &parts[0..end_idx];
let mut current_path = base_dir.to_path_buf();
for (i, part) in module_parts.iter().enumerate() {
if i == module_parts.len() - 1 {
let file_path = current_path.join(format!("{}.rs", part));
if let Some(resolved) = self.find_matching_file(&file_path, rust_files) {
return Some(resolved);
}
let mod_path = current_path.join(part).join("mod.rs");
if let Some(resolved) = self.find_matching_file(&mod_path, rust_files) {
return Some(resolved);
}
} else {
current_path = current_path.join(part);
}
}
}
None
}
fn find_crate_root(&self, source_file: &str, rust_files: &[String]) -> Option<String> {
let source_path = std::path::Path::new(source_file);
let mut current_dir = source_path.parent()?;
let normalized_files: Vec<String> = rust_files
.iter()
.filter_map(|f| {
std::path::Path::new(f)
.canonicalize()
.ok()
.and_then(|p| p.to_str().map(|s| s.to_string()))
})
.collect();
loop {
for root_file in &["lib.rs", "main.rs"] {
let root_path = current_dir.join(root_file);
let root_path_str = root_path.to_string_lossy().to_string();
if rust_files.iter().any(|f| f == &root_path_str) {
return Some(root_path_str);
}
if let Ok(canonical_root) = root_path.canonicalize() {
let canonical_str = canonical_root.to_string_lossy().to_string();
if normalized_files.iter().any(|f| f == &canonical_str) {
for original in rust_files {
if let Ok(canonical_f) = std::path::Path::new(original).canonicalize() {
if canonical_f.to_string_lossy() == canonical_str {
return Some(original.clone());
}
}
}
}
}
}
current_dir = current_dir.parent()?;
}
}
}