1use crate::external_packages::ResolvedPackage;
4use crate::{
5 Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism,
6 simple_function_symbol,
7};
8use std::path::{Path, PathBuf};
9use tree_sitter::Node;
10
11pub struct Glsl;
13
14impl Language for Glsl {
15 fn name(&self) -> &'static str {
16 "GLSL"
17 }
18 fn extensions(&self) -> &'static [&'static str] {
19 &["glsl", "vert", "frag", "geom", "comp", "tesc", "tese"]
20 }
21 fn grammar_name(&self) -> &'static str {
22 "glsl"
23 }
24
25 fn has_symbols(&self) -> bool {
26 true
27 }
28
29 fn container_kinds(&self) -> &'static [&'static str] {
30 &["struct_specifier"]
31 }
32
33 fn function_kinds(&self) -> &'static [&'static str] {
34 &["function_definition"]
35 }
36
37 fn type_kinds(&self) -> &'static [&'static str] {
38 &["struct_specifier"]
39 }
40
41 fn import_kinds(&self) -> &'static [&'static str] {
42 &[]
43 }
44
45 fn public_symbol_kinds(&self) -> &'static [&'static str] {
46 &["function_definition", "struct_specifier"]
47 }
48
49 fn visibility_mechanism(&self) -> VisibilityMechanism {
50 VisibilityMechanism::AllPublic
51 }
52
53 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
54 let name = match self.node_name(node, content) {
55 Some(n) => n.to_string(),
56 None => return Vec::new(),
57 };
58
59 let kind = match node.kind() {
60 "function_definition" => SymbolKind::Function,
61 "struct_specifier" => SymbolKind::Struct,
62 _ => return Vec::new(),
63 };
64
65 vec![Export {
66 name,
67 kind,
68 line: node.start_position().row + 1,
69 }]
70 }
71
72 fn scope_creating_kinds(&self) -> &'static [&'static str] {
73 &["function_definition", "compound_statement"]
74 }
75
76 fn control_flow_kinds(&self) -> &'static [&'static str] {
77 &[
78 "if_statement",
79 "for_statement",
80 "while_statement",
81 "switch_statement",
82 ]
83 }
84
85 fn complexity_nodes(&self) -> &'static [&'static str] {
86 &[
87 "if_statement",
88 "for_statement",
89 "while_statement",
90 "switch_statement",
91 "case_statement",
92 "conditional_expression",
93 ]
94 }
95
96 fn nesting_nodes(&self) -> &'static [&'static str] {
97 &[
98 "function_definition",
99 "if_statement",
100 "for_statement",
101 "while_statement",
102 ]
103 }
104
105 fn signature_suffix(&self) -> &'static str {
106 ""
107 }
108
109 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
110 let name = self.node_name(node, content)?;
111 Some(simple_function_symbol(
112 node,
113 content,
114 name,
115 self.extract_docstring(node, content),
116 ))
117 }
118
119 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
120 if node.kind() != "struct_specifier" {
121 return None;
122 }
123
124 let name = self.node_name(node, content)?;
125 let text = &content[node.byte_range()];
126 let first_line = text.lines().next().unwrap_or(text);
127
128 Some(Symbol {
129 name: name.to_string(),
130 kind: SymbolKind::Struct,
131 signature: first_line.trim().to_string(),
132 docstring: self.extract_docstring(node, content),
133 attributes: Vec::new(),
134 start_line: node.start_position().row + 1,
135 end_line: node.end_position().row + 1,
136 visibility: Visibility::Public,
137 children: Vec::new(),
138 is_interface_impl: false,
139 implements: Vec::new(),
140 })
141 }
142
143 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
144 self.extract_container(node, content)
145 }
146
147 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
148 let mut prev = node.prev_sibling();
150 while let Some(sibling) = prev {
151 let text = &content[sibling.byte_range()];
152 if sibling.kind() == "comment" {
153 if text.starts_with("/*") {
154 let inner = text.trim_start_matches("/*").trim_end_matches("*/").trim();
155 if !inner.is_empty() {
156 return Some(inner.lines().next().unwrap_or(inner).to_string());
157 }
158 } else if text.starts_with("//") {
159 let line = text.strip_prefix("//").unwrap_or(text).trim();
160 return Some(line.to_string());
161 }
162 }
163 prev = sibling.prev_sibling();
164 }
165 None
166 }
167
168 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
169 Vec::new()
170 }
171
172 fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
173 Vec::new()
174 }
175
176 fn format_import(&self, _import: &Import, _names: Option<&[&str]>) -> String {
177 String::new()
179 }
180
181 fn is_public(&self, _node: &Node, _content: &str) -> bool {
182 true
183 }
184 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
185 Visibility::Public
186 }
187
188 fn is_test_symbol(&self, _symbol: &crate::Symbol) -> bool {
189 false
190 }
191
192 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
193 None
194 }
195
196 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
197 node.child_by_field_name("body")
198 }
199
200 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
201 false
202 }
203
204 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
205 node.child_by_field_name("declarator")
206 .and_then(|d| d.child_by_field_name("declarator"))
207 .map(|n| &content[n.byte_range()])
208 .or_else(|| {
209 node.child_by_field_name("name")
210 .map(|n| &content[n.byte_range()])
211 })
212 }
213
214 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
215 let ext = path.extension()?.to_str()?;
216 if !["glsl", "vert", "frag", "geom", "comp", "tesc", "tese"].contains(&ext) {
217 return None;
218 }
219 let stem = path.file_stem()?.to_str()?;
220 Some(stem.to_string())
221 }
222
223 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
224 vec![
225 format!("{}.glsl", module),
226 format!("{}.vert", module),
227 format!("{}.frag", module),
228 ]
229 }
230
231 fn lang_key(&self) -> &'static str {
232 "glsl"
233 }
234
235 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
236 false
237 }
238 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
239 None
240 }
241 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
242 None
243 }
244 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
245 None
246 }
247 fn get_version(&self, _: &Path) -> Option<String> {
248 None
249 }
250 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
251 None
252 }
253 fn indexable_extensions(&self) -> &'static [&'static str] {
254 &["glsl", "vert", "frag", "geom", "comp", "tesc", "tese"]
255 }
256 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
257 Vec::new()
258 }
259
260 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
261 use crate::traits::{has_extension, skip_dotfiles};
262 if skip_dotfiles(name) {
263 return true;
264 }
265 !is_dir && !has_extension(name, self.indexable_extensions())
266 }
267
268 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
269 Vec::new()
270 }
271
272 fn package_module_name(&self, entry_name: &str) -> String {
273 for ext in &[
274 ".glsl", ".vert", ".frag", ".geom", ".comp", ".tesc", ".tese",
275 ] {
276 if let Some(name) = entry_name.strip_suffix(ext) {
277 return name.to_string();
278 }
279 }
280 entry_name.to_string()
281 }
282
283 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
284 if path.is_file() {
285 Some(path.to_path_buf())
286 } else {
287 None
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::validate_unused_kinds_audit;
296
297 #[test]
298 fn unused_node_kinds_audit() {
299 #[rustfmt::skip]
300 let documented_unused: &[&str] = &[
301 "abstract_function_declarator", "alignas_qualifier", "alignof_expression",
302 "assignment_expression", "attribute_declaration", "attribute_specifier",
303 "attributed_statement", "binary_expression", "bitfield_clause", "break_statement",
304 "call_expression", "cast_expression", "comma_expression", "compound_literal_expression",
305 "continue_statement", "declaration", "declaration_list", "do_statement",
306 "else_clause", "enum_specifier", "enumerator", "enumerator_list",
307 "expression_statement", "extension_expression", "extension_storage_class",
308 "field_declaration", "field_declaration_list", "field_expression", "field_identifier",
309 "function_declarator", "generic_expression", "gnu_asm_expression", "gnu_asm_qualifier",
310 "goto_statement", "identifier", "labeled_statement", "layout_qualifiers",
311 "layout_specification", "linkage_specification", "macro_type_specifier",
312 "ms_based_modifier", "ms_call_modifier", "ms_declspec_modifier",
313 "ms_pointer_modifier", "ms_restrict_modifier", "ms_signed_ptr_modifier",
314 "ms_unaligned_ptr_modifier", "ms_unsigned_ptr_modifier", "offsetof_expression",
315 "parameter_declaration", "parenthesized_expression", "pointer_expression",
316 "preproc_elif", "preproc_elifdef", "preproc_else", "preproc_function_def",
317 "preproc_if", "preproc_ifdef", "primitive_type", "qualifier", "return_statement",
318 "seh_except_clause", "seh_finally_clause", "seh_leave_statement", "seh_try_statement",
319 "sizeof_expression", "sized_type_specifier", "statement_identifier",
320 "storage_class_specifier", "subscript_expression", "type_definition",
321 "type_descriptor", "type_identifier", "type_qualifier", "unary_expression",
322 "union_specifier", "update_expression",
323 ];
324 validate_unused_kinds_audit(&Glsl, documented_unused)
325 .expect("GLSL unused node kinds audit failed");
326 }
327}