Skip to main content

oxilean_parse/wasm_source_map/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    AbsoluteMapping, DecodedSegment, SourceMap, SourceMapOptions, SourceMapStats, SourcePos2,
7    VlqEncoder, WasmAnnotation, WasmAnnotationTable,
8};
9
10/// Base64 alphabet used by VLQ encoding.
11const 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/// Decode the VLQ-encoded mappings string from a Source Map v3 into a
91/// `Vec<Vec<DecodedSegment>>` (outer = lines, inner = segments per line).
92#[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/// Decode one VLQ segment (multiple VLQ values) into a `DecodedSegment`.
156#[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/// Convert a `SourceMap` into a flat list of `AbsoluteMapping`s.
194#[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/// Generate a JSON source map with options.
209#[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/// Compute a "compression ratio" for VLQ-encoded mapping data.
237#[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/// Render a source map as a human-readable summary.
248#[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/// Merge a list of source maps into one.
257#[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/// Validate that VLQ encoding is self-consistent for a range of values.
266#[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/// Converts a flat byte offset to a SourcePos2 using a line map.
702#[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/// A simple WASM instruction counter.
778#[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/// Returns annotations for a specific source file.
788#[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/// Returns the line range covered by annotations in a table.
801#[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/// Returns the total number of annotations in a table.
842#[allow(dead_code)]
843#[allow(missing_docs)]
844pub fn total_annotations(table: &WasmAnnotationTable) -> usize {
845    table.annotations.len()
846}
847/// Returns the maximum wasm offset in an annotation table, if any.
848#[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/// Returns the minimum wasm offset in an annotation table, if any.
854#[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/// Returns annotations sorted by wasm offset.
891#[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/// Returns unique source indices referenced in an annotation table.
899#[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}