From e2047c94353a5d3880889cff441626db80e8f1f3 Mon Sep 17 00:00:00 2001 From: lauren Date: Wed, 20 May 2026 17:35:30 -0700 Subject: [PATCH] [rust-compiler] Fix off-by-one in SWC source-location conversion SWC's BytePos is 1-based (BytePos(0) is the DUMMY/synthetic sentinel) while the Babel AST that the rest of the compiler consumes uses 0-based offsets in loc.{start,end}.{line,column,index}. `position()` in the SWC convert_ast was binary-searching the 1-based offset against the 0-based line_offsets table, producing line/column/index values that were off by 1, and that flipped to "next line, column 0" when the referenced byte happened to be the byte right before a `\n`. Shift the offset down by 1 before the line lookup. BaseNode.start/end keeps the SWC-native 1-based value because the SWC scope collector keys its node_to_scope map on span.lo.0 and the HIR builder looks it up via base.start. Also drop the e2e harness's `oneBasedColumns = variant === 'swc'` workaround that was masking part of this divergence. Test plan: - bash compiler/scripts/test-e2e.sh --variant swc: Before: Total 1682/1795 (113 failures) After: Total 1742/1795 (53 failures, 60 fixed) - bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged) - bash compiler/scripts/test-e2e.sh --variant oxc: 1702/1795 (unchanged) - cargo test --workspace: 56 passed, 0 failed (unchanged) --- compiler/crates/react_compiler_swc/src/convert_ast.rs | 9 ++++++--- compiler/scripts/test-e2e.ts | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/crates/react_compiler_swc/src/convert_ast.rs b/compiler/crates/react_compiler_swc/src/convert_ast.rs index 8ced013e5442..4c77f2a15eb2 100644 --- a/compiler/crates/react_compiler_swc/src/convert_ast.rs +++ b/compiler/crates/react_compiler_swc/src/convert_ast.rs @@ -196,16 +196,19 @@ impl<'a> ConvertCtx<'a> { } } + /// `BytePos` is 1-based; emit 0-based `loc` to match Babel. + /// (`BaseNode.start`/`end` stays 1-based: `convert_scope` keys on it.) fn position(&self, offset: u32) -> Position { - let line_idx = match self.line_offsets.binary_search(&offset) { + let zero_based = offset.saturating_sub(1); + let line_idx = match self.line_offsets.binary_search(&zero_based) { Ok(idx) => idx, Err(idx) => idx.saturating_sub(1), }; let line_start = self.line_offsets[line_idx]; Position { line: (line_idx as u32) + 1, - column: offset - line_start, - index: Some(offset), + column: zero_based - line_start, + index: Some(zero_based), } } diff --git a/compiler/scripts/test-e2e.ts b/compiler/scripts/test-e2e.ts index 244bf4096bda..a010a86c4989 100644 --- a/compiler/scripts/test-e2e.ts +++ b/compiler/scripts/test-e2e.ts @@ -484,11 +484,9 @@ async function runVariant( for (let i = 0; i < fixtureInfos.length; i++) { const {fixturePath, relPath, source, firstLine, isFlow} = fixtureInfos[i]; const tsCode = tsBaselines.get(fixturePath)!; - // TS baseline uses Babel (0-based columns), no adjustment needed + // All variants emit 0-based columns/indices. oneBasedColumns = false; const tsEvents = normalizeEvents(tsRawEvents.get(fixturePath)!); - // SWC uses 1-based columns/indices (BytePos); OXC is 0-based like Babel - oneBasedColumns = variant === 'swc'; writeProgress( ` ${variant}: ${i + 1}/${fixtureInfos.length} (${s.passed} passed, ${s.failed} failed)`,