swc-project/swc · error · anyhow::Error
failed to restore source context because the source length c
Error message
failed to restore source context because the source length changed while passing AST between JavaScript and native code
What it means
The Node binding (@swc/core native layer) can accept an already-parsed program instead of raw source: JS sends a ProgramEnvelope { program, sourceContext } and bindings/binding_core_node rebuilds a SourceFile from sourceContext.source, then verifies the fresh file's byte length equals the recorded range (endPos - startPos) before rebasing every AST span into the new source map (prepare_program_with_context). A mismatch triggers this bail! — SWC spans are raw byte offsets into the global source map, so a length drift would silently corrupt all spans and sourcemaps, and the check refuses to proceed.
Source
Thrown at bindings/binding_core_node/src/ast_context.rs:88
}
pub fn prepare_program_with_context(
c: &Compiler,
mut program: Program,
source_context: ProgramSourceContext,
) -> Result<(swc_core::common::sync::Lrc<SourceFile>, Program), Error> {
let fm = c.cm.new_source_file(
source_context.file_name().into(),
source_context.source.clone(),
);
let expected_len = source_context
.end_pos
.checked_sub(source_context.start_pos)
.context("invalid source context byte range")?;
if fm.byte_length() != expected_len {
bail!(
"failed to restore source context because the source length changed while passing AST \
between JavaScript and native code"
);
}
rebase_program_spans(&mut program, &source_context, &fm)
.context("failed to rebase AST spans to restored source context")?;
Ok((fm, program))
}
fn rebase_program_spans(
program: &mut Program,
source_context: &ProgramSourceContext,
fm: &SourceFile,
) -> Result<(), Error> {
let old_start = source_context.start_pos;
let old_end = source_context.end_pos;View on GitHub (pinned to 5176682b65)
Solutions
- Pass the byte-exact same source string that produced the AST's spans (freeze it in a const immediately after parsing).
- Re-parse the modified source instead of reusing the stale AST — fresh spans always match the new text.
- Normalize line endings/BOM once, before parsing, and feed the identical normalized string to every subsequent call.
- Align @swc/core JS and native package versions so envelope handling matches.
Example fix
// before: source mutated between parse and native call
const ast = await parse(src);
src = src.replaceAll("\r\n", "\n"); // length changed -> span corruption
await print(ast, { sourceContext });
// after: normalize first, then parse, then reuse both unchanged
src = src.replaceAll("\r\n", "\n");
const ast = await parse(src);
await print(ast, { sourceContext }); Defensive patterns
Strategy: try-catch
Validate before calling
import { Buffer } from "node:buffer";
// run before any @swc/core call that receives a parsed program envelope
export function sourceContextIsConsistent({ source, startPos, endPos }) {
const start = Number(startPos ?? 0);
const end = Number(endPos ?? 0);
if (!Number.isInteger(start) || !Number.isInteger(end) || end < start) return false;
return Buffer.byteLength(source, "utf8") === end - start;
}
if (!sourceContextIsConsistent(envelope.sourceContext)) {
throw new Error("source drifted from AST spans — re-parse before calling native");
} Type guard
/**
* Narrows a ProgramSourceContext to one whose byte length matches its span range,
* i.e. safe to pass to the native restore path.
*/
function isRestorableSourceContext(c) {
return Buffer.byteLength(c.source, "utf8") === c.endPos - c.startPos;
} Try / catch
try {
result = await callNativeWithProgram(program, sourceContext);
} catch (e) {
if (/failed to restore source context/.test(String(e?.message ?? e))) {
const reparsed = await parse(sourceContext.source); // regenerate spans from current text
result = await callNativeWithProgram(reparsed.program, sourceContext);
} else {
throw e;
}
} Prevention
- Freeze the source string immediately after parse (const) and never normalize it afterward
- Normalize line endings and BOM once, before parsing, then reuse the identical string everywhere
- Never reuse an AST after editing the source — re-parse instead
- Keep @swc/core JS and native layer versions in lockstep
When it happens
Trigger: Calling a @swc/core Node API that receives a parsed program envelope where the source string differs in byte length from the one the AST's spans were produced against — the code was edited, re-encoded (CRLF->LF, BOM added/removed), or replaced with another file's text between the parse and the call.
Common situations: Mutating the code string after parse() and reusing the old AST; normalizing line endings or BOM between parse and print; mixing spans from the original source with a minified/preprocessed string; version skew between the JS layer and the native binding altering envelope semantics.
Related errors
- Invalid attempt to iterate non-iterable instance. In order t
- Duplicated methods (${element.key}) can't be decorated.
- Decorators can't be placed on different accessors with for t
- invalid source context byte range: {old_start}..{old_end}
- determine_export_name({:?})
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/428bf552b2d50b94.
Report an issue: GitHub.