{"record":{"id":"51e6e4fdbc5c735a","repo":"swc-project/swc","slug":"invalid-source-context-byte-range-old-start-o","errorCode":null,"errorMessage":"invalid source context byte range: {old_start}..{old_end}","messagePattern":"invalid source context byte range: (.+?)\\.\\.(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"bindings/binding_core_node/src/ast_context.rs","lineNumber":111,"sourceCode":"\n    rebase_program_spans(&mut program, &source_context, &fm)\n        .context(\"failed to rebase AST spans to restored source context\")?;\n\n    Ok((fm, program))\n}\n\nfn rebase_program_spans(\n    program: &mut Program,\n    source_context: &ProgramSourceContext,\n    fm: &SourceFile,\n) -> Result<(), Error> {\n    let old_start = source_context.start_pos;\n    let old_end = source_context.end_pos;\n    let new_start = fm.start_pos.0;\n    let new_end = fm.end_pos.0;\n\n    if old_start > old_end {\n        bail!(\"invalid source context byte range: {old_start}..{old_end}\");\n    }\n\n    program.visit_mut_with(&mut SpanRebaser {\n        old_start,\n        old_end,\n        new_start,\n        new_end,\n    });\n\n    Ok(())\n}\n\nimpl ProgramSourceContext {\n    fn file_name(&self) -> FileName {\n        match &self.real_filename {\n            Some(filename) => FileName::Real(filename.into()),\n            None => FileName::Anon,\n        }","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/bindings/binding_core_node/src/ast_context.rs#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the envelope JSON exactly as SWC emitted it (serialize_program output) instead of reconstructing sourceContext by hand","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)","Emit and consume envelopes with the same @swc/core version on both sides of the JS/native boundary","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"],"exampleFix":"// before\nconst envelope = {\n  program,\n  sourceContext: { source, realFilename: null, startPos: end, endPos: start },\n};\n\n// after - range ordered, and matching the UTF-8 byte length of source\nconst bytes = new TextEncoder().encode(source).length;\nconst envelope = {\n  program,\n  sourceContext: { source, realFilename: null, startPos: 0, endPos: bytes },\n};","handlingStrategy":"validation","validationCode":"const enc = new TextEncoder();\nfunction assertValidSourceContext(ctx) {\n  if (!Number.isInteger(ctx.startPos) || !Number.isInteger(ctx.endPos))\n    throw new TypeError('startPos/endPos must be integers');\n  if (ctx.startPos > ctx.endPos)\n    throw new RangeError(`startPos ${ctx.startPos} > endPos ${ctx.endPos}`);\n  if (ctx.endPos - ctx.startPos !== enc.encode(ctx.source).length)\n    throw new RangeError('range must equal the UTF-8 byte length of source');\n}\n// call before passing the envelope to the native binding\nassertValidSourceContext(envelope.sourceContext);","typeGuard":"type SourceContext = { source: string; startPos: number; endPos: number };\nconst hasValidSourceContext = (\n  env: { sourceContext?: SourceContext },\n): boolean => {\n  const c = env.sourceContext;\n  return (\n    !!c &&\n    Number.isInteger(c.startPos) &&\n    Number.isInteger(c.endPos) &&\n    c.startPos <= c.endPos\n  );\n};","tryCatchPattern":"try {\n  const res = transformWithAst(envelope);\n} catch (e) {\n  if (String(e?.message).includes('invalid source context byte range')) {\n    throw new Error('AST envelope has an inverted byte range - regenerate it from SWC output instead of hand-editing');\n  }\n  throw e;\n}","preventionTips":["Never hand-write sourceContext; echo back the envelope a previous SWC call returned","Compute lengths with TextEncoder (UTF-8 bytes), never string .length (UTF-16 units)","Pin the same @swc/core version on the producing and consuming side of the AST boundary"],"tags":["swc","ast","spans","byte-range","node-bindings"],"backgroundTag":"invalid-byte-range","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}