codesynapse_core/ts_extract/
scala.rs1use super::{add_contains_edge, add_node_if_missing, make_file_node, run_query_named};
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 TsScalaExtractor;
9
10impl TsScalaExtractor {
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_scala::LANGUAGE.into();
19
20 let class_query = r#"
21 (class_definition
22 name: (identifier) @class.name
23 )
24 "#;
25 if let Ok(mut captures) = run_query_named(source, &lang, class_query) {
26 for name in captures.remove("class.name").unwrap_or_default() {
27 let id = make_id(&[&file_id, &name]);
28 fragment.nodes.push(Node {
29 id: id.clone(),
30 label: name,
31 file_type: "code".to_string(),
32 source_file: path.to_string_lossy().to_string(),
33 source_location: None,
34 community: None,
35 rationale: None,
36 docstring: None,
37 metadata: HashMap::new(),
38 });
39 add_contains_edge(&mut fragment, &file_id, id, path);
40 }
41 }
42
43 let object_query = r#"
44 (object_definition
45 name: (identifier) @object.name
46 )
47 "#;
48 if let Ok(mut captures) = run_query_named(source, &lang, object_query) {
49 for name in captures.remove("object.name").unwrap_or_default() {
50 let id = make_id(&[&file_id, &name]);
51 add_node_if_missing(
52 &mut fragment,
53 Node {
54 id: id.clone(),
55 label: name,
56 file_type: "code".to_string(),
57 source_file: path.to_string_lossy().to_string(),
58 source_location: None,
59 community: None,
60 rationale: None,
61 docstring: None,
62 metadata: HashMap::new(),
63 },
64 );
65 add_contains_edge(&mut fragment, &file_id, id, path);
66 }
67 }
68
69 let method_query = r#"
70 (function_definition
71 name: (identifier) @method.name
72 )
73 "#;
74 if let Ok(mut captures) = run_query_named(source, &lang, method_query) {
75 for name in captures.remove("method.name").unwrap_or_default() {
76 let label = format!("{}()", name);
77 let id = make_id(&[&file_id, &name]);
78 add_node_if_missing(
79 &mut fragment,
80 Node {
81 id: id.clone(),
82 label,
83 file_type: "method".to_string(),
84 source_file: path.to_string_lossy().to_string(),
85 source_location: None,
86 community: None,
87 rationale: None,
88 docstring: None,
89 metadata: HashMap::new(),
90 },
91 );
92 add_contains_edge(&mut fragment, &file_id, id, path);
93 }
94 }
95
96 let source_str = std::str::from_utf8(source).unwrap_or("");
99 for line in source_str.lines() {
100 let trimmed = line.trim();
101 if !trimmed.starts_with("import ") {
102 continue;
103 }
104 let path_part = trimmed[7..].trim();
105 let name = path_part.split('.').next_back().unwrap_or(path_part).trim();
106 if name.is_empty() || name == "_" || name == "*" || name == "{" {
107 continue;
108 }
109 let name = name.to_string();
110 let mod_id = make_id(&[&file_id, &name]);
111 add_node_if_missing(
112 &mut fragment,
113 Node {
114 id: mod_id.clone(),
115 label: name,
116 file_type: "module".to_string(),
117 source_file: path.to_string_lossy().to_string(),
118 source_location: None,
119 community: None,
120 rationale: None,
121 docstring: None,
122 metadata: HashMap::new(),
123 },
124 );
125 fragment.edges.push(Edge {
126 source: file_id.clone(),
127 target: mod_id,
128 relation: "imports".to_string(),
129 confidence: "EXTRACTED".to_string(),
130 source_file: Some(path.to_string_lossy().to_string()),
131 weight: 1.0,
132 context: Some("import".to_string()),
133 });
134 }
135
136 let call_query = r#"
138 (call_expression
139 (identifier) @call.name
140 )
141 "#;
142 if let Ok(mut captures) = run_query_named(source, &lang, call_query) {
143 for name in captures.remove("call.name").unwrap_or_default() {
144 let callee_id = make_id(&[&file_id, &name]);
145 if fragment.nodes.iter().any(|n| n.id == callee_id) {
146 add_node_if_missing(
147 &mut fragment,
148 Node {
149 id: callee_id.clone(),
150 label: format!("{}()", name),
151 file_type: "function".to_string(),
152 source_file: path.to_string_lossy().to_string(),
153 source_location: None,
154 community: None,
155 rationale: None,
156 docstring: None,
157 metadata: HashMap::new(),
158 },
159 );
160 let already = fragment.edges.iter().any(|e| {
161 e.relation == "calls" && e.source == file_id && e.target == callee_id
162 });
163 if !already {
164 fragment.edges.push(Edge {
165 source: file_id.clone(),
166 target: callee_id,
167 relation: "calls".to_string(),
168 confidence: "EXTRACTED".to_string(),
169 source_file: Some(path.to_string_lossy().to_string()),
170 weight: 1.0,
171 context: Some("call".to_string()),
172 });
173 }
174 }
175 }
176 }
177
178 Ok(fragment)
179 }
180}
181
182impl LanguageExtractor for TsScalaExtractor {
183 fn file_extensions(&self) -> Vec<&'static str> {
184 vec!["scala"]
185 }
186 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
187 Self::extract(source, path)
188 }
189 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
190 vec![]
191 }
192 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
193}