Skip to content

fix(codegen): correct sourcemap end mappings for closing delimiters#22001

Merged
Dunqing merged 2 commits into
oxc-project:mainfrom
markdalgleish:fix-sourcemap-closing-delimeter
May 7, 2026
Merged

fix(codegen): correct sourcemap end mappings for closing delimiters#22001
Dunqing merged 2 commits into
oxc-project:mainfrom
markdalgleish:fix-sourcemap-closing-delimeter

Conversation

@markdalgleish

Copy link
Copy Markdown
Contributor

This fixes incorrect sourcemap end mappings for closing delimiters emitted by oxc_codegen.

This came up while investigating Remix 3 test coverage reporting for code transformed with Oxc via the Remix assets package, which we were using in our test package to run in-browser tests. V8 reported uncovered functions and branches correctly, but v8-to-istanbul could not always resolve the end of those covered ranges back through Oxc's sourcemap, which caused coverage to be over-reported. This meant that we saw cases of 100% coverage being incorrectly reported because v8-to-istanbul dropped the whole function block when it could not map the closing } line of an uncalled function.

add_source_mapping_end used Span::end directly even though spans are end-exclusive, so the generated token could land one character past the real source span. Block printers also did not emit end mappings for closing } consistently. Consumers that resolve V8 coverage range ends through the sourcemap can then miss those block boundaries and over-report coverage.

This PR maps end tokens to span.end - 1 and emits them for block closers, then extends the sourcemap tests with:

  • a runtime stacktrace regression for normal function and arrow block bodies
  • a direct regression for the synthesized wrapper-block path used to preserve else binding

The snapshot updates in stacktrace_is_correct are expected for the same reason. Those column numbers come from Node consuming the generated sourcemap at runtime. For example, one frame changes from /project/input.js:10:12 to /project/input.js:10:11 for the source line obj.fn({a})(). In 1-based columns, that line is o1 b2 j3 .4 f5 n6 (7 {8 a9 }10 )11 (12 )13. The error happens in the first call expression, so the end position should resolve to column 11, the first ), which is the closing character of obj.fn({a}). Before this change it resolved to column 12, the next (, because the end mapping pointed one character too far to the right. After switching end mappings to the final real source character and adding the missing block-closer mappings, that frame moves left by one column to the correct source position.

Please note, I created this PR with AI assistance, but I worked closely with the agent to design, review, and iterate on the changes until it was at a level I was happy with.

@codspeed-hq

codspeed-hq Bot commented May 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 48 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing markdalgleish:fix-sourcemap-closing-delimeter (18ef494) with main (32255b1)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@AriPerkkio AriPerkkio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two downstream fixes are likely related:

Remix building their own coverage pipeline will run into all the issues other test runners have fixed earlier.

@camc314 camc314 added the A-codegen Area - Code Generation label May 1, 2026
@Dunqing Dunqing self-assigned this May 6, 2026
@Dunqing Dunqing force-pushed the fix-sourcemap-closing-delimeter branch from 52fd07c to 18ef494 Compare May 7, 2026 02:52

@Dunqing Dunqing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! The fix looks good! However, I found a regression in the call chain; you can view it in Sourcemap Viewer.

Image

As you can see, the red arrow points to the first call's right parenthesis, which points to the second call's left parenthesis.

I will send a fix in a follow-up PR.

@Dunqing Dunqing merged commit 2493bdd into oxc-project:main May 7, 2026
36 checks passed
Dunqing added a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
Dunqing added a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
Dunqing added a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
)

Follow-up on #22001.

Move `add_source_mapping_end(span)` to before `print_ascii_byte(b')')` in `print_arguments` so the call's end mapping lands at the generated position **of** `)` rather than one past it. Each generated paren now has at most one mapping at its exact position, matching the convention used by esbuild and Babel.

