qlty_analysis/lang/
javascript.rs

1use 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}