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" => {
if let Some(name) = super::extract_symbol_by_kind(node, contents, "identifier") {
symbols.push(name);
}
}
"struct_item" | "enum_item" | "trait_item" | "mod_item" | "const_item"
| "macro_definition" => {
if let Some(name) =
super::extract_symbol_by_kinds(node, contents, &["identifier", "name"])
{
symbols.push(name);
}
}
_ => self.extract_identifiers(node, contents, &mut symbols),
}
super::deduplicate_symbols(&mut symbols);
symbols
}
fn extract_identifiers(&self, node: Node, contents: &str, symbols: &mut Vec<String>) {
super::extract_identifiers_default(node, contents, symbols, |kind, _text| {
kind.contains("identifier") || kind.contains("name")
});
}
fn are_node_types_equivalent(&self, type1: &str, type2: &str) -> bool {
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"],
];
super::check_semantic_groups(type1, type2, &semantic_groups)
}
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()) {
imports.extend(expand_rust_use_paths(use_text));
}
}
"mod_item" => {
let has_body = node
.children(&mut node.walk())
.any(|c| c.kind() == "declaration_list");
if !has_body {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
imports.push(format!("self::{}", name));
}
break;
}
}
}
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;
}
}
}
"function_item" | "struct_item" | "enum_item" | "trait_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 expand_rust_use_paths(use_text: &str) -> Vec<String> {
let cleaned = match use_text.trim().strip_prefix("use ") {
Some(s) => s.trim_end_matches(';').trim(),
None => return Vec::new(),
};
let mut results = Vec::new();
expand_use_tree(cleaned, "", &mut results);
results
}
fn expand_use_tree(fragment: &str, prefix: &str, out: &mut Vec<String>) {
let fragment = fragment.trim();
if let Some(brace_start) = fragment.find('{') {
let new_prefix = if prefix.is_empty() {
fragment[..brace_start].trim_end_matches(':').to_string()
} else {
format!(
"{}::{}",
prefix,
fragment[..brace_start].trim_end_matches(':')
)
};
let inner = &fragment[brace_start + 1..];
if let Some(brace_end) = find_matching_brace(inner) {
let group = &inner[..brace_end];
for item in split_top_level_commas(group) {
expand_use_tree(item.trim(), &new_prefix, out);
}
}
} else {
let without_alias = if let Some(pos) = fragment.find(" as ") {
&fragment[..pos]
} else {
fragment
};
let without_glob = without_alias.trim_end_matches('*').trim_end_matches("::");
if without_glob.is_empty() {
return;
}
let full = if prefix.is_empty() {
without_glob.to_string()
} else {
format!("{}::{}", prefix, without_glob)
};
out.push(full);
}
}
fn find_matching_brace(s: &str) -> Option<usize> {
let mut depth = 1usize;
for (i, c) in s.char_indices() {
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
}
None
}
fn split_top_level_commas(s: &str) -> Vec<&str> {
let mut parts = Vec::new();
let mut depth = 0usize;
let mut start = 0;
for (i, c) in s.char_indices() {
match c {
'{' => depth += 1,
'}' => depth = depth.saturating_sub(1),
',' if depth == 0 => {
parts.push(&s[start..i]);
start = i + 1;
}
_ => {}
}
}
parts.push(&s[start..]);
parts
}
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()?;
}
}
}