use srcmap_scopes::{
Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo, decode_scopes,
encode_scopes,
};
fn main() {
let add_scope = OriginalScope {
start: Position { line: 1, column: 0 },
end: Position { line: 3, column: 1 },
name: Some("add".to_string()),
kind: Some("function".to_string()),
is_stack_frame: true,
variables: vec!["a".to_string(), "b".to_string()],
children: vec![],
};
let module_scope = OriginalScope {
start: Position { line: 0, column: 0 },
end: Position {
line: 5,
column: 27,
},
name: None,
kind: Some("module".to_string()),
is_stack_frame: false,
variables: vec!["result".to_string()],
children: vec![add_scope],
};
let inlined_range = GeneratedRange {
start: Position { line: 1, column: 0 },
end: Position {
line: 3,
column: 22,
},
is_stack_frame: true,
is_hidden: false,
definition: Some(1),
call_site: Some(CallSite {
source_index: 0,
line: 5,
column: 14,
}),
bindings: vec![
Binding::Expression("_a".to_string()),
Binding::Expression("_b".to_string()),
],
children: vec![],
};
let wrapper_range = GeneratedRange {
start: Position { line: 0, column: 0 },
end: Position { line: 4, column: 1 },
is_stack_frame: false,
is_hidden: false,
definition: Some(0),
call_site: None,
bindings: vec![Binding::Expression("result".to_string())],
children: vec![inlined_range],
};
let scope_info = ScopeInfo {
scopes: vec![Some(module_scope)],
ranges: vec![wrapper_range],
};
let mut names: Vec<String> = vec![];
let encoded = encode_scopes(&scope_info, &mut names);
println!("=== ECMA-426 Scopes Roundtrip ===\n");
println!("Encoded scopes: {encoded:?}");
println!("Names array: {names:?}\n");
assert!(!encoded.is_empty(), "encoded string must not be empty");
assert!(
!names.is_empty(),
"names array must contain scope/variable names"
);
let decoded = decode_scopes(&encoded, &names, 1).expect("decoding must succeed");
assert_eq!(
decoded.scopes.len(),
1,
"must have exactly one source file's scopes"
);
let root_scope = decoded.scopes[0]
.as_ref()
.expect("source 0 must have scope info");
assert_eq!(root_scope.kind.as_deref(), Some("module"));
assert!(
!root_scope.is_stack_frame,
"module scope is not a stack frame"
);
assert_eq!(root_scope.variables, vec!["result"]);
assert_eq!(root_scope.start, Position { line: 0, column: 0 });
assert_eq!(
root_scope.end,
Position {
line: 5,
column: 27
}
);
println!("Original scope tree (source 0):");
println!(
" Root: kind={:?}, variables={:?}",
root_scope.kind, root_scope.variables
);
assert_eq!(root_scope.children.len(), 1, "module has one child scope");
let func_scope = &root_scope.children[0];
assert_eq!(func_scope.name.as_deref(), Some("add"));
assert_eq!(func_scope.kind.as_deref(), Some("function"));
assert!(func_scope.is_stack_frame, "function scope is a stack frame");
assert_eq!(func_scope.variables, vec!["a", "b"]);
assert_eq!(func_scope.start, Position { line: 1, column: 0 });
assert_eq!(func_scope.end, Position { line: 3, column: 1 });
println!(
" Child: name={:?}, kind={:?}, variables={:?}",
func_scope.name, func_scope.kind, func_scope.variables
);
assert_eq!(
decoded.ranges.len(),
1,
"must have one top-level generated range"
);
let wrapper = &decoded.ranges[0];
assert_eq!(wrapper.definition, Some(0));
assert!(!wrapper.is_stack_frame);
assert!(!wrapper.is_hidden);
assert!(wrapper.call_site.is_none());
assert_eq!(
wrapper.bindings,
vec![Binding::Expression("result".to_string())]
);
println!("\nGenerated ranges:");
println!(
" Wrapper: lines {}-{}, definition={:?}, bindings={:?}",
wrapper.start.line, wrapper.end.line, wrapper.definition, wrapper.bindings
);
assert_eq!(wrapper.children.len(), 1, "wrapper has one child range");
let inlined = &wrapper.children[0];
assert_eq!(inlined.definition, Some(1));
assert!(inlined.is_stack_frame, "inlined range is a stack frame");
assert!(!inlined.is_hidden);
assert_eq!(
inlined.call_site,
Some(CallSite {
source_index: 0,
line: 5,
column: 14,
})
);
assert_eq!(
inlined.bindings,
vec![
Binding::Expression("_a".to_string()),
Binding::Expression("_b".to_string()),
]
);
println!(
" Inlined: lines {}-{}, definition={:?}, call_site={:?}, bindings={:?}",
inlined.start.line,
inlined.end.line,
inlined.definition,
inlined.call_site,
inlined.bindings
);
assert_eq!(
decoded, scope_info,
"decoded scope info must match the original"
);
println!("\nRoundtrip verified: decoded structure matches original.\n");
println!("--- Definition index lookups ---\n");
let scope_0 = decoded
.original_scope_for_definition(0)
.expect("definition 0 must exist");
assert_eq!(scope_0.kind.as_deref(), Some("module"));
println!(
" definition 0: kind={:?}, name={:?}",
scope_0.kind, scope_0.name
);
let scope_1 = decoded
.original_scope_for_definition(1)
.expect("definition 1 must exist");
assert_eq!(scope_1.name.as_deref(), Some("add"));
assert_eq!(scope_1.variables, vec!["a", "b"]);
println!(
" definition 1: kind={:?}, name={:?}",
scope_1.kind, scope_1.name
);
assert!(
decoded.original_scope_for_definition(99).is_none(),
"non-existent definition must return None"
);
println!(" definition 99: None (out of bounds)");
println!("\n--- Unavailable binding ---\n");
let optimized_info = ScopeInfo {
scopes: vec![Some(OriginalScope {
start: Position { line: 0, column: 0 },
end: Position { line: 3, column: 1 },
name: Some("compute".to_string()),
kind: Some("function".to_string()),
is_stack_frame: true,
variables: vec!["x".to_string(), "y".to_string()],
children: vec![],
})],
ranges: vec![GeneratedRange {
start: Position { line: 0, column: 0 },
end: Position { line: 1, column: 0 },
is_stack_frame: true,
is_hidden: false,
definition: Some(0),
call_site: None,
bindings: vec![Binding::Expression("_x".to_string()), Binding::Unavailable],
children: vec![],
}],
};
let mut opt_names: Vec<String> = vec![];
let opt_encoded = encode_scopes(&optimized_info, &mut opt_names);
let opt_decoded = decode_scopes(&opt_encoded, &opt_names, 1).expect("decoding must succeed");
assert_eq!(opt_decoded, optimized_info);
println!(" Bindings: {:?}", opt_decoded.ranges[0].bindings);
println!(" Variable 'x' -> Expression(\"_x\"), variable 'y' -> Unavailable");
println!("\n--- Error handling ---\n");
let empty_result = decode_scopes("", &[], 0);
assert!(empty_result.is_ok(), "empty input is valid");
println!(" Empty input: ok (no scopes, no ranges)");
let bad_vlq = decode_scopes("!!!", &[], 1);
assert!(bad_vlq.is_err(), "invalid VLQ must produce an error");
println!(" Invalid VLQ (\"!!!\"): {}", bad_vlq.unwrap_err());
println!("\nAll assertions passed.");
}