Skip to content

Commit

Permalink
moved generator API to a dedicated package export, solves #1285
Browse files Browse the repository at this point in the history
* move NEWLINE_REGEXP from 'template-string.ts' to 'regex-util.ts',
* refactored our generators to use 'expandToNode' and friends instead of instantiating 'CompositeGeneratorNodes'
  • Loading branch information
sailingKieler committed Nov 22, 2023
1 parent 71127c2 commit 2087e96
Show file tree
Hide file tree
Showing 41 changed files with 558 additions and 581 deletions.
3 changes: 2 additions & 1 deletion examples/domainmodel/src/benchmark/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
71 changes: 32 additions & 39 deletions examples/domainmodel/src/cli/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
try {
Expand Down Expand Up @@ -57,9 +56,11 @@ function generateAbstractElements(destination: string, elements: Array<AbstractE
if (isPackageDeclaration(elem)) {
generateAbstractElementsInternal(elem.elements, path.join(filePath, elem.name.replace(/\./g, '/')));
} else if (isEntity(elem)) {
const fileNode = new CompositeGeneratorNode();
fileNode.append(`package ${packagePath};`, NL, NL);
generateEntity(elem, fileNode);
const fileNode = expandToNode`
package ${packagePath};
${generateEntity(elem)}
`;
fs.writeFileSync(path.join(fullPath, `${elem.name}.java`), toString(fileNode));
}
}
Expand All @@ -69,40 +70,32 @@ function generateAbstractElements(destination: string, elements: Array<AbstractE
return generateAbstractElementsInternal(elements, filePath);
}

function generateEntity(entity: Entity, fileNode: CompositeGeneratorNode): void {
function generateEntity(entity: Entity): Generated {
const maybeExtends = entity.superType ? ` extends ${entity.superType.$refText}` : '';
fileNode.append(`class ${entity.name}${maybeExtends} {`, NL);
fileNode.indent(classBody => {
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};
}
`
];
}
52 changes: 30 additions & 22 deletions examples/requirements/src/cli/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -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('<html>', NL);
fileNode.append('<body>', NL);
fileNode.append('<h1>Requirement coverage (demo generator)</h1>', NL);
fileNode.append(`<div>Source: ${model.$document?.uri.fsPath}</div>`, NL);
fileNode.append('<table border="1">', NL);
fileNode.append('<TR><TH>Requirement ID</TH><TH>Testcase ID</TH></TR>', NL);
model.requirements.forEach(requirement => {
fileNode.append(`<TR><TD>${requirement.name}</TD><TD>`, NL);
testModels.forEach(testModel => testModel.tests.forEach(test => {
if (test.requirements.map(r => r.ref).includes(requirement)) {
fileNode.append(`<div>${test.name} (from ${testModel.$document?.uri.fsPath})<div>`, NL);
}
}));
fileNode.append('</TD></TR>', NL);
});
fileNode.append('</table>', NL);
fileNode.append('</body>', NL);
fileNode.append('</html>', NL);
return toString(fileNode);
/* eslint-disable @typescript-eslint/indent */
return toString(
expandToNode`
<html>
<body>
<h1>Requirement coverage (demo generator)</h1>
<div>Source: ${model.$document?.uri.fsPath}</div>
<table border="1">
<TR><TH>Requirement ID</TH><TH>Testcase ID</TH></TR>
${joinToNode(
model.requirements,
requirement => expandToNode`
<TR><TD>${requirement.name}</TD><TD>
${joinToNode(
testModels.flatMap(testModel => testModel.tests.map(test => ({ testModel, test }))).filter( ({ test }) => test.requirements.map(r => r.ref).includes(requirement) ),
({ testModel, test }) => `<div>${test.name} (from ${testModel.$document?.uri?.fsPath})<div>`,
{ appendNewLineIfNotEmpty: true }
)}
</TD></TR>
`,
{ appendNewLineIfNotEmpty: true }
)}
</table>
</body>
</html>
`.appendNewLine()
);
/* eslint-enable @typescript-eslint/indent */
}

export function generateSummary(model: RequirementModel, testModels: TestModel[], filePath: string, destination: string | undefined): string {
Expand Down
3 changes: 1 addition & 2 deletions examples/statemachine/src/cli/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
8 changes: 4 additions & 4 deletions examples/statemachine/test/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
10 changes: 6 additions & 4 deletions packages/generator-langium/templates/cli/src/cli/generator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
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';

export function generateJavaScript(model: Model, filePath: string, destination: string | undefined): string {
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 });
Expand Down
6 changes: 3 additions & 3 deletions packages/generator-langium/test/yeoman-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
Loading

0 comments on commit 2087e96

Please sign in to comment.