pub struct ScopeInfo {
pub scopes: Vec<Option<OriginalScope>>,
pub ranges: Vec<GeneratedRange>,
}Expand description
Decoded scope information from a source map.
Fields§
§scopes: Vec<Option<OriginalScope>>Original scope trees, one per source file (aligned with sources).
None means no scope info for that source file.
ranges: Vec<GeneratedRange>Top-level generated ranges for the output code.
Implementations§
Source§impl ScopeInfo
impl ScopeInfo
Sourcepub fn original_scope_for_definition(
&self,
definition: u32,
) -> Option<&OriginalScope>
pub fn original_scope_for_definition( &self, definition: u32, ) -> Option<&OriginalScope>
Get the original scope referenced by a generated range’s definition index.
The definition index references scopes in pre-order traversal order across all source files.
Examples found in repository?
examples/debug_scopes.rs (line 297)
40fn main() {
41 // -----------------------------------------------------------------------
42 // 1. Build the original scope tree (what the author wrote)
43 // -----------------------------------------------------------------------
44 //
45 // ECMA-426 original scopes form a tree per source file. Each scope has:
46 // - start/end positions (0-based line and column)
47 // - an optional name (for named functions, classes, etc.)
48 // - an optional kind ("global", "module", "function", "block", "class")
49 // - is_stack_frame: true for function-like scopes that appear in call stacks
50 // - variables: names declared in this scope (parameters, let/const/var)
51 // - children: nested scopes
52 //
53 // Scopes are indexed by a pre-order traversal counter ("definition index"):
54 // - definition 0: the module scope (root)
55 // - definition 1: the `add` function scope (first child)
56
57 let add_scope = OriginalScope {
58 start: Position { line: 1, column: 0 },
59 end: Position { line: 3, column: 1 },
60 name: Some("add".to_string()),
61 kind: Some("function".to_string()),
62 is_stack_frame: true,
63 variables: vec!["a".to_string(), "b".to_string()],
64 children: vec![],
65 };
66
67 let module_scope = OriginalScope {
68 start: Position { line: 0, column: 0 },
69 end: Position {
70 line: 5,
71 column: 27,
72 },
73 name: None,
74 kind: Some("module".to_string()),
75 is_stack_frame: false,
76 variables: vec!["result".to_string()],
77 children: vec![add_scope],
78 };
79
80 // -----------------------------------------------------------------------
81 // 2. Build the generated ranges (what the bundler produced)
82 // -----------------------------------------------------------------------
83 //
84 // Generated ranges describe regions of the output code and how they map
85 // back to original scopes. Key fields:
86 //
87 // - definition: index into the pre-order list of all original scopes,
88 // linking this range to its corresponding original scope
89 // - call_site: if this range is an inlined function body, the location
90 // in original source where the call happened
91 // - bindings: one entry per variable in the referenced original scope,
92 // telling the debugger what JS expression to evaluate for each variable
93 // - is_stack_frame: true if this range should appear in synthetic stacks
94 // - is_hidden: true if the debugger should skip over this range entirely
95
96 let inlined_range = GeneratedRange {
97 start: Position { line: 1, column: 0 },
98 end: Position {
99 line: 3,
100 column: 22,
101 },
102 is_stack_frame: true,
103 is_hidden: false,
104 // definition=1 points to the `add` function scope (pre-order index 1)
105 definition: Some(1),
106 // The call site is where `add(10, 32)` was called in the original source.
107 // The debugger uses this to reconstruct a synthetic call stack:
108 // add @ math.ts:2:2 (current position in the inlined body)
109 // <module> @ math.ts:5:14 (the call site)
110 call_site: Some(CallSite {
111 source_index: 0,
112 line: 5,
113 column: 14,
114 }),
115 // Bindings map the original scope's variables to generated expressions.
116 // The `add` scope has variables ["a", "b"] (in that order), so:
117 // bindings[0] = Expression("_a") → original `a` is `_a` in generated code
118 // bindings[1] = Expression("_b") → original `b` is `_b` in generated code
119 bindings: vec![
120 Binding::Expression("_a".to_string()),
121 Binding::Expression("_b".to_string()),
122 ],
123 children: vec![],
124 };
125
126 let wrapper_range = GeneratedRange {
127 start: Position { line: 0, column: 0 },
128 end: Position { line: 4, column: 1 },
129 is_stack_frame: false,
130 is_hidden: false,
131 // definition=0 points to the module scope (pre-order index 0)
132 definition: Some(0),
133 call_site: None,
134 // The module scope has variables ["result"], and in the generated code
135 // the variable keeps its name, so we bind it to "result".
136 bindings: vec![Binding::Expression("result".to_string())],
137 children: vec![inlined_range],
138 };
139
140 // -----------------------------------------------------------------------
141 // 3. Assemble ScopeInfo and encode
142 // -----------------------------------------------------------------------
143 //
144 // ScopeInfo combines original scope trees (one per source file) with the
145 // generated ranges. The `scopes` vec is indexed by source index — None
146 // means no scope info for that source file.
147
148 let scope_info = ScopeInfo {
149 scopes: vec![Some(module_scope)],
150 ranges: vec![wrapper_range],
151 };
152
153 // Encoding produces a compact VLQ string (stored in the source map's
154 // "scopes" field) and populates the names array with any new name strings.
155 let mut names: Vec<String> = vec![];
156 let encoded = encode_scopes(&scope_info, &mut names);
157
158 println!("=== ECMA-426 Scopes Roundtrip ===\n");
159 println!("Encoded scopes: {encoded:?}");
160 println!("Names array: {names:?}\n");
161
162 assert!(!encoded.is_empty(), "encoded string must not be empty");
163 assert!(
164 !names.is_empty(),
165 "names array must contain scope/variable names"
166 );
167
168 // -----------------------------------------------------------------------
169 // 4. Decode back and verify roundtrip
170 // -----------------------------------------------------------------------
171 //
172 // decode_scopes takes the encoded string, the names array, and the number
173 // of source files (so it knows how many original scope trees to expect).
174
175 let decoded = decode_scopes(&encoded, &names, 1).expect("decoding must succeed");
176
177 // Verify the original scope tree roundtrips correctly
178 assert_eq!(
179 decoded.scopes.len(),
180 1,
181 "must have exactly one source file's scopes"
182 );
183
184 let root_scope = decoded.scopes[0]
185 .as_ref()
186 .expect("source 0 must have scope info");
187
188 assert_eq!(root_scope.kind.as_deref(), Some("module"));
189 assert!(
190 !root_scope.is_stack_frame,
191 "module scope is not a stack frame"
192 );
193 assert_eq!(root_scope.variables, vec!["result"]);
194 assert_eq!(root_scope.start, Position { line: 0, column: 0 });
195 assert_eq!(
196 root_scope.end,
197 Position {
198 line: 5,
199 column: 27
200 }
201 );
202
203 println!("Original scope tree (source 0):");
204 println!(
205 " Root: kind={:?}, variables={:?}",
206 root_scope.kind, root_scope.variables
207 );
208
209 assert_eq!(root_scope.children.len(), 1, "module has one child scope");
210
211 let func_scope = &root_scope.children[0];
212 assert_eq!(func_scope.name.as_deref(), Some("add"));
213 assert_eq!(func_scope.kind.as_deref(), Some("function"));
214 assert!(func_scope.is_stack_frame, "function scope is a stack frame");
215 assert_eq!(func_scope.variables, vec!["a", "b"]);
216 assert_eq!(func_scope.start, Position { line: 1, column: 0 });
217 assert_eq!(func_scope.end, Position { line: 3, column: 1 });
218
219 println!(
220 " Child: name={:?}, kind={:?}, variables={:?}",
221 func_scope.name, func_scope.kind, func_scope.variables
222 );
223
224 // Verify the generated ranges roundtrip correctly
225 assert_eq!(
226 decoded.ranges.len(),
227 1,
228 "must have one top-level generated range"
229 );
230
231 let wrapper = &decoded.ranges[0];
232 assert_eq!(wrapper.definition, Some(0));
233 assert!(!wrapper.is_stack_frame);
234 assert!(!wrapper.is_hidden);
235 assert!(wrapper.call_site.is_none());
236 assert_eq!(
237 wrapper.bindings,
238 vec![Binding::Expression("result".to_string())]
239 );
240
241 println!("\nGenerated ranges:");
242 println!(
243 " Wrapper: lines {}-{}, definition={:?}, bindings={:?}",
244 wrapper.start.line, wrapper.end.line, wrapper.definition, wrapper.bindings
245 );
246
247 assert_eq!(wrapper.children.len(), 1, "wrapper has one child range");
248
249 let inlined = &wrapper.children[0];
250 assert_eq!(inlined.definition, Some(1));
251 assert!(inlined.is_stack_frame, "inlined range is a stack frame");
252 assert!(!inlined.is_hidden);
253 assert_eq!(
254 inlined.call_site,
255 Some(CallSite {
256 source_index: 0,
257 line: 5,
258 column: 14,
259 })
260 );
261 assert_eq!(
262 inlined.bindings,
263 vec![
264 Binding::Expression("_a".to_string()),
265 Binding::Expression("_b".to_string()),
266 ]
267 );
268
269 println!(
270 " Inlined: lines {}-{}, definition={:?}, call_site={:?}, bindings={:?}",
271 inlined.start.line,
272 inlined.end.line,
273 inlined.definition,
274 inlined.call_site,
275 inlined.bindings
276 );
277
278 // Full structural equality check
279 assert_eq!(
280 decoded, scope_info,
281 "decoded scope info must match the original"
282 );
283
284 println!("\nRoundtrip verified: decoded structure matches original.\n");
285
286 // -----------------------------------------------------------------------
287 // 5. Look up original scopes by definition index
288 // -----------------------------------------------------------------------
289 //
290 // A debugger hits a breakpoint in generated code and finds a generated
291 // range with definition=1. It needs to find the corresponding original
292 // scope to know the function name, parameter names, etc.
293
294 println!("--- Definition index lookups ---\n");
295
296 let scope_0 = decoded
297 .original_scope_for_definition(0)
298 .expect("definition 0 must exist");
299 assert_eq!(scope_0.kind.as_deref(), Some("module"));
300 println!(
301 " definition 0: kind={:?}, name={:?}",
302 scope_0.kind, scope_0.name
303 );
304
305 let scope_1 = decoded
306 .original_scope_for_definition(1)
307 .expect("definition 1 must exist");
308 assert_eq!(scope_1.name.as_deref(), Some("add"));
309 assert_eq!(scope_1.variables, vec!["a", "b"]);
310 println!(
311 " definition 1: kind={:?}, name={:?}",
312 scope_1.kind, scope_1.name
313 );
314
315 // Out-of-bounds definition index returns None
316 assert!(
317 decoded.original_scope_for_definition(99).is_none(),
318 "non-existent definition must return None"
319 );
320 println!(" definition 99: None (out of bounds)");
321
322 // -----------------------------------------------------------------------
323 // 6. Demonstrate the Unavailable binding variant
324 // -----------------------------------------------------------------------
325 //
326 // Sometimes a variable is optimized out entirely. The debugger should
327 // show it as "unavailable" rather than silently omitting it.
328
329 println!("\n--- Unavailable binding ---\n");
330
331 let optimized_info = ScopeInfo {
332 scopes: vec![Some(OriginalScope {
333 start: Position { line: 0, column: 0 },
334 end: Position { line: 3, column: 1 },
335 name: Some("compute".to_string()),
336 kind: Some("function".to_string()),
337 is_stack_frame: true,
338 variables: vec!["x".to_string(), "y".to_string()],
339 children: vec![],
340 })],
341 ranges: vec![GeneratedRange {
342 start: Position { line: 0, column: 0 },
343 end: Position { line: 1, column: 0 },
344 is_stack_frame: true,
345 is_hidden: false,
346 definition: Some(0),
347 call_site: None,
348 // x is available as "_x", but y was optimized out
349 bindings: vec![Binding::Expression("_x".to_string()), Binding::Unavailable],
350 children: vec![],
351 }],
352 };
353
354 let mut opt_names: Vec<String> = vec![];
355 let opt_encoded = encode_scopes(&optimized_info, &mut opt_names);
356 let opt_decoded = decode_scopes(&opt_encoded, &opt_names, 1).expect("decoding must succeed");
357
358 assert_eq!(opt_decoded, optimized_info);
359 println!(" Bindings: {:?}", opt_decoded.ranges[0].bindings);
360 println!(" Variable 'x' -> Expression(\"_x\"), variable 'y' -> Unavailable");
361
362 // -----------------------------------------------------------------------
363 // 7. Error handling for invalid input
364 // -----------------------------------------------------------------------
365 //
366 // decode_scopes returns ScopesError for malformed input. This is useful
367 // for tools that validate source maps.
368
369 println!("\n--- Error handling ---\n");
370
371 // Empty encoded string is valid (no scopes, no ranges)
372 let empty_result = decode_scopes("", &[], 0);
373 assert!(empty_result.is_ok(), "empty input is valid");
374 println!(" Empty input: ok (no scopes, no ranges)");
375
376 // Invalid VLQ data: 'z' is not a valid base64 character for VLQ
377 let bad_vlq = decode_scopes("!!!", &[], 1);
378 assert!(bad_vlq.is_err(), "invalid VLQ must produce an error");
379 println!(" Invalid VLQ (\"!!!\"): {}", bad_vlq.unwrap_err());
380
381 println!("\nAll assertions passed.");
382}Trait Implementations§
impl Eq for ScopeInfo
impl StructuralPartialEq for ScopeInfo
Auto Trait Implementations§
impl Freeze for ScopeInfo
impl RefUnwindSafe for ScopeInfo
impl Send for ScopeInfo
impl Sync for ScopeInfo
impl Unpin for ScopeInfo
impl UnsafeUnpin for ScopeInfo
impl UnwindSafe for ScopeInfo
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more