diff --git a/packages/langium/src/generator/template-string.ts b/packages/langium/src/generator/template-string.ts index 8ab543115..2414d8cd1 100644 --- a/packages/langium/src/generator/template-string.ts +++ b/packages/langium/src/generator/template-string.ts @@ -57,7 +57,7 @@ export function expandToString(staticParts: TemplateStringsArray, ...substitutio } export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__'); -export const NEWLINE_REGEXP = /\r?\n/g; +export const NEWLINE_REGEXP = /\r?\n/gm; const nonWhitespace = /\S|$/; // add the alignment of the previous static part to all lines of the following substitution diff --git a/packages/langium/src/grammar/lsp/grammar-formatter.ts b/packages/langium/src/grammar/lsp/grammar-formatter.ts index 7e1f6af24..81e6a1b89 100644 --- a/packages/langium/src/grammar/lsp/grammar-formatter.ts +++ b/packages/langium/src/grammar/lsp/grammar-formatter.ts @@ -8,6 +8,8 @@ import type { AstNode } from '../../syntax-tree.js'; import { AbstractFormatter, Formatting } from '../../lsp/formatter.js'; import * as ast from '../generated/ast.js'; +const indentOrSpace = Formatting.fit(Formatting.oneSpace(), Formatting.indent()); + export class LangiumGrammarFormatter extends AbstractFormatter { protected format(node: AstNode): void { @@ -57,6 +59,26 @@ export class LangiumGrammarFormatter extends AbstractFormatter { formatter.keyword('<').surround(Formatting.noSpace()); formatter.keyword(',').append(Formatting.oneSpace()); formatter.properties('arguments').append(Formatting.noSpace()); + } else if (ast.isInterface(node)) { + const formatter = this.getNodeFormatter(node); + formatter.keyword('interface').append(Formatting.oneSpace()); + formatter.keyword('extends').prepend(Formatting.oneSpace()).append(indentOrSpace); + formatter.keywords(',').prepend(Formatting.noSpace()).append(indentOrSpace); + const bracesOpen = formatter.keyword('{'); + bracesOpen.prepend(Formatting.fit(Formatting.oneSpace(), Formatting.newLine())); + const bracesClose = formatter.keyword('}'); + bracesClose.prepend(Formatting.newLine()); + formatter.interior(bracesOpen, bracesClose).prepend(Formatting.indent()); + } else if (ast.isType(node)) { + const formatter = this.getNodeFormatter(node); + formatter.keyword('type').append(Formatting.oneSpace()); + formatter.keyword('=').prepend(Formatting.oneSpace()).append(indentOrSpace); + formatter.keyword(';').prepend(Formatting.noSpace()).append(Formatting.newLine()); + } else if (ast.isGrammar(node)) { + const formatter = this.getNodeFormatter(node); + const nodes = formatter.nodes(...node.rules, ...node.interfaces, ...node.types, ...node.imports); + nodes.prepend(Formatting.noIndent()); + formatter.keyword('grammar').prepend(Formatting.noSpace()).append(Formatting.oneSpace()); } if (ast.isAbstractElement(node)) { diff --git a/packages/langium/src/test/langium-test.ts b/packages/langium/src/test/langium-test.ts index ff7bca00e..4a61f8aff 100644 --- a/packages/langium/src/test/langium-test.ts +++ b/packages/langium/src/test/langium-test.ts @@ -19,6 +19,7 @@ import * as assert from 'node:assert'; import { stream } from '../utils/stream.js'; import type { AsyncDisposable } from '../utils/disposable.js'; import { Disposable } from '../utils/disposable.js'; +import { normalizeEOL } from '../generator/template-string.js'; export interface ParseHelperOptions extends BuildOptions { /** @@ -479,7 +480,7 @@ export function expectFormatting(services: LangiumServices): (expectedFormatting formatter.formatDocument(document, { options, textDocument: identifier })); const editedDocument = TextDocument.applyEdits(document.textDocument, edits); - expectedFunction(editedDocument, expectedFormatting.after); + expectedFunction(normalizeEOL(editedDocument), normalizeEOL(expectedFormatting.after)); const disposable = Disposable.create(() => clearDocuments(services, [document])); if (expectedFormatting.disposeAfterCheck) { diff --git a/packages/langium/test/grammar/lsp/grammar-formatter.test.ts b/packages/langium/test/grammar/lsp/grammar-formatter.test.ts new file mode 100644 index 000000000..ca735739e --- /dev/null +++ b/packages/langium/test/grammar/lsp/grammar-formatter.test.ts @@ -0,0 +1,63 @@ +/****************************************************************************** +* Copyright 2023 TypeFox GmbH +* This program and the accompanying materials are made available under the +* terms of the MIT License, which is available in the project root. +******************************************************************************/ + +import { describe, test } from 'vitest'; +import { EmptyFileSystem, createLangiumGrammarServices, expandToString } from 'langium'; +import { expectFormatting } from 'langium/test'; + +const services = createLangiumGrammarServices(EmptyFileSystem); +const formatting = expectFormatting(services.grammar); + +describe('Grammar Formatter', () => { + + test('Indents interface properties', async () => { + await formatting({ + before: expandToString` + interface Test { + // This is a comment + a: string + b: number + // This is another comment + c: boolean + } + `, + after: expandToString` + interface Test { + // This is a comment + a: string + b: number + // This is another comment + c: boolean + } + ` + }); + }); + + test('Formats interface extends references', async () => { + await formatting({ + before: expandToString` + interface A extends B,C, D,E{} + `, + after: expandToString` + interface A extends B, C, D, E { + } + ` + }); + }); + + test('Formats union type definitions', async () => { + await formatting({ + before: expandToString` + type A= B | C | D + ; + `, + after: expandToString` + type A = B | C | D; + ` + }); + }); + +}); diff --git a/packages/langium/test/grammar/lsp/langium-grammar-semantic-token-provider.test.ts b/packages/langium/test/grammar/lsp/grammar-semantic-token-provider.test.ts similarity index 100% rename from packages/langium/test/grammar/lsp/langium-grammar-semantic-token-provider.test.ts rename to packages/langium/test/grammar/lsp/grammar-semantic-token-provider.test.ts