Skip to main content

normalize_languages/
ada.rs

1//! Ada language support.
2
3use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8/// Ada language support.
9pub 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 // Ada uses separate spec/body files
64    }
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        // Ada grammar uses expression-based nodes
111        &["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        // Ada: with Package;
245        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            // Type definitions
374            "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            // Declarations
385            "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 and task types
396            "protected_body", "protected_body_stub", "protected_definition", "protected_type_declaration",
397            "task_body", "task_body_stub", "task_definition", "task_type_declaration",
398            // Stubs
399            "package_body_stub", "subprogram_body_stub",
400            // Statements
401            "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            // Expressions
405            "qualified_expression", "raise_expression",
406            // Potentially useful - control flow
407            "exception_handler", "if_statement", "exit_statement", "case_statement",
408            // Representation clauses
409            "at_clause", "attribute_definition_clause", "component_clause", "enumeration_aggregate",
410            "enumeration_representation_clause", "mod_clause", "record_representation_clause",
411            // Control flow and statements
412            "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            // Contracts and aspects
417            "aspect_specification", "global_aspect_definition",
418            // GNAT-specific
419            "gnatprep_declarative_if_statement", "gnatprep_identifier", "gnatprep_if_statement",
420            // Expressions and operators
421            "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}