diff --git a/examples/domainmodel/src/benchmark/generate.ts b/examples/domainmodel/src/benchmark/generate.ts index a97c71ac7..1c3e84559 100644 --- a/examples/domainmodel/src/benchmark/generate.ts +++ b/examples/domainmodel/src/benchmark/generate.ts @@ -4,7 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { expandToString, URI } from 'langium'; +import { URI } from 'langium'; +import { expandToString } from 'langium/generate'; import type { DomainModelServices } from '../language-server/domain-model-module.js'; export function generateWorkspace(services: DomainModelServices, width: number, size: number): void { diff --git a/examples/domainmodel/src/cli/generator.ts b/examples/domainmodel/src/cli/generator.ts index 8c233c743..ba700e11a 100644 --- a/examples/domainmodel/src/cli/generator.ts +++ b/examples/domainmodel/src/cli/generator.ts @@ -4,18 +4,17 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { IndentNode } from 'langium'; -import type { AbstractElement, Domainmodel, Entity, Feature, Type } from '../language-server/generated/ast.js'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; import chalk from 'chalk'; +import { type Generated, expandToNode, joinToNode, toString } from 'langium/generate'; +import { NodeFileSystem } from 'langium/node'; import _ from 'lodash'; -import { CompositeGeneratorNode, NL, toString } from 'langium'; -import { isEntity, isPackageDeclaration } from '../language-server/generated/ast.js'; -import { extractAstNode, extractDestinationAndName, setRootFolder } from './cli-util.js'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { createDomainModelServices } from '../language-server/domain-model-module.js'; +import type { AbstractElement, Domainmodel, Entity, Feature, Type } from '../language-server/generated/ast.js'; +import { isEntity, isPackageDeclaration } from '../language-server/generated/ast.js'; import { DomainModelLanguageMetaData } from '../language-server/generated/module.js'; -import { NodeFileSystem } from 'langium/node'; +import { extractAstNode, extractDestinationAndName, setRootFolder } from './cli-util.js'; export const generateAction = async (fileName: string, opts: GenerateOptions): Promise => { try { @@ -57,9 +56,11 @@ function generateAbstractElements(destination: string, elements: Array { - const featureData = entity.features.map(f => generateFeature(f, classBody)); - featureData.forEach(([generateField, , ]) => generateField()); - featureData.forEach(([, generateSetter, generateGetter]) => { generateSetter(); generateGetter(); } ); - }); - fileNode.append('}', NL); + const featureData = entity.features.map(generateFeature); + return expandToNode` + class ${entity.name}${maybeExtends} { + ${joinToNode(featureData, ([field]) => field, { appendNewLineIfNotEmpty: true})} + ${joinToNode(featureData, ([, setterAndGetter]) => setterAndGetter, { appendNewLineIfNotEmpty: true} )} + } + `.appendNewLine(); } -function generateFeature(feature: Feature, classBody: IndentNode): [() => void, () => void, () => void] { +function generateFeature(feature: Feature): [ string, Generated ] { const name = feature.name; const type = feature.type.$refText + (feature.many ? '[]' : ''); return [ - () => { // generate the field - classBody.append(`private ${type} ${name};`, NL); - }, - () => { // generate the setter - classBody.append(NL); - classBody.append(`public void set${_.upperFirst(name)}(${type} ${name}) {`, NL); - classBody.indent(methodBody => { - methodBody.append(`this.${name} = ${name};`, NL); - }); - classBody.append('}', NL); - }, - () => { // generate the getter - classBody.append(NL); - classBody.append(`public ${type} get${_.upperFirst(name)}() {`, NL); - classBody.indent(methodBody => { - methodBody.append(`return ${name};`, NL); - }); - classBody.append('}', NL); - } + `private ${type} ${name};`, + expandToNode` + + public void set${_.upperFirst(name)}(${type} ${name}) { + this.${name} = ${name}; + } + + public ${type} get${_.upperFirst(name)}() { + return ${name}; + } + ` ]; } diff --git a/examples/requirements/src/cli/generator.ts b/examples/requirements/src/cli/generator.ts index 973183898..37c26e3e8 100644 --- a/examples/requirements/src/cli/generator.ts +++ b/examples/requirements/src/cli/generator.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { RequirementModel, TestModel } from '../language-server/generated/ast.js'; +import { expandToNode, joinToNode, toString } from 'langium/generate'; import * as fs from 'node:fs'; import * as path from 'node:path'; -import { CompositeGeneratorNode, NL, toString } from 'langium'; +import type { RequirementModel, TestModel } from '../language-server/generated/ast.js'; import { extractDestinationAndName } from './cli-util.js'; /** @@ -17,26 +17,34 @@ import { extractDestinationAndName } from './cli-util.js'; * @returns the content of the HTML file. */ export function generateSummaryFileHTMLContent(model: RequirementModel, testModels: TestModel[]): string { - const fileNode = new CompositeGeneratorNode(); - fileNode.append('', NL); - fileNode.append('', NL); - fileNode.append('

Requirement coverage (demo generator)

', NL); - fileNode.append(`
Source: ${model.$document?.uri.fsPath}
`, NL); - fileNode.append('', NL); - fileNode.append('', NL); - model.requirements.forEach(requirement => { - fileNode.append(`', NL); - }); - fileNode.append('
Requirement IDTestcase ID
${requirement.name}`, NL); - testModels.forEach(testModel => testModel.tests.forEach(test => { - if (test.requirements.map(r => r.ref).includes(requirement)) { - fileNode.append(`
${test.name} (from ${testModel.$document?.uri.fsPath})
`, NL); - } - })); - fileNode.append('
', NL); - fileNode.append('', NL); - fileNode.append('', NL); - return toString(fileNode); + /* eslint-disable @typescript-eslint/indent */ + return toString( + expandToNode` + + +

Requirement coverage (demo generator)

+
Source: ${model.$document?.uri.fsPath}
+ + + ${joinToNode( + model.requirements, + requirement => expandToNode` + + `, + { appendNewLineIfNotEmpty: true } + )} +
Requirement IDTestcase ID
${requirement.name} + ${joinToNode( + testModels.flatMap(testModel => testModel.tests.map(test => ({ testModel, test }))).filter( ({ test }) => test.requirements.map(r => r.ref).includes(requirement) ), + ({ testModel, test }) => `
${test.name} (from ${testModel.$document?.uri?.fsPath})
`, + { appendNewLineIfNotEmpty: true } + )} +
+ + + `.appendNewLine() + ); + /* eslint-enable @typescript-eslint/indent */ } export function generateSummary(model: RequirementModel, testModels: TestModel[], filePath: string, destination: string | undefined): string { diff --git a/examples/statemachine/src/cli/generator.ts b/examples/statemachine/src/cli/generator.ts index 5517badac..17dd4495b 100644 --- a/examples/statemachine/src/cli/generator.ts +++ b/examples/statemachine/src/cli/generator.ts @@ -6,8 +6,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { expandToNode as toNode, joinToNode as join, toString } from 'langium'; -import type { Generated } from 'langium'; +import { type Generated, expandToNode as toNode, joinToNode as join, toString } from 'langium/generate'; import type { State, Statemachine } from '../language-server/generated/ast.js'; import { extractDestinationAndName } from './cli-util.js'; diff --git a/examples/statemachine/test/generator.test.ts b/examples/statemachine/test/generator.test.ts index f363cfb4a..8067d3a6b 100644 --- a/examples/statemachine/test/generator.test.ts +++ b/examples/statemachine/test/generator.test.ts @@ -4,12 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { Generated } from 'langium'; -import type { Statemachine } from '../src/language-server/generated/ast.js'; -import { describe, expect, test } from 'vitest'; -import { EmptyFileSystem, expandToStringWithNL, toString } from 'langium'; +import { EmptyFileSystem } from 'langium'; +import { expandToStringWithNL, toString, type Generated } from 'langium/generate'; import { parseHelper } from 'langium/test'; +import { describe, expect, test } from 'vitest'; import { generateCppContent } from '../src/cli/generator.js'; +import type { Statemachine } from '../src/language-server/generated/ast.js'; import { createStatemachineServices } from '../src/language-server/statemachine-module.js'; describe('Tests the code generator', () => { diff --git a/packages/generator-langium/templates/cli/src/cli/generator.ts b/packages/generator-langium/templates/cli/src/cli/generator.ts index a453cbe98..93036c17f 100644 --- a/packages/generator-langium/templates/cli/src/cli/generator.ts +++ b/packages/generator-langium/templates/cli/src/cli/generator.ts @@ -1,6 +1,6 @@ import type { Model } from '../language/generated/ast.js'; +import { expandToNode, joinToNode, toString } from 'langium/generate'; import * as fs from 'node:fs'; -import { CompositeGeneratorNode, NL, toString } from 'langium'; import * as path from 'node:path'; import { extractDestinationAndName } from './cli-util.js'; @@ -8,9 +8,11 @@ export function generateJavaScript(model: Model, filePath: string, destination: const data = extractDestinationAndName(filePath, destination); const generatedFilePath = `${path.join(data.destination, data.name)}.js`; - const fileNode = new CompositeGeneratorNode(); - fileNode.append('"use strict";', NL, NL); - model.greetings.forEach(greeting => fileNode.append(`console.log('Hello, ${greeting.person.ref?.name}!');`, NL)); + const fileNode = expandToNode` + "use strict"; + + ${joinToNode(model.greetings, greeting => `console.log('Hello, ${greeting.person.ref?.name}!');`, { appendNewLineIfNotEmpty: true })} + `.appendNewLineIfNotEmpty(); if (!fs.existsSync(data.destination)) { fs.mkdirSync(data.destination, { recursive: true }); diff --git a/packages/generator-langium/test/yeoman-generator.test.ts b/packages/generator-langium/test/yeoman-generator.test.ts index 4ec815a14..3b9039439 100644 --- a/packages/generator-langium/test/yeoman-generator.test.ts +++ b/packages/generator-langium/test/yeoman-generator.test.ts @@ -4,11 +4,11 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { normalizeEOL } from 'langium'; -import { createHelpers } from 'yeoman-test'; +import { normalizeEOL } from 'langium/generate'; import * as path from 'node:path'; import * as url from 'node:url'; +import { describe, test } from 'vitest'; +import { createHelpers } from 'yeoman-test'; const __dirname = url.fileURLToPath(new URL('../', import.meta.url)); diff --git a/packages/langium-cli/src/generator/ast-generator.ts b/packages/langium-cli/src/generator/ast-generator.ts index c5f638c36..576b8d714 100644 --- a/packages/langium-cli/src/generator/ast-generator.ts +++ b/packages/langium-cli/src/generator/ast-generator.ts @@ -3,165 +3,152 @@ * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { GeneratorNode, Grammar, LangiumServices } from 'langium'; +import type { Grammar, LangiumServices } from 'langium'; +import { type Generated, expandToNode, joinToNode, toString } from 'langium/generate'; import type { AstTypes, Property } from 'langium/types'; import type { LangiumConfig } from '../package.js'; -import { IndentNode, CompositeGeneratorNode, NL, toString, streamAllContents, MultiMap, GrammarAST } from 'langium'; +import { streamAllContents, MultiMap, GrammarAST } from 'langium'; import { collectAst, collectTypeHierarchy, findReferenceTypes, hasArrayType, isAstType, hasBooleanType, mergeTypesAndInterfaces } from 'langium/types'; import { collectTerminalRegexps, generatedHeader } from './util.js'; export function generateAst(services: LangiumServices, grammars: Grammar[], config: LangiumConfig): string { const astTypes = collectAst(grammars, services.shared.workspace.LangiumDocuments); - const fileNode = new CompositeGeneratorNode(); - fileNode.append( - generatedHeader, - '/* eslint-disable */', NL, - ); const crossRef = grammars.some(grammar => hasCrossReferences(grammar)); const importFrom = config.langiumInternal ? `../../syntax-tree${config.importExtension}` : 'langium'; - fileNode.append( - `import type { AstNode${crossRef ? ', Reference' : ''}, ReferenceInfo, TypeMetaData } from '${importFrom}';`, NL, - `import { AbstractAstReflection } from '${importFrom}';`, NL, NL - ); + /* eslint-disable @typescript-eslint/indent */ + const fileNode = expandToNode` + ${generatedHeader} - generateTerminalConstants(fileNode, grammars, config); + /* eslint-disable */ + import type { AstNode${crossRef ? ', Reference' : ''}, ReferenceInfo, TypeMetaData } from '${importFrom}'; + import { AbstractAstReflection } from '${importFrom}'; - astTypes.unions.forEach(union => fileNode.append(union.toAstTypesString(isAstType(union.type)), NL)); - astTypes.interfaces.forEach(iFace => fileNode.append(iFace.toAstTypesString(true), NL)); - astTypes.unions = astTypes.unions.filter(e => isAstType(e.type)); - fileNode.append(generateAstReflection(config, astTypes)); + ${generateTerminalConstants(grammars, config)} + ${joinToNode(astTypes.unions, union => union.toAstTypesString(isAstType(union.type)), { appendNewLineIfNotEmpty: true })} + ${joinToNode(astTypes.interfaces, iFace => iFace.toAstTypesString(true), { appendNewLineIfNotEmpty: true })} + ${ + astTypes.unions = astTypes.unions.filter(e => isAstType(e.type)), + generateAstReflection(config, astTypes) + } + `; return toString(fileNode); + /* eslint-enable @typescript-eslint/indent */ } function hasCrossReferences(grammar: Grammar): boolean { return Boolean(streamAllContents(grammar).find(GrammarAST.isCrossReference)); } -function generateAstReflection(config: LangiumConfig, astTypes: AstTypes): GeneratorNode { +function generateAstReflection(config: LangiumConfig, astTypes: AstTypes): Generated { const typeNames: string[] = astTypes.interfaces.map(t => t.name) .concat(astTypes.unions.map(t => t.name)) .sort(); const crossReferenceTypes = buildCrossReferenceTypes(astTypes); - const reflectionNode = new CompositeGeneratorNode(); - - reflectionNode.append(`export type ${config.projectName}AstType = {`, NL); - reflectionNode.indent(astTypeBody => { - for (const type of typeNames) { - astTypeBody.append(type, ': ', type, NL); + return expandToNode` + export type ${config.projectName}AstType = { + ${joinToNode(typeNames, name => name + ': ' + name, { appendNewLineIfNotEmpty: true })} } - }); - reflectionNode.append('}', NL, NL); - reflectionNode.append( - `export class ${config.projectName}AstReflection extends AbstractAstReflection {`, NL, NL - ); + export class ${config.projectName}AstReflection extends AbstractAstReflection { - reflectionNode.indent(classBody => { - classBody.append('getAllTypes(): string[] {', NL); - classBody.indent(allTypes => { - allTypes.append(`return [${typeNames.map(e => `'${e}'`).join(', ')}];`, NL); - }); - classBody.append( - '}', NL, NL, - 'protected override computeIsSubtype(subtype: string, supertype: string): boolean {', NL, - buildIsSubtypeMethod(astTypes), '}', NL, NL, - 'getReferenceType(refInfo: ReferenceInfo): string {', NL, - buildReferenceTypeMethod(crossReferenceTypes), '}', NL, NL, - 'getTypeMetaData(type: string): TypeMetaData {', NL, - buildTypeMetaDataMethod(astTypes), '}', NL - ); - }); + getAllTypes(): string[] { + return [${typeNames.map(e => `'${e}'`).join(', ')}]; + } - reflectionNode.append( - '}', NL, NL, - `export const reflection = new ${config.projectName}AstReflection();`, NL - ); + protected override computeIsSubtype(subtype: string, supertype: string): boolean { + ${buildIsSubtypeMethod(astTypes)} + } + + getReferenceType(refInfo: ReferenceInfo): string { + ${buildReferenceTypeMethod(crossReferenceTypes)} + } + + getTypeMetaData(type: string): TypeMetaData { + ${buildTypeMetaDataMethod(astTypes)} + } + } - return reflectionNode; + export const reflection = new ${config.projectName}AstReflection(); + `.appendNewLine(); } -function buildTypeMetaDataMethod(astTypes: AstTypes): GeneratorNode { - const typeSwitchNode = new IndentNode(); - typeSwitchNode.append('switch (type) {', NL); - typeSwitchNode.indent(caseNode => { - for (const interfaceType of astTypes.interfaces) { - const props = interfaceType.properties; - const arrayProps = props.filter(e => hasArrayType(e.type)); - const booleanProps = props.filter(e => hasBooleanType(e.type)); - if (arrayProps.length > 0 || booleanProps.length > 0) { - caseNode.append(`case '${interfaceType.name}': {`, NL); - caseNode.indent(caseContent => { - caseContent.append('return {', NL); - caseContent.indent(returnType => { - returnType.append(`name: '${interfaceType.name}',`, NL); - returnType.append( - 'mandatory: [', NL, - buildMandatoryType(arrayProps, booleanProps), - ']', NL); - }); - caseContent.append('};', NL); - }); - caseNode.append('}', NL); +function buildTypeMetaDataMethod(astTypes: AstTypes): Generated { + /* eslint-disable @typescript-eslint/indent */ + return expandToNode` + switch (type) { + ${ + joinToNode( + astTypes.interfaces, + interfaceType => { + const props = interfaceType.properties; + const arrayProps = props.filter(e => hasArrayType(e.type)); + const booleanProps = props.filter(e => hasBooleanType(e.type)); + return (arrayProps.length > 0 || booleanProps.length > 0) + ? expandToNode` + case '${interfaceType.name}': { + return { + name: '${interfaceType.name}', + mandatory: [ + ${buildMandatoryType(arrayProps, booleanProps)} + ] + }; + } + ` + : undefined; + }, + { + appendNewLineIfNotEmpty: true + } + ) + } + default: { + return { + name: type, + mandatory: [] + }; } } - caseNode.append('default: {', NL); - caseNode.indent(defaultNode => { - defaultNode.append('return {', NL); - defaultNode.indent(defaultType => { - defaultType.append( - 'name: type,', NL, - 'mandatory: []', NL - ); - }); - defaultNode.append('};', NL); - }); - caseNode.append('}', NL); - }); - typeSwitchNode.append('}', NL); - return typeSwitchNode; + `; + /* eslint-enable @typescript-eslint/indent */ } -function buildMandatoryType(arrayProps: Property[], booleanProps: Property[]): GeneratorNode { - const indent = new IndentNode(); +function buildMandatoryType(arrayProps: Property[], booleanProps: Property[]): Generated { const all = arrayProps.concat(booleanProps).sort((a, b) => a.name.localeCompare(b.name)); - for (let i = 0; i < all.length; i++) { - const property = all[i]; - const type = arrayProps.includes(property) ? 'array' : 'boolean'; - indent.append("{ name: '", property.name, "', type: '", type, "' }", i < all.length - 1 ? ',' : '', NL); - } - return indent; + + return joinToNode( + all, + property => { + const type = arrayProps.includes(property) ? 'array' : 'boolean'; + return `{ name: '${property.name}', type: '${type}' }`; + }, + { separator: ',', appendNewLineIfNotEmpty: true} + ); } -function buildReferenceTypeMethod(crossReferenceTypes: CrossReferenceType[]): GeneratorNode { - const typeSwitchNode = new IndentNode(); +function buildReferenceTypeMethod(crossReferenceTypes: CrossReferenceType[]): Generated { const buckets = new MultiMap(crossReferenceTypes.map(e => [e.referenceType, `${e.type}:${e.feature}`])); - typeSwitchNode.append('const referenceId = `${refInfo.container.$type}:${refInfo.property}`;', NL); - typeSwitchNode.append('switch (referenceId) {', NL); - typeSwitchNode.indent(caseNode => { - for (const [target, refs] of buckets.entriesGroupedByKey()) { - for (let i = 0; i < refs.length; i++) { - const ref = refs[i]; - caseNode.append(`case '${ref}':`); - if (i === refs.length - 1) { - caseNode.append(' {', NL); - } else { - caseNode.append(NL); - } + /* eslint-disable @typescript-eslint/indent */ + return expandToNode` + const referenceId = ${'`${refInfo.container.$type}:${refInfo.property}`'}; + switch (referenceId) { + ${ + joinToNode( + buckets.entriesGroupedByKey(), + ([target, refs]) => expandToNode` + ${joinToNode(refs, ref => `case '${ref}':`, { appendNewLineIfNotEmpty: true, skipNewLineAfterLastItem: true})} { + return ${target}; + } + `, + { appendNewLineIfNotEmpty: true } + ) + } + default: { + throw new Error(${'`${referenceId} is not a valid reference id.`'}); } - caseNode.indent(caseContent => { - caseContent.append(`return ${target};`, NL); - }); - caseNode.append('}', NL); } - caseNode.append('default: {', NL); - caseNode.indent(defaultNode => { - defaultNode.append('throw new Error(`${referenceId} is not a valid reference id.`);', NL); - }); - caseNode.append('}', NL); - }); - typeSwitchNode.append('}', NL); - return typeSwitchNode; + `; + /* eslint-enable @typescript-eslint/indent */ } type CrossReferenceType = { @@ -196,34 +183,28 @@ function buildCrossReferenceTypes(astTypes: AstTypes): CrossReferenceType[] { return Array.from(crossReferences.values()).sort((a, b) => a.type.localeCompare(b.type)); } -function buildIsSubtypeMethod(astTypes: AstTypes): GeneratorNode { - const methodNode = new IndentNode(); - methodNode.append( - 'switch (subtype) {', NL - ); - methodNode.indent(switchNode => { - const groups = groupBySupertypes(astTypes); - - for (const [superTypes, typeGroup] of groups.entriesGroupedByKey()) { - for (const typeName of typeGroup) { - switchNode.append(`case ${typeName}:`, NL); +function buildIsSubtypeMethod(astTypes: AstTypes): Generated { + const groups = groupBySupertypes(astTypes); + /* eslint-disable @typescript-eslint/indent */ + return expandToNode` + switch (subtype) { + ${ + joinToNode( + groups.entriesGroupedByKey(), + ([superTypes, typeGroup]) => expandToNode` + ${joinToNode(typeGroup, typeName => `case ${typeName}:`, { appendNewLineIfNotEmpty: true, skipNewLineAfterLastItem: true })} { + return ${superTypes.split(':').sort().map(e => `this.isSubtype(${e}, supertype)`).join(' || ')}; + } + `, + { appendNewLineIfNotEmpty: true} + ) + } + default: { + return false; } - switchNode.contents.pop(); - switchNode.append(' {', NL); - switchNode.indent(caseNode => { - caseNode.append(`return ${superTypes.split(':').sort().map(e => `this.isSubtype(${e}, supertype)`).join(' || ')};`); - }); - switchNode.append(NL, '}', NL); } - - switchNode.append('default: {', NL); - switchNode.indent(defaultNode => { - defaultNode.append('return false;', NL); - }); - switchNode.append('}', NL); - }); - methodNode.append('}', NL); - return methodNode; + `; + /* eslint-enable @typescript-eslint/indent */ } function groupBySupertypes(astTypes: AstTypes): MultiMap { @@ -236,19 +217,17 @@ function groupBySupertypes(astTypes: AstTypes): MultiMap { return superToChild; } -function generateTerminalConstants(fileNode: CompositeGeneratorNode, grammars: Grammar[], config: LangiumConfig) { +function generateTerminalConstants(grammars: Grammar[], config: LangiumConfig): Generated { let collection: Record = {}; grammars.forEach(grammar => { const terminalConstants = collectTerminalRegexps(grammar); collection = {...collection, ...terminalConstants}; }); - fileNode.append(`export const ${config.projectName}Terminals = {`, NL); - fileNode.indent(node => { - for (const [name, regexp] of Object.entries(collection)) { - node.append(`${name}: ${regexp.toString()},`, NL); - } - }); - fileNode.append('};', NL, NL); + return expandToNode` + export const ${config.projectName}Terminals = { + ${joinToNode(Object.entries(collection), ([name, regexp]) => `${name}: ${regexp.toString()},`, { appendNewLineIfNotEmpty: true })} + }; + `.appendNewLine(); } diff --git a/packages/langium-cli/src/generator/grammar-serializer.ts b/packages/langium-cli/src/generator/grammar-serializer.ts index 94e622038..5e59e5db9 100644 --- a/packages/langium-cli/src/generator/grammar-serializer.ts +++ b/packages/langium-cli/src/generator/grammar-serializer.ts @@ -4,53 +4,54 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { URI } from 'vscode-uri'; import type { Grammar, LangiumServices, Reference } from 'langium'; +import { expandToNode, joinToNode, normalizeEOL, toString } from 'langium/generate'; +import type { URI } from 'vscode-uri'; import type { LangiumConfig } from '../package.js'; -import { CompositeGeneratorNode, NL, normalizeEOL, toString } from 'langium'; import { generatedHeader } from './util.js'; export function serializeGrammar(services: LangiumServices, grammars: Grammar[], config: LangiumConfig): string { - const node = new CompositeGeneratorNode(); - node.append(generatedHeader); + const node = expandToNode` + ${generatedHeader} + `.appendNewLine( + ).appendTemplateIf(!!config.langiumInternal)` + + import type { Grammar } from './ast${config.importExtension}'; + import { loadGrammarFromJson } from '../../utils/grammar-util${config.importExtension}'; + `.appendTemplateIf(!config.langiumInternal)` + + import type { Grammar } from 'langium'; + import { loadGrammarFromJson } from 'langium'; + `.appendNewLine(); + + node.append( + joinToNode( + grammars.filter(grammar => grammar.name), + grammar => { + const production = config.mode === 'production'; + const delimiter = production ? "'" : '`'; + const uriConverter = (uri: URI, ref: Reference) => { + // We expect the grammar to be self-contained after the transformations we've done before + throw new Error(`Unexpected reference to symbol '${ref.$refText}' in document: ${uri.toString()}`); + }; + const serializedGrammar = services.serializer.JsonSerializer.serialize(grammar, { + space: production ? undefined : 2, + uriConverter + }); + // The json serializer returns strings with \n line delimiter by default + // We need to translate these line endings to the OS specific line ending + const json = normalizeEOL(serializedGrammar + .replace(/\\/g, '\\\\') + .replace(new RegExp(delimiter, 'g'), '\\' + delimiter)); + return expandToNode` - if (config.langiumInternal) { - node.append( - `import type { Grammar } from './ast${config.importExtension}';`, NL, - `import { loadGrammarFromJson } from '../../utils/grammar-util${config.importExtension}';`); - } else { - node.append( - "import type { Grammar } from 'langium';", NL, - "import { loadGrammarFromJson } from 'langium';"); - } - node.append(NL, NL); + let loaded${grammar.name}Grammar: Grammar | undefined; + export const ${grammar.name}Grammar = (): Grammar => loaded${grammar.name}Grammar ?? (loaded${grammar.name}Grammar = loadGrammarFromJson(${delimiter}${json}${delimiter})); + `; + }, + { appendNewLineIfNotEmpty: true } + ) + ); - for (let i = 0; i < grammars.length; i++) { - const grammar = grammars[i]; - if (grammar.name) { - const production = config.mode === 'production'; - const delimiter = production ? "'" : '`'; - const uriConverter = (uri: URI, ref: Reference) => { - // We expect the grammar to be self-contained after the transformations we've done before - throw new Error(`Unexpected reference to symbol '${ref.$refText}' in document: ${uri.toString()}`); - }; - const serializedGrammar = services.serializer.JsonSerializer.serialize(grammar, { - space: production ? undefined : 2, - uriConverter - }); - // The json serializer returns strings with \n line delimiter by default - // We need to translate these line endings to the OS specific line ending - const json = normalizeEOL(serializedGrammar - .replace(/\\/g, '\\\\') - .replace(new RegExp(delimiter, 'g'), '\\' + delimiter)); - node.append( - 'let loaded', grammar.name, 'Grammar: Grammar | undefined;', NL, - 'export const ', grammar.name, 'Grammar = (): Grammar => loaded', grammar.name, 'Grammar ?? (loaded', grammar.name, 'Grammar = loadGrammarFromJson(', delimiter, json, delimiter, '));', NL - ); - if (i < grammars.length - 1) { - node.append(NL); - } - } - } return toString(node); } diff --git a/packages/langium-cli/src/generator/highlighting/monarch-generator.ts b/packages/langium-cli/src/generator/highlighting/monarch-generator.ts index cd1e9e4b9..4d7b5647c 100644 --- a/packages/langium-cli/src/generator/highlighting/monarch-generator.ts +++ b/packages/langium-cli/src/generator/highlighting/monarch-generator.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { Grammar } from 'langium'; -import type { LangiumLanguageConfig } from '../../package.js'; -import { getTerminalParts, isCommentTerminal, CompositeGeneratorNode, NL, toString, escapeRegExp, GrammarAST, isWhitespaceRegExp } from 'langium'; +import { type Grammar, getTerminalParts, isCommentTerminal, escapeRegExp, GrammarAST, isWhitespaceRegExp } from 'langium'; +import { type Generated, expandToNode, joinToNode, toString } from 'langium/generate'; import { terminalRegex } from 'langium/internal'; +import type { LangiumLanguageConfig } from '../../package.js'; import { collectKeywords } from '../util.js'; /** @@ -190,22 +190,14 @@ function getTokenizerStates(grammar: Grammar): State[] { */ function prettyPrint(monarchGrammar: MonarchGrammar): string { const name = monarchGrammar.languageDefinition.name; - const node = new CompositeGeneratorNode( - `// Monarch syntax highlighting for the ${name} language.`, NL, - 'export default {', NL - ); - - node.indent(grammarDef => { - // add language definitions - prettyPrintLangDef(monarchGrammar.languageDefinition, grammarDef); - grammarDef.append(NL, NL); + const node = expandToNode` + // Monarch syntax highlighting for the ${name} language. + export default { + ${prettyPrintLangDef(monarchGrammar.languageDefinition)} - // add tokenizer parts, simple state machine groupings - prettyPrintTokenizer(monarchGrammar.tokenizer, grammarDef); - grammarDef.append(NL); - - }); - node.append('};', NL); + ${prettyPrintTokenizer(monarchGrammar.tokenizer)} + }; + `.appendNewLine(); return toString(node); } @@ -216,13 +208,12 @@ function prettyPrint(monarchGrammar: MonarchGrammar): string { * @param values Values to add under the given category * @returns GeneratorNode containing this printed language definition entry */ -function genLanguageDefEntry(name: string, values: string[]): CompositeGeneratorNode { - const node = new CompositeGeneratorNode(`${name}: [`, NL); - node.indent(langDefValues => { - langDefValues.append(values.map(v => `'${v}'`).join(',')); - }); - node.append(NL, '],'); - return node; +function genLanguageDefEntry(name: string, values: string[]): Generated { + return expandToNode` + ${name}: [ + ${ values.map(v => `'${v}'`).join(',') } + ], + `; } /** @@ -230,13 +221,13 @@ function genLanguageDefEntry(name: string, values: string[]): CompositeGenerator * @param languageDef LanguageDefinition to pretty print * @param node Existing generator node to append printed language definition to */ -function prettyPrintLangDef(languageDef: LanguageDefinition, node: CompositeGeneratorNode): void { - node.append( - genLanguageDefEntry('keywords', languageDef.keywords), NL, - genLanguageDefEntry('operators', languageDef.operators), NL, - // special case, identify symbols via singular regex - `symbols: ${new RegExp(languageDef.symbols.map(escapeRegExp).join('|')).toString()},` - ); +function prettyPrintLangDef(languageDef: LanguageDefinition): Generated { + return expandToNode` + ${genLanguageDefEntry('keywords', languageDef.keywords)} + ${genLanguageDefEntry('operators', languageDef.operators)} + ${/* special case, identify symbols via singular regex*/ undefined} + symbols: ${new RegExp(languageDef.symbols.map(escapeRegExp).join('|')).toString()}, + `; } /** @@ -244,15 +235,12 @@ function prettyPrintLangDef(languageDef: LanguageDefinition, node: CompositeGene * @param tokenizer Tokenizer portion to print out * @param node Existing generator node to append printed tokenizer to */ -function prettyPrintTokenizer(tokenizer: Tokenizer, node: CompositeGeneratorNode): void { - node.append('tokenizer: {', NL); - node.indent(tokenizerStates => { - for (const state of tokenizer.states) { - prettyPrintState(state, tokenizerStates); - tokenizerStates.append(NL); +function prettyPrintTokenizer(tokenizer: Tokenizer): Generated { + return expandToNode` + tokenizer: { + ${joinToNode(tokenizer.states, prettyPrintState, { appendNewLineIfNotEmpty: true})} } - }); - node.append('}'); + `; } /** @@ -260,14 +248,12 @@ function prettyPrintTokenizer(tokenizer: Tokenizer, node: CompositeGeneratorNode * @param state Tokenizer state to pretty print * @param node Existing enerator node to append printed state to */ -function prettyPrintState(state: State, node: CompositeGeneratorNode): void { - node.append(state.name + ': [', NL); - node.indent(inode => { - for (const rule of state.rules) { - inode.append(prettyPrintRule(rule), NL); - } - }); - node.append('],'); +function prettyPrintState(state: State): Generated { + return expandToNode` + ${state.name}: [ + ${joinToNode(state.rules, prettyPrintRule, { appendNewLineIfNotEmpty: true })} + ], + `; } /** @@ -276,14 +262,14 @@ function prettyPrintState(state: State, node: CompositeGeneratorNode): void { * @param ruleOrState Rule to pretty print. If it's a state, we include that state's contents implicitly within this context. * @returns Generator node containing this printed rule */ -function prettyPrintRule(ruleOrState: Rule | State): CompositeGeneratorNode { +function prettyPrintRule(ruleOrState: Rule | State): Generated { if (isRule(ruleOrState)) { // extract rule pattern, either just a string or a regex w/ parts const rulePatt = ruleOrState.regex instanceof RegExp ? ruleOrState.regex : new RegExp(ruleOrState.regex); - return new CompositeGeneratorNode('{ regex: ' + rulePatt.toString() + ', action: ' + prettyPrintAction(ruleOrState.action) + ' },'); + return expandToNode`{ regex: ${rulePatt.toString()}, action: ${prettyPrintAction(ruleOrState.action)} },`; } else { // include another state by name, implicitly includes all of its contents - return new CompositeGeneratorNode(`{ include: '@${ruleOrState.name}' },`); + return expandToNode`{ include: '@${ruleOrState.name}' },`; } } diff --git a/packages/langium-cli/src/generator/highlighting/prism-generator.ts b/packages/langium-cli/src/generator/highlighting/prism-generator.ts index e01964046..63733238b 100644 --- a/packages/langium-cli/src/generator/highlighting/prism-generator.ts +++ b/packages/langium-cli/src/generator/highlighting/prism-generator.ts @@ -3,11 +3,11 @@ * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { Grammar } from 'langium'; -import type { LangiumLanguageConfig } from '../../package.js'; -import { CompositeGeneratorNode, escapeRegExp, GrammarAST, isCommentTerminal, NL, toString } from 'langium'; +import { GrammarAST, escapeRegExp, isCommentTerminal, type Grammar } from 'langium'; +import { expandToNode, joinToNode, toString, type Generated } from 'langium/generate'; import { terminalRegex } from 'langium/internal'; import _ from 'lodash'; +import type { LangiumLanguageConfig } from '../../package.js'; import { collectKeywords } from '../util.js'; interface HighlightElement { @@ -52,46 +52,44 @@ export function generatePrismHighlighting(grammar: Grammar, config: LangiumLangu } function generate(highlighter: PrismHighlighter, grammarName: string): string { - const generatorNode = new CompositeGeneratorNode( - '// This file is generated using a best effort guess for your language.', NL, - '// It is not guaranteed contain all expected prism syntax highlighting rules.', NL, - '// For more documentation, take a look at https://prismjs.com/extending.html', NL, - 'Prism.languages.', _.camelCase(grammarName), ' = {', NL + /* eslint-disable @typescript-eslint/indent */ + return toString( + expandToNode` + // This file is generated using a best effort guess for your language. + // It is not guaranteed contain all expected prism syntax highlighting rules. + // For more documentation, take a look at https://prismjs.com/extending.html' + Prism.languages.${_.camelCase(grammarName)} = { + ${joinToNode( + Object.entries(highlighter), + ([name, value]) => { + const propertyName = !idRegex.test(name) ? `"${name}"` : name; + return Array.isArray(value) ? expandToNode` + ${propertyName}: [ + ${joinToNode(value, generateElement, { separator: ',', appendNewLineIfNotEmpty: true })} + ] + ` : expandToNode` + ${propertyName}: ${generateElement(value)} + `; + }, + { separator: ',', appendNewLineIfNotEmpty: true } + )} + }; + `.appendNewLine() ); - generatorNode.indent(propertyIndent => { - for (const [name, value] of Object.entries(highlighter)) { - let propertyName = name; - if (!idRegex.test(name)) { - propertyName = `"${name}"`; - } - propertyIndent.append(propertyName, ': '); - if (Array.isArray(value)) { - propertyIndent.append('[', NL); - propertyIndent.indent(arrayIndent => { - for (const element of value) { - generateElement(arrayIndent, element); - } - }); - propertyIndent.append('],', NL); - } else { - generateElement(propertyIndent, value); - } - } - }); - generatorNode.append('};', NL); - return toString(generatorNode); + /* eslint-enable @typescript-eslint/indent */ } -function generateElement(node: CompositeGeneratorNode, element: HighlightElement): void { - node.append('{', NL); - node.indent(objectIndent => { - objectIndent.append('pattern: ', element.pattern); - if (element.greedy) { - objectIndent.append(',', NL, 'greedy: true'); +function generateElement(element: HighlightElement): Generated { + const props = [ + `pattern: ${element.pattern}`, + element.greedy ? 'greedy: true' : undefined + ].filter(Boolean); + + return expandToNode` + { + ${joinToNode(props, { separator: ',', appendNewLineIfNotEmpty: true })} } - objectIndent.append(NL); - }); - node.append('},', NL); + `; } function getTerminals(grammar: Grammar): GrammarAST.TerminalRule[] { diff --git a/packages/langium-cli/src/generator/highlighting/textmate-generator.ts b/packages/langium-cli/src/generator/highlighting/textmate-generator.ts index 009311fe7..097c1c275 100644 --- a/packages/langium-cli/src/generator/highlighting/textmate-generator.ts +++ b/packages/langium-cli/src/generator/highlighting/textmate-generator.ts @@ -4,9 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ import type { Grammar } from 'langium'; -import type { LangiumLanguageConfig } from '../../package.js'; -import { EOL, escapeRegExp, getCaseInsensitivePattern, getTerminalParts, GrammarAST, isCommentTerminal, stream } from 'langium'; +import { escapeRegExp, getCaseInsensitivePattern, getTerminalParts, GrammarAST, isCommentTerminal, stream } from 'langium'; import { terminalRegex } from 'langium/internal'; +import type { LangiumLanguageConfig } from '../../package.js'; import { collectKeywords } from '../util.js'; /* eslint-disable dot-notation */ @@ -57,7 +57,7 @@ export function generateTextMate(grammar: Grammar, config: LangiumLanguageConfig repository: getRepository(grammar, config) }; - return JSON.stringify(json, null, 2) + EOL; + return JSON.stringify(json, null, 2) + '\n'; } function getPatterns(grammar: Grammar, config: LangiumLanguageConfig): Pattern[] { diff --git a/packages/langium-cli/src/generator/module-generator.ts b/packages/langium-cli/src/generator/module-generator.ts index 4e04b944a..6552d4df4 100644 --- a/packages/langium-cli/src/generator/module-generator.ts +++ b/packages/langium-cli/src/generator/module-generator.ts @@ -5,122 +5,111 @@ ******************************************************************************/ import type { Grammar, IParserConfig } from 'langium'; +import { type Generated, expandToNode, joinToNode, toString } from 'langium/generate'; import type { LangiumConfig, LangiumLanguageConfig } from '../package.js'; -import { CompositeGeneratorNode, NL, toString } from 'langium'; import { generatedHeader } from './util.js'; export function generateModule(grammars: Grammar[], config: LangiumConfig, grammarConfigMap: Map): string { + const grammarsWithName = grammars.filter(grammar => !!grammar.name); const parserConfig = config.chevrotainParserConfig; const hasIParserConfigImport = Boolean(parserConfig) || grammars.some(grammar => grammarConfigMap.get(grammar)?.chevrotainParserConfig !== undefined); - const node = new CompositeGeneratorNode(); + let needsGeneralParserConfig = undefined; - node.append(generatedHeader); - if (config.langiumInternal) { - node.append(`import type { LanguageMetaData } from '../language-meta-data${config.importExtension}';`, NL); - node.append(`import type { Module } from '../../dependency-injection${config.importExtension}';`, NL); - node.append(`import type { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices } from '../../services${config.importExtension}';`, NL); - if (hasIParserConfigImport) { - node.append(`import type { IParserConfig } from '../../parser/parser-config${config.importExtension}';`, NL); - } - } else { - node.append(`import type { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices, LanguageMetaData, Module${hasIParserConfigImport ? ', IParserConfig' : ''} } from 'langium';`, NL); - } - node.append( - 'import { ', - config.projectName, - `AstReflection } from './ast${config.importExtension}';`, - NL, - 'import { ' - ); - for (let i = 0; i < grammars.length; i++) { - const grammar = grammars[i]; - if (grammar.name) { - node.append(grammar.name, 'Grammar'); - if (i < grammars.length - 1) { - node.append(', '); - } - } - } - node.append(` } from './grammar${config.importExtension}';`, NL, NL); + /* eslint-disable @typescript-eslint/indent */ + const node = expandToNode` + ${generatedHeader} + `.appendNewLine( + ).appendIf(!!config.langiumInternal, + expandToNode` - for (const grammar of grammars) { - if (grammar.name) { - const config = grammarConfigMap.get(grammar)!; - node.append('export const ', grammar.name, 'LanguageMetaData = {', NL); - node.indent(metaData => { - metaData.append(`languageId: '${config.id}',`, NL); - metaData.append(`fileExtensions: [${config.fileExtensions && config.fileExtensions.map(e => appendQuotesAndDot(e)).join(', ')}],`, NL); - metaData.append(`caseInsensitive: ${Boolean(config.caseInsensitive)}`, NL); - }); - node.append('} as const satisfies LanguageMetaData;', NL, NL); - } - } + import type { LanguageMetaData } from '../language-meta-data${config.importExtension}'; + import type { Module } from '../../dependency-injection${config.importExtension}'; + import type { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices } from '../../services${config.importExtension}'; + `.appendTemplateIf(hasIParserConfigImport)` - let needsGeneralParserConfig = false; - for (const grammar of grammars) { - const grammarConfig = grammarConfigMap.get(grammar)!; - const grammarParserConfig = grammarConfig.chevrotainParserConfig; - if (grammarParserConfig && grammar.name) { - node.append('export const ', grammar.name, 'ParserConfig: IParserConfig = ', generateParserConfig(grammarParserConfig)); - } else { - needsGeneralParserConfig = true; - } - } + import type { IParserConfig } from '../../parser/parser-config${config.importExtension}'; + ` + ).appendTemplateIf(!config.langiumInternal)` - if (needsGeneralParserConfig && parserConfig) { - node.append('export const parserConfig: IParserConfig = ', generateParserConfig(parserConfig)); - } + import type { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices, LanguageMetaData, Module${hasIParserConfigImport ? ', IParserConfig' : ''} } from 'langium'; + `.appendTemplate` - node.append('export const ', config.projectName, 'GeneratedSharedModule: Module = {', NL); - node.indent(moduleNode => { - moduleNode.append( - 'AstReflection: () => new ', config.projectName, 'AstReflection()', NL - ); - }); - node.append('};', NL, NL); + import { ${config.projectName}AstReflection } from './ast${config.importExtension}'; + import { ${joinToNode(grammarsWithName, grammar => grammar.name + 'Grammar', { separator: ', '}) } } from './grammar${config.importExtension}'; + ${joinToNode( + grammarsWithName, + grammar => { + const config = grammarConfigMap.get(grammar)!; + return expandToNode` - for (let i = 0; i < grammars.length; i++) { - const grammar = grammars[i]; - if (grammar.name) { - const grammarConfig = grammarConfigMap.get(grammar)!; - node.append('export const ', grammar.name, 'GeneratedModule: Module = {', NL); - node.indent(moduleNode => { - moduleNode.append( - 'Grammar: () => ', grammar.name!, 'Grammar(),', NL, - 'LanguageMetaData: () => ', grammar.name!, 'LanguageMetaData,', NL, - 'parser: {' - ); - if (grammarConfig.chevrotainParserConfig ?? parserConfig) { - moduleNode.append(NL); - moduleNode.indent(parserGroupNode => { - const parserConfigName = grammarConfig.chevrotainParserConfig - ? grammar.name + 'ParserConfig' - : 'parserConfig'; - parserGroupNode.append('ParserConfig: () => ', parserConfigName, NL); - }); + export const ${ grammar.name }LanguageMetaData = { + languageId: '${config.id}', + fileExtensions: [${config.fileExtensions && joinToNode(config.fileExtensions, e => appendQuotesAndDot(e), { separator: ', ' })}], + caseInsensitive: ${Boolean(config.caseInsensitive)} + } as const satisfies LanguageMetaData; + `; + }, + { appendNewLineIfNotEmpty: true } + )} + ${joinToNode( + grammarsWithName, + grammar => { + const grammarParserConfig = grammarConfigMap.get(grammar)!.chevrotainParserConfig; + if (grammarParserConfig) { + return expandToNode` + + export const ${grammar.name}ParserConfig: IParserConfig = { + ${generateParserConfig(grammarParserConfig)} + }; + `; + } else { + needsGeneralParserConfig = true; + return; } - moduleNode.append('}', NL); - }); - node.append('};', NL); - if (i < grammars.length - 1) { - node.append(NL); - } - } - } + }, + { appendNewLineIfNotEmpty: true } + )} + ${needsGeneralParserConfig && parserConfig && expandToNode` + + export const parserConfig: IParserConfig = { + ${generateParserConfig(parserConfig)} + }; + `} + + export const ${config.projectName}GeneratedSharedModule: Module = { + AstReflection: () => new ${config.projectName}AstReflection() + }; + ${joinToNode( + grammarsWithName, + grammar => { + const grammarConfig = grammarConfigMap.get(grammar)!; + return expandToNode` + + export const ${grammar.name}GeneratedModule: Module = { + Grammar: () => ${grammar.name}Grammar(), + LanguageMetaData: () => ${grammar.name}LanguageMetaData, + parser: {${(grammarConfig.chevrotainParserConfig || parserConfig) && expandToNode` + ${'' /** needed to add the linebreak after the opening brace in case content is to be added, and to enable 'expandToNode' to identify the correct intendation of the subseqent parts. */} + ${grammarConfig.chevrotainParserConfig ? `ParserConfig: () => ${grammar.name}ParserConfig` : undefined} + ${!grammarConfig.chevrotainParserConfig && parserConfig ? 'ParserConfig: () => parserConfig' : undefined} + `}} + }; + `; + }, + { appendNewLineIfNotEmpty: true} + )} + `; + /* eslint-enable @typescript-eslint/indent */ return toString(node); } -function generateParserConfig(config: IParserConfig): CompositeGeneratorNode { - const node = new CompositeGeneratorNode(); - node.append('{', NL); - node.indent(configNode => { - for (const [key, value] of Object.entries(config)) { - configNode.append(`${key}: ${typeof value === 'string' ? `'${value}'` : value},`, NL); - } - }); - node.append('};', NL, NL); - return node; +function generateParserConfig(config: IParserConfig): Generated { + return joinToNode( + Object.entries(config), + ([key, value]) => `${key}: ${typeof value === 'string' ? `'${value}'` : value},`, + { appendNewLineIfNotEmpty: true } + ); } function appendQuotesAndDot(input: string): string { diff --git a/packages/langium-cli/src/generator/types-generator.ts b/packages/langium-cli/src/generator/types-generator.ts index e0595371c..45a897b90 100644 --- a/packages/langium-cli/src/generator/types-generator.ts +++ b/packages/langium-cli/src/generator/types-generator.ts @@ -4,7 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ import type { Grammar, LangiumServices } from 'langium'; -import { CompositeGeneratorNode, NL, toString } from 'langium'; +import { joinToNode, toString } from 'langium/generate'; import { collectAst } from 'langium/types'; import { LangiumGrammarGrammar } from 'langium/internal'; import { collectKeywords } from './util.js'; @@ -12,10 +12,11 @@ import { collectKeywords } from './util.js'; export function generateTypesFile(services: LangiumServices, grammars: Grammar[]): string { const { unions, interfaces } = collectAst(grammars, services.shared.workspace.LangiumDocuments); const reservedWords = new Set(collectKeywords(LangiumGrammarGrammar())); - const fileNode = new CompositeGeneratorNode(); - unions.forEach(union => fileNode.append(union.toDeclaredTypesString(reservedWords)).append(NL)); - interfaces.forEach(iFace => fileNode.append(iFace.toDeclaredTypesString(reservedWords)).append(NL)); + const fileNode = joinToNode([ + joinToNode(unions, union => union.toDeclaredTypesString(reservedWords), { appendNewLineIfNotEmpty: true }), + joinToNode(interfaces, iFace => iFace.toDeclaredTypesString(reservedWords), { appendNewLineIfNotEmpty: true }) + ]); return toString(fileNode); } diff --git a/packages/langium-cli/src/generator/util.ts b/packages/langium-cli/src/generator/util.ts index b6ca80b12..b8f1fc634 100644 --- a/packages/langium-cli/src/generator/util.ts +++ b/packages/langium-cli/src/generator/util.ts @@ -3,8 +3,8 @@ * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { GeneratorNode, Grammar } from 'langium'; -import { CompositeGeneratorNode, getAllReachableRules, GrammarAST, NL, stream, streamAllContents } from 'langium'; +import { type Grammar, getAllReachableRules, GrammarAST, stream, streamAllContents } from 'langium'; +import { type Generated, expandToNode } from 'langium/generate'; import fs from 'fs-extra'; import * as path from 'node:path'; import * as url from 'node:url'; @@ -49,15 +49,13 @@ function getLangiumCliVersion(): string { return pack.version; } -function getGeneratedHeader(): GeneratorNode { - const node = new CompositeGeneratorNode(); - node.contents.push( - '/******************************************************************************', NL, - ` * This file was generated by langium-cli ${cliVersion}.`, NL, - ' * DO NOT EDIT MANUALLY!', NL, - ' ******************************************************************************/', NL, NL - ); - return node; +function getGeneratedHeader(): Generated { + return expandToNode` + /****************************************************************************** + * This file was generated by langium-cli ${cliVersion}. + * DO NOT EDIT MANUALLY! + ******************************************************************************/ + `; } export function collectKeywords(grammar: Grammar): string[] { diff --git a/packages/langium-cli/test/generator/ast-generator.test.ts b/packages/langium-cli/test/generator/ast-generator.test.ts index 7d08e04f7..a53c2efc2 100644 --- a/packages/langium-cli/test/generator/ast-generator.test.ts +++ b/packages/langium-cli/test/generator/ast-generator.test.ts @@ -4,13 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { Grammar } from 'langium'; -import type { LangiumConfig } from '../../src/package.js'; -import { describe, expect, test } from 'vitest'; -import { createLangiumGrammarServices, EmptyFileSystem, expandToString, normalizeEOL } from 'langium'; +import { EmptyFileSystem, createLangiumGrammarServices, type Grammar } from 'langium'; +import { expandToString, normalizeEOL } from 'langium/generate'; import { parseHelper } from 'langium/test'; -import { RelativePath } from '../../src/package.js'; +import { describe, expect, test } from 'vitest'; import { generateAst } from '../../src/generator/ast-generator.js'; +import type { LangiumConfig } from '../../src/package.js'; +import { RelativePath } from '../../src/package.js'; const services = createLangiumGrammarServices(EmptyFileSystem); const parse = parseHelper(services.grammar); diff --git a/packages/langium-cli/test/generator/types-generator.test.ts b/packages/langium-cli/test/generator/types-generator.test.ts index 29c190b03..60fbc4004 100644 --- a/packages/langium-cli/test/generator/types-generator.test.ts +++ b/packages/langium-cli/test/generator/types-generator.test.ts @@ -5,9 +5,10 @@ ******************************************************************************/ import type { Grammar } from 'langium'; -import { describe, expect, test } from 'vitest'; -import { createLangiumGrammarServices, EmptyFileSystem, expandToStringWithNL } from 'langium'; +import { EmptyFileSystem, createLangiumGrammarServices } from 'langium'; +import { expandToStringWithNL } from 'langium/generate'; import { parseHelper } from 'langium/test'; +import { describe, expect, test } from 'vitest'; import { generateTypesFile } from '../../src/generator/types-generator.js'; const { grammar } = createLangiumGrammarServices(EmptyFileSystem); diff --git a/packages/langium-railroad/src/grammar-railroad.ts b/packages/langium-railroad/src/grammar-railroad.ts index 6a7e69e31..f625f0813 100644 --- a/packages/langium-railroad/src/grammar-railroad.ts +++ b/packages/langium-railroad/src/grammar-railroad.ts @@ -4,7 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EOL, GrammarAST, expandToString, findNameAssignment } from 'langium'; +import { GrammarAST, findNameAssignment } from 'langium'; +import { expandToStringLF, expandToStringLFWithNL } from 'langium/generate'; import type { FakeSVG } from 'railroad-diagrams'; import { default as rr } from 'railroad-diagrams'; @@ -40,14 +41,16 @@ export interface GrammarDiagramOptions { } function styling(options?: GrammarDiagramOptions) { - return expandToString` - -${options?.css ? expandToString` -` : ''}`; + return expandToStringLF` + + ${options?.css ? expandToStringLF` + + ` : ''} + `; } /** @@ -60,21 +63,20 @@ ${options?.css ? expandToString` * @returns A complete HTML document containing all diagrams. */ export function createGrammarDiagramHtml(rules: GrammarAST.ParserRule[], options?: GrammarDiagramOptions): string { - let text = ` - - -${styling(options)}${options?.javascript ? ` -` : ''} - - -`; - text += createGrammarDiagram(rules); - text += ` -`; - - return text; + return expandToStringLFWithNL` + + + + ${styling(options)}${options?.javascript ? ` + ` : ''} + + + ${createGrammarDiagram(rules)} + + + `; } /** @@ -104,7 +106,7 @@ export function createGrammarDiagramSvg(rules: GrammarAST.ParserRule[], options? export function createGrammarDiagram(rules: GrammarAST.ParserRule[]): string { const text: string[] = []; for (const nonTerminal of rules) { - text.push('

