1use crate::facts::RawNode;
7use icb_common::{IcbError, Language, NodeKind};
8use tree_sitter::Parser;
9
10use super::common::{child_of_kind, traverse_node};
11
12pub fn parse_ruby(source: &str) -> Result<Vec<RawNode>, IcbError> {
14 let mut parser = Parser::new();
15 parser
16 .set_language(&tree_sitter_ruby::language())
17 .map_err(|e| IcbError::Parse(format!("cannot set tree-sitter-ruby language: {e}")))?;
18
19 let tree = parser
20 .parse(source, None)
21 .ok_or_else(|| IcbError::Parse("tree-sitter parse returned None for Ruby source".into()))?;
22
23 let mut facts = Vec::new();
24
25 let classifier = |node: &tree_sitter::Node,
26 source: &str|
27 -> Option<(NodeKind, Option<String>, bool)> {
28 match node.kind() {
29 "method" | "singleton_method" => {
30 let name = child_of_kind(*node, "identifier")
31 .and_then(|n| n.utf8_text(source.as_bytes()).ok())
32 .map(|s| s.to_string());
33 Some((NodeKind::Function, name, true))
34 }
35 "class" | "module" => {
36 let name = child_of_kind(*node, "constant")
37 .and_then(|n| n.utf8_text(source.as_bytes()).ok())
38 .map(|s| s.to_string());
39 Some((NodeKind::Class, name, true))
40 }
41 "call" => {
42 let name_node = child_of_kind(*node, "identifier")
43 .or_else(|| child_of_kind(*node, "constant"))
44 .or_else(|| child_of_kind(*node, "method_identifier"));
45 let name = name_node
46 .and_then(|n| n.utf8_text(source.as_bytes()).ok())
47 .map(|s| s.to_string());
48 Some((NodeKind::CallSite, name, false))
49 }
50 "lambda" => Some((NodeKind::Function, Some("lambda".into()), true)),
51 "do_block" | "brace_block" => Some((NodeKind::Function, Some("block".into()), true)),
52 _ => None,
53 }
54 };
55
56 traverse_node(
57 tree.root_node(),
58 source,
59 &mut facts,
60 None,
61 Language::Ruby,
62 &classifier,
63 );
64 Ok(facts)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use icb_common::NodeKind;
71
72 #[test]
73 fn test_simple_method() {
74 let code = "def foo; end";
75 let facts = parse_ruby(code).unwrap();
76 let funcs: Vec<_> = facts
77 .iter()
78 .filter(|n| n.kind == NodeKind::Function)
79 .collect();
80 assert_eq!(funcs.len(), 1);
81 assert_eq!(funcs[0].name.as_deref(), Some("foo"));
82 }
83
84 #[test]
85 fn test_class() {
86 let code = "class MyClass; end";
87 let facts = parse_ruby(code).unwrap();
88 let classes: Vec<_> = facts
89 .iter()
90 .filter(|n| n.kind == NodeKind::Class && n.name.as_deref() == Some("MyClass"))
91 .collect();
92 assert!(!classes.is_empty(), "expected class MyClass");
93 }
94
95 #[test]
96 fn test_call() {
97 let code = "puts 'hello'";
98 let facts = parse_ruby(code).unwrap();
99 let calls: Vec<_> = facts
100 .iter()
101 .filter(|n| n.kind == NodeKind::CallSite)
102 .collect();
103 assert_eq!(calls.len(), 1);
104 assert_eq!(calls[0].name.as_deref(), Some("puts"));
105 }
106
107 #[test]
108 fn test_lambda() {
109 let code = "-> { }";
110 let facts = parse_ruby(code).unwrap();
111 let lambdas: Vec<_> = facts
112 .iter()
113 .filter(|n| n.name.as_deref() == Some("lambda"))
114 .collect();
115 assert!(!lambdas.is_empty(), "expected at least one lambda");
116 }
117}