qlty_analysis/lang/
kotlin.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(class_declaration
8    (type_identifier) @name) @definition.class
9"#;
10
11const FUNCTION_DECLARATION_QUERY: &str = r#"
12(function_declaration
13	(simple_identifier) @name
14    (function_value_parameters) @parameters) @definition.function
15"#;
16
17const FIELD_QUERY: &str = r#"
18[
19    (class_parameter
20        (simple_identifier) @name) @field
21    (class_body
22        (property_declaration
23            (variable_declaration) @name) @field)
24    (class_body
25        (property_declaration
26            (multi_variable_declaration
27            (variable_declaration
28                (simple_identifier) @name))) @field)
29    (
30        (this_expression)
31        (navigation_suffix
32            (simple_identifier) @name)) @field
33]"#;
34
35pub struct Kotlin {
36    pub class_query: tree_sitter::Query,
37    pub function_declaration_query: tree_sitter::Query,
38    pub field_query: tree_sitter::Query,
39}
40
41impl Kotlin {
42    pub const NAME: &'static str = "kotlin";
43    pub const THIS: &'static str = "this";
44    pub const THIS_EXPRESSION: &'static str = "this_expression";
45    pub const IF: &'static str = "if_expression";
46    pub const SOURCE_FILE: &'static str = "source_file";
47    pub const WHEN: &'static str = "when_expression";
48    pub const FOR: &'static str = "for_statement";
49    pub const WHILE: &'static str = "while_statement";
50    pub const DO_WHILE: &'static str = "do_while_statement";
51    pub const JUMP: &'static str = "jump_expression";
52    pub const SIMPLE_IDENTIFIER: &'static str = "simple_identifier";
53    pub const NAVIGATION_EXPRESSION: &'static str = "navigation_expression";
54    pub const CONJUNCTION_EXPRESSION: &'static str = "conjunction_expression";
55    pub const DISJUNCTION_EXPRESSION: &'static str = "disjunction_expression";
56    pub const CALL_EXPRESSION: &'static str = "call_expression";
57    pub const WHEN_ENTRY: &'static str = "when_entry";
58    pub const LAMBDA_LITERAL: &'static str = "lambda_literal";
59    pub const LINE_COMMENT: &'static str = "line_comment";
60    pub const MULTILINE_COMMENT: &'static str = "multiline_comment";
61    pub const STRING_LITERAL: &'static str = "string_literal";
62    pub const FUNCTION_DECLARATION: &'static str = "function_declaration";
63    pub const CATCH_BLOCK: &'static str = "catch_block";
64    pub const TRY_EXPRESSION: &'static str = "try_expression";
65    pub const PRIMARY_CONSTRUCTOR: &'static str = "primary_constructor";
66    pub const SECONDARY_CONSTRUCTOR: &'static str = "secondary_constructor";
67    pub const AND: &'static str = "&&";
68    pub const OR: &'static str = "||";
69}
70
71impl Default for Kotlin {
72    fn default() -> Self {
73        let language = tree_sitter_kotlin::language();
74
75        Self {
76            class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
77            function_declaration_query: tree_sitter::Query::new(
78                &language,
79                FUNCTION_DECLARATION_QUERY,
80            )
81            .unwrap(),
82            field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
83        }
84    }
85}
86
87impl Language for Kotlin {
88    fn name(&self) -> &str {
89        Self::NAME
90    }
91
92    fn self_keyword(&self) -> Option<&str> {
93        Some(Self::THIS)
94    }
95
96    fn binary_nodes(&self) -> Vec<&str> {
97        vec![Self::CONJUNCTION_EXPRESSION, Self::DISJUNCTION_EXPRESSION]
98    }
99
100    fn boolean_operator_nodes(&self) -> Vec<&str> {
101        vec![Self::AND, Self::OR]
102    }
103
104    fn call_nodes(&self) -> Vec<&str> {
105        vec![Self::CALL_EXPRESSION]
106    }
107
108    fn case_nodes(&self) -> Vec<&str> {
109        vec![Self::WHEN_ENTRY]
110    }
111
112    fn closure_nodes(&self) -> Vec<&str> {
113        vec![Self::LAMBDA_LITERAL]
114    }
115
116    fn comment_nodes(&self) -> Vec<&str> {
117        vec![Self::LINE_COMMENT, Self::MULTILINE_COMMENT]
118    }
119
120    // no ternary operator
121    fn conditional_assignment_nodes(&self) -> Vec<&str> {
122        vec![]
123    }
124
125    // there isn't a else node/clause according to tree-sitter-kotlin
126    fn else_nodes(&self) -> Vec<&str> {
127        vec![]
128    }
129
130    fn except_nodes(&self) -> Vec<&str> {
131        vec![Self::CATCH_BLOCK]
132    }
133
134    fn field_nodes(&self) -> Vec<&str> {
135        vec![Self::THIS_EXPRESSION]
136    }
137
138    fn function_nodes(&self) -> Vec<&str> {
139        vec![Self::FUNCTION_DECLARATION]
140    }
141
142    fn if_nodes(&self) -> Vec<&str> {
143        vec![Self::IF]
144    }
145
146    fn invisible_container_nodes(&self) -> Vec<&str> {
147        vec![Self::SOURCE_FILE]
148    }
149
150    fn jump_nodes(&self) -> Vec<&str> {
151        vec![Self::JUMP]
152    }
153
154    fn loop_nodes(&self) -> Vec<&str> {
155        vec![Self::FOR, Self::WHILE, Self::DO_WHILE]
156    }
157
158    fn return_nodes(&self) -> Vec<&str> {
159        vec![Self::JUMP]
160    }
161
162    fn string_nodes(&self) -> Vec<&str> {
163        vec![Self::STRING_LITERAL]
164    }
165
166    fn switch_nodes(&self) -> Vec<&str> {
167        vec![Self::WHEN]
168    }
169
170    fn ternary_nodes(&self) -> Vec<&str> {
171        vec![]
172    }
173
174    fn try_expression_nodes(&self) -> Vec<&str> {
175        vec![Self::TRY_EXPRESSION]
176    }
177
178    fn constructor_names(&self) -> Vec<&str> {
179        vec![Self::PRIMARY_CONSTRUCTOR, Self::SECONDARY_CONSTRUCTOR]
180    }
181
182    fn class_query(&self) -> &tree_sitter::Query {
183        &self.class_query
184    }
185
186    // is_decorator_function there seem to be some decorator patterns but no nodes in treesitter
187    // https://reflectoring.io/kotlin-design-patterns/#:~:text=the%20Printer%20interface.-,Decorator%20Pattern,pattern%20using%20interfaces%20and%20classes.
188
189    fn function_declaration_query(&self) -> &tree_sitter::Query {
190        &self.function_declaration_query
191    }
192
193    fn field_query(&self) -> &tree_sitter::Query {
194        &self.field_query
195    }
196
197    fn iterator_method_identifiers(&self) -> Vec<&str> {
198        vec![
199            "all",
200            "any",
201            "count",
202            "dropLastWhile",
203            "dropWhile",
204            "filter",
205            "filterNot",
206            "find",
207            "findLast",
208            "forEach",
209            "map",
210            "none",
211            "partition",
212            "reduce",
213            "reduceRight",
214            "scan",
215        ]
216    }
217
218    fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
219        let function_node = node.child(0).unwrap();
220        let function_kind = function_node.kind();
221
222        match function_kind {
223            Self::SIMPLE_IDENTIFIER => (
224                Some("".to_string()),
225                node_source(&function_node, source_file),
226            ),
227            Self::NAVIGATION_EXPRESSION => {
228                let (receiver, object) = self.field_identifiers(source_file, &function_node);
229
230                (Some(receiver), object)
231            }
232            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
233        }
234    }
235
236    fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
237        if node.kind() == Self::THIS_EXPRESSION {
238            let parent = node.parent().unwrap();
239
240            if parent.kind() == Self::NAVIGATION_EXPRESSION {
241                let navigation_suffix = parent.child(parent.child_count() - 1).unwrap();
242                (
243                    Self::THIS.to_string(),
244                    node_source(
245                        &navigation_suffix
246                            .child(navigation_suffix.child_count() - 1)
247                            .unwrap(),
248                        source_file,
249                    ),
250                )
251            } else {
252                // handle `this` without navigation expression
253                ("".to_string(), "".to_string())
254            }
255        } else {
256            // node.child_count() - 1 in case there are any inline comments before the call obj
257            // example
258            // foo.bar
259            //     .baz(1) // this comment is child(1)
260            //     .bax(param)
261            (
262                node_source(&node.child(0).unwrap(), source_file),
263                node_source(
264                    &node
265                        .child(node.child_count() - 1)
266                        .expect("No child node found in navigation_suffix")
267                        .child(1)
268                        .unwrap(),
269                    source_file,
270                ),
271            )
272        }
273    }
274
275    fn tree_sitter_language(&self) -> tree_sitter::Language {
276        tree_sitter_kotlin::language()
277    }
278
279    fn has_field_names(&self) -> bool {
280        true
281    }
282
283    fn sanitize_parameter_name(&self, parameter_name: String) -> Option<String> {
284        Some(String::from(parameter_name.split(":").next().unwrap()))
285    }
286
287    fn function_name_node<'a>(&'a self, node: &'a Node) -> Node<'a> {
288        node.child(0).unwrap()
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use super::*;
295    use std::collections::HashSet;
296    use tree_sitter::Tree;
297
298    #[test]
299    fn mutually_exclusive() {
300        let lang = Kotlin::default();
301        let mut kinds: Vec<&str> = vec![];
302
303        kinds.extend(lang.if_nodes());
304        kinds.extend(lang.else_nodes());
305        kinds.extend(lang.conditional_assignment_nodes());
306        kinds.extend(lang.switch_nodes());
307        kinds.extend(lang.case_nodes());
308        kinds.extend(lang.ternary_nodes());
309        kinds.extend(lang.loop_nodes());
310        kinds.extend(lang.except_nodes());
311        kinds.extend(lang.try_expression_nodes());
312        kinds.extend(lang.return_nodes());
313        kinds.extend(lang.binary_nodes());
314        kinds.extend(lang.field_nodes());
315        kinds.extend(lang.call_nodes());
316        kinds.extend(lang.function_nodes());
317        kinds.extend(lang.closure_nodes());
318        kinds.extend(lang.comment_nodes());
319        kinds.extend(lang.string_nodes());
320        kinds.extend(lang.boolean_operator_nodes());
321
322        let unique: HashSet<_> = kinds.iter().cloned().collect();
323        assert_eq!(unique.len(), kinds.len());
324    }
325
326    #[test]
327    fn field_identifier_read() {
328        let source_file = File::from_string(Kotlin::NAME, "this.foo");
329        let tree = source_file.parse();
330        let root_node = tree.root_node();
331        let field = root_node.named_child(0).unwrap();
332        let language = Kotlin::default();
333
334        assert_eq!(
335            language.field_identifiers(&source_file, &field),
336            ("this".to_string(), "foo".to_string())
337        );
338    }
339
340    #[test]
341    fn field_identifier_write() {
342        let source_file = File::from_string(Kotlin::NAME, "this.foo = 1");
343        let tree = source_file.parse();
344        let root_node = tree.root_node();
345        let expression = root_node.named_child(0).unwrap();
346        let field = expression.named_child(0).unwrap();
347        let language = Kotlin::default();
348
349        assert_eq!(
350            language.field_identifiers(&source_file, &field),
351            ("this".to_string(), "foo".to_string())
352        );
353    }
354
355    #[test]
356    fn field_identifier_collaborator() {
357        let source_file = File::from_string(Kotlin::NAME, "other.foo");
358        let tree = source_file.parse();
359        let root_node = tree.root_node();
360        let field = root_node.named_child(0).unwrap();
361        let language = Kotlin::default();
362
363        assert_eq!(
364            language.field_identifiers(&source_file, &field),
365            ("other".to_string(), "foo".to_string())
366        );
367    }
368
369    #[test]
370    fn call_identifier() {
371        let source_file = File::from_string(Kotlin::NAME, "foo()");
372        let tree = source_file.parse();
373        let call = call_node(&tree);
374        let language = Kotlin::default();
375
376        assert_eq!(
377            language.call_identifiers(&source_file, &call),
378            (Some("".to_string()), "foo".to_string())
379        );
380    }
381
382    #[test]
383    fn call_attribute() {
384        let source_file = File::from_string(Kotlin::NAME, "foo.bar()");
385        let tree = source_file.parse();
386        let call = call_node(&tree);
387        let language = Kotlin::default();
388
389        assert_eq!(
390            language.call_identifiers(&source_file, &call),
391            (Some("foo".to_string()), "bar".to_string())
392        );
393    }
394
395    fn call_node(tree: &Tree) -> Node {
396        let root_node = tree.root_node();
397        root_node.named_child(0).unwrap()
398    }
399}