1use std::ops::Range;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct SourceLocation {
13 pub file: String,
15 pub line: usize,
17 pub column: usize,
19}
20
21impl SourceLocation {
22 pub fn new(file: String, line: usize, column: usize) -> Self {
24 Self { file, line, column }
25 }
26
27 pub fn at_start(file: String) -> Self {
29 Self { file, line: 1, column: 1 }
30 }
31
32 pub fn without_file(line: usize, column: usize) -> Self {
34 Self {
35 file: String::new(),
36 line,
37 column,
38 }
39 }
40}
41
42pub struct SourceMapper<'a> {
44 source: &'a str,
45}
46
47impl<'a> SourceMapper<'a> {
48 pub fn new(source: &'a str) -> Self {
50 Self { source }
51 }
52
53 pub fn span_to_location(&self, span: &Range<usize>, file: String) -> SourceLocation {
55 let (line, column) = self.span_to_position(span);
56 SourceLocation::new(file, line, column)
57 }
58
59 pub fn span_to_position(&self, span: &Range<usize>) -> (usize, usize) {
61 let start = span.start;
62 let mut line = 1;
63 let mut col = 1;
64
65 for (i, ch) in self.source.char_indices() {
66 if i >= start {
67 break;
68 }
69 if ch == '\n' {
70 line += 1;
71 col = 1;
72 } else {
73 col += 1;
74 }
75 }
76
77 (line, col)
78 }
79
80 pub fn optional_span_to_location(
82 &self,
83 span: Option<&Range<usize>>,
84 file: String,
85 ) -> SourceLocation {
86 match span {
87 Some(s) => self.span_to_location(s, file),
88 None => SourceLocation::at_start(file),
89 }
90 }
91
92 pub fn optional_span_to_position(&self, span: Option<&Range<usize>>) -> (usize, usize) {
94 span.map(|s| self.span_to_position(s)).unwrap_or((1, 1))
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum BlockContext {
101 Action(String),
103 Variable(String),
105 Signer(String),
107 Output(String),
109 Flow(String),
111 Addon(String),
113 Unknown,
115}
116
117impl BlockContext {
118 pub fn name(&self) -> Option<&str> {
120 match self {
121 BlockContext::Action(name)
122 | BlockContext::Variable(name)
123 | BlockContext::Signer(name)
124 | BlockContext::Output(name)
125 | BlockContext::Flow(name)
126 | BlockContext::Addon(name) => Some(name),
127 BlockContext::Unknown => None,
128 }
129 }
130
131 pub fn block_type(&self) -> &str {
133 use crate::types::ConstructType;
134
135 match self {
136 BlockContext::Action(_) => ConstructType::Action.into(),
137 BlockContext::Variable(_) => ConstructType::Variable.into(),
138 BlockContext::Signer(_) => ConstructType::Signer.into(),
139 BlockContext::Output(_) => ConstructType::Output.into(),
140 BlockContext::Flow(_) => ConstructType::Flow.into(),
141 BlockContext::Addon(_) => ConstructType::Addon.into(),
142 BlockContext::Unknown => "unknown",
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum ReferenceType {
150 Input,
152 Variable,
154 Action,
156 Signer,
158 FlowInput,
160 Output,
162}
163
164#[derive(Debug, Clone)]
166pub struct InputReference {
167 pub name: String,
169 pub full_path: String,
171 pub location: SourceLocation,
173 pub context: BlockContext,
175 pub reference_type: ReferenceType,
177}
178
179impl InputReference {
180 pub fn new(
182 name: String,
183 full_path: String,
184 location: SourceLocation,
185 context: BlockContext,
186 reference_type: ReferenceType,
187 ) -> Self {
188 Self {
189 name,
190 full_path,
191 location,
192 context,
193 reference_type,
194 }
195 }
196
197 pub fn input(name: String, location: SourceLocation, context: BlockContext) -> Self {
199 let full_path = format!("input.{}", name);
200 Self::new(name, full_path, location, context, ReferenceType::Input)
201 }
202
203 pub fn variable(name: String, location: SourceLocation, context: BlockContext) -> Self {
205 let full_path = format!("var.{}", name);
206 Self::new(name, full_path, location, context, ReferenceType::Variable)
207 }
208
209 pub fn flow_input(name: String, location: SourceLocation, context: BlockContext) -> Self {
211 let full_path = format!("flow.{}", name);
212 Self::new(name, full_path, location, context, ReferenceType::FlowInput)
213 }
214
215 pub fn action(name: String, location: SourceLocation, context: BlockContext) -> Self {
217 let full_path = format!("action.{}", name);
218 Self::new(name, full_path, location, context, ReferenceType::Action)
219 }
220
221 pub fn signer(name: String, location: SourceLocation, context: BlockContext) -> Self {
223 let full_path = format!("signer.{}", name);
224 Self::new(name, full_path, location, context, ReferenceType::Signer)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_source_location_new() {
234 let loc = SourceLocation::new("test.tx".to_string(), 10, 5);
235 assert_eq!(loc.file, "test.tx");
236 assert_eq!(loc.line, 10);
237 assert_eq!(loc.column, 5);
238 }
239
240 #[test]
241 fn test_source_location_at_start() {
242 let loc = SourceLocation::at_start("test.tx".to_string());
243 assert_eq!(loc.line, 1);
244 assert_eq!(loc.column, 1);
245 }
246
247 #[test]
248 fn test_source_mapper_simple() {
249 let source = "hello world";
250 let mapper = SourceMapper::new(source);
251
252 let (line, col) = mapper.span_to_position(&(0..5));
253 assert_eq!(line, 1);
254 assert_eq!(col, 1);
255
256 let (line, col) = mapper.span_to_position(&(6..11));
257 assert_eq!(line, 1);
258 assert_eq!(col, 7);
259 }
260
261 #[test]
262 fn test_source_mapper_multiline() {
263 let source = "line 1\nline 2\nline 3";
264 let mapper = SourceMapper::new(source);
265
266 let (line, col) = mapper.span_to_position(&(0..1));
268 assert_eq!(line, 1);
269 assert_eq!(col, 1);
270
271 let (line, col) = mapper.span_to_position(&(7..8));
273 assert_eq!(line, 2);
274 assert_eq!(col, 1);
275
276 let (line, col) = mapper.span_to_position(&(14..15));
278 assert_eq!(line, 3);
279 assert_eq!(col, 1);
280 }
281
282 #[test]
283 fn test_source_mapper_newline_boundary() {
284 let source = "abc\ndefg";
285 let mapper = SourceMapper::new(source);
286
287 let (line, col) = mapper.span_to_position(&(3..4));
289 assert_eq!(line, 1);
290 assert_eq!(col, 4);
291
292 let (line, col) = mapper.span_to_position(&(4..5));
294 assert_eq!(line, 2);
295 assert_eq!(col, 1);
296 }
297
298 #[test]
299 fn test_source_mapper_optional_none() {
300 let source = "test";
301 let mapper = SourceMapper::new(source);
302
303 let loc = mapper.optional_span_to_location(None, "test.tx".to_string());
304 assert_eq!(loc.line, 1);
305 assert_eq!(loc.column, 1);
306 }
307
308 #[test]
309 fn test_block_context_name() {
310 use crate::types::ConstructType;
311
312 let ctx = BlockContext::Action("deploy".to_string());
313 assert_eq!(ctx.name(), Some("deploy"));
314 assert_eq!(ctx.block_type(), ConstructType::Action);
315
316 let ctx = BlockContext::Unknown;
317 assert_eq!(ctx.name(), None);
318 assert_eq!(ctx.block_type(), "unknown");
319 }
320
321 #[test]
322 fn test_input_reference_constructors() {
323 let loc = SourceLocation::new("test.tx".to_string(), 5, 10);
324 let ctx = BlockContext::Action("deploy".to_string());
325
326 let input_ref = InputReference::input("api_key".to_string(), loc.clone(), ctx.clone());
327 assert_eq!(input_ref.name, "api_key");
328 assert_eq!(input_ref.full_path, "input.api_key");
329 assert_eq!(input_ref.reference_type, ReferenceType::Input);
330
331 let var_ref = InputReference::variable("my_var".to_string(), loc.clone(), ctx.clone());
332 assert_eq!(var_ref.full_path, "var.my_var");
333 assert_eq!(var_ref.reference_type, ReferenceType::Variable);
334
335 let flow_ref = InputReference::flow_input("chain_id".to_string(), loc.clone(), ctx);
336 assert_eq!(flow_ref.full_path, "flow.chain_id");
337 assert_eq!(flow_ref.reference_type, ReferenceType::FlowInput);
338 }
339
340 #[test]
341 fn test_block_context_equality() {
342 let ctx1 = BlockContext::Action("deploy".to_string());
343 let ctx2 = BlockContext::Action("deploy".to_string());
344 let ctx3 = BlockContext::Action("other".to_string());
345
346 assert_eq!(ctx1, ctx2);
347 assert_ne!(ctx1, ctx3);
348 }
349}