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