1use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8pub struct Ada;
10
11impl Language for Ada {
12 fn name(&self) -> &'static str {
13 "Ada"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["ada", "adb", "ads"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "ada"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &[
28 "package_declaration",
29 "package_body",
30 "generic_package_declaration",
31 ]
32 }
33
34 fn function_kinds(&self) -> &'static [&'static str] {
35 &[
36 "subprogram_declaration",
37 "subprogram_body",
38 "expression_function_declaration",
39 ]
40 }
41
42 fn type_kinds(&self) -> &'static [&'static str] {
43 &[
44 "full_type_declaration",
45 "private_type_declaration",
46 "incomplete_type_declaration",
47 ]
48 }
49
50 fn import_kinds(&self) -> &'static [&'static str] {
51 &["with_clause", "use_clause"]
52 }
53
54 fn public_symbol_kinds(&self) -> &'static [&'static str] {
55 &[
56 "package_declaration",
57 "subprogram_declaration",
58 "full_type_declaration",
59 ]
60 }
61
62 fn visibility_mechanism(&self) -> VisibilityMechanism {
63 VisibilityMechanism::AllPublic }
65
66 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
67 match node.kind() {
68 "package_declaration" | "package_body" | "generic_package_declaration" => {
69 if let Some(name) = self.node_name(node, content) {
70 return vec![Export {
71 name: name.to_string(),
72 kind: SymbolKind::Module,
73 line: node.start_position().row + 1,
74 }];
75 }
76 }
77 "subprogram_declaration" | "subprogram_body" | "expression_function_declaration" => {
78 if let Some(name) = self.node_name(node, content) {
79 return vec![Export {
80 name: name.to_string(),
81 kind: SymbolKind::Function,
82 line: node.start_position().row + 1,
83 }];
84 }
85 }
86 "full_type_declaration" | "private_type_declaration" => {
87 if let Some(name) = self.node_name(node, content) {
88 return vec![Export {
89 name: name.to_string(),
90 kind: SymbolKind::Type,
91 line: node.start_position().row + 1,
92 }];
93 }
94 }
95 _ => {}
96 }
97 Vec::new()
98 }
99
100 fn scope_creating_kinds(&self) -> &'static [&'static str] {
101 &[
102 "package_body",
103 "subprogram_body",
104 "block_statement",
105 "loop_statement",
106 ]
107 }
108
109 fn control_flow_kinds(&self) -> &'static [&'static str] {
110 &["case_expression", "if_expression", "quantified_expression"]
112 }
113
114 fn complexity_nodes(&self) -> &'static [&'static str] {
115 &[
116 "case_expression",
117 "if_expression",
118 "case_expression_alternative",
119 ]
120 }
121
122 fn nesting_nodes(&self) -> &'static [&'static str] {
123 &["case_expression", "if_expression", "declare_expression"]
124 }
125
126 fn signature_suffix(&self) -> &'static str {
127 ""
128 }
129
130 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
131 match node.kind() {
132 "subprogram_declaration" | "subprogram_body" | "expression_function_declaration" => {
133 let name = self.node_name(node, content)?;
134 let text = &content[node.byte_range()];
135 let first_line = text.lines().next().unwrap_or(text);
136
137 Some(Symbol {
138 name: name.to_string(),
139 kind: SymbolKind::Function,
140 signature: first_line.trim().to_string(),
141 docstring: None,
142 attributes: Vec::new(),
143 start_line: node.start_position().row + 1,
144 end_line: node.end_position().row + 1,
145 visibility: Visibility::Public,
146 children: Vec::new(),
147 is_interface_impl: false,
148 implements: Vec::new(),
149 })
150 }
151 _ => None,
152 }
153 }
154
155 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
156 match node.kind() {
157 "package_declaration" | "package_body" | "generic_package_declaration" => {
158 let name = self.node_name(node, content)?;
159 let text = &content[node.byte_range()];
160 let first_line = text.lines().next().unwrap_or(text);
161
162 Some(Symbol {
163 name: name.to_string(),
164 kind: SymbolKind::Module,
165 signature: first_line.trim().to_string(),
166 docstring: None,
167 attributes: Vec::new(),
168 start_line: node.start_position().row + 1,
169 end_line: node.end_position().row + 1,
170 visibility: Visibility::Public,
171 children: Vec::new(),
172 is_interface_impl: false,
173 implements: Vec::new(),
174 })
175 }
176 _ => None,
177 }
178 }
179
180 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
181 match node.kind() {
182 "full_type_declaration"
183 | "private_type_declaration"
184 | "incomplete_type_declaration" => {
185 let name = self.node_name(node, content)?;
186 let text = &content[node.byte_range()];
187 let first_line = text.lines().next().unwrap_or(text);
188
189 Some(Symbol {
190 name: name.to_string(),
191 kind: SymbolKind::Type,
192 signature: first_line.trim().to_string(),
193 docstring: None,
194 attributes: Vec::new(),
195 start_line: node.start_position().row + 1,
196 end_line: node.end_position().row + 1,
197 visibility: Visibility::Public,
198 children: Vec::new(),
199 is_interface_impl: false,
200 implements: Vec::new(),
201 })
202 }
203 _ => None,
204 }
205 }
206
207 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
208 None
209 }
210
211 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
212 Vec::new()
213 }
214
215 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
216 match node.kind() {
217 "with_clause" => {
218 let text = &content[node.byte_range()];
219 vec![Import {
220 module: text.trim().to_string(),
221 names: Vec::new(),
222 alias: None,
223 is_wildcard: false,
224 is_relative: false,
225 line: node.start_position().row + 1,
226 }]
227 }
228 "use_clause" => {
229 let text = &content[node.byte_range()];
230 vec![Import {
231 module: text.trim().to_string(),
232 names: Vec::new(),
233 alias: None,
234 is_wildcard: true,
235 is_relative: false,
236 line: node.start_position().row + 1,
237 }]
238 }
239 _ => Vec::new(),
240 }
241 }
242
243 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
244 format!("with {};", import.module)
246 }
247
248 fn is_public(&self, _node: &Node, _content: &str) -> bool {
249 true
250 }
251 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
252 Visibility::Public
253 }
254
255 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
256 let name = symbol.name.as_str();
257 match symbol.kind {
258 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
259 crate::SymbolKind::Module => name == "tests" || name == "test",
260 _ => false,
261 }
262 }
263
264 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
265 None
266 }
267
268 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
269 node.child_by_field_name("declarations")
270 .or_else(|| node.child_by_field_name("statements"))
271 }
272
273 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
274 false
275 }
276
277 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
278 if let Some(name_node) = node.child_by_field_name("name") {
279 return Some(&content[name_node.byte_range()]);
280 }
281 let mut cursor = node.walk();
282 for child in node.children(&mut cursor) {
283 if child.kind() == "identifier" || child.kind() == "defining_identifier" {
284 return Some(&content[child.byte_range()]);
285 }
286 }
287 None
288 }
289
290 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
291 let ext = path.extension()?.to_str()?;
292 if !["ada", "adb", "ads"].contains(&ext) {
293 return None;
294 }
295 let stem = path.file_stem()?.to_str()?;
296 Some(stem.replace('-', "."))
297 }
298
299 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
300 let base = module.to_lowercase().replace('.', "-");
301 vec![format!("{}.ads", base), format!("{}.adb", base)]
302 }
303
304 fn lang_key(&self) -> &'static str {
305 "ada"
306 }
307
308 fn is_stdlib_import(&self, import_name: &str, _project_root: &Path) -> bool {
309 import_name.starts_with("Ada.") || import_name.starts_with("GNAT.")
310 }
311
312 fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
313 None
314 }
315 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
316 None
317 }
318 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
319 None
320 }
321 fn get_version(&self, _: &Path) -> Option<String> {
322 None
323 }
324 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
325 None
326 }
327 fn indexable_extensions(&self) -> &'static [&'static str] {
328 &["ada", "adb", "ads"]
329 }
330 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
331 Vec::new()
332 }
333
334 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
335 use crate::traits::{has_extension, skip_dotfiles};
336 if skip_dotfiles(name) {
337 return true;
338 }
339 !is_dir && !has_extension(name, self.indexable_extensions())
340 }
341
342 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
343 Vec::new()
344 }
345
346 fn package_module_name(&self, entry_name: &str) -> String {
347 entry_name
348 .strip_suffix(".ads")
349 .or_else(|| entry_name.strip_suffix(".adb"))
350 .or_else(|| entry_name.strip_suffix(".ada"))
351 .unwrap_or(entry_name)
352 .to_string()
353 }
354
355 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
356 if path.is_file() {
357 Some(path.to_path_buf())
358 } else {
359 None
360 }
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use crate::validate_unused_kinds_audit;
368
369 #[test]
370 fn unused_node_kinds_audit() {
371 #[rustfmt::skip]
372 let documented_unused: &[&str] = &[
373 "access_definition", "access_to_object_definition", "access_to_subprogram_definition",
375 "array_type_definition", "decimal_fixed_point_definition", "derived_type_definition",
376 "enumeration_type_definition", "floating_point_definition", "formal_access_type_definition",
377 "formal_array_type_definition", "formal_decimal_fixed_point_definition",
378 "formal_derived_type_definition", "formal_discrete_type_definition",
379 "formal_floating_point_definition", "formal_interface_type_definition",
380 "formal_modular_type_definition", "formal_ordinary_fixed_point_definition",
381 "formal_private_type_definition", "formal_signed_integer_type_definition",
382 "interface_type_definition", "modular_type_definition", "ordinary_fixed_point_definition",
383 "record_type_definition", "signed_integer_type_definition",
384 "body_stub", "component_declaration", "component_definition", "discriminant_specification",
386 "discriminant_specification_list", "entry_declaration", "exception_declaration",
387 "formal_abstract_subprogram_declaration", "formal_complete_type_declaration",
388 "formal_concrete_subprogram_declaration", "formal_incomplete_type_declaration",
389 "formal_object_declaration", "formal_package_declaration", "formal_subprogram_declaration",
390 "generic_formal_part", "generic_renaming_declaration", "generic_subprogram_declaration",
391 "null_procedure_declaration", "number_declaration", "object_declaration",
392 "object_renaming_declaration", "package_renaming_declaration", "parameter_specification",
393 "private_extension_declaration", "single_protected_declaration", "single_task_declaration",
394 "subprogram_renaming_declaration", "subtype_declaration",
395 "protected_body", "protected_body_stub", "protected_definition", "protected_type_declaration",
397 "task_body", "task_body_stub", "task_definition", "task_type_declaration",
398 "package_body_stub", "subprogram_body_stub",
400 "abort_statement", "accept_statement", "assignment_statement", "case_statement_alternative",
402 "delay_relative_statement", "delay_until_statement", "goto_statement", "null_statement",
403 "procedure_call_statement", "raise_statement", "requeue_statement", "simple_return_statement",
404 "qualified_expression", "raise_expression",
406 "exception_handler", "if_statement", "exit_statement", "case_statement",
408 "at_clause", "attribute_definition_clause", "component_clause", "enumeration_aggregate",
410 "enumeration_representation_clause", "mod_clause", "record_representation_clause",
411 "asynchronous_select", "conditional_entry_call", "entry_body", "entry_barrier",
413 "entry_call_alternative", "entry_index_specification", "extended_return_object_declaration",
414 "extended_return_statement", "handled_sequence_of_statements", "loop_label",
415 "loop_parameter_specification", "timed_entry_call",
416 "aspect_specification", "global_aspect_definition",
418 "gnatprep_declarative_if_statement", "gnatprep_identifier", "gnatprep_if_statement",
420 "binary_adding_operator", "choice_parameter_specification", "chunk_specification",
422 "elsif_expression_item", "elsif_statement_item", "exception_choice", "exception_choice_list",
423 "exception_renaming_declaration", "expression", "formal_part", "function_call",
424 "function_specification", "general_access_modifier", "identifier", "index_subtype_definition",
425 "iterator_specification", "multiplying_operator", "procedure_specification", "quantifier",
426 "real_range_specification", "record_definition", "reduction_specification",
427 "relational_operator", "subpool_specification", "unary_adding_operator",
428 ];
429 validate_unused_kinds_audit(&Ada, documented_unused)
430 .expect("Ada unused node kinds audit failed");
431 }
432}