swc-project/swc · error · anyhow::Error

invalid source context byte range: {old_start}..{old_end}

Error message

invalid source context byte range: {old_start}..{old_end}

What it means

Thrown by the Node binding's AST round-trip path: when a serialized program envelope is passed back into native code, rebase_program_spans restores the original SourceMap positions and rejects an inverted byte range (startPos > endPos). The sourceContext object normally originates from SWC's own serialize_program, so a valid pipeline never produces this; it fires when the envelope JSON was hand-built or patched with wrong positions. Note that the earlier checked_sub guard in prepare_program_with_context usually intercepts the same condition first with a message that lacks the range.

Source

Thrown at bindings/binding_core_node/src/ast_context.rs:111

    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;
    let new_start = fm.start_pos.0;
    let new_end = fm.end_pos.0;

    if old_start > old_end {
        bail!("invalid source context byte range: {old_start}..{old_end}");
    }

    program.visit_mut_with(&mut SpanRebaser {
        old_start,
        old_end,
        new_start,
        new_end,
    });

    Ok(())
}

impl ProgramSourceContext {
    fn file_name(&self) -> FileName {
        match &self.real_filename {
            Some(filename) => FileName::Real(filename.into()),
            None => FileName::Anon,
        }

View on GitHub (pinned to 5176682b65)

Solutions

  1. Pass the envelope JSON exactly as SWC emitted it (serialize_program output) instead of reconstructing sourceContext by hand
  2. If you must build it: keep startPos <= endPos and make endPos - startPos equal the UTF-8 byte length of source (new TextEncoder().encode(source).length)
  3. Emit and consume envelopes with the same @swc/core version on both sides of the JS/native boundary
  4. Check whether you actually hit the sibling guard in prepare_program_with_context ('invalid source context byte range' without the a..b suffix, or the 'source length changed' bail) and fix the length mismatch instead

Example fix

// before
const envelope = {
  program,
  sourceContext: { source, realFilename: null, startPos: end, endPos: start },
};

// after - range ordered, and matching the UTF-8 byte length of source
const bytes = new TextEncoder().encode(source).length;
const envelope = {
  program,
  sourceContext: { source, realFilename: null, startPos: 0, endPos: bytes },
};
Defensive patterns

Strategy: validation

Validate before calling

const enc = new TextEncoder();
function assertValidSourceContext(ctx) {
  if (!Number.isInteger(ctx.startPos) || !Number.isInteger(ctx.endPos))
    throw new TypeError('startPos/endPos must be integers');
  if (ctx.startPos > ctx.endPos)
    throw new RangeError(`startPos ${ctx.startPos} > endPos ${ctx.endPos}`);
  if (ctx.endPos - ctx.startPos !== enc.encode(ctx.source).length)
    throw new RangeError('range must equal the UTF-8 byte length of source');
}
// call before passing the envelope to the native binding
assertValidSourceContext(envelope.sourceContext);

Type guard

type SourceContext = { source: string; startPos: number; endPos: number };
const hasValidSourceContext = (
  env: { sourceContext?: SourceContext },
): boolean => {
  const c = env.sourceContext;
  return (
    !!c &&
    Number.isInteger(c.startPos) &&
    Number.isInteger(c.endPos) &&
    c.startPos <= c.endPos
  );
};

Try / catch

try {
  const res = transformWithAst(envelope);
} catch (e) {
  if (String(e?.message).includes('invalid source context byte range')) {
    throw new Error('AST envelope has an inverted byte range - regenerate it from SWC output instead of hand-editing');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a native SWC API that accepts a serialized program envelope (ProgramEnvelope with camelCase sourceContext: {source, realFilename, startPos, endPos}) where startPos > endPos, e.g. swapping the two positions or recomputing them from character offsets instead of byte offsets.

Common situations: Custom codemod or AST-explorer tooling that regenerates envelope JSON and computes start/end positions itself; feeding an envelope produced by a different @swc/core version whose context shape changed; counting UTF-16 code units (JS string length) instead of UTF-8 bytes against a range built from byte offsets.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/51e6e4fdbc5c735a. Report an issue: GitHub.