1use super::types::{
6 AbsoluteMapping, DecodedSegment, SourceMap, SourceMapOptions, SourceMapStats, SourcePos2,
7 VlqEncoder, WasmAnnotation, WasmAnnotationTable,
8};
9
10const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
12#[cfg(test)]
13mod tests {
14 use super::*;
15 use crate::wasm_source_map::*;
16 #[test]
17 fn test_source_mapping_new() {
18 let m = SourceMapping::new(1, 2, 3, 4, "foo.oxilean");
19 assert_eq!(m.generated_line, 1);
20 assert_eq!(m.generated_col, 2);
21 assert_eq!(m.source_line, 3);
22 assert_eq!(m.source_col, 4);
23 assert_eq!(m.source_file, "foo.oxilean");
24 }
25 #[test]
26 fn test_vlq_encode_zero() {
27 let encoded = VlqEncoder::encode_vlq(0);
28 assert_eq!(encoded, b"A");
29 }
30 #[test]
31 fn test_vlq_encode_positive() {
32 let encoded = VlqEncoder::encode_vlq(1);
33 assert_eq!(encoded, b"C");
34 }
35 #[test]
36 fn test_vlq_encode_negative() {
37 let encoded = VlqEncoder::encode_vlq(-1);
38 assert_eq!(encoded, b"D");
39 }
40 #[test]
41 fn test_vlq_roundtrip() {
42 for val in [-100i64, -1, 0, 1, 42, 1000, 65535] {
43 let encoded = VlqEncoder::encode_vlq(val);
44 let (decoded, _) = VlqEncoder::decode_vlq(&encoded);
45 assert_eq!(decoded, val, "roundtrip failed for {val}");
46 }
47 }
48 #[test]
49 fn test_source_map_add_source_dedup() {
50 let mut sm = SourceMap::new();
51 let idx1 = sm.add_source("a.lean");
52 let idx2 = sm.add_source("b.lean");
53 let idx3 = sm.add_source("a.lean");
54 assert_eq!(idx1, 0);
55 assert_eq!(idx2, 1);
56 assert_eq!(idx3, 0);
57 assert_eq!(sm.sources.len(), 2);
58 }
59 #[test]
60 fn test_source_map_lookup_source() {
61 let mut sm = SourceMap::new();
62 sm.add_source("f.lean");
63 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "f.lean"));
64 sm.add_mapping(SourceMapping::new(0, 5, 1, 5, "f.lean"));
65 sm.add_mapping(SourceMapping::new(1, 0, 2, 0, "f.lean"));
66 let found = sm.lookup_source(0, 7);
67 assert!(found.is_some());
68 assert_eq!(
69 found.expect("test operation should succeed").generated_col,
70 5
71 );
72 assert!(sm.lookup_source(5, 0).is_none());
73 }
74 #[test]
75 fn test_wasm_source_map_builder() {
76 let mut builder = WasmSourceMapBuilder::new("main.lean");
77 builder.record_token(0, 0, 1, 0);
78 builder.record_token(0, 4, 1, 4);
79 let sm = builder.build();
80 assert_eq!(sm.mappings.len(), 2);
81 assert_eq!(sm.sources[0], "main.lean");
82 }
83 #[test]
84 fn test_source_map_to_json_version() {
85 let sm = SourceMap::new();
86 let json = sm.to_json();
87 assert!(json.contains("\"version\":3"));
88 }
89}
90#[allow(dead_code)]
93pub fn decode_mappings(mappings: &str) -> Vec<Vec<DecodedSegment>> {
94 let mut result: Vec<Vec<DecodedSegment>> = Vec::new();
95 let mut current_line: Vec<DecodedSegment> = Vec::new();
96 let mut prev_gen_col: i64 = 0;
97 let mut prev_src_file: i64 = 0;
98 let mut prev_src_line: i64 = 0;
99 let mut prev_src_col: i64 = 0;
100 let mut segment_bytes = Vec::new();
101 for ch in mappings.chars() {
102 match ch {
103 ';' => {
104 if !segment_bytes.is_empty() {
105 if let Some(seg) = decode_one_segment(
106 &segment_bytes,
107 &mut prev_gen_col,
108 &mut prev_src_file,
109 &mut prev_src_line,
110 &mut prev_src_col,
111 ) {
112 current_line.push(seg);
113 }
114 segment_bytes.clear();
115 }
116 result.push(current_line.clone());
117 current_line.clear();
118 prev_gen_col = 0;
119 }
120 ',' => {
121 if !segment_bytes.is_empty() {
122 if let Some(seg) = decode_one_segment(
123 &segment_bytes,
124 &mut prev_gen_col,
125 &mut prev_src_file,
126 &mut prev_src_line,
127 &mut prev_src_col,
128 ) {
129 current_line.push(seg);
130 }
131 segment_bytes.clear();
132 }
133 }
134 _ => {
135 segment_bytes.push(ch as u8);
136 }
137 }
138 }
139 if !segment_bytes.is_empty() {
140 if let Some(seg) = decode_one_segment(
141 &segment_bytes,
142 &mut prev_gen_col,
143 &mut prev_src_file,
144 &mut prev_src_line,
145 &mut prev_src_col,
146 ) {
147 current_line.push(seg);
148 }
149 }
150 if !current_line.is_empty() {
151 result.push(current_line);
152 }
153 result
154}
155#[allow(dead_code)]
157pub(super) fn decode_one_segment(
158 bytes: &[u8],
159 prev_gen_col: &mut i64,
160 prev_src_file: &mut i64,
161 prev_src_line: &mut i64,
162 prev_src_col: &mut i64,
163) -> Option<DecodedSegment> {
164 let mut values = Vec::new();
165 let mut pos = 0;
166 while pos < bytes.len() {
167 let (val, consumed) = VlqEncoder::decode_vlq(&bytes[pos..]);
168 if consumed == 0 {
169 break;
170 }
171 values.push(val);
172 pos += consumed;
173 }
174 if values.is_empty() {
175 return None;
176 }
177 let gen_col_delta = values[0];
178 *prev_gen_col += gen_col_delta;
179 if values.len() >= 4 {
180 *prev_src_file += values[1];
181 *prev_src_line += values[2];
182 *prev_src_col += values[3];
183 Some(DecodedSegment::full(
184 *prev_gen_col,
185 *prev_src_file,
186 *prev_src_line,
187 *prev_src_col,
188 ))
189 } else {
190 Some(DecodedSegment::generated_only(*prev_gen_col))
191 }
192}
193#[allow(dead_code)]
195pub fn to_absolute_mappings(sm: &SourceMap) -> Vec<AbsoluteMapping> {
196 sm.mappings
197 .iter()
198 .map(|m| {
199 let idx = sm
200 .sources
201 .iter()
202 .position(|s| *s == m.source_file)
203 .unwrap_or(0);
204 AbsoluteMapping::from_mapping(m, idx)
205 })
206 .collect()
207}
208#[allow(dead_code)]
210pub fn generate_source_map_json(sm: &SourceMap, opts: &SourceMapOptions) -> String {
211 let sources_json = sm
212 .sources
213 .iter()
214 .map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
215 .collect::<Vec<_>>()
216 .join(",");
217 let mappings_str = sm.encode_mappings();
218 let mut fields = format!(
219 "\"version\":{},\"sources\":[{}],\"mappings\":\"{}\"",
220 sm.version, sources_json, mappings_str
221 );
222 if let Some(root) = &opts.source_root {
223 fields.push_str(&format!(",\"sourceRoot\":\"{}\"", root));
224 }
225 if !sm.names.is_empty() && opts.include_names {
226 let names_json = sm
227 .names
228 .iter()
229 .map(|n| format!("\"{}\"", n.replace('"', "\\\"")))
230 .collect::<Vec<_>>()
231 .join(",");
232 fields.push_str(&format!(",\"names\":[{}]", names_json));
233 }
234 format!("{{{}}}", fields)
235}
236#[allow(dead_code)]
238pub fn vlq_compression_ratio(unencoded_values: &[i64], encoded: &str) -> f64 {
239 let unencoded_size = unencoded_values.len() * 8;
240 let encoded_size = encoded.len();
241 if unencoded_size == 0 {
242 1.0
243 } else {
244 encoded_size as f64 / unencoded_size as f64
245 }
246}
247#[allow(dead_code)]
249pub fn summarize_source_map(sm: &SourceMap) -> String {
250 let stats = SourceMapStats::from_map(sm);
251 format!(
252 "SourceMap v{}: {} source(s), {} mapping(s) across {} line(s), ~{} bytes encoded",
253 sm.version, stats.source_count, stats.mapping_count, stats.line_count, stats.encoded_size
254 )
255}
256#[allow(dead_code)]
258pub fn merge_source_maps(maps: Vec<SourceMap>) -> SourceMap {
259 let mut result = SourceMap::new();
260 for sm in maps {
261 result.merge(&sm);
262 }
263 result
264}
265#[allow(dead_code)]
267pub fn validate_vlq_codec(values: &[i64]) -> bool {
268 for &v in values {
269 let encoded = VlqEncoder::encode_vlq(v);
270 let (decoded, _) = VlqEncoder::decode_vlq(&encoded);
271 if decoded != v {
272 return false;
273 }
274 }
275 true
276}
277#[cfg(test)]
278mod extended_wasm_tests {
279 use super::*;
280 use crate::wasm_source_map::*;
281 #[test]
282 fn test_decoded_segment_full() {
283 let s = DecodedSegment::full(0, 0, 1, 0);
284 assert!(s.has_source());
285 assert_eq!(s.src_file, Some(0));
286 }
287 #[test]
288 fn test_decoded_segment_generated_only() {
289 let s = DecodedSegment::generated_only(3);
290 assert!(!s.has_source());
291 assert_eq!(s.gen_col, 3);
292 }
293 #[test]
294 fn test_source_position_display() {
295 let p = SourcePosition::new("foo.lean", 3, 5);
296 assert_eq!(format!("{}", p), "foo.lean:3:5");
297 }
298 #[test]
299 fn test_generated_position_display() {
300 let p = GeneratedPosition::new(1, 2);
301 assert_eq!(format!("{}", p), "1:2");
302 }
303 #[test]
304 fn test_source_map_add_name() {
305 let mut sm = SourceMap::new();
306 let i1 = sm.add_name("myFunc");
307 let i2 = sm.add_name("otherFunc");
308 let i3 = sm.add_name("myFunc");
309 assert_eq!(i1, 0);
310 assert_eq!(i2, 1);
311 assert_eq!(i3, 0);
312 }
313 #[test]
314 fn test_source_map_validate_ok() {
315 let mut sm = SourceMap::new();
316 sm.add_source("a.lean");
317 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "a.lean"));
318 assert!(sm.validate().is_ok());
319 }
320 #[test]
321 fn test_source_map_validate_err() {
322 let mut sm = SourceMap::new();
323 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "unknown.lean"));
324 assert!(sm.validate().is_err());
325 }
326 #[test]
327 fn test_source_map_sort_mappings() {
328 let mut sm = SourceMap::new();
329 sm.add_source("f.lean");
330 sm.add_mapping(SourceMapping::new(0, 5, 1, 5, "f.lean"));
331 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "f.lean"));
332 sm.sort_mappings();
333 assert_eq!(sm.mappings[0].generated_col, 0);
334 assert_eq!(sm.mappings[1].generated_col, 5);
335 }
336 #[test]
337 fn test_source_map_merge() {
338 let mut sm1 = SourceMap::new();
339 sm1.add_source("a.lean");
340 sm1.add_mapping(SourceMapping::new(0, 0, 1, 0, "a.lean"));
341 let mut sm2 = SourceMap::new();
342 sm2.add_source("b.lean");
343 sm2.add_mapping(SourceMapping::new(1, 0, 2, 0, "b.lean"));
344 sm1.merge(&sm2);
345 assert_eq!(sm1.mapping_count(), 2);
346 assert_eq!(sm1.source_count(), 2);
347 }
348 #[test]
349 fn test_base64_encode_decode_roundtrip() {
350 let data = b"hello source map world";
351 let encoded = Base64Util::encode(data);
352 let decoded = Base64Util::decode(&encoded);
353 assert_eq!(decoded, data);
354 }
355 #[test]
356 fn test_base64_encode_empty() {
357 let encoded = Base64Util::encode(b"");
358 assert!(encoded.is_empty());
359 }
360 #[test]
361 fn test_vlq_stream_push_finish() {
362 let mut stream = VlqStream::new();
363 stream.push(0);
364 stream.push(1);
365 stream.push(-1);
366 let s = stream.finish();
367 assert!(!s.is_empty());
368 }
369 #[test]
370 fn test_vlq_stream_empty() {
371 let stream = VlqStream::new();
372 assert!(stream.is_empty());
373 }
374 #[test]
375 fn test_source_map_stats_from_map() {
376 let mut sm = SourceMap::new();
377 sm.add_source("x.lean");
378 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "x.lean"));
379 sm.add_mapping(SourceMapping::new(0, 5, 1, 5, "x.lean"));
380 let stats = SourceMapStats::from_map(&sm);
381 assert_eq!(stats.mapping_count, 2);
382 assert_eq!(stats.source_count, 1);
383 }
384 #[test]
385 fn test_source_map_stats_display() {
386 let sm = SourceMap::new();
387 let stats = SourceMapStats::from_map(&sm);
388 let s = format!("{}", stats);
389 assert!(s.contains("SourceMapStats"));
390 }
391 #[test]
392 fn test_summarize_source_map() {
393 let sm = SourceMap::new();
394 let s = summarize_source_map(&sm);
395 assert!(s.contains("SourceMap"));
396 }
397 #[test]
398 fn test_merge_source_maps() {
399 let mut sm1 = SourceMap::new();
400 sm1.add_source("a.lean");
401 sm1.add_mapping(SourceMapping::new(0, 0, 1, 0, "a.lean"));
402 let mut sm2 = SourceMap::new();
403 sm2.add_source("b.lean");
404 sm2.add_mapping(SourceMapping::new(1, 0, 2, 0, "b.lean"));
405 let merged = merge_source_maps(vec![sm1, sm2]);
406 assert_eq!(merged.mapping_count(), 2);
407 }
408 #[test]
409 fn test_validate_vlq_codec() {
410 let vals = vec![-100, -1, 0, 1, 42, 1000];
411 assert!(validate_vlq_codec(&vals));
412 }
413 #[test]
414 fn test_source_range_contains() {
415 let r = SourceRange::new(
416 SourcePosition::new("f.lean", 1, 0),
417 SourcePosition::new("f.lean", 3, 10),
418 );
419 assert!(r.contains_position(&SourcePosition::new("f.lean", 2, 5)));
420 assert!(!r.contains_position(&SourcePosition::new("f.lean", 5, 0)));
421 assert!(!r.contains_position(&SourcePosition::new("other.lean", 2, 5)));
422 }
423 #[test]
424 fn test_source_range_display() {
425 let r = SourceRange::new(
426 SourcePosition::new("f.lean", 1, 0),
427 SourcePosition::new("f.lean", 2, 5),
428 );
429 let s = format!("{}", r);
430 assert!(s.contains("->"));
431 }
432 #[test]
433 fn test_source_map_diff_empty() {
434 let sm = SourceMap::new();
435 let diff = SourceMapDiff::compute(&sm, &sm);
436 assert!(diff.is_empty());
437 assert_eq!(diff.change_count(), 0);
438 }
439 #[test]
440 fn test_source_map_diff_added() {
441 let sm_old = SourceMap::new();
442 let mut sm_new = SourceMap::new();
443 sm_new.add_source("a.lean");
444 sm_new.add_mapping(SourceMapping::new(0, 0, 1, 0, "a.lean"));
445 let diff = SourceMapDiff::compute(&sm_old, &sm_new);
446 assert_eq!(diff.added.len(), 1);
447 assert_eq!(diff.removed.len(), 0);
448 }
449 #[test]
450 fn test_source_map_diff_removed() {
451 let mut sm_old = SourceMap::new();
452 sm_old.add_source("a.lean");
453 sm_old.add_mapping(SourceMapping::new(0, 0, 1, 0, "a.lean"));
454 let sm_new = SourceMap::new();
455 let diff = SourceMapDiff::compute(&sm_old, &sm_new);
456 assert_eq!(diff.removed.len(), 1);
457 assert_eq!(diff.added.len(), 0);
458 }
459 #[test]
460 fn test_source_map_index_build_and_lookup() {
461 let mut sm = SourceMap::new();
462 sm.add_source("f.lean");
463 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "f.lean"));
464 sm.add_mapping(SourceMapping::new(0, 5, 1, 5, "f.lean"));
465 sm.add_mapping(SourceMapping::new(1, 0, 2, 0, "f.lean"));
466 let idx = SourceMapIndex::build(&sm);
467 assert_eq!(idx.len(), 3);
468 let found = idx.lookup(0, 3);
469 assert!(found.is_some());
470 }
471 #[test]
472 fn test_source_map_index_empty() {
473 let sm = SourceMap::new();
474 let idx = SourceMapIndex::build(&sm);
475 assert!(idx.is_empty());
476 assert!(idx.lookup(0, 0).is_none());
477 }
478 #[test]
479 fn test_multi_file_source_map() {
480 let mut mf = MultiFileSourceMap::new();
481 let sm = SourceMap::new();
482 mf.add("output.js", sm);
483 assert_eq!(mf.len(), 1);
484 assert!(mf.get("output.js").is_some());
485 assert!(mf.get("missing.js").is_none());
486 }
487 #[test]
488 fn test_multi_file_source_map_index_json() {
489 let mut mf = MultiFileSourceMap::new();
490 mf.add("a.js", SourceMap::new());
491 mf.add("b.js", SourceMap::new());
492 let json = mf.to_index_json();
493 assert!(json.contains("a.js"));
494 assert!(json.contains("b.js"));
495 }
496 #[test]
497 fn test_source_map_options_default() {
498 let opts = SourceMapOptions::default();
499 assert!(!opts.embed_sources);
500 assert!(!opts.include_names);
501 assert!(opts.source_root.is_none());
502 }
503 #[test]
504 fn test_source_map_options_builder() {
505 let opts = SourceMapOptions::new()
506 .with_embedded_sources()
507 .with_source_root("/src");
508 assert!(opts.embed_sources);
509 assert_eq!(opts.source_root.as_deref(), Some("/src"));
510 }
511 #[test]
512 fn test_generate_source_map_json_with_options() {
513 let mut sm = SourceMap::new();
514 sm.add_source("a.lean");
515 let opts = SourceMapOptions::new().with_source_root("/src");
516 let json = generate_source_map_json(&sm, &opts);
517 assert!(json.contains("sourceRoot"));
518 assert!(json.contains("/src"));
519 }
520 #[test]
521 fn test_reverse_source_map() {
522 let mut sm = SourceMap::new();
523 sm.add_source("f.lean");
524 sm.add_mapping(SourceMapping::new(0, 0, 5, 3, "f.lean"));
525 let rev = ReverseSourceMap::build(sm);
526 let pos = rev.original(0, 0);
527 assert!(pos.is_some());
528 let p = pos.expect("test operation should succeed");
529 assert_eq!(p.line, 5);
530 assert_eq!(p.col, 3);
531 }
532 #[test]
533 fn test_reverse_source_map_not_found() {
534 let sm = SourceMap::new();
535 let rev = ReverseSourceMap::build(sm);
536 assert!(rev.original(0, 0).is_none());
537 }
538 #[test]
539 fn test_wasm_builder_set_file() {
540 let mut builder = WasmSourceMapBuilder::new("main.lean");
541 builder.set_file("other.lean");
542 assert_eq!(builder.current_file, "other.lean");
543 assert_eq!(builder.source_map.source_count(), 2);
544 }
545 #[test]
546 fn test_wasm_builder_to_json() {
547 let builder = WasmSourceMapBuilder::new("main.lean");
548 let json = builder.to_json();
549 assert!(json.contains("version"));
550 }
551 #[test]
552 fn test_wasm_builder_mapping_count() {
553 let mut builder = WasmSourceMapBuilder::new("main.lean");
554 builder.record_token(0, 0, 1, 0);
555 builder.record_token(0, 5, 1, 5);
556 assert_eq!(builder.mapping_count(), 2);
557 }
558 #[test]
559 fn test_vlq_compression_ratio() {
560 let values: Vec<i64> = (0..10).collect();
561 let encoded = VlqEncoder::encode_segment(&values);
562 let ratio = vlq_compression_ratio(&values, &encoded);
563 assert!(ratio > 0.0);
564 }
565 #[test]
566 fn test_decode_mappings_empty() {
567 let decoded = decode_mappings("");
568 assert!(decoded.is_empty());
569 }
570 #[test]
571 fn test_to_absolute_mappings() {
572 let mut sm = SourceMap::new();
573 sm.add_source("f.lean");
574 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "f.lean"));
575 let abs = to_absolute_mappings(&sm);
576 assert_eq!(abs.len(), 1);
577 assert_eq!(abs[0].src_file, 0);
578 }
579 #[test]
580 fn test_source_to_generated_map() {
581 let mut sg = SourceToGeneratedMap::new("source.lean");
582 let mut sm = SourceMap::new();
583 sm.add_source("source.lean");
584 sm.add_mapping(SourceMapping::new(5, 3, 10, 2, "source.lean"));
585 sg.add_generated("output.wasm", sm);
586 let results = sg.find_generated(10, 2);
587 assert!(!results.is_empty());
588 }
589 #[test]
590 fn test_source_map_clear_mappings() {
591 let mut sm = SourceMap::new();
592 sm.add_source("f.lean");
593 sm.add_mapping(SourceMapping::new(0, 0, 1, 0, "f.lean"));
594 sm.clear_mappings();
595 assert_eq!(sm.mapping_count(), 0);
596 }
597}
598#[cfg(test)]
599mod wasm_sourcemap_ext_tests {
600 use super::*;
601 use crate::wasm_source_map::*;
602 #[test]
603 fn test_source_map_entry() {
604 let entry = SourceMapEntry::new(0, 0, 1, 0).with_name(2);
605 assert_eq!(entry.gen_col, 0);
606 assert_eq!(entry.name_idx, Some(2));
607 }
608 #[test]
609 fn test_source_map_group_sort() {
610 let mut group = SourceMapGroup::new();
611 group.add(SourceMapEntry::new(5, 0, 1, 5));
612 group.add(SourceMapEntry::new(0, 0, 1, 0));
613 group.sort();
614 assert_eq!(group.entries[0].gen_col, 0);
615 assert_eq!(group.entries[1].gen_col, 5);
616 }
617 #[test]
618 fn test_full_source_map() {
619 let mut sm = FullSourceMap::new();
620 let src_idx = sm.add_source("test.lean");
621 assert_eq!(src_idx, 0);
622 let mut group = SourceMapGroup::new();
623 group.add(SourceMapEntry::new(0, src_idx, 1, 0));
624 sm.add_group(group);
625 assert_eq!(sm.total_segments(), 1);
626 }
627 #[test]
628 fn test_vlq_encode() {
629 let s = VlqCodec::encode(0);
630 assert_eq!(s, "A");
631 let s2 = VlqCodec::encode(1);
632 assert!(!s2.is_empty());
633 }
634 #[test]
635 fn test_source_map_lookup() {
636 let sm = SourceMapBuilder::new()
637 .source("test.lean")
638 .map_col(0, 0, 1, 0)
639 .map_col(5, 0, 1, 5)
640 .build();
641 let entry = sm.lookup(0, 3);
642 assert!(entry.is_some());
643 assert_eq!(entry.expect("test operation should succeed").gen_col, 0);
644 let entry2 = sm.lookup(0, 7);
645 assert_eq!(entry2.expect("test operation should succeed").gen_col, 5);
646 }
647 #[test]
648 fn test_source_map_builder() {
649 let sm = SourceMapBuilder::new()
650 .source("a.lean")
651 .source("b.lean")
652 .map_col(0, 0, 1, 0)
653 .next_line()
654 .map_col(0, 1, 2, 0)
655 .build();
656 assert_eq!(sm.sources.len(), 2);
657 assert_eq!(sm.groups.len(), 2);
658 }
659}
660#[cfg(test)]
661mod wasm_sourcemap_ext2_tests {
662 use super::*;
663 use crate::wasm_source_map::*;
664 #[test]
665 fn test_source_map_merger() {
666 let sm1 = SourceMapBuilder::new()
667 .source("a.lean")
668 .map_col(0, 0, 1, 0)
669 .build();
670 let sm2 = SourceMapBuilder::new()
671 .source("b.lean")
672 .map_col(0, 0, 1, 0)
673 .build();
674 let mut merger = SourceMapMerger::new();
675 merger.merge(sm1);
676 merger.merge(sm2);
677 let merged = merger.finish();
678 assert_eq!(merged.sources.len(), 2);
679 assert_eq!(merged.groups.len(), 2);
680 }
681 #[test]
682 fn test_source_map_validator() {
683 let sm = SourceMapBuilder::new()
684 .source("a.lean")
685 .map_col(0, 0, 1, 0)
686 .build();
687 let errors = SourceMapValidator::validate_source_indices(&sm);
688 assert!(errors.is_empty());
689 }
690 #[test]
691 fn test_source_map_validator_out_of_bounds() {
692 let mut sm = FullSourceMap::new();
693 sm.add_source("a.lean");
694 let mut group = SourceMapGroup::new();
695 group.add(SourceMapEntry::new(0, 99, 1, 0));
696 sm.add_group(group);
697 let errors = SourceMapValidator::validate_source_indices(&sm);
698 assert!(!errors.is_empty());
699 }
700}
701#[allow(dead_code)]
703#[allow(missing_docs)]
704pub fn offset_to_source_pos(src: &str, offset: usize) -> SourcePos2 {
705 let mut line = 1u32;
706 let mut col = 1u32;
707 for (i, c) in src.char_indices() {
708 if i >= offset {
709 break;
710 }
711 if c == '\n' {
712 line += 1;
713 col = 1;
714 } else {
715 col += 1;
716 }
717 }
718 SourcePos2::new(line, col)
719}
720#[cfg(test)]
721mod wasm_ext3_tests {
722 use super::*;
723 use crate::wasm_source_map::*;
724 #[test]
725 fn test_source_map_stats() {
726 let sm = SourceMapBuilder::new()
727 .source("a.lean")
728 .map_col(0, 0, 1, 0)
729 .next_line()
730 .map_col(0, 0, 2, 0)
731 .build();
732 let stats = SourceMapStatsExt::from_map(&sm);
733 assert_eq!(stats.total_segments, 2);
734 assert_eq!(stats.source_count, 1);
735 let out = stats.format();
736 assert!(out.contains("segments=2"));
737 }
738 #[test]
739 fn test_source_pos2() {
740 let p1 = SourcePos2::new(1, 5);
741 let p2 = SourcePos2::new(2, 1);
742 assert!(p1.before(&p2));
743 let r = SourceRangeExt::new(p1, p2);
744 assert!(r.contains(SourcePos2::new(1, 10)));
745 assert!(!r.contains(SourcePos2::new(3, 1)));
746 }
747 #[test]
748 fn test_offset_to_source_pos() {
749 let src = "hello\nworld";
750 let pos = offset_to_source_pos(src, 6);
751 assert_eq!(pos.line, 2);
752 assert_eq!(pos.col, 1);
753 }
754}
755#[cfg(test)]
756mod wasm_annotation_tests {
757 use super::*;
758 use crate::wasm_source_map::*;
759 #[test]
760 fn test_wasm_annotation() {
761 let ann = WasmAnnotation::new(100, 0, 5, 3).with_func("myFunc");
762 assert_eq!(ann.wasm_offset, 100);
763 assert_eq!(ann.func_name.as_deref(), Some("myFunc"));
764 }
765 #[test]
766 fn test_wasm_annotation_table() {
767 let mut table = WasmAnnotationTable::new();
768 table.add(WasmAnnotation::new(0, 0, 1, 0));
769 table.add(WasmAnnotation::new(50, 0, 5, 0));
770 table.add(WasmAnnotation::new(100, 0, 10, 0));
771 let found = table.lookup(75).expect("lookup should succeed");
772 assert_eq!(found.wasm_offset, 50);
773 let found2 = table.lookup(100).expect("lookup should succeed");
774 assert_eq!(found2.line, 10);
775 }
776}
777#[allow(dead_code)]
779#[allow(missing_docs)]
780pub fn count_annotations_in_range(table: &WasmAnnotationTable, lo: u32, hi: u32) -> usize {
781 table
782 .annotations
783 .iter()
784 .filter(|a| a.wasm_offset >= lo && a.wasm_offset < hi)
785 .count()
786}
787#[allow(dead_code)]
789#[allow(missing_docs)]
790pub fn annotations_for_source(
791 table: &WasmAnnotationTable,
792 source_idx: u32,
793) -> Vec<&WasmAnnotation> {
794 table
795 .annotations
796 .iter()
797 .filter(|a| a.source_idx == source_idx)
798 .collect()
799}
800#[allow(dead_code)]
802#[allow(missing_docs)]
803pub fn annotation_line_range(table: &WasmAnnotationTable) -> Option<(u32, u32)> {
804 if table.is_empty() {
805 return None;
806 }
807 let min_line = table
808 .annotations
809 .iter()
810 .map(|a| a.line)
811 .min()
812 .expect("annotations non-empty per is_empty check above");
813 let max_line = table
814 .annotations
815 .iter()
816 .map(|a| a.line)
817 .max()
818 .expect("annotations non-empty per is_empty check above");
819 Some((min_line, max_line))
820}
821#[cfg(test)]
822mod wasm_pad {
823 use super::*;
824 use crate::wasm_source_map::*;
825 #[test]
826 fn test_count_annotations_in_range() {
827 let mut t = WasmAnnotationTable::new();
828 t.add(WasmAnnotation::new(0, 0, 1, 0));
829 t.add(WasmAnnotation::new(10, 0, 2, 0));
830 t.add(WasmAnnotation::new(100, 0, 5, 0));
831 assert_eq!(count_annotations_in_range(&t, 0, 20), 2);
832 }
833 #[test]
834 fn test_annotation_line_range() {
835 let mut t = WasmAnnotationTable::new();
836 t.add(WasmAnnotation::new(0, 0, 3, 0));
837 t.add(WasmAnnotation::new(10, 0, 7, 0));
838 assert_eq!(annotation_line_range(&t), Some((3, 7)));
839 }
840}
841#[allow(dead_code)]
843#[allow(missing_docs)]
844pub fn total_annotations(table: &WasmAnnotationTable) -> usize {
845 table.annotations.len()
846}
847#[allow(dead_code)]
849#[allow(missing_docs)]
850pub fn max_wasm_offset(table: &WasmAnnotationTable) -> Option<u32> {
851 table.annotations.iter().map(|a| a.wasm_offset).max()
852}
853#[allow(dead_code)]
855#[allow(missing_docs)]
856pub fn min_wasm_offset(table: &WasmAnnotationTable) -> Option<u32> {
857 table.annotations.iter().map(|a| a.wasm_offset).min()
858}
859#[cfg(test)]
860mod wasm_pad2 {
861 use super::*;
862 use crate::wasm_source_map::*;
863 #[test]
864 fn test_total_annotations() {
865 let mut t = WasmAnnotationTable::new();
866 t.add(WasmAnnotation::new(0, 0, 1, 0));
867 t.add(WasmAnnotation::new(4, 0, 2, 0));
868 assert_eq!(total_annotations(&t), 2);
869 }
870 #[test]
871 fn test_max_min_wasm_offset() {
872 let mut t = WasmAnnotationTable::new();
873 t.add(WasmAnnotation::new(0, 0, 1, 0));
874 t.add(WasmAnnotation::new(100, 0, 5, 0));
875 assert_eq!(max_wasm_offset(&t), Some(100));
876 assert_eq!(min_wasm_offset(&t), Some(0));
877 }
878 #[test]
879 fn test_coverage_record() {
880 let mut t = WasmAnnotationTable::new();
881 t.add(WasmAnnotation::new(0, 0, 1, 0));
882 t.add(WasmAnnotation::new(4, 0, 2, 0));
883 let mut cov = WasmCoverageRecord::new();
884 cov.mark(0);
885 assert!(cov.was_executed(0));
886 assert!(!cov.was_executed(4));
887 assert!((cov.coverage_fraction(&t) - 0.5).abs() < 1e-9);
888 }
889}
890#[allow(dead_code)]
892#[allow(missing_docs)]
893pub fn annotations_sorted_by_offset(table: &WasmAnnotationTable) -> Vec<&WasmAnnotation> {
894 let mut anns: Vec<&WasmAnnotation> = table.annotations.iter().collect();
895 anns.sort_by_key(|a| a.wasm_offset);
896 anns
897}
898#[allow(dead_code)]
900#[allow(missing_docs)]
901pub fn unique_source_indices(table: &WasmAnnotationTable) -> Vec<u32> {
902 let mut seen = std::collections::HashSet::new();
903 let mut result = Vec::new();
904 for ann in &table.annotations {
905 if seen.insert(ann.source_idx) {
906 result.push(ann.source_idx);
907 }
908 }
909 result.sort();
910 result
911}
912#[cfg(test)]
913mod wasm_pad3 {
914 use super::*;
915 use crate::wasm_source_map::*;
916 #[test]
917 fn test_wasm_offset_range() {
918 let r = WasmOffsetRange::new(10, 20);
919 assert_eq!(r.len(), 10);
920 assert!(r.contains(15));
921 assert!(!r.contains(5));
922 let r2 = WasmOffsetRange::new(15, 25);
923 let ov = r.overlap(&r2);
924 assert_eq!(ov, Some(WasmOffsetRange::new(15, 20)));
925 }
926 #[test]
927 fn test_unique_source_indices() {
928 let mut t = WasmAnnotationTable::new();
929 t.add(WasmAnnotation::new(0, 0, 1, 0));
930 t.add(WasmAnnotation::new(4, 1, 2, 0));
931 t.add(WasmAnnotation::new(8, 0, 3, 0));
932 let idxs = unique_source_indices(&t);
933 assert_eq!(idxs, vec![0, 1]);
934 }
935}