', nonTerminal.name, '

', EOL, createRuleDiagram(nonTerminal)); + text.push('

', nonTerminal.name, '

', '\n', createRuleDiagram(nonTerminal)); } return text.join(''); } diff --git a/packages/langium-vscode/src/language-server/railroad-handler.ts b/packages/langium-vscode/src/language-server/railroad-handler.ts index b520ff076..9aad24d7d 100644 --- a/packages/langium-vscode/src/language-server/railroad-handler.ts +++ b/packages/langium-vscode/src/language-server/railroad-handler.ts @@ -5,12 +5,13 @@ ******************************************************************************/ import type { Grammar, LangiumServices } from 'langium'; -import { DocumentState, GrammarAST, URI, expandToString } from 'langium'; -import type { Connection} from 'vscode-languageserver'; -import { DiagnosticSeverity } from 'vscode-languageserver'; -import { DOCUMENTS_VALIDATED_NOTIFICATION, RAILROAD_DIAGRAM_REQUEST } from './messages.js'; +import { DocumentState, GrammarAST, URI } from 'langium'; import { createGrammarDiagramHtml } from 'langium-railroad'; +import { expandToString } from 'langium/generate'; import { resolveTransitiveImports } from 'langium/internal'; +import type { Connection } from 'vscode-languageserver'; +import { DiagnosticSeverity } from 'vscode-languageserver'; +import { DOCUMENTS_VALIDATED_NOTIFICATION, RAILROAD_DIAGRAM_REQUEST } from './messages.js'; export function registerRailroadConnectionHandler(connection: Connection, services: LangiumServices): void { const documentBuilder = services.shared.workspace.DocumentBuilder; diff --git a/packages/langium/package.json b/packages/langium/package.json index 8eaf6b6c6..92fcff3a0 100644 --- a/packages/langium/package.json +++ b/packages/langium/package.json @@ -30,6 +30,10 @@ "types": "./lib/index.d.ts", "import": "./lib/index.js" }, + "./generate": { + "types": "./lib/generate/index.d.ts", + "import": "./lib/generate/index.js" + }, "./test": { "types": "./lib/test/index.d.ts", "import": "./lib/test/index.js" diff --git a/packages/langium/src/documentation/jsdoc.ts b/packages/langium/src/documentation/jsdoc.ts index 0d329ddde..10938c4f5 100644 --- a/packages/langium/src/documentation/jsdoc.ts +++ b/packages/langium/src/documentation/jsdoc.ts @@ -6,8 +6,7 @@ import type { CstNode } from '../syntax-tree.js'; import { Position, Range } from 'vscode-languageserver'; -import { NEWLINE_REGEXP } from '../generator/template-string.js'; -import { escapeRegExp } from '../utils/regex-util.js'; +import { NEWLINE_REGEXP, escapeRegExp } from '../utils/regex-util.js'; import { URI } from '../utils/uri-util.js'; export interface JSDocComment extends JSDocValue { diff --git a/packages/langium/src/generator/generator-node.ts b/packages/langium/src/generate/generator-node.ts similarity index 100% rename from packages/langium/src/generator/generator-node.ts rename to packages/langium/src/generate/generator-node.ts diff --git a/packages/langium/src/generator/generator-tracing.ts b/packages/langium/src/generate/generator-tracing.ts similarity index 100% rename from packages/langium/src/generator/generator-tracing.ts rename to packages/langium/src/generate/generator-tracing.ts diff --git a/packages/langium/src/generator/index.ts b/packages/langium/src/generate/index.ts similarity index 100% rename from packages/langium/src/generator/index.ts rename to packages/langium/src/generate/index.ts diff --git a/packages/langium/src/generator/node-joiner.ts b/packages/langium/src/generate/node-joiner.ts similarity index 100% rename from packages/langium/src/generator/node-joiner.ts rename to packages/langium/src/generate/node-joiner.ts diff --git a/packages/langium/src/generator/node-processor.ts b/packages/langium/src/generate/node-processor.ts similarity index 100% rename from packages/langium/src/generator/node-processor.ts rename to packages/langium/src/generate/node-processor.ts diff --git a/packages/langium/src/generator/template-node.ts b/packages/langium/src/generate/template-node.ts similarity index 99% rename from packages/langium/src/generator/template-node.ts rename to packages/langium/src/generate/template-node.ts index d75a65307..1cba2e1a0 100644 --- a/packages/langium/src/generator/template-node.ts +++ b/packages/langium/src/generate/template-node.ts @@ -5,10 +5,11 @@ ******************************************************************************/ import type { AstNode, Properties } from '../syntax-tree.js'; +import { NEWLINE_REGEXP } from '../utils/regex-util.js'; import type { Generated, GeneratorNode, IndentNode } from './generator-node.js'; -import type { SourceRegion } from './generator-tracing.js'; import { CompositeGeneratorNode, isGeneratorNode, traceToNode } from './generator-node.js'; -import { findIndentation, NEWLINE_REGEXP } from './template-string.js'; +import type { SourceRegion } from './generator-tracing.js'; +import { findIndentation } from './template-string.js'; /** * A tag function that attaches the template's content to a {@link CompositeGeneratorNode}. diff --git a/packages/langium/src/generator/template-string.ts b/packages/langium/src/generate/template-string.ts similarity index 98% rename from packages/langium/src/generator/template-string.ts rename to packages/langium/src/generate/template-string.ts index 0a7901baa..593eeb802 100644 --- a/packages/langium/src/generator/template-string.ts +++ b/packages/langium/src/generate/template-string.ts @@ -4,6 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ +import { NEWLINE_REGEXP } from '../utils/regex-util.js'; import { EOL, toString } from './generator-node.js'; export function expandToStringWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { @@ -78,7 +79,6 @@ function internalExpandToString(lineSep: string, staticParts: TemplateStringsArr } export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__'); -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/type-system/type-collector/types.ts b/packages/langium/src/grammar/type-system/type-collector/types.ts index 520cfe6bf..258586143 100644 --- a/packages/langium/src/grammar/type-system/type-collector/types.ts +++ b/packages/langium/src/grammar/type-system/type-collector/types.ts @@ -4,9 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ +import { expandToNode, expandToStringWithNL, joinToNode, toString, type Generated } from '../../../generate/index.js'; import type { CstNode } from '../../../syntax-tree.js'; -import type { Assignment, Action, TypeAttribute } from '../../generated/ast.js'; -import { CompositeGeneratorNode, NL, toString } from '../../../generator/generator-node.js'; +import type { Action, Assignment, TypeAttribute } from '../../generated/ast.js'; import { distinctAndSorted } from '../types-util.js'; export interface Property { @@ -119,25 +119,27 @@ export class UnionType { } toAstTypesString(reflectionInfo: boolean): string { - const unionNode = new CompositeGeneratorNode(); - unionNode.append(`export type ${this.name} = ${propertyTypeToString(this.type, 'AstType')};`, NL); + const unionNode = expandToNode` + export type ${this.name} = ${propertyTypeToString(this.type, 'AstType')}; + `.appendNewLine(); if (reflectionInfo) { - unionNode.append(NL); - pushReflectionInfo(unionNode, this.name); + unionNode.appendNewLine() + .append(addReflectionInfo(this.name)); } if (this.dataType) { - pushDataTypeReflectionInfo(unionNode, this); + unionNode.appendNewLine() + .append(addDataTypeReflectionInfo(this)); } return toString(unionNode); } toDeclaredTypesString(reservedWords: Set): string { - const unionNode = new CompositeGeneratorNode(); - unionNode.append(`type ${escapeReservedWords(this.name, reservedWords)} = ${propertyTypeToString(this.type, 'DeclaredType')};`, NL); - return toString(unionNode); + return expandToStringWithNL` + type ${escapeReservedWords(this.name, reservedWords)} = ${propertyTypeToString(this.type, 'DeclaredType')}; + `; } } @@ -214,42 +216,44 @@ export class InterfaceType { } toAstTypesString(reflectionInfo: boolean): string { - const interfaceNode = new CompositeGeneratorNode(); - const interfaceSuperTypes = this.interfaceSuperTypes.map(e => e.name); const superTypes = interfaceSuperTypes.length > 0 ? distinctAndSorted([...interfaceSuperTypes]) : ['AstNode']; - interfaceNode.append(`export interface ${this.name} extends ${superTypes.join(', ')} {`, NL); + const interfaceNode = expandToNode` + export interface ${this.name} extends ${superTypes.join(', ')} { + `.appendNewLine(); interfaceNode.indent(body => { if (this.containerTypes.size > 0) { - body.append(`readonly $container: ${distinctAndSorted([...this.containerTypes].map(e => e.name)).join(' | ')};`, NL); + body.append(`readonly $container: ${distinctAndSorted([...this.containerTypes].map(e => e.name)).join(' | ')};`).appendNewLine(); } if (this.typeNames.size > 0) { - body.append(`readonly $type: ${distinctAndSorted([...this.typeNames]).map(e => `'${e}'`).join(' | ')};`, NL); + body.append(`readonly $type: ${distinctAndSorted([...this.typeNames]).map(e => `'${e}'`).join(' | ')};`).appendNewLine(); } - pushProperties(body, this.properties, 'AstType'); + body.append( + pushProperties(this.properties, 'AstType') + ); }); - interfaceNode.append('}', NL); + interfaceNode.append('}').appendNewLine(); if (reflectionInfo) { - interfaceNode.append(NL); - pushReflectionInfo(interfaceNode, this.name); + interfaceNode + .appendNewLine() + .append(addReflectionInfo(this.name)); } return toString(interfaceNode); } toDeclaredTypesString(reservedWords: Set): string { - const interfaceNode = new CompositeGeneratorNode(); - const name = escapeReservedWords(this.name, reservedWords); const superTypes = distinctAndSorted(this.interfaceSuperTypes.map(e => e.name)).join(', '); - interfaceNode.append(`interface ${name}${superTypes.length > 0 ? ` extends ${superTypes}` : ''} {`, NL); - - interfaceNode.indent(body => pushProperties(body, this.properties, 'DeclaredType', reservedWords)); - - interfaceNode.append('}', NL); - return toString(interfaceNode); + return toString( + expandToNode` + interface ${name}${superTypes.length > 0 ? ` extends ${superTypes}` : ''} { + ${pushProperties(this.properties, 'DeclaredType', reservedWords)} + } + `.appendNewLine() + ); } } @@ -380,11 +384,10 @@ function typeParenthesis(type: PropertyType, name: string): string { } function pushProperties( - node: CompositeGeneratorNode, properties: Property[], mode: 'AstType' | 'DeclaredType', reserved = new Set() -) { +): Generated { function propertyToString(property: Property): string { const name = mode === 'AstType' ? property.name : escapeReservedWords(property.name, reserved); @@ -393,8 +396,11 @@ function pushProperties( return `${name}${optional ? '?' : ''}: ${propType}`; } - distinctAndSorted(properties, (a, b) => a.name.localeCompare(b.name)) - .forEach(property => node.append(propertyToString(property), NL)); + return joinToNode( + distinctAndSorted(properties, (a, b) => a.name.localeCompare(b.name)), + propertyToString, + { appendNewLineIfNotEmpty: true } + ); } export function isMandatoryPropertyType(propertyType: PropertyType): boolean { @@ -412,16 +418,17 @@ export function isMandatoryPropertyType(propertyType: PropertyType): boolean { } } -function pushReflectionInfo(node: CompositeGeneratorNode, name: string) { - node.append(`export const ${name} = '${name}';`, NL); - node.append(NL); +function addReflectionInfo(name: string): Generated { + return expandToNode` + export const ${name} = '${name}'; - node.append(`export function is${name}(item: unknown): item is ${name} {`, NL); - node.indent(body => body.append(`return reflection.isInstance(item, ${name});`, NL)); - node.append('}', NL); + export function is${name}(item: unknown): item is ${name} { + return reflection.isInstance(item, ${name}); + } + `.appendNewLine(); } -function pushDataTypeReflectionInfo(node: CompositeGeneratorNode, union: UnionType) { +function addDataTypeReflectionInfo(union: UnionType): Generated { switch (union.dataType) { case 'string': if (containsOnlyStringTypes(union.type)) { @@ -429,21 +436,19 @@ function pushDataTypeReflectionInfo(node: CompositeGeneratorNode, union: UnionTy const strings = collectStringValuesFromDataType(union.type); const regexes = collectRegexesFromDataType(union.type); if (subTypes.length === 0 && strings.length === 0 && regexes.length === 0) { - generateIsDataTypeFunction(node, union.name, `typeof item === '${union.dataType}'`); + return generateIsDataTypeFunction(union.name, `typeof item === '${union.dataType}'`); } else { const returnString = createDataTypeCheckerFunctionReturnString(subTypes, strings, regexes); - generateIsDataTypeFunction(node, union.name, returnString); + return generateIsDataTypeFunction(union.name, returnString); } } - break; + return; case 'number': case 'boolean': case 'bigint': - generateIsDataTypeFunction(node, union.name, `typeof item === '${union.dataType}'`); - break; + return generateIsDataTypeFunction(union.name, `typeof item === '${union.dataType}'`); case 'Date': - generateIsDataTypeFunction(node, union.name, 'item instanceof Date'); - break; + return generateIsDataTypeFunction(union.name, 'item instanceof Date'); default: return; } @@ -538,8 +543,10 @@ function collectRegexesFromDataType(propertyType: PropertyType): string[] { return regexes; } -function generateIsDataTypeFunction(node: CompositeGeneratorNode, unionName: string, returnString: string) { - node.append(NL, `export function is${unionName}(item: unknown): item is ${unionName} {`, NL); - node.indent(body => body.append(`return ${returnString};`, NL)); - node.append('}', NL); +function generateIsDataTypeFunction(unionName: string, returnString: string): Generated { + return expandToNode` + export function is${unionName}(item: unknown): item is ${unionName} { + return ${returnString}; + } + `.appendNewLine(); } diff --git a/packages/langium/src/index.ts b/packages/langium/src/index.ts index ba312e8ef..8141e4dac 100644 --- a/packages/langium/src/index.ts +++ b/packages/langium/src/index.ts @@ -10,7 +10,6 @@ export * from './service-registry.js'; export * from './services.js'; export * from './syntax-tree.js'; export * from './documentation/index.js'; -export * from './generator/index.js'; export * from './grammar/index.js'; export * from './lsp/index.js'; export * from './parser/index.js'; diff --git a/packages/langium/src/test/langium-test.ts b/packages/langium/src/test/langium-test.ts index d08fce73e..45b59bdb5 100644 --- a/packages/langium/src/test/langium-test.ts +++ b/packages/langium/src/test/langium-test.ts @@ -19,7 +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'; +import { normalizeEOL } from '../generate/template-string.js'; export interface ParseHelperOptions extends BuildOptions { /** diff --git a/packages/langium/src/utils/regex-util.ts b/packages/langium/src/utils/regex-util.ts index 6c7dfaa6a..d7b58646e 100644 --- a/packages/langium/src/utils/regex-util.ts +++ b/packages/langium/src/utils/regex-util.ts @@ -7,6 +7,8 @@ import type { Set, Group, Character, IRegExpAST } from '@chevrotain/regexp-to-ast'; import { RegExpParser, BaseRegExpVisitor } from '@chevrotain/regexp-to-ast'; +export const NEWLINE_REGEXP = /\r?\n/gm; + const regexParser = new RegExpParser(); /** diff --git a/packages/langium/test/generator/generation-tracing.test.ts b/packages/langium/test/generate/generation-tracing.test.ts similarity index 98% rename from packages/langium/test/generator/generation-tracing.test.ts rename to packages/langium/test/generate/generation-tracing.test.ts index 570f91662..0b1d15e01 100644 --- a/packages/langium/test/generator/generation-tracing.test.ts +++ b/packages/langium/test/generate/generation-tracing.test.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from 'vitest'; -import { createServicesForGrammar, expandToNode, expandTracedToNode, expandToString, findNodeForKeyword, findNodesForProperty, joinTracedToNode, joinTracedToNodeIf, toStringAndTrace, traceToNode, TreeStreamImpl } from 'langium'; -import type { SourceRegion, TraceRegion, AstNodeWithTextRegion, AstNode } from 'langium'; +import { TreeStreamImpl, createServicesForGrammar, findNodeForKeyword, findNodesForProperty, type AstNode, type AstNodeWithTextRegion } from 'langium'; +import { expandToNode, expandToString, expandTracedToNode, joinTracedToNode, joinTracedToNodeIf, toStringAndTrace, traceToNode, type SourceRegion, type TraceRegion } from 'langium/generate'; import { parseHelper } from 'langium/test'; +import { beforeAll, describe, expect, test } from 'vitest'; // don't bather because of unexpected indentations, e.g. within template substitutions /* eslint-disable @typescript-eslint/indent */ diff --git a/packages/langium/test/generator/node.test.ts b/packages/langium/test/generate/node.test.ts similarity index 99% rename from packages/langium/test/generator/node.test.ts rename to packages/langium/test/generate/node.test.ts index 5d16fd0dd..d67fa0851 100644 --- a/packages/langium/test/generator/node.test.ts +++ b/packages/langium/test/generate/node.test.ts @@ -6,7 +6,7 @@ import { EOL } from 'os'; import { describe, expect, test } from 'vitest'; -import { CompositeGeneratorNode, IndentNode, NewLineNode, NL, NLEmpty, toString as process } from 'langium'; +import { CompositeGeneratorNode, IndentNode, NewLineNode, NL, NLEmpty, toString as process } from 'langium/generate'; describe('new lines', () => { diff --git a/packages/langium/test/generator/template-node.test.ts b/packages/langium/test/generate/template-node.test.ts similarity index 99% rename from packages/langium/test/generator/template-node.test.ts rename to packages/langium/test/generate/template-node.test.ts index 67176557c..6203cbd6f 100644 --- a/packages/langium/test/generator/template-node.test.ts +++ b/packages/langium/test/generate/template-node.test.ts @@ -4,8 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ +import { stream } from 'langium'; +import { CompositeGeneratorNode, EOL, NL, joinToNode, expandToNode as n, expandToString as s, toString } from 'langium/generate'; import { describe, expect, test } from 'vitest'; -import { joinToNode, expandToNode as n, expandToString as s, stream, CompositeGeneratorNode, EOL, toString, NL } from 'langium'; // deactivate the eslint check 'no-unexpected-multiline' with the message // 'Unexpected newline between template tag and template literal', as that's done on purposes in tests below! diff --git a/packages/langium/test/generator/template-string.test.ts b/packages/langium/test/generate/template-string.test.ts similarity index 98% rename from packages/langium/test/generator/template-string.test.ts rename to packages/langium/test/generate/template-string.test.ts index 0679acc08..0e0d803b5 100644 --- a/packages/langium/test/generator/template-string.test.ts +++ b/packages/langium/test/generate/template-string.test.ts @@ -5,7 +5,7 @@ ******************************************************************************/ import { expect, test } from 'vitest'; -import { expandToStringLF as s } from 'langium'; +import { expandToStringLF as s } from 'langium/generate'; test('Should not throw when substituting null', () => { expect(s`${null}`).toBe('null'); diff --git a/packages/langium/test/grammar/lsp/grammar-formatter.test.ts b/packages/langium/test/grammar/lsp/grammar-formatter.test.ts index ca735739e..22fd38197 100644 --- a/packages/langium/test/grammar/lsp/grammar-formatter.test.ts +++ b/packages/langium/test/grammar/lsp/grammar-formatter.test.ts @@ -4,9 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { EmptyFileSystem, createLangiumGrammarServices, expandToString } from 'langium'; +import { EmptyFileSystem, createLangiumGrammarServices } from 'langium'; +import { expandToString } from 'langium/generate'; import { expectFormatting } from 'langium/test'; +import { describe, test } from 'vitest'; const services = createLangiumGrammarServices(EmptyFileSystem); const formatting = expectFormatting(services.grammar); diff --git a/packages/langium/test/grammar/type-system/inferred-types.test.ts b/packages/langium/test/grammar/type-system/inferred-types.test.ts index 16fb8f221..812a6d0c5 100644 --- a/packages/langium/test/grammar/type-system/inferred-types.test.ts +++ b/packages/langium/test/grammar/type-system/inferred-types.test.ts @@ -7,7 +7,8 @@ import type { Grammar } from 'langium'; import type { AstTypes } from 'langium/types'; import { describe, expect, test } from 'vitest'; -import { createLangiumGrammarServices, EmptyFileSystem, expandToString, EOL } from 'langium'; +import { createLangiumGrammarServices, EmptyFileSystem } from 'langium'; +import { expandToString, EOL } from 'langium/generate'; import { collectAst, mergeTypesAndInterfaces } from 'langium/types'; import { clearDocuments, parseHelper } from 'langium/test'; diff --git a/packages/langium/test/serializer/json-serializer.test.ts b/packages/langium/test/serializer/json-serializer.test.ts index e9def8d19..708573251 100644 --- a/packages/langium/test/serializer/json-serializer.test.ts +++ b/packages/langium/test/serializer/json-serializer.test.ts @@ -5,10 +5,11 @@ ******************************************************************************/ import type { AstNode, Reference } from 'langium'; +import { createServicesForGrammar } from 'langium'; +import { expandToStringLF } from 'langium/generate'; +import { clearDocuments, parseHelper } from 'langium/test'; import { beforeEach, describe, expect, test } from 'vitest'; import { URI } from 'vscode-uri'; -import { createServicesForGrammar, expandToStringLF } from 'langium'; -import { clearDocuments, parseHelper } from 'langium/test'; describe('JsonSerializer', async () => { diff --git a/packages/langium/test/tsconfig.export-main.json b/packages/langium/test/tsconfig.export-main.json index b453a2326..bd54b8cd7 100644 --- a/packages/langium/test/tsconfig.export-main.json +++ b/packages/langium/test/tsconfig.export-main.json @@ -5,6 +5,7 @@ "noEmit": true }, "exclude": [ + // "../src/generate/*", // cs: to be enabled once #1171 is merged "../src/test/**/*" ] } diff --git a/packages/langium/test/utils/cst-utils.test.ts b/packages/langium/test/utils/cst-utils.test.ts index 5d8794e55..0176734d7 100644 --- a/packages/langium/test/utils/cst-utils.test.ts +++ b/packages/langium/test/utils/cst-utils.test.ts @@ -5,9 +5,10 @@ ******************************************************************************/ import type { Grammar, LeafCstNode } from 'langium'; -import { describe, expect, test } from 'vitest'; -import { createLangiumGrammarServices, findLeafNodeAtOffset, EmptyFileSystem, findLeafNodeBeforeOffset, expandToString } from 'langium'; +import { EmptyFileSystem, createLangiumGrammarServices, findLeafNodeAtOffset, findLeafNodeBeforeOffset } from 'langium'; +import { expandToString } from 'langium/generate'; import { parseHelper } from 'langium/test'; +import { describe, expect, test } from 'vitest'; const services = createLangiumGrammarServices(EmptyFileSystem); const parser = parseHelper(services.grammar);