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 Scala;
10
11impl Language for Scala {
12 fn name(&self) -> &'static str {
13 "Scala"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["scala", "sc"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "scala"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &["class_definition", "object_definition", "trait_definition"]
28 }
29 fn function_kinds(&self) -> &'static [&'static str] {
30 &["function_definition"]
31 }
32 fn type_kinds(&self) -> &'static [&'static str] {
33 &["class_definition", "trait_definition"]
34 }
35 fn import_kinds(&self) -> &'static [&'static str] {
36 &["import_declaration"]
37 }
38
39 fn public_symbol_kinds(&self) -> &'static [&'static str] {
40 &[
41 "class_definition",
42 "object_definition",
43 "trait_definition",
44 "function_definition",
45 ]
46 }
47
48 fn visibility_mechanism(&self) -> VisibilityMechanism {
49 VisibilityMechanism::AccessModifier
50 }
51
52 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
53 let name = match self.node_name(node, content) {
56 Some(n) => n.to_string(),
57 None => return Vec::new(),
58 };
59
60 let kind = match node.kind() {
61 "class_definition" => SymbolKind::Class,
62 "object_definition" => SymbolKind::Module,
63 "trait_definition" => SymbolKind::Trait,
64 "function_definition" => SymbolKind::Function,
65 _ => return Vec::new(),
66 };
67
68 vec![Export {
69 name,
70 kind,
71 line: node.start_position().row + 1,
72 }]
73 }
74
75 fn scope_creating_kinds(&self) -> &'static [&'static str] {
76 &["for_expression", "block", "lambda_expression"]
77 }
78
79 fn control_flow_kinds(&self) -> &'static [&'static str] {
80 &[
81 "if_expression",
82 "match_expression",
83 "for_expression",
84 "while_expression",
85 "do_while_expression",
86 "try_expression",
87 "return_expression",
88 "throw_expression",
89 ]
90 }
91
92 fn complexity_nodes(&self) -> &'static [&'static str] {
93 &[
94 "if_expression",
95 "match_expression",
96 "case_clause",
97 "for_expression",
98 "while_expression",
99 "do_while_expression",
100 "try_expression",
101 "catch_clause",
102 "infix_expression", ]
104 }
105
106 fn nesting_nodes(&self) -> &'static [&'static str] {
107 &[
108 "if_expression",
109 "match_expression",
110 "for_expression",
111 "while_expression",
112 "do_while_expression",
113 "try_expression",
114 "function_definition",
115 "class_definition",
116 "object_definition",
117 "trait_definition",
118 "block",
119 ]
120 }
121
122 fn signature_suffix(&self) -> &'static str {
123 " {}"
124 }
125
126 fn extract_function(&self, node: &Node, content: &str, in_container: bool) -> Option<Symbol> {
127 let name = self.node_name(node, content)?;
128 let params = node
129 .child_by_field_name("parameters")
130 .map(|p| content[p.byte_range()].to_string())
131 .unwrap_or_else(|| "()".to_string());
132 let ret = node
133 .child_by_field_name("return_type")
134 .map(|r| format!(": {}", &content[r.byte_range()]))
135 .unwrap_or_default();
136
137 Some(Symbol {
138 name: name.to_string(),
139 kind: if in_container {
140 SymbolKind::Method
141 } else {
142 SymbolKind::Function
143 },
144 signature: format!("def {}{}{}", name, params, ret),
145 docstring: None,
146 attributes: Vec::new(),
147 start_line: node.start_position().row + 1,
148 end_line: node.end_position().row + 1,
149 visibility: Visibility::Public,
150 children: Vec::new(),
151 is_interface_impl: false,
152 implements: Vec::new(),
153 })
154 }
155
156 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
157 let name = self.node_name(node, content)?;
158 let (kind, keyword) = match node.kind() {
159 "object_definition" => (SymbolKind::Module, "object"),
160 "trait_definition" => (SymbolKind::Trait, "trait"),
161 _ => (SymbolKind::Class, "class"),
162 };
163
164 Some(Symbol {
165 name: name.to_string(),
166 kind,
167 signature: format!("{} {}", keyword, name),
168 docstring: None,
169 attributes: Vec::new(),
170 start_line: node.start_position().row + 1,
171 end_line: node.end_position().row + 1,
172 visibility: Visibility::Public,
173 children: Vec::new(),
174 is_interface_impl: false,
175 implements: Vec::new(),
176 })
177 }
178
179 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
180 self.extract_container(node, content)
181 }
182
183 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
184 None
185 }
186
187 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
188 Vec::new()
189 }
190 fn extract_imports(&self, _node: &Node, _content: &str) -> Vec<Import> {
191 Vec::new()
192 }
193
194 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
195 let names_to_use: Vec<&str> = names
197 .map(|n| n.to_vec())
198 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
199 if import.is_wildcard {
200 format!("import {}._", import.module)
201 } else if names_to_use.is_empty() {
202 format!("import {}", import.module)
203 } else if names_to_use.len() == 1 {
204 format!("import {}.{}", import.module, names_to_use[0])
205 } else {
206 format!("import {}.{{{}}}", import.module, names_to_use.join(", "))
207 }
208 }
209
210 fn is_public(&self, _node: &Node, _content: &str) -> bool {
211 true
212 }
213 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
214 Visibility::Public
215 }
216
217 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
218 {
219 let has_test_attr = symbol.attributes.iter().any(|a| a.contains("@Test"));
220 if has_test_attr {
221 return true;
222 }
223 match symbol.kind {
224 crate::SymbolKind::Class => {
225 symbol.name.starts_with("Test") || symbol.name.ends_with("Test")
226 }
227 _ => false,
228 }
229 }
230 }
231
232 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
233 None
234 }
235
236 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
237 node.child_by_field_name("body")
238 }
239 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
240 false
241 }
242
243 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
244 let name_node = node.child_by_field_name("name")?;
245 Some(&content[name_node.byte_range()])
246 }
247
248 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
249 let ext = path.extension()?.to_str()?;
250 if !["scala", "sc"].contains(&ext) {
251 return None;
252 }
253 Some(path.to_string_lossy().to_string())
254 }
255 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
256 vec![format!("{}.scala", module)]
257 }
258
259 fn lang_key(&self) -> &'static str {
260 "scala"
261 }
262 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
263 None
264 }
265 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
266 None
267 }
268 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
269 false
270 }
271 fn get_version(&self, _: &Path) -> Option<String> {
272 None
273 }
274 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
275 None
276 }
277 fn indexable_extensions(&self) -> &'static [&'static str] {
278 &["scala", "sc"]
279 }
280 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
281 None
282 }
283 fn package_module_name(&self, name: &str) -> String {
284 name.strip_suffix(".scala").unwrap_or(name).to_string()
285 }
286 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
287 Vec::new()
288 }
289 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
290 Vec::new()
291 }
292 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
293 if path.is_file() {
294 Some(path.to_path_buf())
295 } else {
296 None
297 }
298 }
299
300 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
301 use crate::traits::{has_extension, skip_dotfiles};
302 if skip_dotfiles(name) {
303 return true;
304 }
305 !is_dir && !has_extension(name, self.indexable_extensions())
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::validate_unused_kinds_audit;
313
314 #[test]
315 fn unused_node_kinds_audit() {
316 #[rustfmt::skip]
317 let documented_unused: &[&str] = &[
318 "access_modifier", "access_qualifier", "arrow_renamed_identifier",
320 "as_renamed_identifier", "block_comment", "case_block", "case_class_pattern",
321 "class_parameter", "class_parameters", "derives_clause", "enum_body",
322 "enum_case_definitions", "enum_definition", "enumerator", "enumerators",
323 "export_declaration", "extends_clause", "extension_definition", "field_expression",
324 "full_enum_case", "identifier", "identifiers", "indented_block", "indented_cases",
325 "infix_modifier", "inline_modifier", "instance_expression", "into_modifier",
326 "macro_body", "modifiers", "name_and_type", "opaque_modifier", "open_modifier",
327 "operator_identifier", "package_clause", "package_identifier", "self_type",
328 "simple_enum_case", "template_body", "tracked_modifier", "transparent_modifier",
329 "val_declaration", "val_definition", "var_declaration", "var_definition",
330 "with_template_body",
331 "finally_clause", "type_case_clause",
333 "ascription_expression", "assignment_expression", "call_expression",
335 "generic_function", "interpolated_string_expression", "parenthesized_expression",
336 "postfix_expression", "prefix_expression", "quote_expression", "splice_expression",
337 "tuple_expression",
338 "annotated_type", "applied_constructor_type", "compound_type",
340 "contravariant_type_parameter", "covariant_type_parameter", "function_declaration",
341 "function_type", "generic_type", "given_definition", "infix_type", "lazy_parameter_type",
342 "literal_type", "match_type", "named_tuple_type", "parameter_types",
343 "projected_type", "repeated_parameter_type", "singleton_type", "stable_identifier",
344 "stable_type_identifier", "structural_type", "tuple_type", "type_arguments",
345 "type_definition", "type_identifier", "type_lambda", "type_parameters", "typed_pattern",
346 ];
347
348 validate_unused_kinds_audit(&Scala, documented_unused)
349 .expect("Scala unused node kinds audit failed");
350 }
351}