qlty_analysis/lang/
tsx.rs

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