From d6966369b380418d15379da584a0eee91ed5be28 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Sat, 11 Apr 2026 14:18:31 +0530 Subject: [PATCH] fix: clamp format result range to document bounds (#153) --- src/services/htmlFormatter.ts | 8 ++++++ src/test/formatter.test.ts | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/services/htmlFormatter.ts b/src/services/htmlFormatter.ts index b9ec4d7..bb393dc 100644 --- a/src/services/htmlFormatter.ts +++ b/src/services/htmlFormatter.ts @@ -92,6 +92,14 @@ export function format(document: TextDocument, range: Range | undefined, options result = indent + result; // keep the indent } } + if (result === value) { + return []; + } + // Clamp the range end to the document end to avoid returning a range that exceeds the document length + const documentEnd = document.positionAt(document.getText().length); + if (range.end.line > documentEnd.line || (range.end.line === documentEnd.line && range.end.character > documentEnd.character)) { + range = Range.create(range.start, documentEnd); + } return [{ range: range, newText: result diff --git a/src/test/formatter.test.ts b/src/test/formatter.test.ts index 0e50a7f..0412e89 100644 --- a/src/test/formatter.test.ts +++ b/src/test/formatter.test.ts @@ -574,3 +574,53 @@ suite('HTML Formatter - Embedded CSS', () => { }); }); + +suite('HTML Formatter - Range validation', () => { + + function assertRangeWithinDocument(content: string, options?: HTMLFormatConfiguration) { + const uri = 'test://test.html'; + const document = TextDocument.create(uri, 'html', 0, content); + const formatOptions = options ?? { tabSize: 2, insertSpaces: true }; + const edits = getLanguageService().format(document, undefined, formatOptions); + const documentEnd = document.positionAt(content.length); + for (const edit of edits) { + assert.ok( + edit.range.end.line < documentEnd.line || + (edit.range.end.line === documentEnd.line && edit.range.end.character <= documentEnd.character), + `Edit range end (${edit.range.end.line}, ${edit.range.end.character}) exceeds document end (${documentEnd.line}, ${documentEnd.character}) for content ${JSON.stringify(content)}` + ); + } + return edits; + } + + test('empty document returns no edits', () => { + const edits = assertRangeWithinDocument(''); + assert.strictEqual(edits.length, 0, 'Expected no edits for an empty document'); + }); + + test('whitespace-only document returns valid range', () => { + assertRangeWithinDocument(' '); + }); + + test('newline-only document returns valid range', () => { + assertRangeWithinDocument('\n'); + }); + + test('CRLF-only document returns valid range', () => { + assertRangeWithinDocument('\r\n'); + }); + + test('empty document with endWithNewline returns valid range', () => { + const edits = assertRangeWithinDocument('', { tabSize: 2, insertSpaces: true, endWithNewline: true }); + // endWithNewline on empty document should produce a valid edit + if (edits.length > 0) { + assert.strictEqual(edits[0].newText, '\n'); + } + }); + + test('already formatted document returns no edits', () => { + const edits = assertRangeWithinDocument('
\n
\n
'); + assert.strictEqual(edits.length, 0, 'Expected no edits for already formatted content'); + }); + +});