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 Wit;
10
11impl Language for Wit {
12 fn name(&self) -> &'static str {
13 "WIT"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["wit"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "wit"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &["interface_item", "world_item"]
28 }
29
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &["func_item"]
32 }
33
34 fn type_kinds(&self) -> &'static [&'static str] {
35 &["type_item", "record_item"]
36 }
37
38 fn import_kinds(&self) -> &'static [&'static str] {
39 &["use_item"]
40 }
41
42 fn public_symbol_kinds(&self) -> &'static [&'static str] {
43 &["interface_item", "world_item", "func_item"]
44 }
45
46 fn visibility_mechanism(&self) -> VisibilityMechanism {
47 VisibilityMechanism::AllPublic
48 }
49
50 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
51 let kind = match node.kind() {
52 "interface_item" => SymbolKind::Interface,
53 "world_item" => SymbolKind::Module,
54 "func_item" => SymbolKind::Function,
55 _ => return Vec::new(),
56 };
57
58 if let Some(name) = self.node_name(node, content) {
59 return vec![Export {
60 name: name.to_string(),
61 kind,
62 line: node.start_position().row + 1,
63 }];
64 }
65 Vec::new()
66 }
67
68 fn scope_creating_kinds(&self) -> &'static [&'static str] {
69 &["interface_item", "world_item"]
70 }
71
72 fn control_flow_kinds(&self) -> &'static [&'static str] {
73 &[]
74 }
75 fn complexity_nodes(&self) -> &'static [&'static str] {
76 &[]
77 }
78 fn nesting_nodes(&self) -> &'static [&'static str] {
79 &["interface_item", "world_item"]
80 }
81
82 fn signature_suffix(&self) -> &'static str {
83 ""
84 }
85
86 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
87 if node.kind() != "func_item" {
88 return None;
89 }
90
91 let name = self.node_name(node, content)?;
92 let text = &content[node.byte_range()];
93
94 Some(Symbol {
95 name: name.to_string(),
96 kind: SymbolKind::Function,
97 signature: text.trim().to_string(),
98 docstring: None,
99 attributes: Vec::new(),
100 start_line: node.start_position().row + 1,
101 end_line: node.end_position().row + 1,
102 visibility: Visibility::Public,
103 children: Vec::new(),
104 is_interface_impl: false,
105 implements: Vec::new(),
106 })
107 }
108
109 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
110 let kind = match node.kind() {
111 "interface_item" => SymbolKind::Interface,
112 "world_item" => SymbolKind::Module,
113 _ => return None,
114 };
115
116 let name = self.node_name(node, content)?;
117 let text = &content[node.byte_range()];
118 let first_line = text.lines().next().unwrap_or(text);
119
120 Some(Symbol {
121 name: name.to_string(),
122 kind,
123 signature: first_line.trim().to_string(),
124 docstring: None,
125 attributes: Vec::new(),
126 start_line: node.start_position().row + 1,
127 end_line: node.end_position().row + 1,
128 visibility: Visibility::Public,
129 children: Vec::new(),
130 is_interface_impl: false,
131 implements: Vec::new(),
132 })
133 }
134
135 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
136 let kind = match node.kind() {
137 "type_item" | "record_item" => SymbolKind::Type,
138 _ => return None,
139 };
140
141 let name = self.node_name(node, content)?;
142 let text = &content[node.byte_range()];
143
144 Some(Symbol {
145 name: name.to_string(),
146 kind,
147 signature: text.trim().to_string(),
148 docstring: None,
149 attributes: Vec::new(),
150 start_line: node.start_position().row + 1,
151 end_line: node.end_position().row + 1,
152 visibility: Visibility::Public,
153 children: Vec::new(),
154 is_interface_impl: false,
155 implements: Vec::new(),
156 })
157 }
158
159 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
160 None
161 }
162
163 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
164 Vec::new()
165 }
166
167 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
168 if node.kind() != "use_item" {
169 return Vec::new();
170 }
171
172 let text = &content[node.byte_range()];
173 vec![Import {
174 module: text.trim().to_string(),
175 names: Vec::new(),
176 alias: None,
177 is_wildcard: false,
178 is_relative: false,
179 line: node.start_position().row + 1,
180 }]
181 }
182
183 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
184 let names_to_use: Vec<&str> = names
186 .map(|n| n.to_vec())
187 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
188 if names_to_use.is_empty() {
189 format!("use {}", import.module)
190 } else {
191 format!("use {}.{{{}}}", import.module, names_to_use.join(", "))
192 }
193 }
194
195 fn is_public(&self, _node: &Node, _content: &str) -> bool {
196 true
197 }
198 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
199 Visibility::Public
200 }
201
202 fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
203 false
204 }
205
206 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
207 None
208 }
209
210 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
211 node.child_by_field_name("body")
212 }
213
214 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
215 false
216 }
217
218 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
219 node.child_by_field_name("name")
220 .map(|n| &content[n.byte_range()])
221 }
222
223 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
224 let ext = path.extension()?.to_str()?;
225 if ext != "wit" {
226 return None;
227 }
228 let stem = path.file_stem()?.to_str()?;
229 Some(stem.to_string())
230 }
231
232 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
233 vec![format!("{}.wit", module)]
234 }
235
236 fn lang_key(&self) -> &'static str {
237 "wit"
238 }
239
240 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
241 false
242 }
243 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
244 None
245 }
246 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
247 None
248 }
249 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
250 None
251 }
252 fn get_version(&self, _: &Path) -> Option<String> {
253 None
254 }
255 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
256 None
257 }
258 fn indexable_extensions(&self) -> &'static [&'static str] {
259 &["wit"]
260 }
261 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
262 Vec::new()
263 }
264
265 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
266 use crate::traits::{has_extension, skip_dotfiles};
267 if skip_dotfiles(name) {
268 return true;
269 }
270 !is_dir && !has_extension(name, self.indexable_extensions())
271 }
272
273 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
274 Vec::new()
275 }
276
277 fn package_module_name(&self, entry_name: &str) -> String {
278 entry_name
279 .strip_suffix(".wit")
280 .unwrap_or(entry_name)
281 .to_string()
282 }
283
284 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
285 if path.is_file() {
286 Some(path.to_path_buf())
287 } else {
288 None
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::validate_unused_kinds_audit;
297
298 #[test]
299 fn unused_node_kinds_audit() {
300 #[rustfmt::skip]
301 let documented_unused: &[&str] = &[
302 "export_item", "import_item",
304 "named_type", "extern_type", "func_type",
306 "enum_items", "enum_case", "variant_items", "variant_case",
308 "resource_method",
310 "definitions", "body", "block_comment",
312 ];
313 validate_unused_kinds_audit(&Wit, documented_unused)
314 .expect("WIT unused node kinds audit failed");
315 }
316}