Skip to main content

normalize_languages/
d.rs

1//! D language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6/// D language support.
7pub struct D;
8
9impl D {
10    /// Recursively collect type names from a D inheritance clause.
11    /// D nests: base_class_list > super_class_or_interface/interfaces/interface >
12    /// qualified_identifier > identifier(s)
13    fn collect_identifiers(node: &Node, content: &str, out: &mut Vec<String>) {
14        if node.kind() == "qualified_identifier" {
15            out.push(content[node.byte_range()].to_string());
16            return;
17        }
18        let mut cursor = node.walk();
19        for child in node.children(&mut cursor) {
20            Self::collect_identifiers(&child, content, out);
21        }
22    }
23}
24
25impl Language for D {
26    fn name(&self) -> &'static str {
27        "D"
28    }
29    fn extensions(&self) -> &'static [&'static str] {
30        &["d", "di"]
31    }
32    fn grammar_name(&self) -> &'static str {
33        "d"
34    }
35
36    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
37        Some(self)
38    }
39
40    fn signature_suffix(&self) -> &'static str {
41        " {}"
42    }
43
44    fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
45        let mut prev = node.prev_sibling();
46        let mut doc_lines = Vec::new();
47        while let Some(sibling) = prev {
48            let text = &content[sibling.byte_range()];
49            match sibling.kind() {
50                "comment" => {
51                    if text.starts_with("///") {
52                        let line = text.strip_prefix("///").unwrap_or(text).trim();
53                        if !line.is_empty() {
54                            doc_lines.push(line.to_string());
55                        }
56                        prev = sibling.prev_sibling();
57                    } else {
58                        break;
59                    }
60                }
61                "block_comment" => {
62                    if text.starts_with("/**") {
63                        let inner = text
64                            .strip_prefix("/**")
65                            .unwrap_or(text)
66                            .strip_suffix("*/")
67                            .unwrap_or(text);
68                        for line in inner.lines() {
69                            let clean = line.trim().strip_prefix('*').unwrap_or(line).trim();
70                            if !clean.is_empty() {
71                                doc_lines.push(clean.to_string());
72                            }
73                        }
74                    }
75                    break;
76                }
77                "nesting_block_comment" => {
78                    if text.starts_with("/++") {
79                        let inner = text
80                            .strip_prefix("/++")
81                            .unwrap_or(text)
82                            .strip_suffix("+/")
83                            .unwrap_or(text);
84                        for line in inner.lines() {
85                            let clean = line.trim().strip_prefix('+').unwrap_or(line).trim();
86                            if !clean.is_empty() {
87                                doc_lines.push(clean.to_string());
88                            }
89                        }
90                    }
91                    break;
92                }
93                _ => break,
94            }
95        }
96        if doc_lines.is_empty() {
97            return None;
98        }
99        doc_lines.reverse();
100        Some(doc_lines.join(" "))
101    }
102
103    fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
104        let mut attrs = Vec::new();
105        let mut cursor = node.walk();
106        for child in node.children(&mut cursor) {
107            if child.kind() == "attribute_specifier" {
108                let text = content[child.byte_range()].trim().to_string();
109                if !text.is_empty() {
110                    attrs.push(text);
111                }
112            }
113        }
114        let mut prev = node.prev_sibling();
115        while let Some(sibling) = prev {
116            if sibling.kind() == "attribute_specifier" {
117                let text = content[sibling.byte_range()].trim().to_string();
118                if !text.is_empty() {
119                    attrs.insert(0, text);
120                }
121                prev = sibling.prev_sibling();
122            } else {
123                break;
124            }
125        }
126        attrs
127    }
128
129    fn build_signature(&self, node: &Node, content: &str) -> String {
130        let name = self.node_name(node, content).unwrap_or("");
131        match node.kind() {
132            "module_declaration" => format!("module {}", name),
133            _ => {
134                let text = &content[node.byte_range()];
135                text.lines().next().unwrap_or(text).trim().to_string()
136            }
137        }
138    }
139
140    fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
141        let mut implements = Vec::new();
142        let mut cursor = node.walk();
143        for child in node.children(&mut cursor) {
144            if child.kind() == "base_class_list" {
145                D::collect_identifiers(&child, content, &mut implements);
146            }
147        }
148        crate::ImplementsInfo {
149            is_interface: false,
150            implements,
151        }
152    }
153
154    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
155        if node.kind() != "import_declaration" {
156            return Vec::new();
157        }
158
159        let text = &content[node.byte_range()];
160        // Strip "import " prefix and trailing ";"
161        let module = text
162            .trim()
163            .strip_prefix("import ")
164            .unwrap_or(text.trim())
165            .trim_end_matches(';')
166            .trim()
167            .to_string();
168        let is_wildcard = module.contains(':');
169        vec![Import {
170            module,
171            names: Vec::new(),
172            alias: None,
173            is_wildcard,
174            is_relative: false,
175            line: node.start_position().row + 1,
176        }]
177    }
178
179    fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
180        // D: import module; or import module : a, b, c;
181        let names_to_use: Vec<&str> = names
182            .map(|n| n.to_vec())
183            .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
184        if names_to_use.is_empty() {
185            format!("import {};", import.module)
186        } else {
187            format!("import {} : {};", import.module, names_to_use.join(", "))
188        }
189    }
190
191    fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
192        let text = &content[node.byte_range()];
193        if text.starts_with("private ") {
194            Visibility::Private
195        } else if text.starts_with("protected ") {
196            Visibility::Protected
197        } else {
198            Visibility::Public
199        }
200    }
201
202    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
203        let name = symbol.name.as_str();
204        match symbol.kind {
205            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
206            crate::SymbolKind::Module => name == "tests" || name == "test",
207            _ => false,
208        }
209    }
210
211    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
212        node.child_by_field_name("body")
213    }
214
215    fn analyze_container_body(
216        &self,
217        body_node: &Node,
218        content: &str,
219        inner_indent: &str,
220    ) -> Option<ContainerBody> {
221        crate::body::analyze_brace_body(body_node, content, inner_indent)
222    }
223
224    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
225        if let Some(name_node) = node.child_by_field_name("name") {
226            return Some(&content[name_node.byte_range()]);
227        }
228        let mut cursor = node.walk();
229        for child in node.children(&mut cursor) {
230            if child.kind() == "identifier" {
231                return Some(&content[child.byte_range()]);
232            }
233            // func_declaration: name is inside func_declarator
234            if child.kind() == "func_declarator" {
235                let mut inner = child.walk();
236                for grandchild in child.children(&mut inner) {
237                    if grandchild.kind() == "identifier" {
238                        return Some(&content[grandchild.byte_range()]);
239                    }
240                }
241            }
242        }
243        None
244    }
245}
246
247impl LanguageSymbols for D {}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use crate::validate_unused_kinds_audit;
253
254    #[test]
255    fn unused_node_kinds_audit() {
256        #[rustfmt::skip]
257        let documented_unused: &[&str] = &[
258            // Expressions
259            "add_expression", "and_and_expression", "and_expression", "assign_expression",
260            "assert_expression", "cat_expression", "cast_expression", "comma_expression",
261            "complement_expression", "conditional_expression", "delete_expression", "equal_expression",
262            "expression", "identity_expression", "import_expression", "in_expression",
263            "index_expression", "is_expression", "key_expression", "lwr_expression",
264            "mixin_expression", "mul_expression", "new_anon_class_expression", "new_expression",
265            "or_expression", "or_or_expression", "postfix_expression", "pow_expression",
266            "primary_expression", "qualified_identifier", "rel_expression", "shift_expression",
267            "slice_expression", "traits_expression", "typeid_expression", "unary_expression",
268            "upr_expression", "value_expression", "xor_expression",
269            // Statements
270            "asm_statement", "break_statement", "case_range_statement", "case_statement",
271            "conditional_statement", "continue_statement", "declaration_statement", "default_statement",
272            "do_statement", "empty_statement", "expression_statement", "final_switch_statement",
273            "foreach_range_statement", "goto_statement", "labeled_statement", "mixin_statement",
274            "out_statement", "pragma_statement", "return_statement", "scope_block_statement",
275            "scope_guard_statement", "scope_statement_list", "statement_list",
276            "statement_list_no_case_no_default", "static_foreach_statement", "synchronized_statement",
277            "then_statement", "throw_statement", "try_statement", "with_statement",
278            // Declarations
279            "anonymous_enum_declaration", "anonymous_enum_member",
280            "anonymous_enum_members", "anon_struct_declaration", "anon_union_declaration",
281            "auto_func_declaration", "class_template_declaration",
282            "conditional_declaration", "debug_specification", "destructor", "empty_declaration",
283            "enum_body", "enum_member", "enum_member_attribute", "enum_member_attributes",
284            "enum_members", "interface_template_declaration", "mixin_declaration",
285            "module", "shared_static_constructor", "shared_static_destructor", "static_constructor",
286            "static_destructor", "static_foreach_declaration", "struct_template_declaration",
287            "template_declaration", "template_mixin_declaration", "union_declaration",
288            "union_template_declaration", "var_declarations", "version_specification",
289            // Foreach-related
290            "aggregate_foreach", "foreach", "foreach_aggregate", "foreach_type",
291            "foreach_type_attribute", "foreach_type_attributes", "foreach_type_list",
292            "range_foreach", "static_foreach",
293            // Function-related
294            "constructor_args", "constructor_template", "function_attribute_kwd",
295            "function_attributes", "function_contracts", "function_literal_body",
296            "function_literal_body2", "member_function_attribute", "member_function_attributes",
297            "missing_function_body", "out_contract_expression", "in_contract_expression",
298            "in_statement", "parameter_with_attributes", "parameter_with_member_attributes",
299            "shortened_function_body", "specified_function_body",
300            // Template-related
301            "template_type_parameter", "template_type_parameter_default",
302            "template_type_parameter_specialization", "type_specialization",
303            // Type-related
304            "aggregate_body", "basic_type", "catch_parameter", "catches", "constructor",
305            "else_statement", "enum_base_type", "finally_statement", "fundamental_type",
306            "if_condition", "interfaces", "linkage_type", "module_alias_identifier",
307            "module_attributes", "module_fully_qualified_name", "module_name", "mixin_type",
308            "mixin_qualified_identifier", "storage_class", "storage_classes", "type",
309            "type_ctor", "type_ctors", "type_suffix", "type_suffixes", "typeof", "interface",
310            // Import-related
311            "import", "import_bind", "import_bind_list", "import_bindings", "import_list",
312            // ASM-related
313            "asm_instruction", "asm_instruction_list", "asm_shift_exp", "asm_type_prefix",
314            "gcc_asm_instruction_list", "gcc_asm_statement", "gcc_basic_asm_instruction",
315            "gcc_ext_asm_instruction", "gcc_goto_asm_instruction",
316            // Misc
317            "alt_declarator_identifier", "base_class_list", "base_interface_list",
318            "block_comment", "declaration_block", "declarator_identifier_list", "dot_identifier", "nesting_block_comment", "static_if_condition", "struct_initializer",
319            "struct_member_initializer", "struct_member_initializers", "super_class_or_interface",
320            "traits_arguments", "traits_keyword", "var_declarator_identifier", "vector_base_type",
321            "attribute_specifier",
322            // structural node, not extracted as symbols
323            "alias_declaration",
324            "auto_declaration",
325            "module_declaration",
326            "block_statement",
327            "import_declaration",
328            "while_statement",
329            "switch_statement",
330            "if_statement",
331            "function_literal",
332            "for_statement",
333            "foreach_statement",
334            "catch",
335        ];
336        validate_unused_kinds_audit(&D, documented_unused)
337            .expect("D unused node kinds audit failed");
338    }
339}