Fix DevServer HMR sourcemap offset issues (#22739)

## Summary
Fixes sourcemap offset issues in DevServer HMR mode that were causing
incorrect line number mappings when debugging.

## Problem

When using DevServer with HMR enabled, sourcemap line numbers were
consistently off by one or more lines when shown in Chrome DevTools. In
some cases, they were off when shown in the terminal as well.

## Solution

### 1. Remove magic +2 offset
Removed an arbitrary "+2" that was added to `runtime.line_count` in
SourceMapStore.zig. The comment said "magic fairy in my dreams said it
would align the source maps" - this was causing positions to be
incorrectly offset.

### 2. Fix double-increment bug
ErrorReportRequest.zig was incorrectly adding 1 to line numbers that
were already 1-based from the browser, causing an off-by-one error.

### 3. Improve type safety
Converted all line/column handling to use `bun.Ordinal` type instead of
raw `i32`, ensuring consistent 0-based vs 1-based conversions throughout
the codebase.

## Test plan
- [x] Added comprehensive sourcemap tests for complex error scenarios
- [x] Tested with React applications in dev mode
- [x] Verified line numbers match correctly in browser dev tools
- [x] Existing sourcemap tests continue to pass

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jarred Sumner
2025-09-17 15:37:09 -07:00
committed by GitHub
parent d85207f179
commit 2eebcee522
10 changed files with 47 additions and 41 deletions

View File

@@ -218,7 +218,7 @@ pub fn parseJSON(
const mapping, const source_index = switch (hint) {
.source_only => |index| .{ null, index },
.all => |loc| brk: {
const mapping = map.?.mappings.find(loc.line, loc.column) orelse
const mapping = map.?.mappings.find(.fromZeroBased(loc.line), .fromZeroBased(loc.column)) orelse
break :brk .{ null, null };
break :brk .{ mapping, std.math.cast(u32, mapping.source_index) };
},
@@ -315,14 +315,14 @@ pub const Mapping = struct {
this.impl = .{ .with_names = with_names };
}
fn findIndexFromGenerated(line_column_offsets: []const LineColumnOffset, line: i32, column: i32) ?usize {
fn findIndexFromGenerated(line_column_offsets: []const LineColumnOffset, line: bun.Ordinal, column: bun.Ordinal) ?usize {
var count = line_column_offsets.len;
var index: usize = 0;
while (count > 0) {
const step = count / 2;
const i: usize = index + step;
const mapping = line_column_offsets[i];
if (mapping.lines.zeroBased() < line or (mapping.lines.zeroBased() == line and mapping.columns.zeroBased() <= column)) {
if (mapping.lines.zeroBased() < line.zeroBased() or (mapping.lines.zeroBased() == line.zeroBased() and mapping.columns.zeroBased() <= column.zeroBased())) {
index = i + 1;
count -|= step + 1;
} else {
@@ -331,7 +331,7 @@ pub const Mapping = struct {
}
if (index > 0) {
if (line_column_offsets[index - 1].lines.zeroBased() == line) {
if (line_column_offsets[index - 1].lines.zeroBased() == line.zeroBased()) {
return index - 1;
}
}
@@ -339,7 +339,7 @@ pub const Mapping = struct {
return null;
}
pub fn findIndex(this: *const List, line: i32, column: i32) ?usize {
pub fn findIndex(this: *const List, line: bun.Ordinal, column: bun.Ordinal) ?usize {
switch (this.impl) {
inline else => |*list| {
if (findIndexFromGenerated(list.items(.generated), line, column)) |i| {
@@ -383,7 +383,7 @@ pub const Mapping = struct {
}
}
pub fn find(this: *const List, line: i32, column: i32) ?Mapping {
pub fn find(this: *const List, line: bun.Ordinal, column: bun.Ordinal) ?Mapping {
switch (this.impl) {
inline else => |*list, tag| {
if (findIndexFromGenerated(list.items(.generated), line, column)) |i| {
@@ -1356,8 +1356,8 @@ pub const SourceContent = struct {
pub fn find(
this: *const SourceMap,
line: i32,
column: i32,
line: bun.Ordinal,
column: bun.Ordinal,
) ?Mapping {
return this.mapping.find(line, column);
}