use anyhow::Result;
use serde::Serialize;
use std::collections::HashMap;
use tree_sitter::Language;
use crate::core::graph::Edge;
use crate::core::parser::SupportedLanguage;
use crate::languages::{make_edge, resolve_scope_id, LanguagePlugin};
pub struct RustPlugin;
impl LanguagePlugin for RustPlugin {
fn language(&self) -> SupportedLanguage {
SupportedLanguage::Rust
}
fn extensions(&self) -> &[&str] {
&["rs"]
}
fn ts_language(&self) -> Language {
tree_sitter_rust::language()
}
fn symbol_query_source(&self) -> &str {
include_str!("../queries/rust/symbols.scm")
}
fn edge_query_source(&self) -> &str {
include_str!("../queries/rust/edges.scm")
}
fn infer_symbol_kind(&self, node_kind: &str) -> &str {
match node_kind {
"function_item" => "function",
"struct_item" => "struct",
"enum_item" => "enum",
"trait_item" => "interface",
"type_item" => "type",
"const_item" | "static_item" => "const",
"enum_variant" => "variant",
_ => "function",
}
}
fn scope_node_types(&self) -> &[&str] {
&["function_item", "impl_item", "trait_item", "mod_item"]
}
fn class_body_node_types(&self) -> &[&str] {
&["enum_variant_list"]
}
fn class_decl_node_types(&self) -> &[&str] {
&["enum_item"]
}
fn extract_metadata(
&self,
node: &tree_sitter::Node,
source: &str,
kind: &str,
) -> Result<String> {
extract_metadata(node, source, kind)
}
fn extract_edge(
&self,
pattern_index: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
extract_rust_edge(pattern_index, captures, file_path, enclosing_scope_id)
}
fn generic_name_stopwords(&self) -> &[&str] {
&[
"new", "default", "from", "into", "run", "build", "try_from", "fmt", "clone", "drop",
]
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct RustMetadata {
pub visibility: String,
pub is_async: bool,
pub is_const: bool,
pub is_unsafe: bool,
pub attributes: Vec<String>,
pub return_type: Option<String>,
pub parameters: Vec<RustParameterInfo>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RustParameterInfo {
pub name: String,
#[serde(rename = "type")]
pub type_annotation: Option<String>,
pub is_mutable: bool,
}
pub fn extract_metadata(node: &tree_sitter::Node, source: &str, kind: &str) -> Result<String> {
let mut meta = RustMetadata {
visibility: "private".to_string(),
..Default::default()
};
let mut child_cursor = node.walk();
for child in node.children(&mut child_cursor) {
match child.kind() {
"visibility_modifier" => {
if let Ok(text) = child.utf8_text(source.as_bytes()) {
meta.visibility = match text.trim() {
"pub" => "pub".to_string(),
s if s.starts_with("pub(crate)") => "pub(crate)".to_string(),
s if s.starts_with("pub(super)") => "pub(super)".to_string(),
s if s.starts_with("pub") => s.to_string(),
_ => "private".to_string(),
};
}
}
"attribute_item" => {
if let Ok(text) = child.utf8_text(source.as_bytes()) {
meta.attributes.push(text.trim().to_string());
}
}
_ => {}
}
}
if kind == "function" || kind == "method" {
let mut fn_cursor = node.walk();
for child in node.children(&mut fn_cursor) {
if let Ok(text) = child.utf8_text(source.as_bytes()) {
match text {
"async" => meta.is_async = true,
"const" => meta.is_const = true,
"unsafe" => meta.is_unsafe = true,
_ => {}
}
}
}
if let Some(return_node) = node.child_by_field_name("return_type") {
if let Ok(text) = return_node.utf8_text(source.as_bytes()) {
let clean = text.trim_start_matches("->").trim();
if !clean.is_empty() {
meta.return_type = Some(clean.to_string());
}
}
}
if let Some(params_node) = node.child_by_field_name("parameters") {
meta.parameters = extract_parameters(¶ms_node, source);
}
}
if kind == "const" {
meta.is_const = true;
}
let json = serde_json::to_string(&meta)?;
Ok(json)
}
fn extract_parameters(params_node: &tree_sitter::Node, source: &str) -> Vec<RustParameterInfo> {
let mut params = Vec::new();
let mut cursor = params_node.walk();
for child in params_node.children(&mut cursor) {
match child.kind() {
"parameter" => {
let mut name = String::new();
let mut type_annotation = None;
let mut is_mutable = false;
if let Some(pattern_node) = child.child_by_field_name("pattern") {
if let Ok(text) = pattern_node.utf8_text(source.as_bytes()) {
let text = text.trim();
if let Some(stripped) = text.strip_prefix("mut ") {
name = stripped.to_string();
is_mutable = true;
} else {
name = text.to_string();
}
}
}
if let Some(type_node) = child.child_by_field_name("type") {
if let Ok(text) = type_node.utf8_text(source.as_bytes()) {
type_annotation = Some(text.trim().to_string());
}
}
if !name.is_empty() {
params.push(RustParameterInfo {
name,
type_annotation,
is_mutable,
});
}
}
"self_parameter" => {
if let Ok(text) = child.utf8_text(source.as_bytes()) {
let text = text.trim();
let is_mutable = text.contains("mut");
params.push(RustParameterInfo {
name: "self".to_string(),
type_annotation: None,
is_mutable,
});
}
}
_ => {}
}
}
params
}
fn extract_rust_edge(
pattern: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
let mut edges = Vec::new();
let from_fn = resolve_scope_id(enclosing_scope_id, file_path, "function");
let module_fn = || format!("{file_path}::__module__::function");
match pattern {
0 | 1 => {
if let Some((imported_name, line)) = captures.get("imported_name") {
edges.push(make_edge(
module_fn(),
imported_name,
"imports",
file_path,
*line,
));
}
}
2 | 3 => {
if let Some((callee, line)) = captures.get("callee") {
edges.push(make_edge(
from_fn.clone(),
callee,
"calls",
file_path,
*line,
));
}
}
4 => {
if let Some((method, line)) = captures.get("method") {
edges.push(make_edge(
from_fn.clone(),
method,
"calls",
file_path,
*line,
));
}
}
5 | 6 => {
if let Some((macro_name, line)) = captures.get("macro_name") {
edges.push(make_edge(
from_fn.clone(),
format!("{macro_name}!"),
"calls",
file_path,
*line,
));
}
}
7..=9 => {
if let Some((type_ref, line)) = captures.get("type_ref") {
edges.push(make_edge(
from_fn.clone(),
type_ref,
"references_type",
file_path,
*line,
));
}
}
10 | 11 => {
if let Some((variant_ref, line)) = captures.get("variant_ref") {
let variant_name = variant_ref.rsplit("::").next().unwrap_or(variant_ref);
edges.push(make_edge(
from_fn.clone(),
variant_name,
"references",
file_path,
*line,
));
}
}
_ => {}
}
edges
}