Skip to main content

normalize_languages/
d.rs

1//! D 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/// D language support.
9pub struct D;
10
11impl Language for D {
12    fn name(&self) -> &'static str {
13        "D"
14    }
15    fn extensions(&self) -> &'static [&'static str] {
16        &["d", "di"]
17    }
18    fn grammar_name(&self) -> &'static str {
19        "d"
20    }
21
22    fn has_symbols(&self) -> bool {
23        true
24    }
25
26    fn container_kinds(&self) -> &'static [&'static str] {
27        &[
28            "module_declaration",
29            "class_declaration",
30            "struct_declaration",
31            "interface_declaration",
32        ]
33    }
34
35    fn function_kinds(&self) -> &'static [&'static str] {
36        &["function_literal", "auto_declaration"]
37    }
38
39    fn type_kinds(&self) -> &'static [&'static str] {
40        &[
41            "alias_declaration",
42            "enum_declaration",
43            "class_declaration",
44            "struct_declaration",
45        ]
46    }
47
48    fn import_kinds(&self) -> &'static [&'static str] {
49        &["import_declaration"]
50    }
51
52    fn public_symbol_kinds(&self) -> &'static [&'static str] {
53        &[
54            "module_declaration",
55            "class_declaration",
56            "struct_declaration",
57            "auto_declaration",
58        ]
59    }
60
61    fn visibility_mechanism(&self) -> VisibilityMechanism {
62        VisibilityMechanism::AccessModifier
63    }
64
65    fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
66        match node.kind() {
67            "module_declaration" => {
68                if let Some(name) = self.node_name(node, content) {
69                    return vec![Export {
70                        name: name.to_string(),
71                        kind: SymbolKind::Module,
72                        line: node.start_position().row + 1,
73                    }];
74                }
75            }
76            "class_declaration" | "struct_declaration" | "interface_declaration" => {
77                if self.is_public(node, content) {
78                    if let Some(name) = self.node_name(node, content) {
79                        return vec![Export {
80                            name: name.to_string(),
81                            kind: SymbolKind::Class,
82                            line: node.start_position().row + 1,
83                        }];
84                    }
85                }
86            }
87            "auto_declaration" | "function_literal" => {
88                if self.is_public(node, content) {
89                    if let Some(name) = self.node_name(node, content) {
90                        return vec![Export {
91                            name: name.to_string(),
92                            kind: SymbolKind::Function,
93                            line: node.start_position().row + 1,
94                        }];
95                    }
96                }
97            }
98            _ => {}
99        }
100        Vec::new()
101    }
102
103    fn scope_creating_kinds(&self) -> &'static [&'static str] {
104        &[
105            "function_literal",
106            "class_declaration",
107            "struct_declaration",
108            "block_statement",
109        ]
110    }
111
112    fn control_flow_kinds(&self) -> &'static [&'static str] {
113        &[
114            "if_statement",
115            "switch_statement",
116            "while_statement",
117            "for_statement",
118            "foreach_statement",
119        ]
120    }
121
122    fn complexity_nodes(&self) -> &'static [&'static str] {
123        &[
124            "if_statement",
125            "switch_statement",
126            "while_statement",
127            "for_statement",
128            "foreach_statement",
129            "catch",
130        ]
131    }
132
133    fn nesting_nodes(&self) -> &'static [&'static str] {
134        &[
135            "if_statement",
136            "switch_statement",
137            "while_statement",
138            "for_statement",
139            "class_declaration",
140        ]
141    }
142
143    fn signature_suffix(&self) -> &'static str {
144        " {}"
145    }
146
147    fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
148        match node.kind() {
149            "function_literal" | "auto_declaration" => {
150                let name = self.node_name(node, content)?;
151                let text = &content[node.byte_range()];
152                let first_line = text.lines().next().unwrap_or(text);
153
154                Some(Symbol {
155                    name: name.to_string(),
156                    kind: SymbolKind::Function,
157                    signature: first_line.trim().to_string(),
158                    docstring: None,
159                    attributes: Vec::new(),
160                    start_line: node.start_position().row + 1,
161                    end_line: node.end_position().row + 1,
162                    visibility: self.get_visibility(node, content),
163                    children: Vec::new(),
164                    is_interface_impl: false,
165                    implements: Vec::new(),
166                })
167            }
168            _ => None,
169        }
170    }
171
172    fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
173        match node.kind() {
174            "module_declaration" => {
175                let name = self.node_name(node, content)?;
176                Some(Symbol {
177                    name: name.to_string(),
178                    kind: SymbolKind::Module,
179                    signature: format!("module {}", name),
180                    docstring: None,
181                    attributes: Vec::new(),
182                    start_line: node.start_position().row + 1,
183                    end_line: node.end_position().row + 1,
184                    visibility: Visibility::Public,
185                    children: Vec::new(),
186                    is_interface_impl: false,
187                    implements: Vec::new(),
188                })
189            }
190            "class_declaration" | "struct_declaration" | "interface_declaration" => {
191                let name = self.node_name(node, content)?;
192                let text = &content[node.byte_range()];
193                let first_line = text.lines().next().unwrap_or(text);
194
195                Some(Symbol {
196                    name: name.to_string(),
197                    kind: SymbolKind::Class,
198                    signature: first_line.trim().to_string(),
199                    docstring: None,
200                    attributes: Vec::new(),
201                    start_line: node.start_position().row + 1,
202                    end_line: node.end_position().row + 1,
203                    visibility: self.get_visibility(node, content),
204                    children: Vec::new(),
205                    is_interface_impl: false,
206                    implements: Vec::new(),
207                })
208            }
209            _ => None,
210        }
211    }
212
213    fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
214        match node.kind() {
215            "alias_declaration" | "enum_declaration" => {
216                let name = self.node_name(node, content)?;
217                let text = &content[node.byte_range()];
218                let first_line = text.lines().next().unwrap_or(text);
219
220                Some(Symbol {
221                    name: name.to_string(),
222                    kind: SymbolKind::Type,
223                    signature: first_line.trim().to_string(),
224                    docstring: None,
225                    attributes: Vec::new(),
226                    start_line: node.start_position().row + 1,
227                    end_line: node.end_position().row + 1,
228                    visibility: self.get_visibility(node, content),
229                    children: Vec::new(),
230                    is_interface_impl: false,
231                    implements: Vec::new(),
232                })
233            }
234            _ => None,
235        }
236    }
237
238    fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
239        None
240    }
241
242    fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
243        Vec::new()
244    }
245
246    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
247        if node.kind() != "import_declaration" {
248            return Vec::new();
249        }
250
251        let text = &content[node.byte_range()];
252        vec![Import {
253            module: text.trim().to_string(),
254            names: Vec::new(),
255            alias: None,
256            is_wildcard: text.contains(':'),
257            is_relative: false,
258            line: node.start_position().row + 1,
259        }]
260    }
261
262    fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
263        // D: import module; or import module : a, b, c;
264        let names_to_use: Vec<&str> = names
265            .map(|n| n.to_vec())
266            .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
267        if names_to_use.is_empty() {
268            format!("import {};", import.module)
269        } else {
270            format!("import {} : {};", import.module, names_to_use.join(", "))
271        }
272    }
273
274    fn is_public(&self, node: &Node, content: &str) -> bool {
275        let text = &content[node.byte_range()];
276        text.starts_with("public ") || !text.starts_with("private ")
277    }
278
279    fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
280        let text = &content[node.byte_range()];
281        if text.starts_with("private ") {
282            Visibility::Private
283        } else if text.starts_with("protected ") {
284            Visibility::Protected
285        } else {
286            Visibility::Public
287        }
288    }
289
290    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
291        let name = symbol.name.as_str();
292        match symbol.kind {
293            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
294            crate::SymbolKind::Module => name == "tests" || name == "test",
295            _ => false,
296        }
297    }
298
299    fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
300        None
301    }
302
303    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
304        node.child_by_field_name("body")
305    }
306
307    fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
308        false
309    }
310
311    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
312        if let Some(name_node) = node.child_by_field_name("name") {
313            return Some(&content[name_node.byte_range()]);
314        }
315        let mut cursor = node.walk();
316        for child in node.children(&mut cursor) {
317            if child.kind() == "identifier" {
318                return Some(&content[child.byte_range()]);
319            }
320        }
321        None
322    }
323
324    fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
325        let ext = path.extension()?.to_str()?;
326        if !["d", "di"].contains(&ext) {
327            return None;
328        }
329        let stem = path.file_stem()?.to_str()?;
330        Some(stem.to_string())
331    }
332
333    fn module_name_to_paths(&self, module: &str) -> Vec<String> {
334        let path = module.replace('.', "/");
335        vec![format!("{}.d", path), format!("{}/package.d", path)]
336    }
337
338    fn lang_key(&self) -> &'static str {
339        "d"
340    }
341
342    fn is_stdlib_import(&self, import_name: &str, _project_root: &Path) -> bool {
343        import_name.starts_with("std.") || import_name.starts_with("core.")
344    }
345
346    fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
347        None
348    }
349    fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
350        None
351    }
352    fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
353        None
354    }
355    fn get_version(&self, _: &Path) -> Option<String> {
356        None
357    }
358    fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
359        None
360    }
361    fn indexable_extensions(&self) -> &'static [&'static str] {
362        &["d", "di"]
363    }
364    fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
365        Vec::new()
366    }
367
368    fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
369        use crate::traits::{has_extension, skip_dotfiles};
370        if skip_dotfiles(name) {
371            return true;
372        }
373        !is_dir && !has_extension(name, self.indexable_extensions())
374    }
375
376    fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
377        Vec::new()
378    }
379
380    fn package_module_name(&self, entry_name: &str) -> String {
381        entry_name
382            .strip_suffix(".d")
383            .or_else(|| entry_name.strip_suffix(".di"))
384            .unwrap_or(entry_name)
385            .to_string()
386    }
387
388    fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
389        if path.is_file() {
390            return Some(path.to_path_buf());
391        }
392        let package_d = path.join("package.d");
393        if package_d.is_file() {
394            return Some(package_d);
395        }
396        None
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use crate::validate_unused_kinds_audit;
404
405    #[test]
406    fn unused_node_kinds_audit() {
407        #[rustfmt::skip]
408        let documented_unused: &[&str] = &[
409            // Expressions
410            "add_expression", "and_and_expression", "and_expression", "assign_expression",
411            "assert_expression", "cat_expression", "cast_expression", "comma_expression",
412            "complement_expression", "conditional_expression", "delete_expression", "equal_expression",
413            "expression", "identity_expression", "import_expression", "in_expression",
414            "index_expression", "is_expression", "key_expression", "lwr_expression",
415            "mixin_expression", "mul_expression", "new_anon_class_expression", "new_expression",
416            "or_expression", "or_or_expression", "postfix_expression", "pow_expression",
417            "primary_expression", "qualified_identifier", "rel_expression", "shift_expression",
418            "slice_expression", "traits_expression", "typeid_expression", "unary_expression",
419            "upr_expression", "value_expression", "xor_expression",
420            // Statements
421            "asm_statement", "break_statement", "case_range_statement", "case_statement",
422            "conditional_statement", "continue_statement", "declaration_statement", "default_statement",
423            "do_statement", "empty_statement", "expression_statement", "final_switch_statement",
424            "foreach_range_statement", "goto_statement", "labeled_statement", "mixin_statement",
425            "out_statement", "pragma_statement", "return_statement", "scope_block_statement",
426            "scope_guard_statement", "scope_statement_list", "statement_list",
427            "statement_list_no_case_no_default", "static_foreach_statement", "synchronized_statement",
428            "then_statement", "throw_statement", "try_statement", "with_statement",
429            // Declarations
430            "anonymous_enum_declaration", "anonymous_enum_member",
431            "anonymous_enum_members", "anon_struct_declaration", "anon_union_declaration",
432            "auto_func_declaration", "class_template_declaration",
433            "conditional_declaration", "debug_specification", "destructor", "empty_declaration",
434            "enum_body", "enum_member", "enum_member_attribute", "enum_member_attributes",
435            "enum_members", "func_declaration", "interface_template_declaration", "mixin_declaration",
436            "module", "shared_static_constructor", "shared_static_destructor", "static_constructor",
437            "static_destructor", "static_foreach_declaration", "struct_template_declaration",
438            "template_declaration", "template_mixin_declaration", "union_declaration",
439            "union_template_declaration", "var_declarations", "version_specification",
440            // Foreach-related
441            "aggregate_foreach", "foreach", "foreach_aggregate", "foreach_type",
442            "foreach_type_attribute", "foreach_type_attributes", "foreach_type_list",
443            "range_foreach", "static_foreach",
444            // Function-related
445            "constructor_args", "constructor_template", "function_attribute_kwd",
446            "function_attributes", "function_contracts", "function_literal_body",
447            "function_literal_body2", "member_function_attribute", "member_function_attributes",
448            "missing_function_body", "out_contract_expression", "in_contract_expression",
449            "in_statement", "parameter_with_attributes", "parameter_with_member_attributes",
450            "shortened_function_body", "specified_function_body",
451            // Template-related
452            "template_type_parameter", "template_type_parameter_default",
453            "template_type_parameter_specialization", "type_specialization",
454            // Type-related
455            "aggregate_body", "basic_type", "catch_parameter", "catches", "constructor",
456            "else_statement", "enum_base_type", "finally_statement", "fundamental_type",
457            "if_condition", "interfaces", "linkage_type", "module_alias_identifier",
458            "module_attributes", "module_fully_qualified_name", "module_name", "mixin_type",
459            "mixin_qualified_identifier", "storage_class", "storage_classes", "type",
460            "type_ctor", "type_ctors", "type_suffix", "type_suffixes", "typeof", "interface",
461            // Import-related
462            "import", "import_bind", "import_bind_list", "import_bindings", "import_list",
463            // ASM-related
464            "asm_instruction", "asm_instruction_list", "asm_shift_exp", "asm_type_prefix",
465            "gcc_asm_instruction_list", "gcc_asm_statement", "gcc_basic_asm_instruction",
466            "gcc_ext_asm_instruction", "gcc_goto_asm_instruction",
467            // Misc
468            "alt_declarator_identifier", "base_class_list", "base_interface_list",
469            "block_comment", "declaration_block", "declarator_identifier_list", "dot_identifier",
470            "identifier", "nesting_block_comment", "static_if_condition", "struct_initializer",
471            "struct_member_initializer", "struct_member_initializers", "super_class_or_interface",
472            "traits_arguments", "traits_keyword", "var_declarator_identifier", "vector_base_type",
473            "attribute_specifier",
474        ];
475        validate_unused_kinds_audit(&D, documented_unused)
476            .expect("D unused node kinds audit failed");
477    }
478}