qlty_analysis/lang/
typescript.rs1use 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}