Skip to content

Commit ff2e01e

Browse files
feat: add usage examples for all crates and CI compile check
Add real-world examples that exercise the public API of each crate: - codec: roundtrip (decode/modify/encode) and vlq_primitives - sourcemap: consumer (full API) and lazy_sourcemap (on-demand parsing) - generator: compiler_output (SourceMapGenerator) and streaming (StreamingGenerator) - remapping: composition (remap/remap_streaming) and bundler_concat (ConcatBuilder) - scopes: debug_scopes (encode/decode roundtrip with inlined function) - symbolicate: error_symbolication (V8/SpiderMonkey parsing, batch, debug ID) All examples use 0-based positions per ECMA-426, include assertions as smoke tests, and serve as documentation for crate users. Also adds an examples compile-check job to CI, fixes reserved keyword `gen` in remap benchmark (Rust 2024 edition), and adds serde_json dev-dependency for remapping examples.
1 parent 5bd2287 commit ff2e01e

14 files changed

Lines changed: 2795 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ jobs:
6262
- name: Run conformance tests
6363
run: cargo test -p srcmap-sourcemap --test conformance
6464

65+
examples:
66+
name: Examples (compile check)
67+
runs-on: ubuntu-latest
68+
steps:
69+
- uses: actions/checkout@v4
70+
- uses: dtolnay/rust-toolchain@stable
71+
- uses: Swatinem/rust-cache@v2
72+
- name: Compile examples
73+
run: cargo build --examples --workspace
74+
6575
bench:
6676
name: Benchmarks (compile check)
6777
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codec/examples/roundtrip.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//! Roundtrip example: decode, inspect, manipulate, and re-encode source map mappings.
2+
//!
3+
//! Simulates what a build tool does when it needs to inspect or transform
4+
//! individual mappings — for example, prepending whitespace to every generated
5+
//! line and adjusting the source map to match.
6+
//!
7+
//! Run with: cargo run -p srcmap-codec --example roundtrip
8+
9+
use srcmap_codec::{DecodeError, Segment, decode, encode};
10+
11+
fn main() {
12+
// -----------------------------------------------------------------------
13+
// 1. Decode a realistic mappings string
14+
// -----------------------------------------------------------------------
15+
//
16+
// This mappings string represents 5 generated lines from a small bundled
17+
// module. It uses a mix of segment types:
18+
//
19+
// - 1-field segments: only a generated column (no source mapping)
20+
// - 4-field segments: generated column + source/line/column
21+
// - 5-field segments: same as 4-field + a name index
22+
//
23+
// ECMA-426 VLQ mappings are delta-encoded on the wire:
24+
// - Generated column resets to 0 at each new line (`;`)
25+
// - Source index, original line, original column, and name index
26+
// are cumulative across the entire string
27+
//
28+
// After decoding, segments contain ABSOLUTE values — the codec handles
29+
// all delta accumulation internally.
30+
//
31+
// Line 0: two 4-field segments — "AAAA,IAAA"
32+
// (col 0 -> src 0, line 0, col 0) and (col 4 -> src 0, line 0, col 0)
33+
// Line 1: one 5-field segment with a name — "EACEC"
34+
// (col 2 -> src 0, line 1, col 2, name 1)
35+
// Line 2: one 4-field segment — "IAEE"
36+
// (col 4 -> src 0, line 3, col 4)
37+
// Line 3: empty line (no mappings)
38+
// Line 4: one 1-field segment (col 0, no source info) — "A"
39+
40+
let mappings_str = "AAAA,IAAA;EACEC;IAEE;;A";
41+
42+
println!("=== Source Map Codec Roundtrip ===\n");
43+
println!("Input mappings: {mappings_str:?}\n");
44+
45+
let mappings = decode(mappings_str).expect("valid mappings string");
46+
47+
// -----------------------------------------------------------------------
48+
// 2. Inspect the decoded structure
49+
// -----------------------------------------------------------------------
50+
51+
println!("Decoded {} lines:\n", mappings.len());
52+
53+
for (line_idx, line) in mappings.iter().enumerate() {
54+
if line.is_empty() {
55+
println!(" Line {line_idx}: (empty)");
56+
continue;
57+
}
58+
59+
println!(" Line {line_idx}: {} segment(s)", line.len());
60+
61+
for (seg_idx, segment) in line.iter().enumerate() {
62+
// Segment implements Deref<Target=[i64]>, so len() and indexing work
63+
match segment.len() {
64+
1 => {
65+
println!(" [{seg_idx}] 1-field: generated_col={}", segment[0],);
66+
}
67+
4 => {
68+
println!(
69+
" [{seg_idx}] 4-field: generated_col={}, source={}, orig_line={}, orig_col={}",
70+
segment[0], segment[1], segment[2], segment[3],
71+
);
72+
}
73+
5 => {
74+
println!(
75+
" [{seg_idx}] 5-field: generated_col={}, source={}, orig_line={}, orig_col={}, name={}",
76+
segment[0], segment[1], segment[2], segment[3], segment[4],
77+
);
78+
}
79+
other => {
80+
println!(" [{seg_idx}] unexpected {other}-field segment");
81+
}
82+
}
83+
}
84+
}
85+
86+
// -----------------------------------------------------------------------
87+
// 3. Manipulate: shift all generated columns by +4
88+
// -----------------------------------------------------------------------
89+
//
90+
// This simulates a build tool that prepends " " (4 spaces) to every
91+
// generated line. Each segment's generated column (field 0) increases by 4.
92+
// Source-side fields are unchanged — they still point to the original code.
93+
94+
println!("\n--- Shifting all generated columns by +4 ---\n");
95+
96+
let shifted: Vec<Vec<Segment>> = mappings
97+
.iter()
98+
.map(|line| {
99+
line.iter()
100+
.map(|seg| match seg.len() {
101+
1 => Segment::one(seg[0] + 4),
102+
4 => Segment::four(seg[0] + 4, seg[1], seg[2], seg[3]),
103+
5 => Segment::five(seg[0] + 4, seg[1], seg[2], seg[3], seg[4]),
104+
_ => *seg,
105+
})
106+
.collect()
107+
})
108+
.collect();
109+
110+
// -----------------------------------------------------------------------
111+
// 4. Re-encode and verify roundtrip
112+
// -----------------------------------------------------------------------
113+
114+
let encoded_original = encode(&mappings);
115+
let encoded_shifted = encode(&shifted);
116+
117+
println!("Original re-encoded: {encoded_original:?}");
118+
println!("Shifted re-encoded: {encoded_shifted:?}\n");
119+
120+
// Verify the original roundtrips exactly
121+
assert_eq!(
122+
encoded_original, mappings_str,
123+
"roundtrip must produce identical output"
124+
);
125+
println!("Roundtrip verified: original encodes back to the same string.");
126+
127+
// Verify the shifted version decodes correctly
128+
let re_decoded = decode(&encoded_shifted).expect("shifted mappings should be valid");
129+
130+
for (line_idx, (orig_line, shifted_line)) in mappings.iter().zip(re_decoded.iter()).enumerate()
131+
{
132+
assert_eq!(
133+
orig_line.len(),
134+
shifted_line.len(),
135+
"line {line_idx} segment count must match"
136+
);
137+
for (seg_idx, (orig_seg, shifted_seg)) in
138+
orig_line.iter().zip(shifted_line.iter()).enumerate()
139+
{
140+
assert_eq!(
141+
shifted_seg[0],
142+
orig_seg[0] + 4,
143+
"line {line_idx} segment {seg_idx}: generated column must be shifted by 4"
144+
);
145+
}
146+
}
147+
println!("Shift verified: all generated columns increased by 4.\n");
148+
149+
// -----------------------------------------------------------------------
150+
// 5. Error handling for invalid input
151+
// -----------------------------------------------------------------------
152+
153+
println!("--- Error handling ---\n");
154+
155+
// Invalid base64 character
156+
match decode("AA!A") {
157+
Err(DecodeError::InvalidBase64 { byte, offset }) => {
158+
println!(
159+
"InvalidBase64: byte 0x{byte:02x} ({:?}) at offset {offset}",
160+
char::from(byte),
161+
);
162+
}
163+
other => panic!("expected InvalidBase64, got {other:?}"),
164+
}
165+
166+
// Truncated VLQ (continuation bit set, but no more input)
167+
match decode("g") {
168+
Err(DecodeError::UnexpectedEof { offset }) => {
169+
println!("UnexpectedEof: at offset {offset}");
170+
}
171+
other => panic!("expected UnexpectedEof, got {other:?}"),
172+
}
173+
174+
// Invalid segment length (2-field segments are not allowed per ECMA-426)
175+
match decode("AC") {
176+
Err(DecodeError::InvalidSegmentLength { fields, offset }) => {
177+
println!("InvalidSegmentLength: {fields} fields at offset {offset}");
178+
}
179+
other => panic!("expected InvalidSegmentLength, got {other:?}"),
180+
}
181+
182+
// VLQ overflow (too many continuation bytes)
183+
match decode("gggggggggggggg") {
184+
Err(DecodeError::VlqOverflow { offset }) => {
185+
println!("VlqOverflow: at offset {offset}");
186+
}
187+
other => panic!("expected VlqOverflow, got {other:?}"),
188+
}
189+
190+
println!("\nAll assertions passed.");
191+
}

0 commit comments

Comments
 (0)