1use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6pub struct D;
8
9impl D {
10 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 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 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 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 "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 "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 "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 "aggregate_foreach", "foreach", "foreach_aggregate", "foreach_type",
291 "foreach_type_attribute", "foreach_type_attributes", "foreach_type_list",
292 "range_foreach", "static_foreach",
293 "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_type_parameter", "template_type_parameter_default",
302 "template_type_parameter_specialization", "type_specialization",
303 "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", "import_bind", "import_bind_list", "import_bindings", "import_list",
312 "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 "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 "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}