Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 25 additions & 3 deletions src/name_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,31 @@ impl<'a, T: AsRef<str>> NameResolver<'a, T> {
&& token.get_dst_col() >= source_position.column.saturating_sub(1);

if is_exactish_match {
token.get_name()
} else {
None
if let Some(name) = token.get_name() {
return Some(name);
}

// If the token at the identifier position has no name, check the
// immediately preceding token. Some source map generators (e.g.
// TypeScript) attach the original function name to the `function`
// keyword token rather than the identifier that follows it.
// We only use the preceding token's name if it maps to the same
// original source position, indicating it's part of the same mapping.
if token.get_dst_col() > 0 {
if let Some(prev_token) = self
.sourcemap
.lookup_token(token.get_dst_line(), token.get_dst_col() - 1)
{
if prev_token.get_src_id() == token.get_src_id()
&& prev_token.get_src_line() == token.get_src_line()
&& prev_token.get_src_col() == token.get_src_col()
{
return prev_token.get_name();
}
}
}
Comment thread
sentry[bot] marked this conversation as resolved.
}

None
}
}
16 changes: 16 additions & 0 deletions tests/fixtures/ts-function-name/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
This fixture reproduces a TypeScript source map pattern where the original function
name is attached to the `function` keyword token rather than to the identifier.

The source map has these segments around the function declaration:

col 7 (within `function` keyword) → original col 9, name = "initServer"
col 8 (space) → original col 9, name = (none)
[no segment at col 9 where `ab` starts]

When looking up col 9 (the `ab` identifier), the nearest token is at col 8
(no name). The fix checks col 7 (one before), finds it maps to the same
original source position, and uses its name "initServer".

The source map was generated programmatically to mimic TypeScript compiler output.
The real-world case this tests: TypeScript compiling `function initServer()` to
`function Uc1bk()`, where Sentry's symbolicator was unable to resolve the name.
1 change: 1 addition & 0 deletions tests/fixtures/ts-function-name/minified.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
function ab(){console.log("hello")}
1 change: 1 addition & 0 deletions tests/fixtures/ts-function-name/minified.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions tests/fixtures/ts-function-name/original.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function initServer() {
console.log("hello");
}
5 changes: 5 additions & 0 deletions tests/fixtures/ts-function-name/sentry-repro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a1b2c3d4-e5f6-7890-abcd-ef1234567890")}catch(e){}}();
function a(){console.log("hello")}a();

//# debugId=a1b2c3d4-e5f6-7890-abcd-ef1234567890
1 change: 1 addition & 0 deletions tests/fixtures/ts-function-name/sentry-repro.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,51 @@ fn should_resolve_exactish() {
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() {
// TypeScript attaches the original name to the `function` keyword token
// rather than to the identifier that follows. The source map has:
// col 7 → name "initServer" (within `function` keyword)
// col 8 → no name (space)
// [no segment at col 9 where `ab` starts]
// lookup_token(0, 9) returns the col-8 token (no name).
// The fix should look back to col 7 and use "initServer".
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);

// The function scope should resolve from "ab" to "initServer"
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() {
// Variant where the source map has THREE segments around the function:
// col 0 → name "initServer" (start of `function` keyword)
// col 8 → no name (space)
// col 9 → no name (identifier `a`)
// The name-bearing segment is 2 tokens back from the identifier, so the
// single-step lookback does not reach it. This documents a limitation:
// when there's an extra no-name segment between the named token and the
// identifier, the name is not resolved.
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'");

// Currently NOT resolved — the name is too many tokens back.
// If the fix is improved to walk further back, update this to "initServer".
assert_eq!(func_scope.2, Some("a".into()));
}
Loading