denoland/deno · warning

Failed to convert range to tsc text span: {:#}

Error message

Failed to convert range to tsc text span: {:#}

What it means

Thrown inside the Deno language server when an inlay-hints request's LSP range (UTF-16 line/character positions) cannot be converted to a tsc TextSpan (character offsets). TextSpan::from_range (cli/lsp/tsc.rs:1943) maps both endpoints through LineIndex::offset_tsc, which fails when a position is out of bounds for the document's current line index. It surfaces as a failed textDocument/inlayHint request in editor/LSP logs, not as a CLI error.

Source

Thrown at cli/lsp/ts_server.rs:1211

          &snapshot,
          token,
        )
      }
    }
  }

  pub async fn provide_inlay_hint(
    &self,
    module: &DocumentModule,
    range: lsp::Range,
    snapshot: &Arc<StateSnapshot>,
    token: &CancellationToken,
  ) -> Result<Option<Vec<lsp::InlayHint>>, AnyError> {
    match self {
      Self::Js(ts_server) => {
        let text_span = tsc::TextSpan::from_range(range, &module.line_index)
          .map_err(|err| {
            anyhow!("Failed to convert range to tsc text span: {:#}", err)
          })?;
        let mut inlay_hints = ts_server
          .provide_inlay_hints(snapshot.clone(), module, text_span, token)
          .await;
        // Silence tsc debug failures.
        // See https://github.com/denoland/deno/issues/30455.
        // TODO(nayeemrmn): Keeps tabs on whether this is still necessary.
        if let Err(err) = &inlay_hints
          && err.to_string().contains("Debug Failure")
        {
          lsp_warn!("Unable to get inlay hints from TypeScript: {:#}", err);
          inlay_hints = Ok(None)
        }
        inlay_hints?
          .map(|inlay_hints| {
            inlay_hints
              .into_iter()
              .map(|inlay_hint| {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Reload the file or restart the language server (VS Code: 'Deno: Restart Language Server' / Reload Window) to resynchronize snapshots
  2. Update Deno - stale-snapshot range conversions have received fixes in past releases
  3. If you maintain the editor extension, clamp the requested range to the document's real line/character bounds before sending the request
  4. If reproducible on current Deno, capture LSP logs (deno lsp with DENO_LOG=debug or the editor's Deno log panel) and report to denoland/deno

Example fix

// before (editor extension sends client-cached range)
const hints = await client.sendRequest('textDocument/inlayHint', {
  textDocument: doc.uri,
  range: cachedRange,
});
// after (clamp to the document the server has)
const lastLine = doc.lineCount - 1;
const endLine = Math.min(cachedRange.end.line, lastLine);
const range = {
  start: { line: 0, character: 0 },
  end: {
    line: endLine,
    character: Math.min(cachedRange.end.character, doc.lineAt(endLine).text.length),
  },
};
const hints = await client.sendRequest('textDocument/inlayHint', {
  textDocument: doc.uri,
  range,
});
Defensive patterns

Strategy: validation

Validate before calling

// Editor/LSP client: clamp the range to the document before requesting inlay hints
function clampRange(doc, range) {
  const lastLine = doc.lineCount - 1;
  const endLine = Math.min(Math.max(range.end.line, 0), lastLine);
  const endChar = Math.min(range.end.character, doc.lineAt(endLine).text.length);
  const startLine = Math.min(Math.max(range.start.line, 0), endLine);
  const startChar = startLine === endLine
    ? Math.min(range.start.character, endChar)
    : Math.min(range.start.character, doc.lineAt(startLine).text.length);
  return { start: { line: startLine, character: startChar }, end: { line: endLine, character: endChar } };
}

Type guard

function isValidLspRange(doc, range) {
  return (
    Number.isInteger(range.start.line) && range.start.line < doc.lineCount &&
    Number.isInteger(range.end.line) && range.end.line < doc.lineCount &&
    range.start.character <= doc.lineAt(range.start.line).text.length &&
    range.end.character <= doc.lineAt(range.end.line).text.length &&
    (range.start.line < range.end.line ||
      (range.start.line === range.end.line && range.start.character <= range.end.character))
  );
}

Try / catch

try {
  const hints = await client.sendRequest('textDocument/inlayHint', params);
} catch (err) {
  // One stale-range failure is benign: drop the hints for this version,
  // do not retry with the same range - wait for the next change event.
  if (/text span|inlay/i.test(String(err))) return null;
  throw err;
}

Prevention

When it happens

Trigger: An editor sends textDocument/inlayHint with a range whose start/end line or character exceeds the document content the server currently holds: range computed against an older document version (rapid edits, formatter run racing the request), a whole-document range on a file that just shrank, or a client extension computing positions past end-of-file.

Common situations: Fast typing or format-on-save invalidating a pending inlay-hints request; editor extensions (VS Code, Neovim/lspconfig, Zed) that cache ranges client-side; notebook cells where position mapping differs between client and server; stale snapshot after rapid open/close of files.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/05dc1e30d5038d95. Report an issue: GitHub.