qlty_analysis/lang/
python.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(class_definition
8    name: (identifier) @name) @definition.class
9"#;
10
11const FUNCTION_DECLARATION_QUERY: &str = r#"
12(function_definition
13    name: (identifier) @name
14    parameters: (_) @parameters) @definition.function
15"#;
16
17const FIELD_QUERY: &str = r#"
18(
19    (attribute
20        object: (identifier) @receiver
21        attribute: (_) @name) @field
22    (#eq? @receiver "self")
23)
24"#;
25
26pub struct Python {
27    pub class_query: tree_sitter::Query,
28    pub field_query: tree_sitter::Query,
29    pub function_declaration_query: tree_sitter::Query,
30}
31
32impl Python {
33    pub const SELF: &'static str = "self";
34
35    pub const ATTRIBUTE: &'static str = "attribute";
36    pub const BOOLEAN: &'static str = "boolean_operator";
37    pub const BREAK: &'static str = "break_statement";
38    pub const CALL: &'static str = "call";
39    pub const COMMENT: &'static str = "comment";
40    pub const CONDITIONAL_EXPRESSION: &'static str = "conditional_expression"; // ternary
41    pub const CONTINUE: &'static str = "continue_statement";
42    pub const ELIF: &'static str = "elif_clause";
43    pub const ELSE: &'static str = "else_clause";
44    pub const EXCEPT: &'static str = "except_clause";
45    pub const FOR: &'static str = "for_statement";
46    pub const FUNCTION: &'static str = "function_definition";
47    pub const IDENTIFIER: &'static str = "identifier";
48    pub const IF: &'static str = "if_statement";
49    pub const LAMBDA: &'static str = "lambda";
50    pub const MATCH: &'static str = "match_statement";
51    pub const MODULE: &'static str = "module";
52    pub const RETURN: &'static str = "return_statement";
53    pub const STRING: &'static str = "string";
54    pub const WHILE: &'static str = "while_statement";
55
56    pub const AND: &'static str = "and";
57    pub const OR: &'static str = "or";
58}
59
60impl Default for Python {
61    fn default() -> Self {
62        let language = tree_sitter_python::language();
63
64        Self {
65            class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
66            function_declaration_query: tree_sitter::Query::new(
67                &language,
68                FUNCTION_DECLARATION_QUERY,
69            )
70            .unwrap(),
71            field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
72        }
73    }
74}
75
76impl Language for Python {
77    fn name(&self) -> &str {
78        "python"
79    }
80
81    fn self_keyword(&self) -> Option<&str> {
82        Some(Self::SELF)
83    }
84
85    fn class_query(&self) -> &tree_sitter::Query {
86        &self.class_query
87    }
88
89    fn function_declaration_query(&self) -> &tree_sitter::Query {
90        &self.function_declaration_query
91    }
92
93    fn field_query(&self) -> &tree_sitter::Query {
94        &self.field_query
95    }
96
97    fn constructor_names(&self) -> Vec<&str> {
98        vec!["__init__"]
99    }
100
101    fn destructor_names(&self) -> Vec<&str> {
102        vec!["__del__"]
103    }
104
105    fn is_instance_method(&self, file: &File, node: &Node) -> bool {
106        let parameters = node.child_by_field_name("parameters").unwrap();
107
108        if let Some(first_parameter) = parameters.named_child(0) {
109            first_parameter.kind() == Self::IDENTIFIER
110                && node_source(&first_parameter, file) == Self::SELF
111        } else {
112            false
113        }
114    }
115
116    fn if_nodes(&self) -> Vec<&str> {
117        vec![Self::IF]
118    }
119
120    fn elsif_nodes(&self) -> Vec<&str> {
121        vec![Self::ELIF]
122    }
123
124    fn else_nodes(&self) -> Vec<&str> {
125        vec![Self::ELSE]
126    }
127
128    fn conditional_assignment_nodes(&self) -> Vec<&str> {
129        vec![]
130    }
131
132    fn invisible_container_nodes(&self) -> Vec<&str> {
133        vec![Self::MODULE]
134    }
135
136    fn switch_nodes(&self) -> Vec<&str> {
137        vec![Self::MATCH]
138    }
139
140    fn case_nodes(&self) -> Vec<&str> {
141        vec!["case_clause"]
142    }
143
144    fn ternary_nodes(&self) -> Vec<&str> {
145        vec![Self::CONDITIONAL_EXPRESSION]
146    }
147
148    fn loop_nodes(&self) -> Vec<&str> {
149        vec![Self::FOR, Self::WHILE]
150    }
151
152    fn except_nodes(&self) -> Vec<&str> {
153        vec![Self::EXCEPT]
154    }
155
156    fn try_expression_nodes(&self) -> Vec<&str> {
157        vec![]
158    }
159
160    fn jump_nodes(&self) -> Vec<&str> {
161        vec![Self::BREAK, Self::CONTINUE]
162    }
163
164    fn return_nodes(&self) -> Vec<&str> {
165        vec![Self::RETURN]
166    }
167
168    fn binary_nodes(&self) -> Vec<&str> {
169        vec![Self::BOOLEAN]
170    }
171
172    fn boolean_operator_nodes(&self) -> Vec<&str> {
173        vec![Self::AND, Self::OR]
174    }
175
176    fn field_nodes(&self) -> Vec<&str> {
177        vec![Self::ATTRIBUTE]
178    }
179
180    fn call_nodes(&self) -> Vec<&str> {
181        vec![Self::CALL]
182    }
183
184    fn function_nodes(&self) -> Vec<&str> {
185        vec![Self::FUNCTION]
186    }
187
188    fn closure_nodes(&self) -> Vec<&str> {
189        vec![Self::LAMBDA]
190    }
191
192    fn comment_nodes(&self) -> Vec<&str> {
193        vec![Self::COMMENT]
194    }
195
196    fn string_nodes(&self) -> Vec<&str> {
197        vec![Self::STRING]
198    }
199
200    fn all_operators(&self) -> Vec<&str> {
201        // Import | DOT | From | COMMA | As | STAR | GTGT | Assert | COLONEQ | Return | Def
202        // | Del | Raise | Pass | Break | Continue | If | Elif | Else | Async | For | In
203        // | While | Try | Except | Finally | With | DASHGT | EQ | Global | Exec | AT | Not
204        // | And | Or | PLUS | DASH | SLASH | PERCENT | SLASHSLASH | STARSTAR | PIPE | AMP
205        // | CARET | LTLT | TILDE | LT | LTEQ | EQEQ | BANGEQ | GTEQ | GT | LTGT | Is | PLUSEQ
206        // | DASHEQ | STAREQ | SLASHEQ | ATEQ | SLASHSLASHEQ | PERCENTEQ | STARSTAREQ | GTGTEQ
207        // | LTLTEQ | AMPEQ | CARETEQ | PIPEEQ | Yield | Await | Await2 | Print
208        vec![Self::ATTRIBUTE, Self::IDENTIFIER]
209    }
210
211    fn all_operands(&self) -> Vec<&str> {
212        vec![
213            "false",
214            "float",
215            "identifier",
216            "integer",
217            "none",
218            "string",
219            "true",
220        ]
221    }
222
223    fn is_decorator_function(&self, node: &Node) -> bool {
224        let body = node.child_by_field_name("body").unwrap();
225
226        if body.named_child_count() == 2 {
227            let first_child = body.named_child(0).unwrap();
228            let second_child = body.named_child(1).unwrap();
229
230            first_child.kind() == Self::FUNCTION && second_child.kind() == Self::RETURN
231        } else {
232            false
233        }
234    }
235
236    fn iterator_method_identifiers(&self) -> Vec<&str> {
237        vec!["filter", "map", "any", "all"]
238    }
239
240    fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
241        let function_node = node.child_by_field_name("function").unwrap();
242        let function_kind = function_node.kind();
243
244        match function_kind {
245            Self::IDENTIFIER => (
246                Some(Self::SELF.to_string()),
247                node_source(&function_node, source_file),
248            ),
249            Self::ATTRIBUTE => {
250                let (receiver, object) = self.field_identifiers(source_file, &function_node);
251
252                (Some(receiver), object)
253            }
254            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
255        }
256    }
257
258    fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
259        (
260            child_source(node, "object", source_file),
261            child_source(node, "attribute", source_file),
262        )
263    }
264
265    fn tree_sitter_language(&self) -> tree_sitter::Language {
266        tree_sitter_python::language()
267    }
268}
269
270#[cfg(test)]
271mod test {
272    use super::*;
273    use std::collections::HashSet;
274    use tree_sitter::Tree;
275
276    #[test]
277    fn mutually_exclusive() {
278        let lang = Python::default();
279        let mut kinds: Vec<&str> = vec![];
280
281        kinds.extend(lang.if_nodes());
282        kinds.extend(lang.else_nodes());
283        kinds.extend(lang.conditional_assignment_nodes());
284        kinds.extend(lang.switch_nodes());
285        kinds.extend(lang.case_nodes());
286        kinds.extend(lang.ternary_nodes());
287        kinds.extend(lang.loop_nodes());
288        kinds.extend(lang.except_nodes());
289        kinds.extend(lang.try_expression_nodes());
290        kinds.extend(lang.jump_nodes());
291        kinds.extend(lang.return_nodes());
292        kinds.extend(lang.binary_nodes());
293        kinds.extend(lang.field_nodes());
294        kinds.extend(lang.call_nodes());
295        kinds.extend(lang.function_nodes());
296        kinds.extend(lang.closure_nodes());
297        kinds.extend(lang.comment_nodes());
298        kinds.extend(lang.string_nodes());
299        kinds.extend(lang.boolean_operator_nodes());
300
301        let unique: HashSet<_> = kinds.iter().cloned().collect();
302        assert_eq!(unique.len(), kinds.len());
303    }
304
305    #[test]
306    fn field_identifier_read() {
307        let source_file = File::from_string("python", "self.foo");
308        let tree = source_file.parse();
309        let root_node = tree.root_node();
310        let expression = root_node.named_child(0).unwrap();
311        let field = expression.named_child(0).unwrap();
312        let language = Python::default();
313
314        assert_eq!(
315            language.field_identifiers(&source_file, &field),
316            ("self".to_string(), "foo".to_string())
317        );
318    }
319
320    #[test]
321    fn field_identifier_write() {
322        let source_file = File::from_string("python", "self.foo = 1");
323        let tree = source_file.parse();
324        let root_node = tree.root_node();
325        let expression = root_node.named_child(0).unwrap();
326        let assignment = expression.named_child(0).unwrap();
327        let field = assignment.named_child(0).unwrap();
328        let language = Python::default();
329
330        assert_eq!(
331            language.field_identifiers(&source_file, &field),
332            ("self".to_string(), "foo".to_string())
333        );
334    }
335
336    #[test]
337    fn field_identifier_collaborator() {
338        let source_file = File::from_string("python", "other.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 = Python::default();
344
345        assert_eq!(
346            language.field_identifiers(&source_file, &field),
347            ("other".to_string(), "foo".to_string())
348        );
349    }
350
351    #[test]
352    fn call_identifier() {
353        let source_file = File::from_string("python", "foo()");
354        let tree = source_file.parse();
355        let call = call_node(&tree);
356        let language = Python::default();
357
358        assert_eq!(
359            language.call_identifiers(&source_file, &call),
360            (Some("self".to_string()), "foo".to_string())
361        );
362    }
363
364    #[test]
365    fn call_attribute() {
366        let source_file = File::from_string("python", "foo.bar()");
367        let tree = source_file.parse();
368        let call = call_node(&tree);
369        let language = Python::default();
370
371        assert_eq!(
372            language.call_identifiers(&source_file, &call),
373            (Some("foo".to_string()), "bar".to_string())
374        );
375    }
376
377    #[test]
378    fn decorator() {
379        let source_file = File::from_string(
380            "python",
381            r#"
382            def foo():
383                def bar():
384                    pass
385                return bar
386        "#,
387        );
388        let tree = source_file.parse();
389        let root_node = tree.root_node();
390        let function = root_node.named_child(0).unwrap();
391        let language = Python::default();
392
393        assert_eq!(language.is_decorator_function(&function), true);
394    }
395
396    #[test]
397    fn not_decorator() {
398        let source_file = File::from_string(
399            "python",
400            r#"
401            def foo():
402                x = 1 # Invalidates decorator exception
403                def bar():
404                    pass
405                return bar
406        "#,
407        );
408        let tree = source_file.parse();
409        let root_node = tree.root_node();
410        let function = root_node.named_child(0).unwrap();
411        let language = Python::default();
412
413        assert_eq!(language.is_decorator_function(&function), false);
414    }
415
416    fn call_node(tree: &Tree) -> Node {
417        let root_node = tree.root_node();
418        let expression = root_node.named_child(0).unwrap();
419        expression.named_child(0).unwrap()
420    }
421}