A second follow-up (#22200) extends the same fix to block-end `}`, curly-braces `}`, and the `]`/`}` of `ArrayExpression` / `ObjectExpression`.

## Why

After #22001, `add_source_mapping_end` correctly points the source side at `span.end - 1` (the closing source char), but the generated position was still placed AFTER `)` was printed. For chained calls like `factory()()`, this collided with the next AST node's start position:

- inner call's end mapping at `gen 7:9 → src 7:8` (just past inner `)`)
- the outer call's `(` lives at `gen 7:9` in the generated code

Result: a V8 stack-trace lookup at `gen 7:9` (the outer `(`) returned `src 7:8` (the inner `)`) — one column off.

## How

Emit the end mapping at the gen position **of** `)`, not after it. Now:

- `gen 7:8 → src 7:8` (inner `)`)
- `gen 7:9` has no direct mapping → falls back to inner end → `src 7:8`
- `gen 7:10 → src 7:10` (outer `)`)

The chained-call shadow at the outer `(` becomes a plain "no mapping here, fall back to inner `)`" — same accepted behavior as esbuild, Babel, and TypeScript. None of them special-case chained calls.

## Visual comparisons

Generated against a fixture covering classes, arrow bodies, arrays, objects, and chained calls in the [source-map viewer](https://source-map-viewer.void.app):

- **Pre-PR vs Full fix** (full picture): https://source-map-viewer.void.app/compare?a=j9GHxMTX&b=UcCYKDih
- **PR-only vs Full fix** (just the changes #22199 + #22200 add): https://source-map-viewer.void.app/compare?a=ngs4XAsh&b=UcCYKDih
- **esbuild vs Full fix** (alignment with esbuild): https://source-map-viewer.void.app/compare?a=26JVF9DI&b=UcCYKDih

## Reference

esbuild (`internal/js_printer/js_printer.go`):

```go
if e.CloseParenLoc.Start > expr.Loc.Start {
    p.addSourceMapping(e.CloseParenLoc)
}
p.print(")")
```

Babel (`packages/babel-generator/src/printer.ts::rightParens`):

```ts
this.sourceWithOffset("end", node.loc, -1);
this.token(")");
```

## Test plan

- [x] New test `call_end_mapping_lands_at_close_paren` — pins both `)` mappings of `factory()()` to their close-paren positions
- [x] `cargo test -p oxc_codegen` passes (99 tests)
- [x] `stacktrace_is_correct` snapshot unchanged
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
)

Follow-up on #22001.

Move `add_source_mapping_end(span)` to before `print_ascii_byte(b')')` in `print_arguments` so the call's end mapping lands at the generated position **of** `)` rather than one past it. Each generated paren now has at most one mapping at its exact position, matching the convention used by esbuild and Babel.

A second follow-up (#22200) extends the same fix to block-end `}`, curly-braces `}`, and the `]`/`}` of `ArrayExpression` / `ObjectExpression`.

## Why

After #22001, `add_source_mapping_end` correctly points the source side at `span.end - 1` (the closing source char), but the generated position was still placed AFTER `)` was printed. For chained calls like `factory()()`, this collided with the next AST node's start position:

- inner call's end mapping at `gen 7:9 → src 7:8` (just past inner `)`)
- the outer call's `(` lives at `gen 7:9` in the generated code

Result: a V8 stack-trace lookup at `gen 7:9` (the outer `(`) returned `src 7:8` (the inner `)`) — one column off.

## How

Emit the end mapping at the gen position **of** `)`, not after it. Now:

- `gen 7:8 → src 7:8` (inner `)`)
- `gen 7:9` has no direct mapping → falls back to inner end → `src 7:8`
- `gen 7:10 → src 7:10` (outer `)`)

The chained-call shadow at the outer `(` becomes a plain "no mapping here, fall back to inner `)`" — same accepted behavior as esbuild, Babel, and TypeScript. None of them special-case chained calls.

## Visual comparisons

Generated against a fixture covering classes, arrow bodies, arrays, objects, and chained calls in the [source-map viewer](https://source-map-viewer.void.app):

- **Pre-PR vs Full fix** (full picture): https://source-map-viewer.void.app/compare?a=j9GHxMTX&b=UcCYKDih
- **PR-only vs Full fix** (just the changes #22199 + #22200 add): https://source-map-viewer.void.app/compare?a=ngs4XAsh&b=UcCYKDih
- **esbuild vs Full fix** (alignment with esbuild): https://source-map-viewer.void.app/compare?a=26JVF9DI&b=UcCYKDih

## Reference

esbuild (`internal/js_printer/js_printer.go`):

```go
if e.CloseParenLoc.Start > expr.Loc.Start {
    p.addSourceMapping(e.CloseParenLoc)
}
p.print(")")
```

Babel (`packages/babel-generator/src/printer.ts::rightParens`):

```ts
this.sourceWithOffset("end", node.loc, -1);
this.token(")");
```

## Test plan

- [x] New test `call_end_mapping_lands_at_close_paren` — pins both `)` mappings of `factory()()` to their close-paren positions
- [x] `cargo test -p oxc_codegen` passes (99 tests)
- [x] `stacktrace_is_correct` snapshot unchanged
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
Dunqing added a commit that referenced this pull request May 7, 2026
Move `add_source_mapping_end(span)` to before the closing
`print_ascii_byte` in four places: `print_block_end`,
`print_curly_braces`, and the `]`/`}` of `ArrayExpression` /
`ObjectExpression`. The end mapping now lands at the generated position
OF the close char, matching the convention used by esbuild and Babel.

Same fix shape as the call-paren change — extends it to the remaining
close-delimiter sites. Previously the mapping landed one column past
the close char, so a closing `}` immediately followed by `;` (the
common arrow-function case `() => { ... };`) had its source `}` map to
the generated `;` instead of the generated `}`.

After this change, the source `}` of `const fn = () => { return 1 }`
maps to the generated `}` (line 2 col 0), not the `;` that follows it.

The `synthesized_block_closing_braces_are_mapped` test from #22001 is
updated to assert the new gen position (col 0 instead of col 1) — same
intent (the synthesized `}` resolves back to the wrapped if's last
char), now anchored at the `}` itself.
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
Stacked on #22199.

Extends the same fix shape from #22199 (move `add_source_mapping_end` before the closing `print_ascii_byte`) to the remaining close-delimiter sites: `print_block_end`, `print_curly_braces`, and the `]`/`}` of `ArrayExpression` / `ObjectExpression`.

## Why

#22199 fixed the call's `)`. The same root cause exists for `}` of blocks/objects and `]` of arrays — the end mapping was placed AFTER the close char, putting it at the position of whatever follows. The most visible case: `() => { ... };` — the source `}` mapped to the generated `;` rather than the generated `}`, so source-map consumers viewing this diff saw the `}` "point at the semicolon."

## How

Five sites total, four in this PR (one already in #22199):

| Site | File |
|---|---|
| `print_block_end` (the `}` of `BlockStatement`) | `lib.rs` |
| `print_curly_braces` (shared by class body, switch, TS enum/interface/typeliteral/module) | `lib.rs` |
| `ArrayExpression` (`]`) | `gen.rs` |
| `ObjectExpression` (`}`) | `gen.rs` |

Each: swap `print_ascii_byte; add_source_mapping_end` → `add_source_mapping_end; print_ascii_byte`. The mapping now lands at the gen position OF the close char.

## Visual comparisons

Fixture covering all 5 sites (classes, arrow bodies, arrays, objects, chained calls):

- **Pre-PR vs Full fix** (full picture): https://source-map-viewer.void.app/compare?a=j9GHxMTX&b=UcCYKDih
- **PR-only vs Full fix** (just the changes #22199 + #22200 add): https://source-map-viewer.void.app/compare?a=ngs4XAsh&b=UcCYKDih
- **esbuild vs Full fix** (alignment with esbuild): https://source-map-viewer.void.app/compare?a=26JVF9DI&b=UcCYKDih

## Reference

esbuild (`internal/js_printer/js_printer.go`):

```go
if e.CloseBraceLoc.Start > expr.Loc.Start {
    p.addSourceMapping(e.CloseBraceLoc)
}
p.print("}")
```

(Same pattern as the call's `)` handling — see #22199.)

## Test plan

Each of the 4 modified sites has direct test coverage:

- [x] `block_end_mapping_lands_at_close_brace` — pins `}` of `() => { return 1 }` (exercises `print_block_end`)
- [x] `class_body_close_brace_lands_at_close_brace` — pins `}` of `class C { a; }` (exercises `print_curly_braces` — shared by classes, switch, TS enum/interface/typeliteral/module)
- [x] `array_close_bracket_lands_at_close_bracket` — pins `]` of `const a = [1, 2]` (exercises `ArrayExpression`)
- [x] `object_close_brace_lands_at_close_brace` — pins `}` of `const o = { a: 1 }` (exercises `ObjectExpression`)
- [x] `synthesized_block_closing_braces_are_mapped` (added by #22001) updated to assert `dst (2, 0)` instead of `(2, 1)` — same intent (the synthesized `}` resolves back to the wrapped if's last char), now anchored at the `}` itself
- [x] `cargo test -p oxc_codegen` passes (11 sourcemap tests, 100 integration total)
- [x] `stacktrace_is_correct` snapshot unchanged

Each new test was verified to fail without the corresponding swap and pass with it.
camc314 added a commit that referenced this pull request May 11, 2026
### 🚀 Features

- 66c9b01 transformer/typescript: Debug_assert that `enum_eval` ran in
semantic (#22252) (Dunqing)
- ffe6475 minifier: Fold `Array` constructor with safe spreads (#22215)
(camc314)

### 🐛 Bug Fixes

- d3d0b18 traverse: Handle `ChainElement::TSNonNullExpression` in
`GatherNodeParts` (#22247) (leaysgur)
- 4e880de transformer/object-rest-spread: Declare temp vars for computed
keys (#22284) (camc314)
- a7c3e22 semantic: Clear member write target for computed keys (#22302)
(camc314)
- 6a8852d codegen: Emit newline after legal-comment orphan flush
(#22304) (Dunqing)
- 5da9fda transformer/explicit-resource-management: Preserve class names
(#22306) (Dunqing)
- b5d970f transformer/explicit-resource-management: Preserve class names
(#22290) (camc314)
- bc54fd4 minifier: Keep function / class names if direct eval is
present in the scope (#22241) (sapphi-red)
- 7a810c0 minifier: Refresh direct eval flags after DCE (#21787)
(Dunqing)
- dd88726 transformer/legacy-decorator: Preserve accessor type
annotation for emitDecoratorMetadata (#21966) (Dunqing)
- 29a3cd7 codegen: Swap mapping/indent order for top-level decls
(#22206) (Dunqing)
- 73b4f40 minifier: Preserve catch binding with direct eval (#22221)
(camc314)
- 0e13d17 minifier: Preserve optional chain base side effects (#22219)
(camc314)
- 0c7c01c transformer/typescript: Inline optional-chain enum member
access (#21834) (Dunqing)
- a6aff7e codegen: Emit block/array/object end mapping at close char
(#22200) (Dunqing)
- a099b03 codegen: Emit call end mapping at `)` position, not past it
(#22199) (Dunqing)
- 5753774 minifier: Cap if-return ternary collapse for firefox (#21841)
(Gurupungav Narayanan)
- 2493bdd codegen: Correct sourcemap end mappings for closing delimiters
(#22001) (Mark Dalgleish)
- 3b385e2 minifier: Bail optimizing `Array` with unknown arg count
(#22188) (camc314)
- 9fa2122 parser: Parse array computed class keys (#22159) (camc314)

### 📚 Documentation

- a4a6892 napi/parser: Correct code comment (#22278) (overlookmotel)
- 9305373 oxc: Update README (#22178) (camc314)

Co-authored-by: Cameron <[email protected]>
graphite-app Bot pushed a commit that referenced this pull request Jun 1, 2026
…#22789)

## Summary

- `oxc_codegen` produced off-by-one V8 stack-trace and coverage columns for call-chain shapes. When a `(`/`.`/`[` is chained directly onto a call — or any expression ending in `)`/`]` — that punctuation was left unmapped, so the lookup fell back to the closing delimiter one column to the left. Surfaced as vitest failures in vite-ecosystem-ci: `test.each([1, 2])(…)` reporting column 17 instead of 18, and `await expect(…).resolves.toBe(4)` reporting 40 instead of 41.
- Root cause: such operands end in `)` or `]`, so there is no trailing identifier token for a source-map consumer to anchor on. The fix maps that punctuation from `print_expr` — keyed on postfix precedence — back to the operand's exclusive end, exactly where V8 reports the frame. This is the trailing end-mapping that Babel and TSC apply to every node, scoped here to just the no-anchor sites. The closing `)`/`}`/`]` keep their existing at-delimiter mapping, which `v8-to-istanbul` relies on for coverage range boundaries (#22001).
- Only these no-anchor chain sites are mapped — not the byte past every delimiter — so the added source-map tokens stay at **+0.6%** over baseline. (A blanket variant that mapped every delimiter regressed codegen ~5% on CodSpeed.)
- Verified end-to-end through `node --enable-source-maps`: each chained frame resolves to the correct column (the outer `(` for curried calls, the `.` for a getter/member on a call result), and regresses by exactly one column when the mapping is disabled. Covered by the `stacktrace_is_correct` integration test and unit tests in `sourcemap.rs`.

## Example

`factory()()` — V8 reports the outer call at the `(` after the inner `)`:

```
without this fix:  at <anonymous> (input.js:7:9)    ← the inner `)`
with this fix:     at <anonymous> (input.js:7:10)   ← the outer `(`   ✓
```

The same one-column drift affected `expect(x).resolves` (the `.` getter access after the `)`).

## Compared with esbuild and tsc

Same input through each tool (identical generated code), visualized with [source-map-viewer](https://source-map-viewer.void.app):

```js
const outer = factory()();          // outer `(`  of factory()()
const member = expect(x).resolves;  // `.`        of expect(x).resolves
const indexed = arr[i]();           // `(`        of arr[i]()
```

| Source token | oxc (this PR) | tsc | esbuild |
| --- | :--: | :--: | :--: |
| outer `(` of `factory()()` | ✓ | ✓ | ✗ |
| `.` of `expect(x).resolves` | ✓ | ✓ | ✗ |
| `(` of `arr[i]()` | ✓ | ✓ | ✗ |

- **vs esbuild** ([compare](https://source-map-viewer.void.app/compare?a=QCnJWx7H&b=Y4FwbiMW)): oxc adds the three chain mappings esbuild lacks — esbuild maps the `]` one column left of the `(`, the exact off-by-one. This PR resolves what esbuild still gets wrong.
- **vs tsc** ([compare](https://source-map-viewer.void.app/compare?a=UjsQoNTX&b=Y4FwbiMW)): all three positions match tsc exactly. tsc maps ~10 more positions because it gives every node a trailing end-mapping; this PR maps only the no-anchor sites (+0.6% vs tsc's full density).

---

This PR was prepared with AI assistance; I reviewed, tested, and verified the changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-codegen Area - Code Generation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants