qlty_analysis/lang/
typescript.rs

1use crate::code::File;
2use crate::lang::{typescript_common::TypeScriptCommon, Language};
3use tree_sitter::Node;
4
5pub struct TypeScript {
6    pub common: TypeScriptCommon,
7    class_query: tree_sitter::Query,
8    function_declaration_query: tree_sitter::Query,
9    field_query: tree_sitter::Query,
10}
11
12impl Default for TypeScript {
13    fn default() -> Self {
14        let language = tree_sitter_typescript::language_typescript();
15        let common = TypeScriptCommon::new(&language);
16        let query = common.class_query();
17        let field = common.field_query();
18        let function = common.function_declaration_query();
19
20        Self {
21            common,
22            field_query: field,
23            class_query: query,
24            function_declaration_query: function,
25        }
26    }
27}
28
29impl Language for TypeScript {
30    fn name(&self) -> &str {
31        "typescript"
32    }
33
34    fn self_keyword(&self) -> Option<&str> {
35        self.common.self_keyword()
36    }
37
38    fn class_query(&self) -> &tree_sitter::Query {
39        &self.class_query
40    }
41
42    fn function_declaration_query(&self) -> &tree_sitter::Query {
43        &self.function_declaration_query
44    }
45
46    fn field_query(&self) -> &tree_sitter::Query {
47        &self.field_query
48    }
49
50    fn constructor_names(&self) -> Vec<&str> {
51        self.common.constructor_names()
52    }
53
54    fn if_nodes(&self) -> Vec<&str> {
55        self.common.if_nodes()
56    }
57
58    fn else_nodes(&self) -> Vec<&str> {
59        self.common.else_nodes()
60    }
61
62    fn conditional_assignment_nodes(&self) -> Vec<&str> {
63        self.common.conditional_assignment_nodes()
64    }
65
66    fn invisible_container_nodes(&self) -> Vec<&str> {
67        self.common.invisible_container_nodes()
68    }
69
70    fn switch_nodes(&self) -> Vec<&str> {
71        self.common.switch_nodes()
72    }
73
74    fn case_nodes(&self) -> Vec<&str> {
75        self.common.case_nodes()
76    }
77
78    fn ternary_nodes(&self) -> Vec<&str> {
79        self.common.ternary_nodes()
80    }
81
82    fn loop_nodes(&self) -> Vec<&str> {
83        self.common.loop_nodes()
84    }
85
86    fn except_nodes(&self) -> Vec<&str> {
87        self.common.except_nodes()
88    }
89
90    fn try_expression_nodes(&self) -> Vec<&str> {
91        self.common.try_expression_nodes()
92    }
93
94    fn jump_nodes(&self) -> Vec<&str> {
95        self.common.jump_nodes()
96    }
97
98    fn return_nodes(&self) -> Vec<&str> {
99        self.common.return_nodes()
100    }
101
102    fn binary_nodes(&self) -> Vec<&str> {
103        self.common.binary_nodes()
104    }
105
106    fn boolean_operator_nodes(&self) -> Vec<&str> {
107        self.common.boolean_operator_nodes()
108    }
109
110    fn field_nodes(&self) -> Vec<&str> {
111        self.common.field_nodes()
112    }
113
114    fn call_nodes(&self) -> Vec<&str> {
115        self.common.call_nodes()
116    }
117
118    fn function_nodes(&self) -> Vec<&str> {
119        self.common.function_nodes()
120    }
121
122    fn closure_nodes(&self) -> Vec<&str> {
123        self.common.closure_nodes()
124    }
125
126    fn comment_nodes(&self) -> Vec<&str> {
127        self.common.comment_nodes()
128    }
129
130    fn string_nodes(&self) -> Vec<&str> {
131        self.common.string_nodes()
132    }
133
134    fn iterator_method_identifiers(&self) -> Vec<&str> {
135        self.common.iterator_method_identifiers()
136    }
137
138    fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
139        self.common.call_identifiers(source_file, node)
140    }
141
142    fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
143        self.common.field_identifiers(source_file, node)
144    }
145
146    fn tree_sitter_language(&self) -> tree_sitter::Language {
147        tree_sitter_typescript::language_typescript()
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use super::*;
154    use std::collections::HashSet;
155    use tree_sitter::Tree;
156
157    #[test]
158    fn mutually_exclusive() {
159        let lang = TypeScript::default();
160        let mut kinds: Vec<&str> = vec![];
161
162        kinds.extend(lang.if_nodes());
163        kinds.extend(lang.else_nodes());
164        kinds.extend(lang.conditional_assignment_nodes());
165        kinds.extend(lang.switch_nodes());
166        kinds.extend(lang.case_nodes());
167        kinds.extend(lang.ternary_nodes());
168        kinds.extend(lang.loop_nodes());
169        kinds.extend(lang.except_nodes());
170        kinds.extend(lang.try_expression_nodes());
171        kinds.extend(lang.jump_nodes());
172        kinds.extend(lang.return_nodes());
173        kinds.extend(lang.binary_nodes());
174        kinds.extend(lang.field_nodes());
175        kinds.extend(lang.call_nodes());
176        kinds.extend(lang.function_nodes());
177        kinds.extend(lang.closure_nodes());
178        kinds.extend(lang.comment_nodes());
179        kinds.extend(lang.string_nodes());
180        kinds.extend(lang.boolean_operator_nodes());
181
182        let unique: HashSet<_> = kinds.iter().cloned().collect();
183        assert_eq!(unique.len(), kinds.len());
184    }
185
186    #[test]
187    fn field_identifier_read() {
188        let source_file = File::from_string("typescript", "self.foo");
189        let tree = source_file.parse();
190        let root_node = tree.root_node();
191        let expression = root_node.named_child(0).unwrap();
192        let field = expression.named_child(0).unwrap();
193        let language = TypeScript::default();
194
195        assert_eq!(
196            language.field_identifiers(&source_file, &field),
197            ("self".to_string(), "foo".to_string())
198        );
199    }
200
201    #[test]
202    fn field_identifier_write() {
203        let source_file = File::from_string("typescript", "self.foo = 1");
204        let tree = source_file.parse();
205        let root_node = tree.root_node();
206        let expression = root_node.named_child(0).unwrap();
207        let assignment = expression.named_child(0).unwrap();
208        let field = assignment.named_child(0).unwrap();
209        let language = TypeScript::default();
210
211        assert_eq!(
212            language.field_identifiers(&source_file, &field),
213            ("self".to_string(), "foo".to_string())
214        );
215    }
216
217    #[test]
218    fn field_identifier_collaborator() {
219        let source_file = File::from_string("typescript", "other.foo");
220        let tree = source_file.parse();
221        let root_node = tree.root_node();
222        let expression = root_node.named_child(0).unwrap();
223        let field = expression.named_child(0).unwrap();
224        let language = TypeScript::default();
225
226        assert_eq!(
227            language.field_identifiers(&source_file, &field),
228            ("other".to_string(), "foo".to_string())
229        );
230    }
231
232    #[test]
233    fn call_identifier() {
234        let source_file = File::from_string("typescript", "foo()");
235        let tree = source_file.parse();
236        let call = call_node(&tree);
237        let language = TypeScript::default();
238
239        assert_eq!(
240            language.call_identifiers(&source_file, &call),
241            (Some("this".to_string()), "foo".to_string())
242        );
243    }
244
245    #[test]
246    fn call_member() {
247        let source_file = File::from_string("typescript", "foo.bar()");
248        let tree = source_file.parse();
249        let call = call_node(&tree);
250        let language = TypeScript::default();
251
252        assert_eq!(
253            language.call_identifiers(&source_file, &call),
254            (Some("foo".into()), "bar".into())
255        );
256    }
257
258    #[test]
259    fn call_with_custom_context() {
260        let source_file = File::from_string("typescript", "foo.call(context);");
261        let tree = source_file.parse();
262        let call = call_node(&tree);
263        let language = TypeScript::default();
264
265        assert_eq!(
266            language.call_identifiers(&source_file, &call),
267            (Some("foo".to_string()), "call".to_string())
268        );
269    }
270
271    #[test]
272    fn call_with_optional_chaining() {
273        let source_file = File::from_string("typescript", "obj?.foo();");
274        let tree = source_file.parse();
275        let call = call_node(&tree);
276        let language = TypeScript::default();
277
278        assert_eq!(
279            language.call_identifiers(&source_file, &call),
280            (Some("obj".to_string()), "foo".to_string())
281        );
282    }
283
284    #[test]
285    fn method_call_on_nested_object() {
286        let source_file = File::from_string("typescript", "obj.nestedObj.foo();");
287        let tree = source_file.parse();
288        let call = call_node(&tree);
289        let language = TypeScript::default();
290
291        assert_eq!(
292            language.call_identifiers(&source_file, &call),
293            (Some("nestedObj".to_string()), "foo".to_string())
294        );
295    }
296
297    #[test]
298    fn call_returned_function() {
299        let source_file = File::from_string("typescript", "getFunction()()");
300        let tree = source_file.parse();
301        let call = call_node(&tree);
302        let language = TypeScript::default();
303
304        assert_eq!(
305            language.call_identifiers(&source_file, &call),
306            (Some("getFunction()".to_string()), "<UNKNOWN>".to_string())
307        );
308    }
309
310    #[test]
311    fn call_function_property() {
312        let source_file = File::from_string("typescript", "foo.bar()");
313        let tree = source_file.parse();
314        let call = call_node(&tree);
315        let language = TypeScript::default();
316
317        assert_eq!(
318            language.call_identifiers(&source_file, &call),
319            (Some("foo".to_string()), "bar".to_string())
320        );
321    }
322
323    #[test]
324    fn call_anonymous_function() {
325        let source_file = File::from_string("typescript", "(function() { return 'Hello'; })();");
326        let tree = source_file.parse();
327        let call = call_node(&tree);
328        let language = TypeScript::default();
329
330        assert_eq!(
331            language.call_identifiers(&source_file, &call),
332            (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string())
333        );
334    }
335
336    #[test]
337    fn call_arrow_function() {
338        let source_file =
339            File::from_string("typescript", "const greeting = () => 'Hello'; greeting();");
340        let tree = source_file.parse();
341        let call = call_deeper_node(&tree);
342        let language = TypeScript::default();
343
344        assert_eq!(
345            language.call_identifiers(&source_file, &call),
346            (Some("this".to_string()), "greeting".to_string())
347        );
348    }
349
350    #[test]
351    fn call_function_returned_by_getter() {
352        let source_file = File::from_string(
353            "typescript",
354            "let obj = { get myFn() { return function() {}; } }; obj.myFn();",
355        );
356        let tree = source_file.parse();
357        let call = call_deeper_node(&tree);
358        let language = TypeScript::default();
359
360        assert_eq!(
361            language.call_identifiers(&source_file, &call),
362            (Some("obj".to_string()), "myFn".to_string())
363        );
364    }
365
366    fn call_node(tree: &Tree) -> Node {
367        let root_node = tree.root_node();
368        let expression = root_node.named_child(0).unwrap();
369        expression.named_child(0).unwrap()
370    }
371
372    fn call_deeper_node(tree: &Tree) -> Node {
373        let root_node = tree.root_node();
374        let expression_statement = root_node.named_child(1).unwrap();
375        expression_statement.named_child(0).unwrap()
376    }
377}