{"record":{"id":"0894562e6540ea25","repo":"biomejs/biome","slug":"invalid-range-start-offset-start-end-offs","errorCode":null,"errorMessage":"invalid range: start offset ({start:?}) > end offset ({end:?}) for {range:?}","messagePattern":"invalid range: start offset \\((.+?)\\) > end offset \\((.+?)\\) for (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/biome_lsp_converters/src/from_proto.rs","lineNumber":40,"sourceCode":"            };\n            line_index.to_utf8(enc, line_col)\n        }\n    };\n\n    line_index\n        .offset(line_col)\n        .with_context(|| format!(\"position {position:?} is out of range\"))\n}\n\n/// The function is used to convert a LSP range to TextRange.\npub fn text_range(\n    line_index: &LineIndex,\n    range: Range,\n    position_encoding: PositionEncoding,\n) -> Result<TextRange> {\n    let start = offset(line_index, range.start, position_encoding)?;\n    let end = offset(line_index, range.end, position_encoding)?;\n    anyhow::ensure!(\n        start <= end,\n        \"invalid range: start offset ({start:?}) > end offset ({end:?}) for {range:?}\"\n    );\n    Ok(TextRange::new(start, end))\n}\n","sourceCodeStart":22,"sourceCodeEnd":46,"githubUrl":"https://github.com/biomejs/biome/blob/7529811358079edb5a2a9d4a5f67a9f639a63f3a/crates/biome_lsp_converters/src/from_proto.rs#L22-L46","documentation":"from_proto::text_range converts an incoming LSP Range to a Biome TextRange by converting both endpoints to byte offsets, then asserts start <= end via anyhow::ensure! (from_proto.rs:38-44). The error fires when the client sent a range whose start position is after its end position (by line, then character), which cannot form a valid TextRange.","triggerScenarios":"A client sends range.start greater than range.end - a backwards user selection the editor did not normalize, or client code constructing Range with the endpoints swapped. Hit by every server handler that converts client-provided ranges (formatting ranges, code action ranges, refactor selections).","commonSituations":"Custom LSP clients or scripts building Position/Range objects by hand; editors that report inverted selections; copy-pasted range literals with start/end reversed.","solutions":["Normalize the range before sending: order the endpoints by (line, character) and swap them if inverted.","When writing a client, pass positions straight from the editor API instead of reconstructing Range objects manually.","On the server side, handle this conversion error and answer the request with an LSP error response rather than letting it propagate."],"exampleFix":"// before: endpoints swapped (start after end)\nconst range = { start: { line: 10, character: 5 }, end: { line: 2, character: 0 } };\n\n// after: normalize before sending\nfunction posLe(a, b) {\n\treturn a.line < b.line || (a.line === b.line && a.character <= b.character);\n}\nconst range = posLe(startPos, endPos)\n\t? { start: startPos, end: endPos }\n\t: { start: endPos, end: startPos };","handlingStrategy":"validation","validationCode":"// Normalize every range before sending it to the server\nfunction posLe(a, b) {\n\treturn a.line < b.line || (a.line === b.line && a.character <= b.character);\n}\n\nfunction normalizeRange(range) {\n\treturn posLe(range.start, range.end) ? range : { start: range.end, end: range.start };\n}\n\nconst params = { textDocument, range: normalizeRange(selection) };","typeGuard":"function posLe(a, b) {\n\treturn a.line < b.line || (a.line === b.line && a.character <= b.character);\n}\n\nfunction isValidRange(range) {\n\treturn (\n\t\trange !== null &&\n\t\ttypeof range === \"object\" &&\n\t\tposLe(range.start, range.end)\n\t);\n}","tryCatchPattern":"try {\n\tconst edits = await connection.sendRequest(\"textDocument/rangeFormatting\", params);\n} catch (err) {\n\tif (String(err?.message ?? err).includes(\"invalid range: start offset\")) {\n\t\treturn connection.sendRequest(\"textDocument/rangeFormatting\", {\n\t\t\t...params,\n\t\t\trange: normalizeRange(params.range),\n\t\t});\n\t}\n\tthrow err;\n}","preventionTips":["Always order range endpoints by (line, character) before constructing an LSP Range; swap inverted selections.","Pass positions obtained directly from the editor API instead of rebuilding them by hand.","Wrap range construction in one normalizeRange helper used everywhere, so a single place enforces ordering."],"tags":["lsp","range","position","protocol","validation"],"backgroundTag":"invalid-lsp-range","analyzedSha":"7529811358079edb5a2a9d4a5f67a9f639a63f3a","analyzedAt":"2026-08-16T22:13:27.842Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}