codesynapse_core/ts_extract/
ruby.rs1use super::{add_contains_edge, add_node_if_missing, make_file_node, run_query_matches_ranged};
2use crate::error::Result;
3use crate::extract::{make_id, ImportNode, LanguageExtractor};
4use crate::types::{Edge, ExtractionFragment, Node};
5use std::collections::HashMap;
6use std::path::Path;
7
8pub struct TsRubyExtractor;
9
10impl TsRubyExtractor {
11 pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
12 let (file_id, _, file_node) = make_file_node(path);
13 let mut fragment = ExtractionFragment {
14 nodes: vec![file_node],
15 edges: vec![],
16 };
17
18 let lang = tree_sitter_ruby::LANGUAGE.into();
19
20 let class_query = r#"
22 (class
23 name: (constant) @class.name
24 superclass: (superclass (constant) @class.super)?
25 ) @class.node
26 "#;
27 if let Ok(matches) = run_query_matches_ranged(source, &lang, class_query) {
28 for (texts, ranges) in &matches {
29 let name = match texts.get("class.name") {
30 Some(n) => n.clone(),
31 None => continue,
32 };
33 let source_location = ranges
34 .get("class.node")
35 .map(|(s, e)| format!("{}:{}", s, e));
36 let class_id = make_id(&[&file_id, &name]);
37 fragment.nodes.push(Node {
38 id: class_id.clone(),
39 label: name.clone(),
40 file_type: "class".to_string(),
41 source_file: path.to_string_lossy().to_string(),
42 source_location,
43 community: None,
44 rationale: None,
45 docstring: None,
46 metadata: {
47 let mut m = HashMap::new();
48 m.insert("kind".to_string(), "class".to_string());
49 m
50 },
51 });
52 add_contains_edge(&mut fragment, &file_id, class_id.clone(), path);
53
54 if let Some(super_name) = texts.get("class.super") {
55 let super_id = make_id(&[super_name]);
56 add_node_if_missing(
57 &mut fragment,
58 Node {
59 id: super_id.clone(),
60 label: super_name.clone(),
61 file_type: "code".to_string(),
62 source_file: path.to_string_lossy().to_string(),
63 source_location: None,
64 community: None,
65 rationale: None,
66 docstring: None,
67 metadata: HashMap::new(),
68 },
69 );
70 fragment.edges.push(Edge {
71 source: class_id,
72 target: super_id,
73 relation: "inherits".to_string(),
74 confidence: "EXTRACTED".to_string(),
75 source_file: Some(path.to_string_lossy().to_string()),
76 weight: 1.0,
77 context: None,
78 });
79 }
80 }
81 }
82
83 let method_query = r#"
85 (method name: (identifier) @fn.name) @fn.node
86 "#;
87 if let Ok(matches) = run_query_matches_ranged(source, &lang, method_query) {
88 for (texts, ranges) in &matches {
89 let name = match texts.get("fn.name") {
90 Some(n) => n.clone(),
91 None => continue,
92 };
93 let source_location = ranges.get("fn.node").map(|(s, e)| format!("{}:{}", s, e));
94 let method_id = make_id(&[&file_id, &name]);
95 let label = format!("{}()", name);
96 add_node_if_missing(
97 &mut fragment,
98 Node {
99 id: method_id.clone(),
100 label,
101 file_type: "method".to_string(),
102 source_file: path.to_string_lossy().to_string(),
103 source_location,
104 community: None,
105 rationale: None,
106 docstring: None,
107 metadata: {
108 let mut m = HashMap::new();
109 m.insert("kind".to_string(), "method".to_string());
110 m
111 },
112 },
113 );
114 add_contains_edge(&mut fragment, &file_id, method_id, path);
115 }
116 }
117
118 let singleton_query = r#"
120 (singleton_method name: (identifier) @fn.name) @fn.node
121 "#;
122 if let Ok(matches) = run_query_matches_ranged(source, &lang, singleton_query) {
123 for (texts, ranges) in &matches {
124 let name = match texts.get("fn.name") {
125 Some(n) => n.clone(),
126 None => continue,
127 };
128 let source_location = ranges.get("fn.node").map(|(s, e)| format!("{}:{}", s, e));
129 let method_id = make_id(&[&file_id, "self", &name]);
130 let label = format!("self.{}()", name);
131 add_node_if_missing(
132 &mut fragment,
133 Node {
134 id: method_id.clone(),
135 label,
136 file_type: "method".to_string(),
137 source_file: path.to_string_lossy().to_string(),
138 source_location,
139 community: None,
140 rationale: None,
141 docstring: None,
142 metadata: {
143 let mut m = HashMap::new();
144 m.insert("kind".to_string(), "method".to_string());
145 m
146 },
147 },
148 );
149 add_contains_edge(&mut fragment, &file_id, method_id, path);
150 }
151 }
152
153 let class_method_query = r#"
155 (class
156 name: (constant) @class.name
157 body: (body_statement
158 (method name: (identifier) @method.name)
159 )
160 )
161 "#;
162 if let Ok(matches) = run_query_matches_ranged(source, &lang, class_method_query) {
163 for (texts, _) in &matches {
164 let class_name = match texts.get("class.name") {
165 Some(n) => n.clone(),
166 None => continue,
167 };
168 let method_name = match texts.get("method.name") {
169 Some(n) => n.clone(),
170 None => continue,
171 };
172 let class_id = make_id(&[&file_id, &class_name]);
173 let method_id = make_id(&[&file_id, &method_name]);
174 fragment.edges.push(Edge {
175 source: class_id,
176 target: method_id,
177 relation: "contains".to_string(),
178 confidence: "EXTRACTED".to_string(),
179 source_file: Some(path.to_string_lossy().to_string()),
180 weight: 1.0,
181 context: None,
182 });
183 }
184 }
185
186 let class_singleton_query = r#"
188 (class
189 name: (constant) @class.name
190 body: (body_statement
191 (singleton_method name: (identifier) @method.name)
192 )
193 )
194 "#;
195 if let Ok(matches) = run_query_matches_ranged(source, &lang, class_singleton_query) {
196 for (texts, _) in &matches {
197 let class_name = match texts.get("class.name") {
198 Some(n) => n.clone(),
199 None => continue,
200 };
201 let method_name = match texts.get("method.name") {
202 Some(n) => n.clone(),
203 None => continue,
204 };
205 let class_id = make_id(&[&file_id, &class_name]);
206 let method_id = make_id(&[&file_id, "self", &method_name]);
207 fragment.edges.push(Edge {
208 source: class_id,
209 target: method_id,
210 relation: "contains".to_string(),
211 confidence: "EXTRACTED".to_string(),
212 source_file: Some(path.to_string_lossy().to_string()),
213 weight: 1.0,
214 context: None,
215 });
216 }
217 }
218
219 Ok(fragment)
220 }
221}
222
223impl LanguageExtractor for TsRubyExtractor {
224 fn file_extensions(&self) -> Vec<&'static str> {
225 vec!["rb"]
226 }
227 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
228 Self::extract(source, path)
229 }
230 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
231 vec![]
232 }
233 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
234}