1use crate::{ContainerBody, Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6pub struct Julia;
8
9impl Language for Julia {
10 fn name(&self) -> &'static str {
11 "Julia"
12 }
13 fn extensions(&self) -> &'static [&'static str] {
14 &["jl"]
15 }
16 fn grammar_name(&self) -> &'static str {
17 "julia"
18 }
19
20 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21 Some(self)
22 }
23
24 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
25 if let Some(name_node) = node.child_by_field_name("name") {
27 return Some(&content[name_node.byte_range()]);
28 }
29 let mut cursor = node.walk();
32 for child in node.children(&mut cursor) {
33 if child.kind() == "signature" || child.kind() == "type_head" {
34 let text = &content[child.byte_range()];
35 let end = text
37 .find(|c: char| c == '(' || c == '<' || c == '{' || c.is_whitespace())
38 .unwrap_or(text.len());
39 if end > 0 {
40 return Some(&content[child.start_byte()..child.start_byte() + end]);
41 }
42 }
43 if child.kind() == "identifier" {
44 return Some(&content[child.byte_range()]);
45 }
46 }
47 None
48 }
49
50 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
51 let prev = node.prev_sibling()?;
52 if prev.kind() != "string_literal" {
53 return None;
54 }
55
56 let text = &content[prev.byte_range()];
57 if !text.starts_with("\"\"\"") {
58 return None;
59 }
60
61 let inner = text
63 .strip_prefix("\"\"\"")
64 .unwrap_or(text)
65 .strip_suffix("\"\"\"")
66 .unwrap_or(text);
67
68 let lines: Vec<&str> = inner
69 .lines()
70 .map(|l| l.trim())
71 .filter(|l| !l.is_empty())
72 .collect();
73
74 if lines.is_empty() {
75 return None;
76 }
77
78 Some(lines.join(" "))
79 }
80
81 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
82 let text = &content[node.byte_range()];
83 let line = node.start_position().row + 1;
84
85 let (keyword, is_wildcard) = if text.starts_with("using ") {
86 ("using ", true)
87 } else if text.starts_with("import ") {
88 ("import ", false)
89 } else {
90 return Vec::new();
91 };
92
93 let rest = text.strip_prefix(keyword).unwrap_or("");
94 let module = rest
95 .split([':', ','])
96 .next()
97 .map(|s| s.trim().to_string())
98 .unwrap_or_default();
99
100 if module.is_empty() {
101 return Vec::new();
102 }
103
104 vec![Import {
105 module,
106 names: Vec::new(),
107 alias: None,
108 is_wildcard,
109 is_relative: false,
110 line,
111 }]
112 }
113
114 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
115 let names_to_use: Vec<&str> = names
117 .map(|n| n.to_vec())
118 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
119 if names_to_use.is_empty() {
120 format!("using {}", import.module)
121 } else {
122 format!("import {}: {}", import.module, names_to_use.join(", "))
123 }
124 }
125
126 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
127 let name = symbol.name.as_str();
128 match symbol.kind {
129 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
130 crate::SymbolKind::Module => name == "tests" || name == "test",
131 _ => false,
132 }
133 }
134
135 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
136 node.child_by_field_name("body")
137 }
138
139 fn analyze_container_body(
140 &self,
141 body_node: &Node,
142 content: &str,
143 inner_indent: &str,
144 ) -> Option<ContainerBody> {
145 crate::body::analyze_end_body(body_node, content, inner_indent)
146 }
147}
148
149impl LanguageSymbols for Julia {}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::validate_unused_kinds_audit;
155
156 #[test]
157 fn unused_node_kinds_audit() {
158 #[rustfmt::skip]
159 let documented_unused: &[&str] = &[
160 "adjoint_expression", "binary_expression", "block",
161 "block_comment", "break_statement", "broadcast_call_expression", "call_expression",
162 "catch_clause", "compound_assignment_expression", "compound_statement",
163 "comprehension_expression", "continue_statement", "curly_expression", "else_clause",
164 "export_statement", "field_expression", "finally_clause", "for_binding", "for_clause",
165 "generator", "global_statement", "identifier", "if_clause", "import_alias",
166 "import_path", "index_expression", "interpolation_expression",
167 "juxtaposition_expression", "local_statement", "macro_identifier",
168 "macrocall_expression", "matrix_expression", "operator", "parametrized_type_expression",
169 "parenthesized_expression", "public_statement", "quote_expression", "quote_statement",
170 "range_expression", "return_statement", "selected_import", "splat_expression",
171 "tuple_expression", "typed_expression", "unary_expression",
172 "unary_typed_expression", "vector_expression", "where_expression",
173 "const_statement",
175 "arrow_function_expression",
176 "if_statement",
177 "using_statement",
178 "primitive_definition",
179 "for_statement",
180 "let_statement",
181 "ternary_expression",
182 "do_clause",
183 "while_statement",
184 "try_statement",
185 "elseif_clause",
186 "import_statement",
187 ];
188 validate_unused_kinds_audit(&Julia, documented_unused)
189 .expect("Julia unused node kinds audit failed");
190 }
191}