Skip to content

Commit 6690076

Browse files
authored
Improve formatter edge case behavior (#1628)
1 parent 245336b commit 6690076

File tree

2 files changed

+43
-13
lines changed

2 files changed

+43
-13
lines changed

packages/langium/src/lsp/formatter.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,21 @@ export abstract class AbstractFormatter implements Formatter {
150150
protected avoidOverlappingEdits(textDocument: TextDocument, textEdits: TextEdit[]): TextEdit[] {
151151
const edits: TextEdit[] = [];
152152
for (const edit of textEdits) {
153-
const last = edits[edits.length - 1];
154-
if (last) {
153+
let last = edits[edits.length - 1];
154+
while (last) {
155155
const currentStart = textDocument.offsetAt(edit.range.start);
156156
const lastEnd = textDocument.offsetAt(last.range.end);
157157
if (currentStart < lastEnd) {
158158
edits.pop();
159+
last = edits[edits.length - 1];
160+
}
161+
else {
162+
break;
159163
}
160164
}
161165
edits.push(edit);
162166
}
163-
return edits;
167+
return edits.filter(edit => this.isNecessary(edit, textDocument));
164168
}
165169

166170
protected iterateAstFormatting(document: LangiumDocument, range?: Range): void {
@@ -200,11 +204,8 @@ export abstract class AbstractFormatter implements Formatter {
200204
return false;
201205
}
202206

203-
/**
204-
* @deprecated This method has been deprecated with 3.1. It now always returns `true` and is no longer used by the default formatter implementation.
205-
*/
206-
protected isNecessary(_edit: TextEdit, _document: TextDocument): boolean {
207-
return true;
207+
protected isNecessary(edit: TextEdit, document: TextDocument): boolean {
208+
return edit.newText !== document.getText(edit.range).replace(/\r/g, '');
208209
}
209210

210211
protected iterateCstFormatting(document: LangiumDocument, formattings: Map<string, FormattingAction>, options: FormattingOptions, range?: Range): TextEdit[] {
@@ -382,7 +383,10 @@ export abstract class AbstractFormatter implements Formatter {
382383
context.indentation += (tabs ?? 0);
383384
const edits: TextEdit[] = [];
384385
if (chars !== undefined) {
385-
edits.push(this.createSpaceTextEdit(betweenRange, chars, formatting.options));
386+
// Do not apply formatting on the same line if preceding node is hidden
387+
if (!a?.hidden) {
388+
edits.push(this.createSpaceTextEdit(betweenRange, chars, formatting.options));
389+
}
386390
} else if (lines !== undefined) {
387391
edits.push(this.createLineTextEdit(betweenRange, lines, context, formatting.options));
388392
} else if (tabs !== undefined) {

packages/langium/test/grammar/lsp/grammar-formatter.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import { EmptyFileSystem } from 'langium';
88
import { expandToString } from 'langium/generate';
99
import { createLangiumGrammarServices } from 'langium/grammar';
10-
import { expectFormatting } from 'langium/test';
11-
import { describe, test } from 'vitest';
10+
import { expectFormatting, parseDocument } from 'langium/test';
11+
import { describe, expect, test } from 'vitest';
1212

1313
const services = createLangiumGrammarServices(EmptyFileSystem);
1414
const formatting = expectFormatting(services.grammar);
@@ -41,10 +41,12 @@ describe('Grammar Formatter', () => {
4141
test('Formats interface extends references', async () => {
4242
await formatting({
4343
before: expandToString`
44-
interface A extends B,C, D,E{}
44+
interface A // This is a comment
45+
extends B,C, D,E{}
4546
`,
4647
after: expandToString`
47-
interface A extends B, C, D, E {
48+
interface A // This is a comment
49+
extends B, C, D, E {
4850
}
4951
`
5052
});
@@ -62,6 +64,30 @@ describe('Grammar Formatter', () => {
6264
});
6365
});
6466

67+
test('No edits if document is already formatted', async () => {
68+
69+
const formatter = services.grammar.lsp.Formatter;
70+
if (!formatter) {
71+
throw new Error(`No formatter registered for language ${services.grammar.LanguageMetaData.languageId}`);
72+
}
73+
const document = await parseDocument(services.grammar, expandToString`
74+
interface Test {
75+
// This is a comment
76+
a: string
77+
b: number
78+
// This is another comment
79+
c: boolean
80+
}
81+
`);
82+
const identifier = { uri: document.uri.toString() };
83+
const options = {
84+
insertSpaces: true,
85+
tabSize: 4
86+
};
87+
const edits = await formatter.formatDocument(document, { options, textDocument: identifier });
88+
expect(edits.length).toBe(0);
89+
});
90+
6591
test('Formats parser rule definitions with alternatives', async () => {
6692
await formatting({
6793
before: expandToString`

0 commit comments

Comments
 (0)