qlty_analysis/lang/
java.rs

1use crate::code::node_source;
2use crate::code::File;
3use crate::lang::Language;
4use tree_sitter::Node;
5
6const CLASS_QUERY: &str = r#"
7[
8    (class_declaration
9        name: (identifier) @name)
10    (interface_declaration
11        name: (identifier) @name)
12] @definition.class
13"#;
14
15const FUNCTION_DECLARATION_QUERY: &str = r#"
16[
17    (method_declaration
18        name: (identifier) @name
19        parameters: (_) @parameters)
20    (constructor_declaration
21        name: (identifier) @name
22        parameters: (_) @parameters)
23] @definition.function
24"#;
25
26const FIELD_QUERY: &str = r#"
27(field_declaration
28    declarator: (variable_declarator
29        name: (identifier) @name)) @field
30"#;
31
32pub struct Java {
33    pub class_query: tree_sitter::Query,
34    pub function_declaration_query: tree_sitter::Query,
35    pub field_query: tree_sitter::Query,
36}
37
38impl Java {
39    pub const SELF: &'static str = "this";
40    pub const BINARY: &'static str = "binary_expression";
41    pub const BLOCK: &'static str = "block";
42    pub const BREAK: &'static str = "break_statement";
43    pub const CATCH: &'static str = "catch_clause";
44    pub const CASE: &'static str = "switch_block_statement_group";
45    pub const LINE_COMMENT: &'static str = "line_comment";
46    pub const BLOCK_COMMENT: &'static str = "block_comment";
47    pub const CONTINUE: &'static str = "continue_statement";
48    pub const DO: &'static str = "do_statement";
49    pub const FIELD_ACCESS: &'static str = "field_access";
50    pub const FIELD_DECLARATION: &'static str = "field_declaration";
51    pub const FOR_IN: &'static str = "enhanced_for_statement";
52    pub const FOR: &'static str = "for_statement";
53    pub const METHOD_DECLARATION: &'static str = "method_declaration";
54    pub const METHOD_INVOCATION: &'static str = "method_invocation";
55    pub const IDENTIFIER: &'static str = "identifier";
56    pub const IF: &'static str = "if_statement";
57    pub const LAMBDA: &'static str = "lambda_expression";
58    pub const PROGRAM: &'static str = "program";
59    pub const RETURN: &'static str = "return_statement";
60    pub const STRING: &'static str = "string_literal";
61    pub const SWITCH: &'static str = "switch_expression";
62    pub const TEMPLATE_STRING: &'static str = "template_expression";
63    pub const TERNARY: &'static str = "ternary_expression";
64    pub const TRY: &'static str = "try_statement";
65    pub const TRY_WITH_RESOURCES: &'static str = "try_with_resources_statement";
66    pub const WHILE: &'static str = "while_statement";
67
68    pub const AND: &'static str = "&&";
69    pub const OR: &'static str = "||";
70}
71
72impl Default for Java {
73    fn default() -> Self {
74        let language = tree_sitter_java::language();
75
76        Self {
77            class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
78            field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
79            function_declaration_query: tree_sitter::Query::new(
80                &language,
81                FUNCTION_DECLARATION_QUERY,
82            )
83            .unwrap(),
84        }
85    }
86}
87
88impl Language for Java {
89    fn name(&self) -> &str {
90        "java"
91    }
92
93    fn self_keyword(&self) -> Option<&str> {
94        Some(Self::SELF)
95    }
96
97    fn class_query(&self) -> &tree_sitter::Query {
98        &self.class_query
99    }
100
101    fn function_declaration_query(&self) -> &tree_sitter::Query {
102        &self.function_declaration_query
103    }
104
105    fn field_query(&self) -> &tree_sitter::Query {
106        &self.field_query
107    }
108
109    fn if_nodes(&self) -> Vec<&str> {
110        vec![Self::IF]
111    }
112
113    fn block_nodes(&self) -> Vec<&str> {
114        vec![Self::BLOCK]
115    }
116
117    fn conditional_assignment_nodes(&self) -> Vec<&str> {
118        vec![]
119    }
120
121    fn invisible_container_nodes(&self) -> Vec<&str> {
122        vec![Self::PROGRAM]
123    }
124
125    fn switch_nodes(&self) -> Vec<&str> {
126        vec![Self::SWITCH]
127    }
128
129    fn case_nodes(&self) -> Vec<&str> {
130        vec![Self::CASE]
131    }
132
133    fn ternary_nodes(&self) -> Vec<&str> {
134        vec![Self::TERNARY]
135    }
136
137    fn loop_nodes(&self) -> Vec<&str> {
138        vec![Self::FOR, Self::FOR_IN, Self::WHILE, Self::DO]
139    }
140
141    fn except_nodes(&self) -> Vec<&str> {
142        vec![Self::CATCH]
143    }
144
145    fn try_expression_nodes(&self) -> Vec<&str> {
146        vec![Self::TRY, Self::TRY_WITH_RESOURCES]
147    }
148
149    fn jump_nodes(&self) -> Vec<&str> {
150        vec![Self::BREAK, Self::CONTINUE]
151    }
152
153    fn return_nodes(&self) -> Vec<&str> {
154        vec![Self::RETURN]
155    }
156
157    fn binary_nodes(&self) -> Vec<&str> {
158        vec![Self::BINARY]
159    }
160
161    fn boolean_operator_nodes(&self) -> Vec<&str> {
162        vec![Self::AND, Self::OR]
163    }
164
165    fn field_nodes(&self) -> Vec<&str> {
166        vec![Self::FIELD_DECLARATION]
167    }
168
169    fn call_nodes(&self) -> Vec<&str> {
170        vec![Self::METHOD_INVOCATION]
171    }
172
173    fn function_nodes(&self) -> Vec<&str> {
174        vec![Self::METHOD_DECLARATION]
175    }
176
177    fn closure_nodes(&self) -> Vec<&str> {
178        vec![Self::LAMBDA]
179    }
180
181    fn comment_nodes(&self) -> Vec<&str> {
182        vec![Self::LINE_COMMENT, Self::BLOCK_COMMENT]
183    }
184
185    fn string_nodes(&self) -> Vec<&str> {
186        vec![Self::STRING, Self::TEMPLATE_STRING]
187    }
188
189    fn is_jump_label(&self, node: &Node) -> bool {
190        node.kind() == Self::IDENTIFIER
191    }
192
193    fn has_labeled_jumps(&self) -> bool {
194        true
195    }
196
197    fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
198        match node.kind() {
199            Self::METHOD_INVOCATION => {
200                let (receiver, object) = self.field_identifiers(source_file, node);
201
202                (Some(receiver), object)
203            }
204            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
205        }
206    }
207
208    fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
209        let object_node = node.child_by_field_name("object");
210        let property_node = node
211            .child_by_field_name("name")
212            .or_else(|| node.child_by_field_name("field"));
213
214        match (&object_node, &property_node) {
215            (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
216                let object_source =
217                    get_node_source_or_default(obj.child_by_field_name("field"), source_file);
218                let property_source = get_node_source_or_default(Some(*prop), source_file);
219                (object_source, property_source)
220            }
221            (Some(obj), Some(prop)) => (
222                get_node_source_or_default(Some(*obj), source_file),
223                get_node_source_or_default(Some(*prop), source_file),
224            ),
225            (None, Some(prop)) => (
226                Self::SELF.to_owned(),
227                get_node_source_or_default(Some(*prop), source_file),
228            ),
229            _ => ("<UNKNOWN>".to_string(), "<UNKNOWN>".to_string()),
230        }
231    }
232
233    fn tree_sitter_language(&self) -> tree_sitter::Language {
234        tree_sitter_java::language()
235    }
236}
237
238fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String {
239    node.as_ref()
240        .map(|n| node_source(n, source_file))
241        .unwrap_or("<UNKNOWN>".to_string())
242}
243
244#[cfg(test)]
245mod test {
246    use super::*;
247    use std::collections::HashSet;
248    use tree_sitter::Tree;
249
250    #[test]
251    fn mutually_exclusive() {
252        let lang = Java::default();
253        let mut kinds: Vec<&str> = vec![];
254
255        kinds.extend(lang.if_nodes());
256        kinds.extend(lang.conditional_assignment_nodes());
257        kinds.extend(lang.switch_nodes());
258        kinds.extend(lang.case_nodes());
259        kinds.extend(lang.ternary_nodes());
260        kinds.extend(lang.loop_nodes());
261        kinds.extend(lang.except_nodes());
262        kinds.extend(lang.try_expression_nodes());
263        kinds.extend(lang.jump_nodes());
264        kinds.extend(lang.return_nodes());
265        kinds.extend(lang.binary_nodes());
266        kinds.extend(lang.field_nodes());
267        kinds.extend(lang.call_nodes());
268        kinds.extend(lang.function_nodes());
269        kinds.extend(lang.closure_nodes());
270        kinds.extend(lang.comment_nodes());
271        kinds.extend(lang.string_nodes());
272        kinds.extend(lang.boolean_operator_nodes());
273        kinds.extend(lang.block_nodes());
274
275        let unique: HashSet<_> = kinds.iter().cloned().collect();
276        assert_eq!(unique.len(), kinds.len());
277    }
278
279    #[test]
280    fn field_identifier_read() {
281        let source_file = File::from_string("java", "this.foo;");
282        let tree = source_file.parse();
283        let root_node = tree.root_node();
284        let expression = root_node.named_child(0).unwrap();
285        let field = expression.named_child(0).unwrap();
286        let language = Java::default();
287
288        assert_eq!(
289            language.field_identifiers(&source_file, &field),
290            ("this".to_string(), "foo".to_string())
291        );
292    }
293
294    #[test]
295    fn field_identifier_write() {
296        let source_file = File::from_string("java", "this.foo = 1;");
297        let tree = source_file.parse();
298        let root_node = tree.root_node();
299        let expression = root_node.named_child(0).unwrap();
300        let assignment = expression.named_child(0).unwrap();
301        let field = assignment.named_child(0).unwrap();
302        let language = Java::default();
303
304        assert_eq!(
305            language.field_identifiers(&source_file, &field),
306            ("this".to_string(), "foo".to_string())
307        );
308    }
309
310    #[test]
311    fn field_identifier_collaborator() {
312        let source_file = File::from_string("java", "other.foo;");
313        let tree = source_file.parse();
314        let root_node = tree.root_node();
315        let expression = root_node.named_child(0).unwrap();
316        let field = expression.named_child(0).unwrap();
317        let language = Java::default();
318
319        assert_eq!(
320            language.field_identifiers(&source_file, &field),
321            ("other".to_string(), "foo".to_string())
322        );
323    }
324
325    #[test]
326    fn call_identifier() {
327        let source_file = File::from_string("java", "foo()");
328        let tree = source_file.parse();
329        let call = call_node(&tree);
330        let language = Java::default();
331
332        assert_eq!(
333            language.call_identifiers(&source_file, &call),
334            (Some("this".to_string()), "foo".to_string())
335        );
336    }
337
338    #[test]
339    fn call_member() {
340        let source_file = File::from_string("java", "foo.bar()");
341        let tree = source_file.parse();
342        let call = call_node(&tree);
343        let language = Java::default();
344
345        assert_eq!(
346            language.call_identifiers(&source_file, &call),
347            (Some("foo".into()), "bar".into())
348        );
349    }
350
351    #[test]
352    fn call_with_custom_context() {
353        let source_file = File::from_string("java", "foo.call(context);");
354        let tree = source_file.parse();
355        let call = call_node(&tree);
356        let language = Java::default();
357
358        assert_eq!(
359            language.call_identifiers(&source_file, &call),
360            (Some("foo".to_string()), "call".to_string())
361        );
362    }
363
364    #[test]
365    fn method_call_on_nested_object() {
366        let source_file = File::from_string("java", "obj.nestedObj.foo();");
367        let tree = source_file.parse();
368        let call = call_node(&tree);
369        let language = Java::default();
370
371        assert_eq!(
372            language.call_identifiers(&source_file, &call),
373            (Some("nestedObj".to_string()), "foo".to_string())
374        );
375    }
376
377    #[test]
378    fn nested_field_access() {
379        let source_file = File::from_string("java", "obj.nestedObj.oneMoreObj;");
380        let tree = source_file.parse();
381        let root_node = tree.root_node();
382        let expression = root_node.named_child(0).unwrap();
383        let field = expression.named_child(0).unwrap();
384        let language = Java::default();
385
386        assert_eq!(
387            language.field_identifiers(&source_file, &field),
388            ("nestedObj".to_string(), "oneMoreObj".to_string())
389        );
390    }
391
392    #[test]
393    fn call_function_property() {
394        let source_file = File::from_string("java", "foo.bar()");
395        let tree = source_file.parse();
396        let call = call_node(&tree);
397        let language = Java::default();
398
399        assert_eq!(
400            language.call_identifiers(&source_file, &call),
401            (Some("foo".to_string()), "bar".to_string())
402        );
403    }
404
405    fn call_node(tree: &Tree) -> Node {
406        let root_node = tree.root_node();
407        let expression = root_node.named_child(0).unwrap();
408        expression.named_child(0).unwrap()
409    }
410}