From 7b0a010c0eb0221d163ae0b32c75289de59f13dc Mon Sep 17 00:00:00 2001 From: Christian Schneider Date: Fri, 17 Nov 2023 15:29:10 +0100 Subject: [PATCH] generator-langium: extended yeoman generator extension to offer parsing, linking, and validation test stubs (#1282) * providing an additional automated test running 'npm test' and checking its proper termination --- packages/generator-langium/src/index.ts | 92 ++++++++----- .../templates/test/.package.json | 10 ++ .../templates/test/.tsconfig.json | 10 ++ .../templates/test/.vscode-extensions.json | 11 ++ .../test/test/linking/linking.test.ts | 53 ++++++++ .../test/test/parsing/parsing.test.ts | 60 +++++++++ .../test/test/validating/validating.test.ts | 66 ++++++++++ .../templates/test/tsconfig.src.json | 11 ++ .../templates/test/vitest.config.ts | 20 +++ .../test/yeoman-generator.test.ts | 123 +++++++++++++----- packages/generator-langium/tsconfig.src.json | 13 +- vite.config.ts | 5 +- 12 files changed, 407 insertions(+), 67 deletions(-) create mode 100644 packages/generator-langium/templates/test/.package.json create mode 100644 packages/generator-langium/templates/test/.tsconfig.json create mode 100644 packages/generator-langium/templates/test/.vscode-extensions.json create mode 100644 packages/generator-langium/templates/test/test/linking/linking.test.ts create mode 100644 packages/generator-langium/templates/test/test/parsing/parsing.test.ts create mode 100644 packages/generator-langium/templates/test/test/validating/validating.test.ts create mode 100644 packages/generator-langium/templates/test/tsconfig.src.json create mode 100644 packages/generator-langium/templates/test/vitest.config.ts diff --git a/packages/generator-langium/src/index.ts b/packages/generator-langium/src/index.ts index f7207d226..be9555041 100644 --- a/packages/generator-langium/src/index.ts +++ b/packages/generator-langium/src/index.ts @@ -5,6 +5,7 @@ ******************************************************************************/ import Generator from 'yeoman-generator'; +import type { CopyOptions } from 'mem-fs-editor'; import _ from 'lodash'; import chalk from 'chalk'; import * as path from 'node:path'; @@ -18,6 +19,7 @@ const TEMPLATE_CORE_DIR = '../templates/core'; const TEMPLATE_VSCODE_DIR = '../templates/vscode'; const TEMPLATE_CLI_DIR = '../templates/cli'; const TEMPLATE_WEB_DIR = '../templates/web'; +const TEMPLATE_TEST_DIR = '../templates/test'; const USER_DIR = '.'; const EXTENSION_NAME = /<%= extension-name %>/g; @@ -31,13 +33,18 @@ const LANGUAGE_PATH_ID = /language-id/g; const NEWLINES = /\r?\n/g; -interface Answers { +export interface Answers { extensionName: string; rawLanguageName: string; fileExtensions: string; includeVSCode: boolean; includeCLI: boolean; includeWeb: boolean; + includeTest: boolean; +} + +export interface PostAnwers { + openWith: 'code' | false } function printLogo(log: (message: string) => void): void { @@ -53,7 +60,7 @@ function description(...d: string[]): string { return chalk.reset(chalk.dim(d.join(' ') + '\n')) + chalk.green('?'); } -class LangiumGenerator extends Generator { +export class LangiumGenerator extends Generator { private answers: Answers; constructor(args: string | string[], options: Record) { @@ -62,7 +69,7 @@ class LangiumGenerator extends Generator { async prompting(): Promise { printLogo(this.log); - this.answers = await this.prompt([ + this.answers = await this.prompt([ { type: 'input', name: 'extensionName', @@ -129,6 +136,15 @@ class LangiumGenerator extends Generator { ), message: 'Include Web worker?', default: 'yes' + }, + { + type: 'confirm', + name: 'includeTest', + prefix: description( + 'You can add the setup for language tests using Vitest.' + ), + message: 'Include language tests?', + default: 'yes' } ]); } @@ -154,6 +170,11 @@ class LangiumGenerator extends Generator { ); const languageId = _.kebabCase(this.answers.rawLanguageName); + const templateCopyOptions: CopyOptions = { + process: content => this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), + processDestinationPath: path => this._replaceTemplateNames(languageId, path) + }; + this.sourceRoot(path.join(__dirname, TEMPLATE_CORE_DIR)); const pkgJson = this.fs.readJSON(path.join(this.sourceRoot(), '.package.json')); this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4); @@ -162,12 +183,7 @@ class LangiumGenerator extends Generator { this.fs.copy( this.templatePath(path), this._extensionPath(path), - { - process: content => - this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), - processDestinationPath: path => - this._replaceTemplateNames(languageId, path), - } + templateCopyOptions ); } @@ -183,10 +199,7 @@ class LangiumGenerator extends Generator { this.fs.copy( this.templatePath(path), this._extensionPath(path), - { - process: content => this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), - processDestinationPath: path => this._replaceTemplateNames(languageId, path) - } + templateCopyOptions ); } } @@ -199,10 +212,7 @@ class LangiumGenerator extends Generator { this.fs.copy( this.templatePath(path), this._extensionPath(path), - { - process: content => this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), - processDestinationPath: path => this._replaceTemplateNames(languageId, path) - } + templateCopyOptions ); } } @@ -216,25 +226,37 @@ class LangiumGenerator extends Generator { this.fs.copy( this.templatePath(path), this._extensionPath(path), - { - process: content => - this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), - processDestinationPath: path => - this._replaceTemplateNames(languageId, path), - } + templateCopyOptions ); } } + if (this.answers.includeTest) { + this.sourceRoot(path.join(__dirname, TEMPLATE_TEST_DIR)); + + this.fs.copy( + this.templatePath('.'), + this._extensionPath(), + templateCopyOptions + ); + + // update the scripts section in the package.json to use 'tsconfig.src.json' for building + const pkgJson = this.fs.readJSON(this.templatePath('.package.json')); + this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4); + + // update the 'includes' property in the existing 'tsconfig.json' and adds '"noEmit": true' + const tsconfigJson = this.fs.readJSON(this.templatePath('.tsconfig.json')); + this.fs.extendJSON(this._extensionPath('tsconfig.json'), tsconfigJson, undefined, 4); + + // the initial '.vscode/extensions.json' can't be extended as above, as it contains comments, which is tolerated by vscode, + // but not by `this.fs.extendJSON(...)`, so + this.fs.copy(this.templatePath('.vscode-extensions.json'), this._extensionPath('.vscode/extensions.json'), templateCopyOptions); + } + this.fs.copy( this._extensionPath('package-template.json'), this._extensionPath('package.json'), - { - process: content => - this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content), - processDestinationPath: path => - this._replaceTemplateNames(languageId, path), - } + templateCopyOptions ); this.fs.delete(this._extensionPath('package-template.json')); } @@ -244,23 +266,23 @@ class LangiumGenerator extends Generator { const opts = { cwd: extensionPath }; if(!this.args.includes('skip-install')) { - this.spawnCommandSync('npm', ['install'], opts); + this.spawnSync('npm', ['install'], opts); } - this.spawnCommandSync('npm', ['run', 'langium:generate'], opts); + this.spawnSync('npm', ['run', 'langium:generate'], opts); if (this.answers.includeVSCode || this.answers.includeCLI) { - this.spawnCommandSync('npm', ['run', 'build'], opts); + this.spawnSync('npm', ['run', 'build'], opts); } if (this.answers.includeWeb) { - this.spawnCommandSync('npm', ['run', 'build:web'], opts); + this.spawnSync('npm', ['run', 'build:web'], opts); } } async end(): Promise { const code = await which('code').catch(() => undefined); if (code) { - const answer = await this.prompt({ + const answer = await this.prompt({ type: 'list', name: 'openWith', message: 'Do you want to open the new folder with Visual Studio Code?', @@ -277,7 +299,7 @@ class LangiumGenerator extends Generator { ] }); if (answer?.openWith) { - this.spawnCommand(answer.openWith, [this._extensionPath()]); + this.spawn(answer.openWith, [this._extensionPath()]); } } } diff --git a/packages/generator-langium/templates/test/.package.json b/packages/generator-langium/templates/test/.package.json new file mode 100644 index 000000000..f3003db6c --- /dev/null +++ b/packages/generator-langium/templates/test/.package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "vitest": "0.34" + }, + "scripts": { + "build": "tsc -b tsconfig.src.json", + "watch": "tsc -b tsconfig.src.json --watch", + "test": "vitest run" + } +} \ No newline at end of file diff --git a/packages/generator-langium/templates/test/.tsconfig.json b/packages/generator-langium/templates/test/.tsconfig.json new file mode 100644 index 000000000..2701afb66 --- /dev/null +++ b/packages/generator-langium/templates/test/.tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] + } + \ No newline at end of file diff --git a/packages/generator-langium/templates/test/.vscode-extensions.json b/packages/generator-langium/templates/test/.vscode-extensions.json new file mode 100644 index 000000000..1252a6cd7 --- /dev/null +++ b/packages/generator-langium/templates/test/.vscode-extensions.json @@ -0,0 +1,11 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "langium.langium-vscode", + "ZixuanChen.vitest-explorer", + "kingwl.vscode-vitest-runner" + ] +} diff --git a/packages/generator-langium/templates/test/test/linking/linking.test.ts b/packages/generator-langium/templates/test/test/linking/linking.test.ts new file mode 100644 index 000000000..89fdefa4f --- /dev/null +++ b/packages/generator-langium/templates/test/test/linking/linking.test.ts @@ -0,0 +1,53 @@ +import { afterEach, beforeAll, describe, expect, test } from "vitest"; +import { EmptyFileSystem, type LangiumDocument } from "langium"; +import { expandToString as s} from "langium/generate"; +import { parseHelper } from "langium/test"; +import { create<%= LanguageName %>Services } from "../../src/language/<%= language-id %>-module.js"; +import { Model, isModel } from "../../src/language/generated/ast.js"; + +let services: ReturnTypeServices>; +let parse: ReturnType>; +let document: LangiumDocument | undefined; + +beforeAll(async () => { + services = create<%= LanguageName %>Services(EmptyFileSystem); + parse = parseHelper(services.<%= LanguageName %>); + + // activate the following if your linking test requires elements from a built-in library, for example + // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); +}); + +afterEach(async () => { + document && await services.shared.workspace.DocumentBuilder.update([], [ document?.uri ]); +}); + +describe('Linking tests', () => { + + test('linking of greetings', async () => { + document = await parse(` + person Langium + Hello Langium! + `); + + expect( + // here we first check for validity of the parsed document object by means of the reusable function + // 'checkDocumentValid()' to sort out (critical) typos first, + // and then evaluate the cross references we're interested in by checking + // the referenced AST element as well as for a potential error message; + checkDocumentValid(document) + || document.parseResult.value.greetings.map(g => g.person.ref?.name || g.person.error?.message).join('\n') + ).toBe(s` + Langium + `); + }); +}); + +function checkDocumentValid(document: LangiumDocument): string | undefined { + return document.parseResult.parserErrors.length && s` + Parser errors: + ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} + ` + || document.parseResult.value === undefined && `ParseResult is 'undefined'.` + || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` + || undefined; +} diff --git a/packages/generator-langium/templates/test/test/parsing/parsing.test.ts b/packages/generator-langium/templates/test/test/parsing/parsing.test.ts new file mode 100644 index 000000000..04d4f2797 --- /dev/null +++ b/packages/generator-langium/templates/test/test/parsing/parsing.test.ts @@ -0,0 +1,60 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { EmptyFileSystem, type LangiumDocument } from "langium"; +import { expandToString as s} from "langium/generate"; +import { parseHelper } from "langium/test"; +import { create<%= LanguageName %>Services } from "../../src/language/<%= language-id %>-module.js"; +import { Model, isModel } from "../../src/language/generated/ast.js"; + +let services: ReturnTypeServices>; +let parse: ReturnType>; +let document: LangiumDocument | undefined; + +beforeAll(async () => { + services = create<%= LanguageName %>Services(EmptyFileSystem); + parse = parseHelper(services.<%= LanguageName %>); + + // activate the following if your linking test requires elements from a built-in library, for example + // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); +}); + +describe('Parsing tests', () => { + + test('parse simple model', async () => { + document = await parse(` + person Langium + Hello Langium! + `); + + // check for absensce of parser errors the classic way: + // deacivated, find a much more human readable way below! + // expect(document.parseResult.parserErrors).toHaveLength(0); + + expect( + // here we use a (tagged) template expression to create a human readable representation + // of the AST part we are interested in and that is to be compared to our expectation; + // prior to the tagged template expression we check for validity of the parsed document object + // by means of the reusable function 'checkDocumentValid()' to sort out (critical) typos first; + checkDocumentValid(document) || s` + Persons: + ${document.parseResult.value?.persons?.map(p => p.name)?.join('\n ')} + Greetings to: + ${document.parseResult.value?.greetings?.map(g => g.person.$refText)?.join('\n ')} + ` + ).toBe(s` + Persons: + Langium + Greetings to: + Langium + `); + }); +}); + +function checkDocumentValid(document: LangiumDocument): string | undefined { + return document.parseResult.parserErrors.length && s` + Parser errors: + ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} + ` + || document.parseResult.value === undefined && `ParseResult is 'undefined'.` + || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` + || undefined; +} diff --git a/packages/generator-langium/templates/test/test/validating/validating.test.ts b/packages/generator-langium/templates/test/test/validating/validating.test.ts new file mode 100644 index 000000000..7c155d926 --- /dev/null +++ b/packages/generator-langium/templates/test/test/validating/validating.test.ts @@ -0,0 +1,66 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { EmptyFileSystem, type LangiumDocument } from "langium"; +import { expandToString as s} from "langium/generate"; +import { parseHelper } from "langium/test"; +import type { Diagnostic } from "vscode-languageserver-types"; +import { create<%= LanguageName %>Services } from "../../src/language/<%= language-id %>-module.js"; +import { Model, isModel } from "../../src/language/generated/ast.js"; + +let services: ReturnTypeServices>; +let parse: ReturnType>; +let document: LangiumDocument | undefined; + +beforeAll(async () => { + services = create<%= LanguageName %>Services(EmptyFileSystem); + const doParse = parseHelper(services.<%= LanguageName %>); + parse = (input: string) => doParse(input, { validation: true }); + + // activate the following if your linking test requires elements from a built-in library, for example + // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); +}); + +describe('Validating', () => { + + test('check no errors', async () => { + document = await parse(` + person Langium + `); + + expect( + // here we first check for validity of the parsed document object by means of the reusable function + // 'checkDocumentValid()' to sort out (critical) typos first, + // and then evaluate the diagnostics by converting them into human readable strings; + // note that 'toHaveLength()' works for arrays and strings alike ;-) + checkDocumentValid(document) || document?.diagnostics?.map(diagnosticToString)?.join('\n') + ).toHaveLength(0); + }); + + test('check capital letter validation', async () => { + document = await parse(` + person langium + `); + + expect( + checkDocumentValid(document) || document?.diagnostics?.map(diagnosticToString)?.join('\n') + ).toEqual( + // 'expect.stringContaining()' makes our test robust against future additions of further validation rules + expect.stringContaining(s` + [1:19..1:26]: Person name should start with a capital. + `) + ); + }); +}); + +function checkDocumentValid(document: LangiumDocument): string | undefined { + return document.parseResult.parserErrors.length && s` + Parser errors: + ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} + ` + || document.parseResult.value === undefined && `ParseResult is 'undefined'.` + || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` + || undefined; +} + +function diagnosticToString(d: Diagnostic) { + return `[${d.range.start.line}:${d.range.start.character}..${d.range.end.line}:${d.range.end.character}]: ${d.message}`; +} diff --git a/packages/generator-langium/templates/test/tsconfig.src.json b/packages/generator-langium/templates/test/tsconfig.src.json new file mode 100644 index 000000000..928fe6d89 --- /dev/null +++ b/packages/generator-langium/templates/test/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + }, + "include": [ + "src/**/*.ts" + ] + } + \ No newline at end of file diff --git a/packages/generator-langium/templates/test/vitest.config.ts b/packages/generator-langium/templates/test/vitest.config.ts new file mode 100644 index 000000000..47173bfcf --- /dev/null +++ b/packages/generator-langium/templates/test/vitest.config.ts @@ -0,0 +1,20 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://vitest.dev/config/ + */ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // coverage: { + // provider: 'v8', + // reporter: ['text', 'html'], + // include: ['src'], + // exclude: ['**/generated'], + // }, + deps: { + interopDefault: true + }, + include: ['**/*.test.ts'] + } +}); diff --git a/packages/generator-langium/test/yeoman-generator.test.ts b/packages/generator-langium/test/yeoman-generator.test.ts index 3b9039439..9ab3d8cca 100644 --- a/packages/generator-langium/test/yeoman-generator.test.ts +++ b/packages/generator-langium/test/yeoman-generator.test.ts @@ -8,58 +8,123 @@ import { normalizeEOL } from 'langium/generate'; import * as path from 'node:path'; import * as url from 'node:url'; import { describe, test } from 'vitest'; +import type * as Generator from 'yeoman-generator'; import { createHelpers } from 'yeoman-test'; +import type { Answers, LangiumGenerator, PostAnwers } from '../src/index.js'; -const __dirname = url.fileURLToPath(new URL('../', import.meta.url)); - -const answersForCore = { +const answersForCore: Answers & PostAnwers = { extensionName: 'hello-world', rawLanguageName: 'Hello World', - fileExtension: '.hello', + fileExtensions: '.hello', includeVSCode: false, includeCLI: false, includeWeb: false, + includeTest: false, openWith: false }; describe('Check yeoman generator works', () => { - const moduleRoot = path.join(__dirname, 'app'); + const packageTestDir = url.fileURLToPath(new URL('.', import.meta.url)); + const moduleRoot = path.join(packageTestDir, '../app'); + + const files = (targetRoot: string) => [ + targetRoot + '/.eslintrc.json', + targetRoot + '/.gitignore', + targetRoot + '/langium-config.json', + targetRoot + '/langium-quickstart.md', + targetRoot + '/tsconfig.json', + targetRoot + '/package.json', + targetRoot + '/.vscode/extensions.json', + targetRoot + '/.vscode/tasks.json', + targetRoot + '/src/language/hello-world-module.ts', + targetRoot + '/src/language/hello-world-validator.ts', + targetRoot + '/src/language/hello-world.langium' + ]; + + const testFiles = (targetRoot: string) => [ + targetRoot + '/tsconfig.src.json', + targetRoot + '/test/parsing/parsing.test.ts', + targetRoot + '/test/linking/linking.test.ts', + targetRoot + '/test/validating/validating.test.ts', + ]; + + test('1 Should produce files for Core', async () => { + + const context = createHelpers({}).run(path.join(moduleRoot)); + + // generate in examples + const targetRoot = path.resolve(packageTestDir, '../../../examples'); + const extensionName = answersForCore.extensionName; - test('Should produce files for Core', async () => { + // remove examples/hello-world (if existing) now and finally (don't delete everything else in examples) + context.targetDirectory = path.resolve(targetRoot, extensionName); + context.cleanTestDirectory(true); - const context = createHelpers({}).run(moduleRoot); - context.targetDirectory = path.join(__dirname, '../../../examples/hello-world'); // generate in examples - const targetRoot = path.join(__dirname, '../../../examples'); - context.cleanTestDirectory(true); // clean-up examples/hello-world await context - .onGenerator(async (generator) => { - // will generate into examples/hello-world instead of examples/hello-world/hello-world - generator.destinationRoot(targetRoot); // types are wrong, should be string + .withOptions({ + // we need to explicitly tell the generator it's destinationRoot + destinationRoot: targetRoot + }) + .onTargetDirectory(workingDir => { + // just for double checking + console.log(`Generating into directory: ${workingDir}`); }) .withAnswers(answersForCore) .withArguments('skip-install') .then((result) => { - const files = [ - targetRoot + '/hello-world/.eslintrc.json', - targetRoot + '/hello-world/.gitignore', - targetRoot + '/hello-world/langium-config.json', - targetRoot + '/hello-world/langium-quickstart.md', - targetRoot + '/hello-world/tsconfig.json', - targetRoot + '/hello-world/package.json', - targetRoot + '/hello-world/.vscode/extensions.json', - targetRoot + '/hello-world/.vscode/tasks.json', - targetRoot + '/hello-world/src/language/hello-world-module.ts', - targetRoot + '/hello-world/src/language/hello-world-validator.ts', - targetRoot + '/hello-world/src/language/hello-world.langium' - ]; - result.assertFile(files); - result.assertJsonFileContent(targetRoot + '/hello-world/package.json', PACKAGE_JSON_EXPECTATION); - result.assertFileContent(targetRoot + '/hello-world/.vscode/tasks.json', TASKS_JSON_EXPECTATION); + const projectRoot = targetRoot + '/' + extensionName; + + result.assertFile(files(projectRoot)); + result.assertNoFile(testFiles(projectRoot)); + + result.assertJsonFileContent(projectRoot + '/package.json', PACKAGE_JSON_EXPECTATION); + result.assertFileContent(projectRoot + '/.vscode/tasks.json', TASKS_JSON_EXPECTATION); }).finally(() => { context.cleanTestDirectory(true); }); }, 120_000); + test('2 Should produce files for Core & test', async () => { + + const context = createHelpers({}).run(path.join(moduleRoot)); + + // generate in examples + const targetRoot = path.resolve(packageTestDir, '../../../examples'); + const extensionName = 'hello-world-vitest'; + + // remove examples/hello-world (if existing) now and finally (don't delete everything else in examples) + context.targetDirectory = path.resolve(targetRoot, extensionName); + context.cleanTestDirectory(true); + + await context + .withOptions({ + // we need to explicitly tell the generator it's destinationRoot + destinationRoot: targetRoot + }) + .onTargetDirectory(workingDir => { + // just for double checking + console.log(`Generating into directory: ${workingDir}`); + }) + .withAnswers( { + ...answersForCore, + extensionName, + includeCLI: true, + includeTest: true + }).then((result) => { + result.generator.spawnSync('npm', ['install', '../../packages/langium'], { + cwd: result.generator._extensionPath() + }); + const returnVal = result.generator.spawnSync('npm', ['test'], { + cwd: result.generator._extensionPath(), + stdio: 'inherit' + }); + + result.assertTextEqual(String(returnVal.exitCode), '0'); + + }).finally(() => { + context.cleanTestDirectory(true); + }); + }, 120_000); }); // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/packages/generator-langium/tsconfig.src.json b/packages/generator-langium/tsconfig.src.json index 5d42891b8..e0f48d1f5 100644 --- a/packages/generator-langium/tsconfig.src.json +++ b/packages/generator-langium/tsconfig.src.json @@ -2,7 +2,18 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "src", - "outDir": "app" + "outDir": "app", + + // there is kind of a bug in the type definitions of yeoman-generator, + // namely in 'yeoman-generator/dist/generator.d.ts' that states + // import EventEmitter from 'node:events'; + // instead of + // import { EventEmitter } from 'node:events'; + // this breaks the inheritance hierarchy of class 'Generator', whose type definition + // is an implicit composition of class 'BaseGenerator' and interface 'BaseGenerator', + // both defined in 'yeoman-generator/dist/generator.d.ts' + // the following setting instructs TSC to tolerate that. + "allowSyntheticDefaultImports": true }, "include": [ "src/**/*" diff --git a/vite.config.ts b/vite.config.ts index c0769d646..5ceb9e649 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,11 +12,12 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'html'], include: ['packages/langium/src'], - exclude: ['**/generated'], + exclude: ['**/generated', '**/templates'], }, deps: { interopDefault: true }, - include: ['**/*.test.ts'] + include: ['**/test/**/*.test.ts'], + exclude: ['**/node_modules/**', '**/dist/**', '**/generated/**', '**/templates/**'] } });