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 VB;
10
11impl Language for VB {
12 fn name(&self) -> &'static str {
13 "Visual Basic"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["vb", "vbs"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "vb"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &[
28 "class_block",
29 "module_block",
30 "structure_block",
31 "interface_block",
32 ]
33 }
34
35 fn function_kinds(&self) -> &'static [&'static str] {
36 &["method_declaration", "property_declaration"]
37 }
38
39 fn type_kinds(&self) -> &'static [&'static str] {
40 &["enum_block", "delegate_declaration"]
41 }
42
43 fn import_kinds(&self) -> &'static [&'static str] {
44 &["imports_statement"]
45 }
46
47 fn public_symbol_kinds(&self) -> &'static [&'static str] {
48 &["class_block", "module_block", "method_declaration"]
49 }
50
51 fn visibility_mechanism(&self) -> VisibilityMechanism {
52 VisibilityMechanism::AccessModifier
53 }
54
55 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
56 match node.kind() {
57 "class_block" | "module_block" | "structure_block" => {
58 if let Some(name) = self.node_name(node, content) {
59 return vec![Export {
60 name: name.to_string(),
61 kind: SymbolKind::Class,
62 line: node.start_position().row + 1,
63 }];
64 }
65 }
66 "method_declaration" => {
67 if let Some(name) = self.node_name(node, content) {
68 return vec![Export {
69 name: name.to_string(),
70 kind: SymbolKind::Function,
71 line: node.start_position().row + 1,
72 }];
73 }
74 }
75 _ => {}
76 }
77 Vec::new()
78 }
79
80 fn scope_creating_kinds(&self) -> &'static [&'static str] {
81 &["class_block", "module_block", "method_declaration"]
82 }
83
84 fn control_flow_kinds(&self) -> &'static [&'static str] {
85 &[
86 "if_statement",
87 "select_case_statement",
88 "while_statement",
89 "for_statement",
90 "for_each_statement",
91 "do_statement",
92 ]
93 }
94
95 fn complexity_nodes(&self) -> &'static [&'static str] {
96 &[
97 "if_statement",
98 "select_case_statement",
99 "while_statement",
100 "for_statement",
101 "for_each_statement",
102 "case_clause",
103 ]
104 }
105
106 fn nesting_nodes(&self) -> &'static [&'static str] {
107 &[
108 "if_statement",
109 "select_case_statement",
110 "while_statement",
111 "for_statement",
112 "do_statement",
113 ]
114 }
115
116 fn signature_suffix(&self) -> &'static str {
117 ""
118 }
119
120 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
121 match node.kind() {
122 "method_declaration" | "property_declaration" => {
123 let name = self.node_name(node, content)?;
124 let text = &content[node.byte_range()];
125 let first_line = text.lines().next().unwrap_or(text);
126
127 Some(Symbol {
128 name: name.to_string(),
129 kind: SymbolKind::Function,
130 signature: first_line.trim().to_string(),
131 docstring: None,
132 attributes: Vec::new(),
133 start_line: node.start_position().row + 1,
134 end_line: node.end_position().row + 1,
135 visibility: self.get_visibility(node, content),
136 children: Vec::new(),
137 is_interface_impl: false,
138 implements: Vec::new(),
139 })
140 }
141 _ => None,
142 }
143 }
144
145 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
146 match node.kind() {
147 "class_block" | "module_block" | "structure_block" | "interface_block" => {
148 let name = self.node_name(node, content)?;
149 let text = &content[node.byte_range()];
150 let first_line = text.lines().next().unwrap_or(text);
151
152 Some(Symbol {
153 name: name.to_string(),
154 kind: SymbolKind::Class,
155 signature: first_line.trim().to_string(),
156 docstring: None,
157 attributes: Vec::new(),
158 start_line: node.start_position().row + 1,
159 end_line: node.end_position().row + 1,
160 visibility: self.get_visibility(node, content),
161 children: Vec::new(),
162 is_interface_impl: false,
163 implements: Vec::new(),
164 })
165 }
166 _ => None,
167 }
168 }
169
170 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
171 match node.kind() {
172 "enum_block" | "delegate_declaration" => {
173 let name = self.node_name(node, content)?;
174 let text = &content[node.byte_range()];
175 let first_line = text.lines().next().unwrap_or(text);
176
177 Some(Symbol {
178 name: name.to_string(),
179 kind: SymbolKind::Type,
180 signature: first_line.trim().to_string(),
181 docstring: None,
182 attributes: Vec::new(),
183 start_line: node.start_position().row + 1,
184 end_line: node.end_position().row + 1,
185 visibility: self.get_visibility(node, content),
186 children: Vec::new(),
187 is_interface_impl: false,
188 implements: Vec::new(),
189 })
190 }
191 _ => None,
192 }
193 }
194
195 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
196 None
197 }
198
199 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
200 Vec::new()
201 }
202
203 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
204 if node.kind() != "imports_statement" {
205 return Vec::new();
206 }
207
208 let text = &content[node.byte_range()];
209 vec![Import {
210 module: text.trim().to_string(),
211 names: Vec::new(),
212 alias: None,
213 is_wildcard: false,
214 is_relative: false,
215 line: node.start_position().row + 1,
216 }]
217 }
218
219 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
220 format!("Imports {}", import.module)
222 }
223
224 fn is_public(&self, node: &Node, content: &str) -> bool {
225 let text = &content[node.byte_range()];
226 text.to_lowercase().contains("public") || !text.to_lowercase().contains("private")
227 }
228
229 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
230 let text = &content[node.byte_range()];
231 let lower = text.to_lowercase();
232 if lower.contains("private") {
233 Visibility::Private
234 } else if lower.contains("protected") {
235 Visibility::Protected
236 } else {
237 Visibility::Public
238 }
239 }
240
241 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
242 let name = symbol.name.as_str();
243 match symbol.kind {
244 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
245 crate::SymbolKind::Module => name == "tests" || name == "test",
246 _ => false,
247 }
248 }
249
250 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
251 None
252 }
253
254 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
255 node.child_by_field_name("body")
256 }
257
258 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
259 false
260 }
261
262 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
263 node.child_by_field_name("name")
264 .map(|n| &content[n.byte_range()])
265 }
266
267 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
268 let ext = path.extension()?.to_str()?;
269 if !["vb", "vbs"].contains(&ext) {
270 return None;
271 }
272 let stem = path.file_stem()?.to_str()?;
273 Some(stem.to_string())
274 }
275
276 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
277 vec![format!("{}.vb", module), format!("{}.vbs", module)]
278 }
279
280 fn lang_key(&self) -> &'static str {
281 "vb"
282 }
283
284 fn is_stdlib_import(&self, import_name: &str, _project_root: &Path) -> bool {
285 import_name.starts_with("System.") || import_name.starts_with("Microsoft.")
286 }
287
288 fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
289 None
290 }
291 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
292 None
293 }
294 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
295 None
296 }
297 fn get_version(&self, _: &Path) -> Option<String> {
298 None
299 }
300 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
301 None
302 }
303 fn indexable_extensions(&self) -> &'static [&'static str] {
304 &["vb", "vbs"]
305 }
306 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
307 Vec::new()
308 }
309
310 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
311 use crate::traits::{has_extension, skip_dotfiles};
312 if skip_dotfiles(name) {
313 return true;
314 }
315 !is_dir && !has_extension(name, self.indexable_extensions())
316 }
317
318 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
319 Vec::new()
320 }
321
322 fn package_module_name(&self, entry_name: &str) -> String {
323 entry_name
324 .strip_suffix(".vb")
325 .or_else(|| entry_name.strip_suffix(".vbs"))
326 .unwrap_or(entry_name)
327 .to_string()
328 }
329
330 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
331 if path.is_file() {
332 Some(path.to_path_buf())
333 } else {
334 None
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::validate_unused_kinds_audit;
343
344 #[test]
345 fn unused_node_kinds_audit() {
346 #[rustfmt::skip]
347 let documented_unused: &[&str] = &[
348 "namespace_block",
350 "field_declaration", "constructor_declaration", "event_declaration",
352 "type_declaration", "const_declaration", "enum_member",
353 "statement", "assignment_statement", "compound_assignment_statement",
355 "call_statement", "dim_statement", "redim_statement", "re_dim_clause",
356 "exit_statement", "continue_statement", "return_statement", "goto_statement",
357 "label_statement", "throw_statement", "empty_statement",
358 "try_statement", "catch_block", "finally_block",
360 "case_block", "case_else_block", "else_clause", "elseif_clause",
361 "with_statement", "with_initializer",
362 "using_statement", "sync_lock_statement",
363 "expression", "binary_expression", "unary_expression", "ternary_expression",
365 "parenthesized_expression", "lambda_expression", "new_expression",
366 "type", "generic_type", "array_type", "primitive_type",
368 "type_parameters", "type_parameter", "type_constraint",
369 "type_argument_list", "array_rank_specifier",
370 "as_clause", "inherits_clause", "implements_clause",
372 "modifier", "modifiers",
374 "add_handler_block", "remove_handler_block", "raise_event_block",
376 "identifier", "attribute_block", "option_statements",
378 "relational_operator", "lambda_parameter",
379 ];
380 validate_unused_kinds_audit(&VB, documented_unused)
381 .expect("Visual Basic unused node kinds audit failed");
382 }
383}