use std::ops::Range;
use js_source_scopes::{
extract_scope_names, NameResolver, ScopeIndex, ScopeLookupResult, ScopeName, SourceContext,
SourcePosition,
};
fn fixture(name: &str) -> String {
std::fs::read_to_string(format!("tests/fixtures/{name}")).unwrap()
}
fn resolve_original_scopes(
minified: &str,
map: &str,
scopes: Vec<(Range<u32>, Option<ScopeName>)>,
) -> Vec<(Range<u32>, Option<String>, Option<String>)> {
let ctx = SourceContext::new(&minified).unwrap();
let sm = sourcemap::decode_slice(map.as_bytes()).unwrap();
let resolver = NameResolver::new(&ctx, &sm);
let resolved_scopes = scopes.into_iter().map(|(range, name)| {
let minified_name = name.as_ref().map(|n| n.to_string());
let original_name = name.map(|n| resolver.resolve_name(&n));
(range, minified_name, original_name)
});
resolved_scopes.collect()
}
#[test]
fn resolves_scopes_simple() {
let minified = std::fs::read_to_string("tests/fixtures/simple/minified.js").unwrap();
let map = std::fs::read_to_string("tests/fixtures/simple/minified.js.map").unwrap();
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
assert_eq!(
resolved_scopes,
[(0..14, Some("t".into()), Some("abcd".into()))]
);
}
#[test]
fn resolves_scope_names() {
let src = std::fs::read_to_string("tests/fixtures/trace/sync.mjs").unwrap();
let scopes = extract_scope_names(&src).unwrap();
let scopes: Vec<_> = scopes
.into_iter()
.map(|s| (s.0, s.1.map(|n| n.to_string()).filter(|s| !s.is_empty())))
.collect();
let index = ScopeIndex::new(scopes).unwrap();
let ctx = SourceContext::new(&src).unwrap();
use ScopeLookupResult::*;
let lookup = |l: u32, c: u32| {
let offset = ctx
.position_to_offset(SourcePosition::new(l - 1, c - 1))
.unwrap();
index.lookup(offset)
};
assert_eq!(lookup(84, 11), NamedScope("obj.objectLiteralAnon"));
assert_eq!(lookup(81, 9), NamedScope("obj.objectLiteralMethod"));
assert_eq!(lookup(76, 7), NamedScope("localReassign"));
assert_eq!(
lookup(71, 28),
NamedScope("Klass.prototype.prototypeMethod")
);
assert_eq!(lookup(40, 10), NamedScope("BaseKlass.#privateMethod"));
assert_eq!(lookup(36, 24), NamedScope("BaseKlass.classCallbackArrow"));
assert_eq!(lookup(65, 34), AnonymousScope);
assert_eq!(lookup(65, 22), NamedScope("Klass.classCallbackBound"));
assert_eq!(lookup(65, 5), NamedScope("Klass.classCallbackBound"));
assert_eq!(lookup(61, 22), NamedScope("Klass.classCallbackSelf"));
assert_eq!(lookup(61, 5), NamedScope("Klass.classCallbackSelf"));
assert_eq!(lookup(56, 12), AnonymousScope);
assert_eq!(lookup(55, 22), NamedScope("Klass.classMethod"));
assert_eq!(lookup(55, 5), NamedScope("Klass.classMethod"));
assert_eq!(lookup(32, 10), NamedScope("new BaseKlass"));
assert_eq!(lookup(50, 5), NamedScope("new Klass"));
assert_eq!(lookup(46, 5), NamedScope("Klass.staticMethod"));
assert_eq!(lookup(22, 17), AnonymousScope);
assert_eq!(lookup(21, 26), AnonymousScope);
assert_eq!(lookup(21, 9), AnonymousScope);
assert_eq!(lookup(19, 24), NamedScope("namedImmediateCallback"));
assert_eq!(lookup(19, 7), NamedScope("namedImmediateCallback"));
assert_eq!(lookup(17, 22), NamedScope("namedDeclaredCallback"));
assert_eq!(lookup(17, 5), NamedScope("namedDeclaredCallback"));
assert_eq!(lookup(27, 20), NamedScope("arrowFn"));
assert_eq!(lookup(27, 3), NamedScope("arrowFn"));
assert_eq!(lookup(12, 3), NamedScope("anonFn"));
assert_eq!(lookup(8, 3), NamedScope("namedFnExpr"));
assert_eq!(lookup(4, 3), NamedScope("namedFn"));
}
#[test]
fn resolves_token_from_names() {
let minified = std::fs::read_to_string("tests/fixtures/preact.module.js").unwrap();
let map = std::fs::read_to_string("tests/fixtures/preact.module.js.map").unwrap();
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
for (range, minified, original) in resolved_scopes {
println!("{range:?}");
println!(" minified: {minified:?}");
println!(" original: {original:?}");
}
}
#[test]
fn parses_large_vendors() {
let minified = std::fs::read_to_string("tests/fixtures/vendors/vendors.js").unwrap();
let map = std::fs::read_to_string("tests/fixtures/vendors/vendors.js.map").unwrap();
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
for (range, minified, original) in resolved_scopes {
println!("{range:?}");
println!(" minified: {minified:?}");
println!(" original: {original:?}");
}
}
#[test]
fn should_not_resolve_mismatch() {
let minified = fixture("prototype-chain/minified.js");
let map = fixture("prototype-chain/minified.js.map");
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
assert_eq!(resolved_scopes[1].2, Some("Foo.prototype.bar".into()));
}
#[test]
fn should_resolve_exactish() {
let minified = fixture("off-by-one/test.min.js");
let map = fixture("off-by-one/test.map");
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
assert_eq!(resolved_scopes[2].2, Some("onFailure".into()));
assert_eq!(resolved_scopes[3].2, Some("invoke".into()));
assert_eq!(resolved_scopes[4].2, Some("test".into()));
}
#[test]
fn should_resolve_name_from_function_keyword_token() {
let minified = fixture("ts-function-name/minified.js");
let map = fixture("ts-function-name/minified.js.map");
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
assert_eq!(resolved_scopes[0].1, Some("ab".into()));
assert_eq!(resolved_scopes[0].2, Some("initServer".into()));
}
#[test]
fn should_resolve_name_from_function_keyword_token_three_segments() {
let minified = fixture("ts-function-name/sentry-repro.js");
let map = fixture("ts-function-name/sentry-repro.js.map");
let scopes = extract_scope_names(&minified).unwrap();
let resolved_scopes = resolve_original_scopes(&minified, &map, scopes);
let func_scope = resolved_scopes
.iter()
.find(|(_, m, _)| m.as_deref() == Some("a"))
.expect("should find function 'a'");
assert_eq!(func_scope.2, Some("a".into()));
}