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 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 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 "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 "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 "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 "aggregate_foreach", "foreach", "foreach_aggregate", "foreach_type",
442 "foreach_type_attribute", "foreach_type_attributes", "foreach_type_list",
443 "range_foreach", "static_foreach",
444 "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_type_parameter", "template_type_parameter_default",
453 "template_type_parameter_specialization", "type_specialization",
454 "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", "import_bind", "import_bind_list", "import_bindings", "import_list",
463 "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 "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}