use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
use tree_sitter::Node;
pub struct Rust;
impl Language for Rust {
fn name(&self) -> &'static str {
"Rust"
}
fn extensions(&self) -> &'static [&'static str] {
&["rs"]
}
fn grammar_name(&self) -> &'static str {
"rust"
}
fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
Some(self)
}
fn signature_suffix(&self) -> &'static str {
" {}"
}
fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
extract_docstring(node, content)
}
fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
extract_attributes(node, content)
}
fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
if node.kind() == "impl_item" {
let type_node = match node.child_by_field_name("type") {
Some(n) => n,
None => return crate::ImplementsInfo::default(),
};
let _ = &content[type_node.byte_range()]; let is_interface = node.child_by_field_name("trait").is_some();
let implements = if let Some(trait_node) = node.child_by_field_name("trait") {
vec![content[trait_node.byte_range()].to_string()]
} else {
Vec::new()
};
crate::ImplementsInfo {
is_interface,
implements,
}
} else {
crate::ImplementsInfo::default()
}
}
fn refine_kind(
&self,
node: &Node,
_content: &str,
tag_kind: crate::SymbolKind,
) -> crate::SymbolKind {
match node.kind() {
"struct_item" => crate::SymbolKind::Struct,
"enum_item" => crate::SymbolKind::Enum,
"type_item" => crate::SymbolKind::Type,
"union_item" => crate::SymbolKind::Struct,
"trait_item" => crate::SymbolKind::Trait,
_ => tag_kind,
}
}
fn build_signature(&self, node: &Node, content: &str) -> String {
match node.kind() {
"function_item" | "function_signature_item" => {
let name = match self.node_name(node, content) {
Some(n) => n,
None => {
return content[node.byte_range()]
.lines()
.next()
.unwrap_or("")
.trim()
.to_string();
}
};
let vis = self.extract_visibility_prefix(node, content);
let params = node
.child_by_field_name("parameters")
.map(|p| content[p.byte_range()].to_string())
.unwrap_or_else(|| "()".to_string());
let return_type = node
.child_by_field_name("return_type")
.map(|r| format!(" -> {}", &content[r.byte_range()]))
.unwrap_or_default();
format!("{}fn {}{}{}", vis, name, params, return_type)
}
"impl_item" => {
let type_node = node.child_by_field_name("type");
let type_name = type_node
.map(|n| content[n.byte_range()].to_string())
.unwrap_or_default();
if let Some(trait_node) = node.child_by_field_name("trait") {
let trait_name = &content[trait_node.byte_range()];
format!("impl {} for {}", trait_name, type_name)
} else {
format!("impl {}", type_name)
}
}
"trait_item" => {
let name = self.node_name(node, content).unwrap_or("");
let vis = self.extract_visibility_prefix(node, content);
format!("{}trait {}", vis, name)
}
"mod_item" => {
let name = self.node_name(node, content).unwrap_or("");
let vis = self.extract_visibility_prefix(node, content);
format!("{}mod {}", vis, name)
}
"struct_item" => {
let name = self.node_name(node, content).unwrap_or("");
let vis = self.extract_visibility_prefix(node, content);
format!("{}struct {}", vis, name)
}
"enum_item" => {
let name = self.node_name(node, content).unwrap_or("");
let vis = self.extract_visibility_prefix(node, content);
format!("{}enum {}", vis, name)
}
"type_item" => {
let name = self.node_name(node, content).unwrap_or("");
let vis = self.extract_visibility_prefix(node, content);
format!("{}type {}", vis, name)
}
_ => {
let text = &content[node.byte_range()];
text.lines().next().unwrap_or(text).trim().to_string()
}
}
}
fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
if node.kind() != "use_declaration" {
return Vec::new();
}
let line = node.start_position().row + 1;
let text = &content[node.byte_range()];
let module = text.trim_start_matches("use ").trim_end_matches(';').trim();
let mut names = Vec::new();
let is_relative = module.starts_with("crate")
|| module.starts_with("self")
|| module.starts_with("super");
if let Some(brace_start) = module.find('{') {
let prefix = module[..brace_start].trim_end_matches("::");
let brace_end = {
let mut depth = 0u32;
let mut end = None;
for (i, c) in module[brace_start..].char_indices() {
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
end = Some(brace_start + i);
break;
}
}
_ => {}
}
}
end
};
if let Some(brace_end) = brace_end {
let items = &module[brace_start + 1..brace_end];
for item in items.split(',') {
let trimmed = item.trim();
if !trimmed.is_empty() {
names.push(trimmed.to_string());
}
}
}
vec![Import {
module: prefix.to_string(),
names,
alias: None,
is_wildcard: false,
is_relative,
line,
}]
} else {
let (module_part, alias) = if let Some(as_pos) = module.find(" as ") {
(&module[..as_pos], Some(module[as_pos + 4..].to_string()))
} else {
(module, None)
};
vec![Import {
module: module_part.to_string(),
names: Vec::new(),
alias,
is_wildcard: module_part.ends_with("::*"),
is_relative,
line,
}]
}
}
fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
let names_to_use: Vec<&str> = names
.map(|n| n.to_vec())
.unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
if import.is_wildcard {
format!("use {};", import.module)
} else if names_to_use.is_empty() {
format!("use {};", import.module)
} else if names_to_use.len() == 1 {
format!("use {}::{};", import.module, names_to_use[0])
} else {
format!("use {}::{{{}}};", import.module, names_to_use.join(", "))
}
}
fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "visibility_modifier" {
let vis = &content[child.byte_range()];
if vis == "pub" {
return Visibility::Public;
} else if vis.starts_with("pub(crate)") {
return Visibility::Internal;
} else if vis.starts_with("pub(super)") || vis.starts_with("pub(in") {
return Visibility::Protected;
}
}
}
Visibility::Private
}
fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
let in_attrs = symbol
.attributes
.iter()
.any(|a| a.contains("#[test]") || a.contains("#[cfg(test)]"));
let in_sig =
symbol.signature.contains("#[test]") || symbol.signature.contains("#[cfg(test)]");
if in_attrs || in_sig {
return true;
}
match symbol.kind {
crate::SymbolKind::Function | crate::SymbolKind::Method => {
symbol.name.starts_with("test_")
}
crate::SymbolKind::Module => symbol.name == "tests",
_ => false,
}
}
fn test_file_globs(&self) -> &'static [&'static str] {
&[
"**/tests/**",
"**/test_*.rs",
"**/*_test.rs",
"**/*_tests.rs",
]
}
fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
node.child_by_field_name("body")
}
fn analyze_container_body(
&self,
body_node: &Node,
content: &str,
inner_indent: &str,
) -> Option<ContainerBody> {
crate::body::analyze_brace_body(body_node, content, inner_indent)
}
fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
let name_node = node
.child_by_field_name("name")
.or_else(|| node.child_by_field_name("type"))?;
Some(&content[name_node.byte_range()])
}
fn extract_module_doc(&self, src: &str) -> Option<String> {
extract_rust_module_doc(src)
}
}
impl LanguageSymbols for Rust {}
impl Rust {
fn extract_visibility_prefix(&self, node: &Node, content: &str) -> String {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "visibility_modifier" {
return format!("{} ", &content[child.byte_range()]);
}
}
String::new()
}
}
fn extract_docstring(node: &Node, content: &str) -> Option<String> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "attributes" {
let mut doc_lines = Vec::new();
let mut attr_cursor = child.walk();
for attr_child in child.children(&mut attr_cursor) {
if attr_child.kind() == "line_outer_doc_comment" {
let text = &content[attr_child.byte_range()];
let doc = text.trim_start_matches("///").trim();
if !doc.is_empty() {
doc_lines.push(doc.to_string());
}
}
}
if !doc_lines.is_empty() {
return Some(doc_lines.join("\n"));
}
}
}
None
}
fn extract_attributes(node: &Node, content: &str) -> Vec<String> {
let mut attrs = Vec::new();
if let Some(attr_node) = node.child_by_field_name("attributes") {
let mut cursor = attr_node.walk();
for child in attr_node.children(&mut cursor) {
if child.kind() == "attribute_item" {
attrs.push(content[child.byte_range()].to_string());
}
}
}
let mut prev = node.prev_sibling();
while let Some(sibling) = prev {
if sibling.kind() == "attribute_item" {
attrs.insert(0, content[sibling.byte_range()].to_string());
prev = sibling.prev_sibling();
} else {
break;
}
}
attrs
}
fn extract_rust_module_doc(src: &str) -> Option<String> {
let mut lines = Vec::new();
for line in src.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//!") {
let text = trimmed.strip_prefix("//!").unwrap_or("").trim_start();
lines.push(text.to_string());
} else if trimmed.is_empty() && lines.is_empty() {
} else {
break;
}
}
if lines.is_empty() {
return None;
}
while lines.last().map(|l: &String| l.is_empty()).unwrap_or(false) {
lines.pop();
}
if lines.is_empty() {
None
} else {
Some(lines.join("\n"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::validate_unused_kinds_audit;
#[test]
fn unused_node_kinds_audit() {
#[rustfmt::skip]
let documented_unused: &[&str] = &[
"block_comment", "field_declaration", "field_declaration_list", "field_expression", "field_identifier", "identifier", "lifetime", "lifetime_parameter", "ordered_field_declaration_list", "scoped_identifier", "scoped_type_identifier", "shorthand_field_identifier", "type_identifier", "visibility_modifier",
"else_clause", "enum_variant", "enum_variant_list", "match_block", "match_pattern", "trait_bounds", "where_clause",
"array_expression", "assignment_expression", "async_block", "await_expression", "generic_function", "index_expression", "parenthesized_expression", "range_expression", "reference_expression", "struct_expression", "try_expression", "tuple_expression", "type_cast_expression", "unary_expression", "unit_expression", "yield_expression",
"abstract_type", "array_type", "bounded_type", "bracketed_type", "dynamic_type", "function_type", "generic_type", "generic_type_with_turbofish", "higher_ranked_trait_bound", "never_type", "pointer_type", "primitive_type", "qualified_type", "reference_type", "removed_trait_bound", "tuple_type", "type_arguments", "type_binding", "type_parameter", "type_parameters", "unit_type", "unsafe_bound_type",
"block_outer_doc_comment", "extern_modifier", "function_modifiers", "mutable_specifier",
"struct_pattern", "tuple_struct_pattern",
"fragment_specifier", "macro_arguments_declaration", "macro_body_v2", "macro_definition_v2",
"block_expression_with_attribute", "const_block", "expression_statement", "expression_with_attribute", "extern_crate_declaration", "foreign_mod_item", "function_signature_item", "gen_block", "let_declaration", "try_block", "unsafe_block", "use_as_clause", "empty_statement", "closure_expression",
"continue_expression",
"match_expression",
"use_declaration",
"for_expression",
"match_arm",
"break_expression",
"while_expression",
"loop_expression",
"return_expression",
"if_expression",
"block",
"binary_expression",
];
validate_unused_kinds_audit(&Rust, documented_unused)
.expect("Rust unused node kinds audit failed");
}
}