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[
8(class
9 name: [
10 (constant) @name
11 (scope_resolution
12 name: (_) @name)
13 ]) @definition.class
14(singleton_class
15 value: [
16 (constant) @name
17 (scope_resolution
18 name: (_) @name)
19 ]) @definition.class
20]
21"#;
22
23const FUNCTION_DECLARATION_QUERY: &str = r#"
24[
25 (method
26 name: (_) @name
27 parameters: (_)? @parameters)
28 (singleton_method
29 object: (_)
30 name: (_) @name
31 parameters: (_)? @parameters)
32] @definition.function
33"#;
34
35const FIELD_QUERY: &str = r#"
36[
37 (call
38 method: (identifier) @accessor_name
39 arguments: (argument_list (simple_symbol) @name)
40 (#eq? @accessor_name "attr_accessor"))
41
42 (call
43 method: (identifier) @writer_name
44 arguments: (argument_list (simple_symbol) @name)
45 (#eq? @writer_name "attr_writer"))
46
47 (call
48 method: (identifier) @reader_name
49 arguments: (argument_list (simple_symbol) @name)
50 (#eq? @reader_name "attr_reader"))
51
52 (call
53 receiver: (constant) @receiver_name
54 method: (identifier) @method_name
55 arguments: (argument_list (simple_symbol) @name))
56 (instance_variable) @name
57 (class_variable) @name
58] @field
59"#;
60
61pub struct Ruby {
62 pub class_query: tree_sitter::Query,
63 pub function_declaration_query: tree_sitter::Query,
64 pub field_query: tree_sitter::Query,
65}
66
67impl Ruby {
68 pub const BEGIN: &'static str = "begin";
69 pub const BINARY: &'static str = "binary";
70 pub const BLOCK: &'static str = "block";
71 pub const BREAK: &'static str = "break";
72 pub const CALL: &'static str = "call";
73 pub const CASE: &'static str = "case";
74 pub const CLASS_VARIABLE: &'static str = "class_variable";
75 pub const COMMENT: &'static str = "comment";
76 pub const CONDITIONAL: &'static str = "conditional";
77 pub const CONSTANT: &'static str = "constant";
78 pub const DO_BLOCK: &'static str = "do_block";
79 pub const ELSE: &'static str = "else";
80 pub const ELSIF: &'static str = "elsif";
81 pub const FOR: &'static str = "for";
82 pub const GLOBAL_VARIABLE: &'static str = "global_variable";
83 pub const IDENTIFIER: &'static str = "identifier";
84 pub const IF: &'static str = "if";
85 pub const INITIALIZE: &'static str = "initialize";
86 pub const INSTANCE_VARIABLE: &'static str = "instance_variable";
87 pub const METHOD_CALL: &'static str = "method_call";
88 pub const METHOD: &'static str = "method";
89 pub const NEXT: &'static str = "next";
90 pub const OPERATOR_ASSIGNMENT: &'static str = "operator_assignment";
91 pub const PROGRAM: &'static str = "program";
92 pub const RESCUE: &'static str = "rescue";
93 pub const RETURN: &'static str = "return";
94 pub const SELF: &'static str = "self";
95 pub const SINGLETON_METHOD: &'static str = "singleton_method";
96 pub const STRING: &'static str = "string";
97 pub const UNTIL: &'static str = "until";
98 pub const WHEN: &'static str = "when";
99 pub const WHILE: &'static str = "while";
100
101 pub const LOGICAL_AND: &'static str = "&&";
102 pub const LOGICAL_OR: &'static str = "||";
103 pub const AND: &'static str = "and";
104 pub const OR: &'static str = "or";
105}
106
107impl Default for Ruby {
108 fn default() -> Self {
109 let language = tree_sitter_ruby::language();
110
111 Self {
112 class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
113 function_declaration_query: tree_sitter::Query::new(
114 &language,
115 FUNCTION_DECLARATION_QUERY,
116 )
117 .unwrap(),
118 field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
119 }
120 }
121}
122
123impl Language for Ruby {
124 fn name(&self) -> &str {
125 "ruby"
126 }
127
128 fn self_keyword(&self) -> Option<&str> {
129 Some(Self::SELF)
130 }
131
132 fn class_query(&self) -> &tree_sitter::Query {
133 &self.class_query
134 }
135
136 fn function_declaration_query(&self) -> &tree_sitter::Query {
137 &self.function_declaration_query
138 }
139
140 fn field_query(&self) -> &tree_sitter::Query {
141 &self.field_query
142 }
143
144 fn invisible_container_nodes(&self) -> Vec<&str> {
145 vec![Self::PROGRAM]
146 }
147
148 fn if_nodes(&self) -> Vec<&str> {
149 vec![Self::IF]
150 }
151
152 fn elsif_nodes(&self) -> Vec<&str> {
153 vec![Self::ELSIF]
154 }
155
156 fn else_nodes(&self) -> Vec<&str> {
157 vec![Self::ELSE]
158 }
159
160 fn ternary_nodes(&self) -> Vec<&str> {
161 vec![Self::CONDITIONAL]
162 }
163
164 fn switch_nodes(&self) -> Vec<&str> {
165 vec![Self::CASE]
166 }
167
168 fn case_nodes(&self) -> Vec<&str> {
169 vec![Self::WHEN]
170 }
171
172 fn loop_nodes(&self) -> Vec<&str> {
173 vec![Self::WHILE, Self::UNTIL, Self::FOR]
174 }
175
176 fn except_nodes(&self) -> Vec<&str> {
177 vec![Self::RESCUE]
178 }
179
180 fn try_expression_nodes(&self) -> Vec<&str> {
181 vec![Self::BEGIN]
182 }
183
184 fn conditional_assignment_nodes(&self) -> Vec<&str> {
185 vec![Self::OPERATOR_ASSIGNMENT]
186 }
187
188 fn jump_nodes(&self) -> Vec<&str> {
189 vec![Self::BREAK, Self::NEXT]
190 }
191
192 fn return_nodes(&self) -> Vec<&str> {
193 vec![Self::RETURN]
194 }
195
196 fn binary_nodes(&self) -> Vec<&str> {
197 vec![Self::BINARY]
198 }
199
200 fn boolean_operator_nodes(&self) -> Vec<&str> {
201 vec![Self::LOGICAL_AND, Self::LOGICAL_OR, Self::AND, Self::OR]
202 }
203
204 fn field_nodes(&self) -> Vec<&str> {
205 vec![Self::INSTANCE_VARIABLE, Self::CLASS_VARIABLE]
206 }
207
208 fn call_nodes(&self) -> Vec<&str> {
209 vec![Self::CALL, Self::METHOD_CALL]
210 }
211
212 fn function_nodes(&self) -> Vec<&str> {
213 vec![Self::METHOD, Self::SINGLETON_METHOD]
214 }
215
216 fn closure_nodes(&self) -> Vec<&str> {
217 vec![Self::BLOCK, Self::DO_BLOCK]
218 }
219
220 fn comment_nodes(&self) -> Vec<&str> {
221 vec![Self::COMMENT]
222 }
223
224 fn string_nodes(&self) -> Vec<&str> {
225 vec![Self::STRING]
226 }
227
228 fn constructor_names(&self) -> Vec<&str> {
229 vec![Self::INITIALIZE]
230 }
231
232 fn iterator_method_identifiers(&self) -> Vec<&str> {
233 vec![
234 "each",
235 "map",
236 "collect",
237 "select",
238 "find_all",
239 "reject",
240 "find",
241 "detect",
242 "any?",
243 "all?",
244 "none?",
245 "one?",
246 "partition",
247 "group_by",
248 "each_with_index",
249 "reverse_each",
250 "each_entry",
251 "each_slice",
252 "each_cons",
253 "flat_map",
254 "collect_concat",
255 "zip",
256 "cycle",
257 "lazy",
258 ]
259 }
260
261 fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
262 let function_kind = node.kind();
263
264 match function_kind {
265 Self::IDENTIFIER => (Some(Self::SELF.to_string()), node_source(node, source_file)),
266 Self::METHOD => {
267 let (receiver, object) = self.field_identifiers(source_file, node);
268
269 (Some(receiver), object)
270 }
271 Self::CALL => {
272 let receiver = if node.child_by_field_name("receiver").is_some() {
273 child_source(node, "receiver", source_file)
274 } else {
275 Self::SELF.to_string()
276 };
277
278 let method = if node.child_by_field_name("method").is_some() {
279 child_source(node, "method", source_file)
280 } else if node.child_by_field_name("arguments").is_some() {
281 "call".to_string()
282 } else {
283 "<UNKNOWN>".to_string()
284 };
285
286 (Some(receiver), method)
287 }
288 _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
289 }
290 }
291
292 fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
293 match node.kind() {
294 Self::CLASS_VARIABLE | Self::CONSTANT | Self::GLOBAL_VARIABLE => {
295 (Self::SELF.to_string(), node_source(node, source_file))
296 }
297 Self::INSTANCE_VARIABLE => {
298 let source = node_source(node, source_file);
299 let modified_source = source.strip_prefix('@').unwrap_or(&source);
300 (Self::SELF.to_string(), modified_source.to_string())
301 }
302 Self::METHOD => {
303 let method_node = node.child_by_field_name("method").unwrap();
304 (
305 Self::SELF.to_string(),
306 node_source(&method_node, source_file),
307 )
308 }
309 Self::CALL => (
310 child_source(node, "receiver", source_file),
311 child_source(node, "method", source_file),
312 ),
313 _ => (node.kind().to_string(), "<UNKNOWN>".to_string()),
314 }
315 }
316
317 fn tree_sitter_language(&self) -> tree_sitter::Language {
318 tree_sitter_ruby::language()
319 }
320}
321
322#[cfg(test)]
323
324mod test {
325 use super::*;
326 use std::collections::HashSet;
327 use tree_sitter::Tree;
328
329 #[test]
330 fn mutually_exclusive() {
331 let lang = Ruby::default();
332 let mut kinds: Vec<&str> = vec![];
333
334 kinds.extend(lang.invisible_container_nodes());
335 kinds.extend(lang.if_nodes());
336 kinds.extend(lang.else_nodes());
337 kinds.extend(lang.conditional_assignment_nodes());
338 kinds.extend(lang.switch_nodes());
339 kinds.extend(lang.case_nodes());
340 kinds.extend(lang.ternary_nodes());
341 kinds.extend(lang.loop_nodes());
342 kinds.extend(lang.except_nodes());
343 kinds.extend(lang.try_expression_nodes());
344 kinds.extend(lang.jump_nodes());
345 kinds.extend(lang.return_nodes());
346 kinds.extend(lang.binary_nodes());
347 kinds.extend(lang.field_nodes());
348 kinds.extend(lang.call_nodes());
349 kinds.extend(lang.function_nodes());
350 kinds.extend(lang.closure_nodes());
351 kinds.extend(lang.comment_nodes());
352 kinds.extend(lang.string_nodes());
353 kinds.extend(lang.boolean_operator_nodes());
354
355 let unique: HashSet<_> = kinds.iter().cloned().collect();
356 assert_eq!(unique.len(), kinds.len());
357 }
358
359 #[test]
360 fn field_identifier_read() {
361 let source_file = File::from_string("ruby", "self.foo");
362 let tree = source_file.parse();
363 let root_node = tree.root_node();
364 let expression = root_node.named_child(0).unwrap();
365 let language = Ruby::default();
366
367 assert_eq!(
368 language.field_identifiers(&source_file, &expression),
369 ("self".to_string(), "foo".to_string())
370 );
371 }
372
373 #[test]
374 fn field_identifier_write() {
375 let source_file = File::from_string("ruby", "self.foo = 1");
376 let tree = source_file.parse();
377 let root_node = tree.root_node();
378 let assignment = root_node.named_child(0).unwrap();
379 let field = assignment.child(0).unwrap();
380 let language = Ruby::default();
381
382 assert_eq!(
383 language.field_identifiers(&source_file, &field),
384 ("self".to_string(), "foo".to_string())
385 );
386 }
387
388 #[test]
389 fn field_identifier_collaborator() {
390 let source_file = File::from_string("ruby", "other.foo");
391 let tree = source_file.parse();
392 let root_node = tree.root_node();
393 let expression = root_node.named_child(0).unwrap();
394 let language = Ruby::default();
395
396 assert_eq!(
397 language.field_identifiers(&source_file, &expression),
398 ("other".to_string(), "foo".to_string())
399 );
400 }
401
402 #[test]
403 fn field_identifier_class_variables() {
404 let source_file = File::from_string("ruby", "@@foo");
405 let tree = source_file.parse();
406 let root_node = tree.root_node();
407 let expression = root_node.named_child(0).unwrap();
408 let language = Ruby::default();
409
410 assert_eq!(
411 language.field_identifiers(&source_file, &expression),
412 ("self".to_string(), "@@foo".to_string())
413 );
414 }
415
416 #[test]
417 fn field_identifier_instance_variables() {
418 let source_file = File::from_string("ruby", "@foo");
419 let tree = source_file.parse();
420 let root_node = tree.root_node();
421 let expression = root_node.named_child(0).unwrap();
422 let language = Ruby::default();
423
424 assert_eq!(
425 language.field_identifiers(&source_file, &expression),
426 ("self".to_string(), "foo".to_string())
427 );
428 }
429
430 #[test]
431 fn field_identifier_constant() {
432 let source_file = File::from_string("ruby", "MY_CONSTANT = 42");
433 let tree = source_file.parse();
434 let root_node = tree.root_node();
435 let expression = root_node.named_child(0).unwrap();
436 let field = expression.child(0).unwrap();
437 let language = Ruby::default();
438
439 assert_eq!(
440 language.field_identifiers(&source_file, &field),
441 ("self".to_string(), "MY_CONSTANT".to_string())
442 );
443 }
444
445 #[test]
446 fn field_identifier_global_variable() {
447 let source_file = File::from_string("ruby", "$global_var = 100");
448 let tree = source_file.parse();
449 let root_node = tree.root_node();
450 let expression = root_node.named_child(0).unwrap();
451 let field = expression.child(0).unwrap();
452 let language = Ruby::default();
453
454 assert_eq!(
455 language.field_identifiers(&source_file, &field),
456 ("self".to_string(), "$global_var".to_string())
457 );
458 }
459
460 #[test]
461 fn call_identifier() {
462 let source_file = File::from_string("ruby", "foo()");
463 let tree = source_file.parse();
464 let call = tree.root_node().child(0).unwrap();
465 let language = Ruby::default();
466
467 assert_eq!(
468 language.call_identifiers(&source_file, &call),
469 (Some("self".to_string()), "foo".to_string())
470 );
471 }
472
473 #[test]
474 fn call_identifier_with_module() {
475 let source_file = File::from_string("ruby", "Module::Class.method()");
476 let tree = source_file.parse();
477 let call = call_node(&tree);
478 let language = Ruby::default();
479
480 assert_eq!(
481 language.call_identifiers(&source_file, &call),
482 (Some("Module::Class".into()), "method".into())
483 );
484 }
485
486 #[test]
487 fn call_identifier_with_syntax_sugar() {
488 let source_file = File::from_string("ruby", "recognize_path.('some_path')");
489 let tree = source_file.parse();
490 let call = call_node(&tree);
491 let language = Ruby::default();
492
493 assert_eq!(
494 language.call_identifiers(&source_file, &call),
495 (Some("recognize_path".into()), "call".into())
496 );
497 }
498
499 #[test]
500 fn call_identifier_with_send() {
501 let source_file = File::from_string("ruby", "object.send(:method_name)");
502 let tree = source_file.parse();
503 let call = call_node(&tree);
504 let language = Ruby::default();
505
506 assert_eq!(
507 language.call_identifiers(&source_file, &call),
508 (Some("object".into()), "send".into())
509 );
510 }
511
512 #[test]
513 fn call_identifier_with_safe_navigation_operator() {
514 let source_file = File::from_string("ruby", "object&.method()");
515 let tree = source_file.parse();
516 let call = call_node(&tree);
517 let language = Ruby::default();
518
519 assert_eq!(
520 language.call_identifiers(&source_file, &call),
521 (Some("object".into()), "method".into())
522 );
523 }
524
525 #[test]
526 fn call_with_splat() {
527 let source_file = File::from_string("ruby", "foo(*args)");
528 let tree = source_file.parse();
529 let call = call_node(&tree);
530 let language = Ruby::default();
531
532 assert_eq!(
533 language.call_identifiers(&source_file, &call),
534 (Some("self".into()), "foo".into())
535 );
536 }
537
538 #[test]
539 fn tap_method() {
540 let source_file = File::from_string("ruby", "some_object.tap { |x| puts x }");
541 let tree = source_file.parse();
542 let call = call_node(&tree);
543 let language = Ruby::default();
544
545 assert_eq!(
546 language.call_identifiers(&source_file, &call),
547 (Some("some_object".into()), "tap".into())
548 );
549 }
550
551 #[test]
552 fn call_member() {
553 let source_file = File::from_string("ruby", "foo.bar()");
554 let tree = source_file.parse();
555 let call = call_node(&tree);
556 let language = Ruby::default();
557
558 assert_eq!(
559 language.call_identifiers(&source_file, &call),
560 (Some("foo".into()), "bar".into())
561 );
562 }
563
564 fn call_node(tree: &Tree) -> Node {
565 let root_node = tree.root_node();
566 let expression = root_node.named_child(0).unwrap();
567 return expression;
568 }
569}