brokk_bifrost_ruby/
syntax.rs1use brokk_bifrost_core::analyzer::model::Range;
9use tree_sitter::Node;
10
11pub fn single_static_string_content_node(node: Node<'_>) -> Option<Node<'_>> {
12 if node.named_child_count() != 1 {
13 return None;
14 }
15 let content = node.named_child(0)?;
16 (content.kind() == "string_content").then_some(content)
17}
18
19pub fn ruby_call_arguments(node: Node<'_>) -> Vec<Node<'_>> {
20 let Some(arguments) = ruby_call_arguments_node(node) else {
21 return Vec::new();
22 };
23 let mut cursor = arguments.walk();
24 arguments
25 .named_children(&mut cursor)
26 .filter(|child| is_runtime_node(child.kind()))
27 .collect()
28}
29
30pub fn ruby_first_call_argument(node: Node<'_>) -> Option<Node<'_>> {
31 let arguments = ruby_call_arguments_node(node)?;
32 let mut cursor = arguments.walk();
33 arguments
34 .named_children(&mut cursor)
35 .find(|child| is_runtime_node(child.kind()))
36}
37
38fn ruby_call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
39 node.child_by_field_name("arguments")
40}
41
42pub fn is_runtime_node(kind: &str) -> bool {
46 !matches!(
47 kind,
48 "comment"
49 | "method_parameters"
50 | "lambda_parameters"
51 | "block_parameters"
52 | "block_parameter"
53 | "optional_parameter"
54 | "keyword_parameter"
55 | "splat_parameter"
56 | "hash_splat_parameter"
57 | "forward_parameter"
58 | "destructured_parameter"
59 | "exception_variable"
60 | "hash_key_symbol"
61 | "bare_symbol"
62 )
63}
64
65pub fn ruby_semantic_identifier_range(node: Node<'_>, source: &str) -> Range {
72 let node_range = || Range {
73 start_byte: node.start_byte(),
74 end_byte: node.end_byte(),
75 start_line: node.start_position().row,
76 end_line: node.end_position().row,
77 };
78
79 match node.kind() {
80 "simple_symbol" => {
81 let text = source.get(node.start_byte()..node.end_byte()).unwrap_or("");
82 if text.strip_prefix(':').is_none_or(str::is_empty) {
83 return node_range();
84 }
85 Range {
86 start_byte: node.start_byte() + ':'.len_utf8(),
87 end_byte: node.end_byte(),
88 start_line: node.start_position().row,
89 end_line: node.end_position().row,
90 }
91 }
92 "delimited_symbol" => {
93 let Some(content) = single_static_string_content_node(node) else {
94 return node_range();
95 };
96 Range {
97 start_byte: content.start_byte(),
98 end_byte: content.end_byte(),
99 start_line: content.start_position().row,
100 end_line: content.end_position().row,
101 }
102 }
103 _ => node_range(),
104 }
105}