1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! Simulates a TypeScript-to-JavaScript compiler emitting a source map.
//!
//! Demonstrates the full `SourceMapGenerator` workflow: registering sources,
//! setting source content, adding names, mapping tokens, deduplication via
//! `maybe_add_mapping`, ignore lists, and round-trip verification.
//!
//! Run with: `cargo run -p srcmap-generator --example compiler_output`
#![allow(clippy::print_stdout, reason = "Examples are intended to print walkthrough output")]
use srcmap_generator::SourceMapGenerator;
fn main() {
// -- Original TypeScript source (src/app.ts) --
//
// Line 0: import { log } from "./utils";
// Line 1: export function greet(name: string): string {
// Line 2: const message = `Hello, ${name}!`;
// Line 3: log(message);
// Line 4: return message;
// Line 5: }
let ts_source = r#"import { log } from "./utils";
export function greet(name: string): string {
const message = `Hello, ${name}!`;
log(message);
return message;
}"#;
// -- Generated JavaScript (dist/app.js) --
//
// Line 0: import { log } from "./utils";
// Line 1: function greet(name) {
// Line 2: const message = `Hello, ${name}!`;
// Line 3: log(message);
// Line 4: return message;
// Line 5: }
// Line 6: export { greet };
// ---------------------------------------------------------------
// 1. Create generator
// ---------------------------------------------------------------
let mut builder = SourceMapGenerator::new(Some("dist/app.js".to_string()));
// ---------------------------------------------------------------
// 2. Register sources and set source content
// ---------------------------------------------------------------
let src_app = builder.add_source("src/app.ts");
builder.set_source_content(src_app, ts_source);
// Verify deduplication: adding the same source returns the same index.
assert_eq!(builder.add_source("src/app.ts"), src_app);
// ---------------------------------------------------------------
// 3. Register a node_modules helper and mark it as ignored
// ---------------------------------------------------------------
let src_helper = builder.add_source("node_modules/tslib/tslib.es6.js");
builder.add_to_ignore_list(src_helper);
// ---------------------------------------------------------------
// 4. Register names
// ---------------------------------------------------------------
let name_greet = builder.add_name("greet");
let name_name = builder.add_name("name");
let name_message = builder.add_name("message");
let name_log = builder.add_name("log");
// ---------------------------------------------------------------
// 5. Add mappings (all positions are 0-based per ECMA-426)
// ---------------------------------------------------------------
// Generated line 0: `import { log } from "./utils";`
// Maps 1:1 to original line 0.
builder.add_mapping(0, 0, src_app, 0, 0); // `import`
builder.add_mapping(0, 7, src_app, 0, 7); // `{`
builder.add_named_mapping(0, 9, src_app, 0, 9, name_log); // `log`
builder.add_mapping(0, 13, src_app, 0, 13); // `}`
builder.add_mapping(0, 15, src_app, 0, 15); // `from`
builder.add_mapping(0, 20, src_app, 0, 20); // `"./utils"`
// Generated line 1: `function greet(name) {`
// Original line 1: `export function greet(name: string): string {`
// The `export` keyword is removed, so the function starts at column 0.
builder.add_mapping(1, 0, src_app, 1, 7); // `function`
builder.add_named_mapping(1, 9, src_app, 1, 16, name_greet); // `greet`
builder.add_mapping(1, 14, src_app, 1, 21); // `(`
builder.add_named_mapping(1, 15, src_app, 1, 22, name_name); // `name`
builder.add_mapping(1, 19, src_app, 1, 35); // `)` — skipping `: string): string`
builder.add_mapping(1, 21, src_app, 1, 45); // `{`
// Generated line 2: ` const message = \`Hello, ${name}!\`;`
// Original line 2: ` const message = \`Hello, ${name}!\`;`
builder.add_mapping(2, 2, src_app, 2, 2); // `const`
builder.add_named_mapping(2, 8, src_app, 2, 8, name_message); // `message`
builder.add_mapping(2, 16, src_app, 2, 16); // `=`
builder.add_mapping(2, 18, src_app, 2, 18); // template literal start
builder.add_named_mapping(2, 30, src_app, 2, 30, name_name); // `name` inside template
// Generated line 3: ` log(message);`
// Original line 3: ` log(message);`
builder.add_named_mapping(3, 2, src_app, 3, 2, name_log); // `log`
builder.add_mapping(3, 5, src_app, 3, 5); // `(`
builder.add_named_mapping(3, 6, src_app, 3, 6, name_message); // `message`
builder.add_mapping(3, 13, src_app, 3, 13); // `)`
// Generated line 4: ` return message;`
// Original line 4: ` return message;`
builder.add_mapping(4, 2, src_app, 4, 2); // `return`
builder.add_named_mapping(4, 9, src_app, 4, 9, name_message); // `message`
// Generated line 5: `}`
// Original line 5: `}`
builder.add_mapping(5, 0, src_app, 5, 0);
// Generated line 6: `export { greet };`
// This is a synthetic re-export — maps back to the original export on line 1.
builder.add_mapping(6, 0, src_app, 1, 0); // `export`
builder.add_named_mapping(6, 9, src_app, 1, 16, name_greet); // `greet`
// ---------------------------------------------------------------
// 6. Demonstrate maybe_add_mapping (deduplication)
// ---------------------------------------------------------------
let count_before = builder.mapping_count();
// This mapping is identical to the last mapping on line 6 (same source position),
// so maybe_add_mapping should skip it and return false.
let added = builder.maybe_add_mapping(6, 15, src_app, 1, 16);
assert!(!added, "Duplicate mapping should have been skipped");
assert_eq!(builder.mapping_count(), count_before);
// This mapping differs in original column, so it should be added.
let added = builder.maybe_add_mapping(6, 15, src_app, 1, 0);
assert!(added, "Distinct mapping should have been added");
assert_eq!(builder.mapping_count(), count_before + 1);
// ---------------------------------------------------------------
// 7. Set source root and debug ID
// ---------------------------------------------------------------
builder.set_source_root("../");
builder.set_debug_id("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
// ---------------------------------------------------------------
// 8. Serialize to JSON
// ---------------------------------------------------------------
let json = builder.to_json();
println!("Generated source map ({} bytes):\n", json.len());
println!("{json}\n");
// Basic JSON structure checks
assert!(json.contains(r#""version":3"#));
assert!(json.contains(r#""file":"dist/app.js""#));
assert!(json.contains(r#""sourceRoot":"../""#));
assert!(json.contains(r#""sources":["src/app.ts""#));
assert!(json.contains(r#""sourcesContent":["#));
assert!(json.contains(r#""names":["greet","name","message","log"]"#));
assert!(json.contains(r#""ignoreList":[1]"#));
assert!(json.contains(r#""debugId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890""#));
println!("Total mappings: {}\n", builder.mapping_count());
// ---------------------------------------------------------------
// 9. Verify by parsing and doing lookups
// ---------------------------------------------------------------
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
// Look up generated line 1, column 9 — should map to `greet` in src/app.ts
// (source_root "../" is prepended to the source path during parsing)
let loc = sm.original_position_for(1, 9).unwrap();
assert_eq!(sm.source(loc.source), "../src/app.ts");
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 16);
assert_eq!(loc.name.map(|n| sm.name(n)), Some("greet"));
println!(
"Lookup gen(1,9) -> original({},{}) source={} name={:?}",
loc.line,
loc.column,
sm.source(loc.source),
loc.name.map(|n| sm.name(n))
);
// Look up generated line 3, column 2 — should map to `log` in src/app.ts
let loc = sm.original_position_for(3, 2).unwrap();
assert_eq!(sm.source(loc.source), "../src/app.ts");
assert_eq!(loc.line, 3);
assert_eq!(loc.column, 2);
assert_eq!(loc.name.map(|n| sm.name(n)), Some("log"));
println!(
"Lookup gen(3,2) -> original({},{}) source={} name={:?}",
loc.line,
loc.column,
sm.source(loc.source),
loc.name.map(|n| sm.name(n))
);
// Look up generated line 4, column 9 — should map to `message` in src/app.ts
let loc = sm.original_position_for(4, 9).unwrap();
assert_eq!(sm.source(loc.source), "../src/app.ts");
assert_eq!(loc.line, 4);
assert_eq!(loc.column, 9);
assert_eq!(loc.name.map(|n| sm.name(n)), Some("message"));
println!(
"Lookup gen(4,9) -> original({},{}) source={} name={:?}",
loc.line,
loc.column,
sm.source(loc.source),
loc.name.map(|n| sm.name(n))
);
// ---------------------------------------------------------------
// 10. Also verify via to_decoded_map (skip JSON round-trip)
// ---------------------------------------------------------------
let decoded = builder.to_decoded_map();
let loc = decoded.original_position_for(1, 15).unwrap();
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 22);
assert_eq!(loc.name.map(|n| decoded.name(n)), Some("name"));
println!(
"Decoded map lookup gen(1,15) -> original({},{}) name={:?}",
loc.line,
loc.column,
loc.name.map(|n| decoded.name(n))
);
println!("\nAll assertions passed.");
}