qlty_analysis/lang/
php.rs1use crate::code::node_source;
2use crate::code::File;
3use crate::lang::Language;
4use anyhow::Context;
5use tree_sitter::Node;
6
7const CLASS_QUERY: &str = r#"
8[
9 (class_declaration
10 name: (name) @name)
11 (trait_declaration
12 name: (name) @name)
13 (interface_declaration
14 name: (name) @name)
15] @definition.class
16"#;
17
18const FUNCTION_DECLARATION_QUERY: &str = r#"
19[
20 (method_declaration
21 name: (name) @name
22 parameters: (_) @parameters)
23 (function_definition
24 name: (name) @name
25 parameters: (_) @parameters)
26 (assignment_expression
27 left:
28 name: (_) @name
29 right: [
30 (arrow_function
31 parameters: (_) @parameters)
32 (anonymous_function_creation_expression
33 parameters: (_) @parameters)
34 ])
35] @definition.function
36"#;
37
38const FIELD_QUERY: &str = r#"
39[
40 (class_declaration
41 name: (name)
42 body: (declaration_list
43 (property_declaration
44 (property_element
45 (variable_name (_) @name)
46 (property_initializer
47 (_) @field)
48 )
49 )
50 )
51 )
52 (assignment_expression
53 left: (member_access_expression
54 object: (_) @obj.name
55 name: (_) @name
56 (#eq? @obj.name "$this")
57 )
58 right: (_) @field
59 )
60 (member_access_expression
61 object: (_) @obj.name
62 name: (_) @name
63 (#eq? @obj.name "$this")
64 ) @field
65]
66"#;
67
68pub struct Php {
69 pub class_query: tree_sitter::Query,
70 pub function_declaration_query: tree_sitter::Query,
71 pub field_query: tree_sitter::Query,
72}
73
74impl Php {
75 pub const NAME: &'static str = "php";
76 pub const THIS: &'static str = "$this";
77
78 pub const BINARY_EXPRESSION: &'static str = "binary_expression";
80 pub const BOOLEAN_OPERATOR: &'static str = "boolean_operator";
81 pub const CALL_EXPRESSION: &'static str = "function_call_expression";
82 pub const CASE_STATEMENT: &'static str = "case_statement";
83 pub const ARROW_FUNCTION: &'static str = "arrow_function";
84 pub const ANONYMOUS_FUNCTION: &'static str = "anonymous_function_creation_expression";
85 pub const COMMENT: &'static str = "comment";
86 pub const CONDITIONAL_ASSIGNMENT: &'static str = "conditional_assignment";
87 pub const CATCH_CLAUSE: &'static str = "catch_clause";
88 pub const ELSE_CLAUSE: &'static str = "else_clause";
89 pub const ELSE_IF_CLAUSE: &'static str = "else_if_clause";
90 pub const FUNCTION_DEFINITION: &'static str = "function_definition";
91 pub const METHOD_DECLARATION: &'static str = "method_declaration";
92 pub const IF_STATEMENT: &'static str = "if_statement";
93 pub const IDENTIFIER: &'static str = "identifier";
94 pub const WHILE_STATEMENT: &'static str = "while_statement";
95 pub const FOR_STATEMENT: &'static str = "for_statement";
96 pub const FOREACH_STATEMENT: &'static str = "foreach_statement";
97 pub const DO_WHILE_STATEMENT: &'static str = "do_statement";
98 pub const SWITCH_STATEMENT: &'static str = "switch_statement";
99 pub const RETURN_STATEMENT: &'static str = "return_statement";
100 pub const BREAK_STATEMENT: &'static str = "break_statement";
101 pub const CONTINUE_STATEMENT: &'static str = "continue_statement";
102 pub const TERNARY_EXPRESSION: &'static str = "conditional_assignment";
103 pub const TRY_STATEMENT: &'static str = "try_statement";
104 pub const STRING: &'static str = "string";
105 pub const ENCASPED_STRING: &'static str = "encapsed_string";
106 pub const PROGRAM: &'static str = "program";
107 pub const AND: &'static str = "&&";
108 pub const OR: &'static str = "||";
109 pub const CONSTRUCTOR_NAME: &'static str = "__construct";
110 pub const MEMBER_CALL_EXPRESSION: &'static str = "member_call_expression";
111 pub const MEMBER_ACCESS_EXPRESSION: &'static str = "member_access_expression";
112}
113
114impl Default for Php {
115 fn default() -> Self {
116 let language = tree_sitter_php::language_php();
117
118 Self {
119 class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
120 field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
121 function_declaration_query: tree_sitter::Query::new(
122 &language,
123 FUNCTION_DECLARATION_QUERY,
124 )
125 .unwrap(),
126 }
127 }
128}
129
130impl Language for Php {
131 fn name(&self) -> &str {
132 Self::NAME
133 }
134
135 fn self_keyword(&self) -> Option<&str> {
136 Some(Self::THIS)
137 }
138
139 fn binary_nodes(&self) -> Vec<&str> {
140 vec![Self::BINARY_EXPRESSION]
141 }
142
143 fn boolean_operator_nodes(&self) -> Vec<&str> {
144 vec![Self::AND, Self::OR]
145 }
146
147 fn call_nodes(&self) -> Vec<&str> {
148 vec![Self::CALL_EXPRESSION]
149 }
150
151 fn case_nodes(&self) -> Vec<&str> {
152 vec![Self::CASE_STATEMENT]
153 }
154
155 fn closure_nodes(&self) -> Vec<&str> {
156 vec![Self::ARROW_FUNCTION, Self::ANONYMOUS_FUNCTION]
157 }
158
159 fn comment_nodes(&self) -> Vec<&str> {
160 vec![Self::COMMENT]
161 }
162
163 fn conditional_assignment_nodes(&self) -> Vec<&str> {
164 vec![Self::CONDITIONAL_ASSIGNMENT]
165 }
166
167 fn else_nodes(&self) -> Vec<&str> {
168 vec![Self::ELSE_CLAUSE]
169 }
170
171 fn elsif_nodes(&self) -> Vec<&str> {
172 vec![Self::ELSE_IF_CLAUSE]
173 }
174
175 fn except_nodes(&self) -> Vec<&str> {
176 vec![Self::CATCH_CLAUSE]
177 }
178
179 fn field_nodes(&self) -> Vec<&str> {
180 vec![Self::MEMBER_CALL_EXPRESSION, Self::MEMBER_ACCESS_EXPRESSION]
181 }
182
183 fn function_nodes(&self) -> Vec<&str> {
184 vec![Self::FUNCTION_DEFINITION, Self::METHOD_DECLARATION]
185 }
186
187 fn if_nodes(&self) -> Vec<&str> {
188 vec![Self::IF_STATEMENT]
189 }
190
191 fn invisible_container_nodes(&self) -> Vec<&str> {
192 vec![Self::PROGRAM]
193 }
194
195 fn jump_nodes(&self) -> Vec<&str> {
196 vec![Self::BREAK_STATEMENT, Self::CONTINUE_STATEMENT]
197 }
198
199 fn loop_nodes(&self) -> Vec<&str> {
200 vec![
201 Self::WHILE_STATEMENT,
202 Self::FOR_STATEMENT,
203 Self::FOREACH_STATEMENT,
204 Self::DO_WHILE_STATEMENT,
205 ]
206 }
207
208 fn return_nodes(&self) -> Vec<&str> {
209 vec![Self::RETURN_STATEMENT]
210 }
211
212 fn string_nodes(&self) -> Vec<&str> {
213 vec![Self::STRING, Self::ENCASPED_STRING]
214 }
215
216 fn switch_nodes(&self) -> Vec<&str> {
217 vec![Self::SWITCH_STATEMENT]
218 }
219
220 fn ternary_nodes(&self) -> Vec<&str> {
221 vec![Self::TERNARY_EXPRESSION]
222 }
223
224 fn try_expression_nodes(&self) -> Vec<&str> {
225 vec![Self::TRY_STATEMENT]
226 }
227
228 fn constructor_names(&self) -> Vec<&str> {
229 vec![Self::CONSTRUCTOR_NAME]
230 }
231
232 fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
233 let function_node = node.child(0).unwrap();
234 let function_kind = function_node.kind();
235
236 match function_kind {
237 Self::CALL_EXPRESSION => (
238 None,
239 node_source(&function_node.child(0).unwrap(), source_file),
240 ),
241 Self::MEMBER_CALL_EXPRESSION => {
242 let object = function_node.child(0).unwrap();
243 let object_name = node_source(&object, source_file);
244 let method = function_node.child(2).unwrap();
245 let method_name = node_source(&method, source_file);
246 (Some(object_name), method_name)
247 }
248 _ => (Some("<UNKNOWN>".to_string()), String::new()),
249 }
250 }
251
252 fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
253 (
254 node_source(&node.child(0).unwrap(), source_file),
255 node_source(
256 &node
257 .child_by_field_name("name")
258 .with_context(|| format!("file_path: {:?}", source_file.path))
259 .unwrap(),
260 source_file,
261 ),
262 )
263 }
264
265 fn tree_sitter_language(&self) -> tree_sitter::Language {
266 tree_sitter_php::language_php()
267 }
268
269 fn class_query(&self) -> &tree_sitter::Query {
270 &self.class_query
271 }
272
273 fn function_declaration_query(&self) -> &tree_sitter::Query {
274 &self.function_declaration_query
275 }
276
277 fn field_query(&self) -> &tree_sitter::Query {
278 &self.field_query
279 }
280}
281
282#[cfg(test)]
283mod test {
284 use super::*;
285 use std::collections::HashSet;
286 use tree_sitter::Tree;
287
288 #[test]
289 fn mutually_exclusive() {
290 let php = Php::default();
291 let mut kinds: Vec<&str> = vec![];
292
293 kinds.extend(php.binary_nodes());
294 kinds.extend(php.boolean_operator_nodes());
295 kinds.extend(php.call_nodes());
296 kinds.extend(php.case_nodes());
297 kinds.extend(php.closure_nodes());
298 kinds.extend(php.comment_nodes());
299 kinds.extend(php.conditional_assignment_nodes());
300 kinds.extend(php.else_nodes());
301 kinds.extend(php.except_nodes());
302 kinds.extend(php.field_nodes());
303 kinds.extend(php.function_nodes());
304 kinds.extend(php.if_nodes());
305 kinds.extend(php.invisible_container_nodes());
306 kinds.extend(php.jump_nodes());
307 kinds.extend(php.loop_nodes());
308 kinds.extend(php.return_nodes());
309 kinds.extend(php.string_nodes());
310 kinds.extend(php.switch_nodes());
311 kinds.extend(php.try_expression_nodes());
312
313 let unique: HashSet<_> = kinds.iter().cloned().collect();
314 assert_eq!(unique.len(), kinds.len());
315 }
316
317 #[test]
318 fn field_identifier_read() {
319 let source_file = File::from_string(Php::NAME, "<?php $foobar->foo;?>");
320 let tree = source_file.parse();
321 let root_node = tree.root_node();
322 let expression = root_node.named_child(1).unwrap();
323 let field = expression.named_child(0).unwrap();
324 let language = Php::default();
325
326 assert_eq!(
327 language.field_identifiers(&source_file, &field),
328 ("$foobar".to_string(), "foo".to_string())
329 );
330 }
331
332 #[test]
333 fn field_identifier_write() {
334 let source_file = File::from_string(Php::NAME, "<?php $foobar->foo = 1;?>");
335 let tree = source_file.parse();
336 let root_node = tree.root_node();
337 let expression = root_node.named_child(1).unwrap();
338 let field = expression.named_child(0).unwrap().named_child(0).unwrap();
339 let language = Php::default();
340
341 assert_eq!(
342 language.field_identifiers(&source_file, &field),
343 ("$foobar".to_string(), "foo".to_string())
344 );
345 }
346
347 #[test]
348 fn this_field_identifier_read() {
349 let source_file = File::from_string(Php::NAME, "<?php $this->foo;?>");
350 let tree = source_file.parse();
351 let root_node = tree.root_node();
352 let expression = root_node.named_child(1).unwrap();
353 let field = expression.named_child(0).unwrap();
354 let language = Php::default();
355
356 assert_eq!(
357 language.field_identifiers(&source_file, &field),
358 ("$this".to_string(), "foo".to_string())
359 );
360 }
361
362 #[test]
363 fn this_field_identifier_write() {
364 let source_file = File::from_string(Php::NAME, "<?php $this->foo = 1;?>");
365 let tree = source_file.parse();
366 let root_node = tree.root_node();
367 let expression = root_node.named_child(1).unwrap();
368 let field = expression.named_child(0).unwrap().named_child(0).unwrap();
369 let language = Php::default();
370
371 assert_eq!(
372 language.field_identifiers(&source_file, &field),
373 ("$this".to_string(), "foo".to_string())
374 );
375 }
376
377 #[test]
378 fn call_identifier() {
379 let source_file = File::from_string(Php::NAME, "<?php foo(); ?>");
380 let tree = source_file.parse();
381 let call = call_node(&tree);
382 let language = Php::default();
383
384 assert_eq!(
385 language.call_identifiers(&source_file, &call),
386 (None, "foo".to_string())
387 );
388 }
389
390 #[test]
391 fn call_attribute() {
392 let source_file = File::from_string(Php::NAME, "<?php $foo->bar() ?>");
393 let tree = source_file.parse();
394 let call = call_node(&tree);
395 let language = Php::default();
396
397 assert_eq!(
398 language.call_identifiers(&source_file, &call),
399 (Some("$foo".to_string()), "bar".to_string())
400 );
401 }
402
403 #[test]
404 fn this_attribute() {
405 let source_file = File::from_string(Php::NAME, "<?php $this->bar() ?>");
406 let tree = source_file.parse();
407 let call = call_node(&tree);
408 let language = Php::default();
409
410 assert_eq!(
411 language.call_identifiers(&source_file, &call),
412 (Some("$this".to_string()), "bar".to_string())
413 );
414 }
415
416 fn call_node(tree: &Tree) -> Node {
417 let root_node = tree.root_node();
418 root_node.named_child(1).unwrap()
419 }
420}