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