qlty_analysis/lang/
kotlin.rs1use 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 fn conditional_assignment_nodes(&self) -> Vec<&str> {
122 vec![]
123 }
124
125 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 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 ("".to_string(), "".to_string())
254 }
255 } else {
256 (
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}