@hydranium/cli 1.0.0-next.22 → 1.0.0-next.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -5
- package/bin/hydranium-cli.js +13 -0
- package/lib/cli-version.js.map +1 -1
- package/lib/cli.js +0 -0
- package/lib/commands/analyze-heap-args.js.map +1 -1
- package/lib/commands/analyze-heap.d.ts +3 -0
- package/lib/commands/analyze-heap.d.ts.map +1 -1
- package/lib/commands/analyze-heap.js +2 -1
- package/lib/commands/analyze-heap.js.map +1 -1
- package/lib/commands/ast-ground-truth-args.d.ts +3 -2
- package/lib/commands/ast-ground-truth-args.d.ts.map +1 -1
- package/lib/commands/ast-ground-truth-args.js +11 -9
- package/lib/commands/ast-ground-truth-args.js.map +1 -1
- package/lib/commands/ast-ground-truth.js +1 -1
- package/lib/commands/ast-ground-truth.js.map +1 -1
- package/lib/commands/generate-ast-builder.d.ts +49 -0
- package/lib/commands/generate-ast-builder.d.ts.map +1 -0
- package/lib/commands/generate-ast-builder.js +200 -0
- package/lib/commands/generate-ast-builder.js.map +1 -0
- package/lib/commands/generate-transfer-model-args.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model-args.js +11 -1
- package/lib/commands/generate-transfer-model-args.js.map +1 -1
- package/lib/commands/generate-transfer-model-config.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model-config.js +3 -1
- package/lib/commands/generate-transfer-model-config.js.map +1 -1
- package/lib/commands/generate-transfer-model.d.ts +9 -0
- package/lib/commands/generate-transfer-model.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model.js +16 -3
- package/lib/commands/generate-transfer-model.js.map +1 -1
- package/lib/commands/harness-args.d.ts +37 -42
- package/lib/commands/harness-args.d.ts.map +1 -1
- package/lib/commands/harness-args.js +65 -25
- package/lib/commands/harness-args.js.map +1 -1
- package/lib/commands/headless-harness.d.ts +37 -20
- package/lib/commands/headless-harness.d.ts.map +1 -1
- package/lib/commands/headless-harness.js +68 -17
- package/lib/commands/headless-harness.js.map +1 -1
- package/lib/commands/init-args.js +4 -4
- package/lib/commands/init-args.js.map +1 -1
- package/lib/commands/init-prompt.d.ts.map +1 -1
- package/lib/commands/init-templates.d.ts +0 -9
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +230 -124
- package/lib/commands/init-templates.js.map +1 -1
- package/lib/commands/init-wizard.js.map +1 -1
- package/lib/commands/init-workspace.d.ts +13 -3
- package/lib/commands/init-workspace.d.ts.map +1 -1
- package/lib/commands/init-workspace.js +21 -6
- package/lib/commands/init-workspace.js.map +1 -1
- package/lib/commands/init.d.ts +13 -6
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +10 -12
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/lint-grammar-args.d.ts +3 -2
- package/lib/commands/lint-grammar-args.d.ts.map +1 -1
- package/lib/commands/lint-grammar-args.js +13 -10
- package/lib/commands/lint-grammar-args.js.map +1 -1
- package/lib/commands/lint-grammar-report.js.map +1 -1
- package/lib/commands/lint-grammar.js +1 -1
- package/lib/commands/lint-grammar.js.map +1 -1
- package/lib/commands/measure-memory-args.d.ts +3 -2
- package/lib/commands/measure-memory-args.d.ts.map +1 -1
- package/lib/commands/measure-memory-args.js +20 -10
- package/lib/commands/measure-memory-args.js.map +1 -1
- package/lib/commands/measure-memory-driver.js +3 -2
- package/lib/commands/measure-memory-driver.js.map +1 -1
- package/lib/commands/measure-memory.js +1 -1
- package/lib/commands/measure-memory.js.map +1 -1
- package/lib/commands/model-docs-args.d.ts +4 -3
- package/lib/commands/model-docs-args.d.ts.map +1 -1
- package/lib/commands/model-docs-args.js +12 -10
- package/lib/commands/model-docs-args.js.map +1 -1
- package/lib/commands/model-docs.js +1 -1
- package/lib/commands/model-docs.js.map +1 -1
- package/lib/commands/projects-args.d.ts.map +1 -1
- package/lib/commands/projects-args.js +5 -5
- package/lib/commands/projects-args.js.map +1 -1
- package/lib/commands/projects.d.ts +3 -4
- package/lib/commands/projects.d.ts.map +1 -1
- package/lib/commands/projects.js +2 -2
- package/lib/commands/projects.js.map +1 -1
- package/lib/commands/query-args.d.ts.map +1 -1
- package/lib/commands/query-args.js +5 -5
- package/lib/commands/query-args.js.map +1 -1
- package/lib/commands/query.d.ts +3 -4
- package/lib/commands/query.d.ts.map +1 -1
- package/lib/commands/query.js +2 -2
- package/lib/commands/query.js.map +1 -1
- package/lib/commands/reflect-args.d.ts +3 -2
- package/lib/commands/reflect-args.d.ts.map +1 -1
- package/lib/commands/reflect-args.js +13 -10
- package/lib/commands/reflect-args.js.map +1 -1
- package/lib/commands/reflect-report.js.map +1 -1
- package/lib/commands/reflect.js +1 -1
- package/lib/commands/reflect.js.map +1 -1
- package/lib/commands/save-args.d.ts.map +1 -1
- package/lib/commands/save-args.js +6 -4
- package/lib/commands/save-args.js.map +1 -1
- package/lib/commands/save.d.ts +8 -4
- package/lib/commands/save.d.ts.map +1 -1
- package/lib/commands/save.js +31 -6
- package/lib/commands/save.js.map +1 -1
- package/lib/commands/validate-args.d.ts +3 -2
- package/lib/commands/validate-args.d.ts.map +1 -1
- package/lib/commands/validate-args.js +13 -10
- package/lib/commands/validate-args.js.map +1 -1
- package/lib/commands/validate-report.js.map +1 -1
- package/lib/commands/validate.js +1 -1
- package/lib/commands/validate.js.map +1 -1
- package/lib/commands/watch-args.d.ts.map +1 -1
- package/lib/commands/watch-args.js +6 -4
- package/lib/commands/watch-args.js.map +1 -1
- package/lib/commands/watch.d.ts +3 -4
- package/lib/commands/watch.d.ts.map +1 -1
- package/lib/commands/watch.js +20 -2
- package/lib/commands/watch.js.map +1 -1
- package/lib/driver-heap.d.ts +52 -0
- package/lib/driver-heap.d.ts.map +1 -0
- package/lib/driver-heap.js +51 -0
- package/lib/driver-heap.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/log-level.d.ts +21 -11
- package/lib/log-level.d.ts.map +1 -1
- package/lib/log-level.js +25 -14
- package/lib/log-level.js.map +1 -1
- package/lib/spawn-data-server.d.ts +6 -1
- package/lib/spawn-data-server.d.ts.map +1 -1
- package/lib/spawn-data-server.js +2 -1
- package/lib/spawn-data-server.js.map +1 -1
- package/lib/stderr-connection-logger.d.ts +12 -0
- package/lib/stderr-connection-logger.d.ts.map +1 -0
- package/lib/stderr-connection-logger.js +16 -0
- package/lib/stderr-connection-logger.js.map +1 -0
- package/lib/testing/echo-server.js +8 -1
- package/lib/testing/echo-server.js.map +1 -1
- package/package.json +39 -39
- package/src/commands/analyze-heap.ts +4 -1
- package/src/commands/ast-ground-truth-args.ts +15 -12
- package/src/commands/ast-ground-truth.ts +1 -1
- package/src/commands/generate-ast-builder.ts +246 -0
- package/src/commands/generate-transfer-model-args.ts +11 -1
- package/src/commands/generate-transfer-model-config.ts +3 -1
- package/src/commands/generate-transfer-model.ts +25 -3
- package/src/commands/harness-args.ts +73 -28
- package/src/commands/headless-harness.ts +86 -26
- package/src/commands/init-args.ts +4 -4
- package/src/commands/init-templates.ts +239 -126
- package/src/commands/init-workspace.ts +31 -7
- package/src/commands/init.ts +24 -19
- package/src/commands/lint-grammar-args.ts +17 -13
- package/src/commands/lint-grammar.ts +1 -1
- package/src/commands/measure-memory-args.ts +25 -13
- package/src/commands/measure-memory-driver.ts +3 -2
- package/src/commands/measure-memory.ts +1 -1
- package/src/commands/model-docs-args.ts +16 -13
- package/src/commands/model-docs.ts +1 -1
- package/src/commands/projects-args.ts +5 -4
- package/src/commands/projects.ts +4 -6
- package/src/commands/query-args.ts +5 -4
- package/src/commands/query.ts +4 -6
- package/src/commands/reflect-args.ts +17 -13
- package/src/commands/reflect.ts +1 -1
- package/src/commands/save-args.ts +6 -3
- package/src/commands/save.ts +49 -10
- package/src/commands/validate-args.ts +17 -13
- package/src/commands/validate.ts +1 -1
- package/src/commands/watch-args.ts +6 -3
- package/src/commands/watch.ts +28 -7
- package/src/driver-heap.ts +65 -0
- package/src/index.ts +1 -0
- package/src/log-level.ts +42 -14
- package/src/spawn-data-server.ts +14 -2
- package/src/stderr-connection-logger.ts +18 -0
- package/src/testing/echo-server.ts +10 -2
|
@@ -71,15 +71,6 @@
|
|
|
71
71
|
* over; the adopter's own formatter settles it.
|
|
72
72
|
*/
|
|
73
73
|
import { readCliVersion } from '../cli-version.js';
|
|
74
|
-
/**
|
|
75
|
-
* The version every framework package carries before the first release.
|
|
76
|
-
*
|
|
77
|
-
* A scaffold made by a CLI still at this version pins a range the registry
|
|
78
|
-
* cannot serve, so the emitted README keeps its yalc note and `init` keeps its
|
|
79
|
-
* install warning while — and only while — it holds: from a published CLI the
|
|
80
|
-
* derived pins resolve and either note would be false as printed.
|
|
81
|
-
*/
|
|
82
|
-
export const UNPUBLISHED_FRAMEWORK_VERSION = '0.0.0';
|
|
83
74
|
/**
|
|
84
75
|
* The version `init` pins every `@hydranium/*` dependency at: the scaffolding
|
|
85
76
|
* CLI's own.
|
|
@@ -156,7 +147,7 @@ function columnsFor(composition) {
|
|
|
156
147
|
}
|
|
157
148
|
/**
|
|
158
149
|
* A named import on one line while it fits the column budget, else one symbol
|
|
159
|
-
* per line. Emitted files are supposed to pass a
|
|
150
|
+
* per line. Emitted files are supposed to pass a format check as-is, and the
|
|
160
151
|
* generated-module import grows by one symbol per grammar.
|
|
161
152
|
*/
|
|
162
153
|
function importList(symbols, from, columns, typeOnly = false) {
|
|
@@ -204,7 +195,12 @@ const HEAD_FRAMEWORK_DEPENDENCIES = {
|
|
|
204
195
|
const HEAD_THIRD_PARTY_DEPENDENCIES = {
|
|
205
196
|
lsp: {
|
|
206
197
|
langium: '4.3.1',
|
|
207
|
-
|
|
198
|
+
// Exact, as are the protocol and transport it pins, so Langium and the
|
|
199
|
+
// framework share their copies; across copies, an error a handler throws
|
|
200
|
+
// or returns loses its code, and a later patch can pin another protocol.
|
|
201
|
+
'vscode-languageserver': '10.0.1',
|
|
202
|
+
'vscode-jsonrpc': '9.0.0',
|
|
203
|
+
'vscode-languageserver-protocol': '3.18.1'
|
|
208
204
|
},
|
|
209
205
|
data: {},
|
|
210
206
|
glsp: {
|
|
@@ -216,6 +212,22 @@ const HEAD_THIRD_PARTY_DEPENDENCIES = {
|
|
|
216
212
|
'reflect-metadata': '~0.2.2'
|
|
217
213
|
}
|
|
218
214
|
};
|
|
215
|
+
/** Whether an npm version is 11.6.0 or later, the first that installs `vitest` 4.1's peer set; `11.6.0-rc.1` is earlier. */
|
|
216
|
+
function installsVitest41(npmVersion) {
|
|
217
|
+
const [release, prerelease] = (npmVersion ?? '0.0.0').split('-', 2);
|
|
218
|
+
const [major, minor, patch] = release.split('.').map(Number);
|
|
219
|
+
return major > 11 || (major === 11 && (minor > 6 || (minor === 6 && (patch > 0 || prerelease === undefined))));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Whether the scaffold holds `vitest` below 4.1, whose peer set crashes npm
|
|
223
|
+
* before 11.6 (every npm a Node 22 release bundles) on the first install. Only a
|
|
224
|
+
* workspace root declaring a newer npm lifts it, and only when `init` runs under
|
|
225
|
+
* an npm known to be newer: npm does not enforce `packageManager`.
|
|
226
|
+
*/
|
|
227
|
+
function pinsVitest(composition) {
|
|
228
|
+
const workspace = composition.packaging.workspace;
|
|
229
|
+
return !(installsVitest41(workspace?.packageManagerNpm) && installsVitest41(workspace?.npmVersion));
|
|
230
|
+
}
|
|
219
231
|
/** The dependency block for a head set, merged and sorted as npm writes it. */
|
|
220
232
|
function dependencyBlock(composition, indent) {
|
|
221
233
|
const merged = {};
|
|
@@ -234,7 +246,7 @@ function dependencyBlock(composition, indent) {
|
|
|
234
246
|
// npm convention, and `npm install` rewrites `package.json` at two spaces
|
|
235
247
|
// regardless — so three would not survive the adopter's first install.
|
|
236
248
|
//
|
|
237
|
-
// The KEY ORDER is
|
|
249
|
+
// The KEY ORDER is the one Oxfmt's `sortPackageJson` canonicalises to, `private`
|
|
238
250
|
// before `license` included. Any other order is rewritten by the scaffolded
|
|
239
251
|
// project's own first `format` run — a diff on a file the adopter never touched
|
|
240
252
|
// — and it makes a byte-compare against a formatted copy of this emission
|
|
@@ -265,16 +277,9 @@ const PACKAGE_JSON = `{
|
|
|
265
277
|
"version": "0.0.0",
|
|
266
278
|
__PRIVATE__ "description": "__NAME__ language server, built with the Hydranium framework.",
|
|
267
279
|
"keywords": [
|
|
268
|
-
|
|
269
|
-
"langium",
|
|
270
|
-
"language-server",
|
|
271
|
-
"__PROJECT_ID__"
|
|
272
|
-
],
|
|
280
|
+
__KEYWORDS__ ],
|
|
273
281
|
"license": "UNLICENSED",
|
|
274
282
|
"author": "",
|
|
275
|
-
"type": "module",
|
|
276
|
-
"main": "lib/index.js",
|
|
277
|
-
"types": "lib/index.d.ts",
|
|
278
283
|
"bin": {
|
|
279
284
|
__BIN__ },
|
|
280
285
|
"files": [
|
|
@@ -282,11 +287,14 @@ __BIN__ },
|
|
|
282
287
|
"src",
|
|
283
288
|
"syntaxes"
|
|
284
289
|
],
|
|
290
|
+
"type": "module",
|
|
291
|
+
"main": "lib/index.js",
|
|
292
|
+
"types": "lib/index.d.ts",
|
|
285
293
|
"scripts": {
|
|
286
294
|
"build": "npm run generate && tsc",
|
|
287
|
-
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-
|
|
295
|
+
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-hydranium tsconfig.tsbuildinfo",
|
|
288
296
|
"generate": "npm run langium:generate && npm run generate:transfer-model",
|
|
289
|
-
"generate:transfer-model": "hydranium-cli generate-transfer-model --ast-file src/language-server/generated/ast.ts --augmentation-file src/language-server/ast.ts --out-file src/language-server/generated-
|
|
297
|
+
"generate:transfer-model": "hydranium-cli generate-transfer-model --ast-file src/language-server/generated/ast.ts --augmentation-file src/language-server/ast.ts --out-file src/language-server/generated-hydranium/transfer-model.ts --ast-builder-file src/language-server/generated-hydranium/ast-builder.ts --element-type-name __NAME__Element --terminals-name __NAME__Terminals --regen-command \\"Run: __NPM_RUN__ generate:transfer-model\\"",
|
|
290
298
|
"langium:generate": "langium generate",
|
|
291
299
|
"langium:watch": "langium generate --watch",
|
|
292
300
|
__LINT__ "start": "node lib/main.js --stdio",
|
|
@@ -302,8 +310,8 @@ __DEPENDENCIES__
|
|
|
302
310
|
"@types/node": "^22.0.0",
|
|
303
311
|
"langium-cli": "4.3.0",
|
|
304
312
|
"rimraf": "^5.0.0",
|
|
305
|
-
"typescript": "^
|
|
306
|
-
"vitest": "
|
|
313
|
+
"typescript": "^7.0.2",
|
|
314
|
+
"vitest": "__VITEST__"
|
|
307
315
|
},
|
|
308
316
|
"engines": {
|
|
309
317
|
"node": ">=22.13"
|
|
@@ -331,7 +339,7 @@ function npmRun(composition) {
|
|
|
331
339
|
* name. Without the second key a scaffolded project has no command line that
|
|
332
340
|
* reaches its own data head at all.
|
|
333
341
|
*
|
|
334
|
-
* Alphabetical, because `
|
|
342
|
+
* Alphabetical, because Oxfmt's `sortPackageJson` sorts `bin` and any other
|
|
335
343
|
* order is rewritten by the scaffolded project's first `format` run.
|
|
336
344
|
*/
|
|
337
345
|
function binBlock(composition) {
|
|
@@ -345,15 +353,29 @@ function binBlock(composition) {
|
|
|
345
353
|
function packageJson(composition) {
|
|
346
354
|
const { scope } = composition.packaging;
|
|
347
355
|
const packageName = scope === undefined ? composition.projectId : `${scope}/${composition.projectId}`;
|
|
348
|
-
|
|
356
|
+
const keywords = ['hydranium', 'langium', 'language-server', composition.projectId]
|
|
357
|
+
.sort()
|
|
358
|
+
.map(keyword => ` ${JSON.stringify(keyword)}`)
|
|
359
|
+
.join(',\n') + '\n';
|
|
360
|
+
// Emitted only where a root lint config says the repo lints. The absence is
|
|
349
361
|
// the dangerous direction: a task runner runs a script only where one is
|
|
350
362
|
// declared, so a package with no `lint` is SKIPPED rather than reported, and
|
|
351
363
|
// that reads as a clean lint. A wrong invocation is one visible line to edit.
|
|
352
|
-
const
|
|
364
|
+
const workspace = composition.packaging.workspace;
|
|
365
|
+
const oxlintConfig = workspace?.oxlintConfig === undefined
|
|
366
|
+
? undefined
|
|
367
|
+
: `${'../'.repeat(workspace.targetPath.split('/').length)}${workspace.oxlintConfig}`;
|
|
368
|
+
const lint = workspace?.oxlintConfig !== undefined
|
|
369
|
+
? ` "lint": "oxlint --report-unused-disable-directives --format stylish --config ${oxlintConfig} src test --max-warnings 0",\n`
|
|
370
|
+
: workspace?.eslintConfig !== undefined
|
|
371
|
+
? ' "lint": "eslint src test --max-warnings 0",\n'
|
|
372
|
+
: '';
|
|
353
373
|
return project(PACKAGE_JSON, composition)
|
|
354
374
|
.replace('__PACKAGE_NAME__', packageName)
|
|
375
|
+
.replace('__KEYWORDS__', keywords)
|
|
355
376
|
.replace('__BIN__', binBlock(composition))
|
|
356
377
|
.replace('__LINT__', lint)
|
|
378
|
+
.replace('__VITEST__', pinsVitest(composition) ? '~4.0.18' : '^4.0.0')
|
|
357
379
|
.replace('__PRIVATE__', composition.packaging.private ? ' "private": true,\n' : '')
|
|
358
380
|
.replace('__NPM_RUN__', npmRun(composition))
|
|
359
381
|
.replace('__FRAMEWORK_PIN__', frameworkPin(composition))
|
|
@@ -406,6 +428,11 @@ const TSCONFIG_COMPILER_OPTIONS = [
|
|
|
406
428
|
['outDir', 'lib'],
|
|
407
429
|
['strict', true],
|
|
408
430
|
['esModuleInterop', true],
|
|
431
|
+
// A server that renders its own user-facing messages loads a catalogue, and
|
|
432
|
+
// a catalogue is JSON. Without this the import does not resolve at all, and
|
|
433
|
+
// without the `include` glob below a composite project rejects it with
|
|
434
|
+
// TS6307 naming neither cause.
|
|
435
|
+
['resolveJsonModule', true],
|
|
409
436
|
['skipLibCheck', true],
|
|
410
437
|
['declaration', true],
|
|
411
438
|
['experimentalDecorators', true],
|
|
@@ -420,9 +447,19 @@ const TSCONFIG_COMPILER_OPTIONS = [
|
|
|
420
447
|
* base is wrong rather than that the member is redundant.
|
|
421
448
|
*/
|
|
422
449
|
const PACKAGE_LOCAL_OPTIONS = ['rootDir', 'outDir'];
|
|
423
|
-
/**
|
|
424
|
-
|
|
425
|
-
|
|
450
|
+
/**
|
|
451
|
+
* The enum-valued options of {@link TSCONFIG_COMPILER_OPTIONS}, whose values
|
|
452
|
+
* `tsc` reads case-insensitively, so `nodenext` and `NodeNext` are one value.
|
|
453
|
+
* An enum-valued option added there belongs here too.
|
|
454
|
+
*/
|
|
455
|
+
const CASE_INSENSITIVE_OPTIONS = ['target', 'lib', 'module', 'moduleResolution'];
|
|
456
|
+
/** Compare two option values. Sound here because compiler options are scalars and flat arrays. */
|
|
457
|
+
function optionEquals(key, left, right) {
|
|
458
|
+
const normalise = (value) => {
|
|
459
|
+
const text = JSON.stringify(value);
|
|
460
|
+
return CASE_INSENSITIVE_OPTIONS.includes(key) ? text?.toLowerCase() : text;
|
|
461
|
+
};
|
|
462
|
+
return normalise(left) === normalise(right);
|
|
426
463
|
}
|
|
427
464
|
/**
|
|
428
465
|
* `tsconfig.json`, extending the workspace's base config when there is one.
|
|
@@ -439,11 +476,11 @@ function tsconfigJson(composition) {
|
|
|
439
476
|
? TSCONFIG_COMPILER_OPTIONS
|
|
440
477
|
: [
|
|
441
478
|
...TSCONFIG_COMPILER_OPTIONS.filter(([key]) => PACKAGE_LOCAL_OPTIONS.includes(key)),
|
|
442
|
-
...TSCONFIG_COMPILER_OPTIONS.filter(([key, value]) => !PACKAGE_LOCAL_OPTIONS.includes(key) && !
|
|
479
|
+
...TSCONFIG_COMPILER_OPTIONS.filter(([key, value]) => !PACKAGE_LOCAL_OPTIONS.includes(key) && !optionEquals(key, base[key], value))
|
|
443
480
|
];
|
|
444
481
|
const extendsLine = workspace?.baseTsconfig === undefined ? '' : ` "extends": "${workspace.baseTsconfig}",\n`;
|
|
445
482
|
const body = options.map(([key, value]) => ` "${key}": ${JSON.stringify(value)}`).join(',\n');
|
|
446
|
-
return `{\n${extendsLine} "compilerOptions": {\n${body}\n },\n "include": ["src"]\n}\n`;
|
|
483
|
+
return `{\n${extendsLine} "compilerOptions": {\n${body}\n },\n "include": ["src", "src/**/*.json"]\n}\n`;
|
|
447
484
|
}
|
|
448
485
|
// `isolatedModules` is what makes this check agree with the transform that
|
|
449
486
|
// actually runs the tests. `npm test` is `typecheck:test && vitest run`, and
|
|
@@ -460,7 +497,7 @@ const TSCONFIG_TEST = `{
|
|
|
460
497
|
"isolatedModules": true,
|
|
461
498
|
"types": ["node"]
|
|
462
499
|
},
|
|
463
|
-
"include": ["src", "test"]
|
|
500
|
+
"include": ["src", "test", "src/**/*.json"]
|
|
464
501
|
}
|
|
465
502
|
`;
|
|
466
503
|
const VITEST_CONFIG = `import { defineConfig } from 'vitest/config';
|
|
@@ -478,6 +515,11 @@ export default defineConfig({
|
|
|
478
515
|
}
|
|
479
516
|
});
|
|
480
517
|
`;
|
|
518
|
+
/**
|
|
519
|
+
* The `.gitignore`, written for a workspace member too: its root may not ignore
|
|
520
|
+
* `lib/` (a `yo langium` root ignores `out/`), and a rule the root repeats
|
|
521
|
+
* costs nothing.
|
|
522
|
+
*/
|
|
481
523
|
const GITIGNORE = `node_modules/
|
|
482
524
|
lib/
|
|
483
525
|
*.tsbuildinfo
|
|
@@ -485,26 +527,6 @@ lib/
|
|
|
485
527
|
# Langium-generated TextMate grammar (rewritten by every \`langium generate\`).
|
|
486
528
|
syntaxes/
|
|
487
529
|
`;
|
|
488
|
-
/**
|
|
489
|
-
* The workspace-member `.gitignore` — one entry, and the one entry a root
|
|
490
|
-
* cannot be assumed to have.
|
|
491
|
-
*
|
|
492
|
-
* A member inherits the root's rules, and `node_modules/`, `lib/` and
|
|
493
|
-
* `*.tsbuildinfo` are in every monorepo root already, so repeating them here
|
|
494
|
-
* would be three lines to keep in step for no coverage. `syntaxes/` is not like
|
|
495
|
-
* them: it is a Langium artefact, so a root that has never held a Langium
|
|
496
|
-
* package has no rule for it, and the first `langium generate` then offers
|
|
497
|
-
* generated output up for commit with nothing to warn the adopter. Dropping the
|
|
498
|
-
* whole file rather than this subset is the mistake this template exists to
|
|
499
|
-
* undo.
|
|
500
|
-
*/
|
|
501
|
-
const GITIGNORE_WORKSPACE_MEMBER = `# The workspace root already covers \`node_modules/\`, \`lib/\` and \`*.tsbuildinfo\`.
|
|
502
|
-
# It has no reason to know about this one: the TextMate grammar is a Langium
|
|
503
|
-
# artefact, rewritten by every \`langium generate\`, so a root that has never held
|
|
504
|
-
# a Langium package ignores nothing here and the generated file is offered for
|
|
505
|
-
# commit.
|
|
506
|
-
syntaxes/
|
|
507
|
-
`;
|
|
508
530
|
/** The terminals every grammar needs, shared verbatim by the fragment and the lone-grammar case. */
|
|
509
531
|
const TERMINALS = `hidden terminal WS: /\\s+/;
|
|
510
532
|
terminal ID: /[_a-zA-Z][\\w_]*/;
|
|
@@ -644,13 +666,35 @@ export type __NAME__Services = LangiumServices &
|
|
|
644
666
|
LspServerAddedServices & {
|
|
645
667
|
shared: __NAME__SharedServices;
|
|
646
668
|
};
|
|
669
|
+
|
|
670
|
+
/** What a host or a test may vary about this composition. */
|
|
671
|
+
export interface __NAME__Options {
|
|
672
|
+
/**
|
|
673
|
+
* Shared modules layered in after the framework's own bindings.
|
|
674
|
+
*
|
|
675
|
+
* The framework constructs most shared services with no options —
|
|
676
|
+
* \`DocumentBuilder: services => new HydraniumDocumentBuilder(services)\` — so
|
|
677
|
+
* rebinding the slot is the only way to boot one configured differently, and
|
|
678
|
+
* a factory that hard-codes its composition leaves a test nowhere to do it.
|
|
679
|
+
* That is what this is for: pass a module binding \`workspace.DocumentBuilder\`
|
|
680
|
+
* to exercise a builder option, or to substitute a subclass.
|
|
681
|
+
*
|
|
682
|
+
* Composed LAST, after \`__NAME__SharedModule\`, so it wins over every other
|
|
683
|
+
* tier including this file's own bindings — which is what makes it usable for
|
|
684
|
+
* a slot the adopter overrides. Production code should not reach for it.
|
|
685
|
+
*/
|
|
686
|
+
readonly extraSharedModules?: ReadonlyArray<Module<__NAME__SharedServices, DeepPartial<__NAME__SharedServices>>>;
|
|
687
|
+
}
|
|
647
688
|
${configurationRoot}
|
|
648
689
|
${sharedModule}
|
|
649
690
|
|
|
650
691
|
${languageModules}
|
|
651
692
|
|
|
652
693
|
/** Compose the Langium DI tree for __NAME__ — returns the shared + language services. */
|
|
653
|
-
export function create__NAME__Services(
|
|
694
|
+
export function create__NAME__Services(
|
|
695
|
+
context: Partial<ServerModuleContext> = EmptyFileSystem,
|
|
696
|
+
options: __NAME__Options = {}
|
|
697
|
+
): {
|
|
654
698
|
shared: __NAME__SharedServices;
|
|
655
699
|
${returnType}
|
|
656
700
|
} {
|
|
@@ -660,7 +704,8 @@ ${returnType}
|
|
|
660
704
|
sharedModules: {
|
|
661
705
|
generated: __NAME__GeneratedSharedModule,
|
|
662
706
|
adopter: __NAME__SharedModule,
|
|
663
|
-
extra: [createLspServerSharedModule(fullContext)]
|
|
707
|
+
extra: [createLspServerSharedModule(fullContext)],
|
|
708
|
+
overrides: options.extraSharedModules
|
|
664
709
|
},
|
|
665
710
|
languageModules: {
|
|
666
711
|
generated: ${primary.grammar}GeneratedModule,
|
|
@@ -724,9 +769,9 @@ const SERIALIZER = `// Concrete-syntax emitter for __GRAMMAR__ — the parser's
|
|
|
724
769
|
//
|
|
725
770
|
// Without it every structured write fails: the framework's default binding at
|
|
726
771
|
// \`services.serializer.Serializer\` THROWS, because a concrete syntax is
|
|
727
|
-
// language knowledge no framework can derive. \`
|
|
728
|
-
// the data head's \`saveModelDocument\` and a GLSP \`SaveModelAction\`
|
|
729
|
-
// it.
|
|
772
|
+
// language knowledge no framework can derive. A client session's \`update\` /
|
|
773
|
+
// \`save\`, the data head's \`saveModelDocument\` and a GLSP \`SaveModelAction\`
|
|
774
|
+
// all reach it.
|
|
730
775
|
//
|
|
731
776
|
// \`AbstractSerializer\`'s generic property walk lays out FORMAT-structured
|
|
732
777
|
// output — its YAML and JSON subclasses are what it exists for — and cannot
|
|
@@ -902,7 +947,7 @@ import { startGlspServer } from '@hydranium/glsp-server/node';
|
|
|
902
947
|
// \`TransferElement\` structurally, so naming the AST type here compiles fine and
|
|
903
948
|
// silently tells every typed client that a reference is a resolvable object
|
|
904
949
|
// rather than a name.
|
|
905
|
-
${importList(roots, './language-server/generated-
|
|
950
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
906
951
|
`
|
|
907
952
|
: '';
|
|
908
953
|
// The commands live in `head-ports.ts`, not here: a host shell has to name
|
|
@@ -979,11 +1024,17 @@ void glspServer;
|
|
|
979
1024
|
// than a library entry — import \`./index.js\` instead to compose the language.
|
|
980
1025
|
|
|
981
1026
|
${reflectImport}${glspImports}${importList(coreNodeSymbols, '@hydranium/core/node', columns)}
|
|
982
|
-
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}
|
|
1027
|
+
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}// The framework's entry point, NOT Langium's \`@hydranium/langium/lsp\` one. It is
|
|
1028
|
+
// signature-compatible and delegates straight through; what it adds first is
|
|
1029
|
+
// \`assertLspHeadComposed\`, which fails the start if the LSP head's SHARED module
|
|
1030
|
+
// was never composed. Reaching for Langium's is the natural mistake and it is
|
|
1031
|
+
// silent: the server boots, links and completes, while echo suppression, the
|
|
1032
|
+
// didChangeContent debounce and the last-client-close rebuild are all inert.
|
|
1033
|
+
import { startLanguageServer, withHydraniumLspFeatures } from '@hydranium/core/lsp';
|
|
983
1034
|
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node';
|
|
984
1035
|
${diagramImports}${transferImports}import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
985
1036
|
${portCommands === '' ? '' : '\n' + portCommands}
|
|
986
|
-
const connection = createConnection(ProposedFeatures.all);
|
|
1037
|
+
const connection = createConnection(withHydraniumLspFeatures(ProposedFeatures.all));
|
|
987
1038
|
const { shared } = create__NAME__Services({ connection, ...NodeFileSystem });
|
|
988
1039
|
startLanguageServer(shared);
|
|
989
1040
|
${dataBlock}${glspBlock}`;
|
|
@@ -1031,7 +1082,7 @@ function dataServerMainFile(composition) {
|
|
|
1031
1082
|
${importList(['NodeFileSystem', 'startStdioServer'], '@hydranium/core/node', columns)}
|
|
1032
1083
|
import { DataServer } from '@hydranium/data-server';
|
|
1033
1084
|
// The TRANSFER root${plural}, not the AST one${plural} — same reasoning as \`main.ts\`.
|
|
1034
|
-
${importList(roots, './language-server/generated-
|
|
1085
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1035
1086
|
import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
1036
1087
|
|
|
1037
1088
|
const { shared } = create__NAME__Services({ ...NodeFileSystem });
|
|
@@ -1147,8 +1198,8 @@ function serializationTest(composition) {
|
|
|
1147
1198
|
const transferTypes = composition.grammars.map(grammar => `${grammar.entryRule} as Transfer${grammar.entryRule}`);
|
|
1148
1199
|
const text = `// Round-trips each grammar through its serializer: parse the source, serialize
|
|
1149
1200
|
// the model, compare the text. The serializer is the parser's inverse, so this
|
|
1150
|
-
// is the assertion that keeps \`
|
|
1151
|
-
// language server can read back.
|
|
1201
|
+
// is the assertion that keeps a client session's \`update\` / \`save\` writing
|
|
1202
|
+
// files the language server can read back.
|
|
1152
1203
|
//
|
|
1153
1204
|
// The transfer case is the one that catches the mistake worth catching.
|
|
1154
1205
|
// \`ModelService.modelToText\` short-circuits only a RAW STRING, so a typed model
|
|
@@ -1158,7 +1209,7 @@ function serializationTest(composition) {
|
|
|
1158
1209
|
import { parseHelper } from '@hydranium/core/testing';
|
|
1159
1210
|
import { describe, expect, it } from 'vitest';
|
|
1160
1211
|
${importList(astTypes, '../src/language-server/ast.js', columns, true)}
|
|
1161
|
-
${importList(transferTypes, '../src/language-server/generated-
|
|
1212
|
+
${importList(transferTypes, '../src/language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1162
1213
|
import { createServices } from '../src/services.js';
|
|
1163
1214
|
|
|
1164
1215
|
${composition.grammars.map(grammar => render(SERIALIZATION_SUITE, composition, grammar)).join('\n\n')}
|
|
@@ -1317,20 +1368,6 @@ function readme(composition) {
|
|
|
1317
1368
|
'',
|
|
1318
1369
|
'## Getting started',
|
|
1319
1370
|
'',
|
|
1320
|
-
// Conditional on the pin this scaffold actually carries — see
|
|
1321
|
-
// `UNPUBLISHED_FRAMEWORK_VERSION`.
|
|
1322
|
-
...(composition.frameworkVersion === UNPUBLISHED_FRAMEWORK_VERSION
|
|
1323
|
-
? [
|
|
1324
|
-
'> **Pre-publish note.** `@hydranium/*` is not on npm yet, so the `0.0.0`',
|
|
1325
|
-
'> pins below are placeholders and `npm install` will fail with a 404 until',
|
|
1326
|
-
'> the framework is released. Until then, supply the packages from a local',
|
|
1327
|
-
'> framework checkout with [yalc](https://github.com/wclr/yalc) — a plain',
|
|
1328
|
-
'> `file:` path or `npm link` is not enough, because the framework packages',
|
|
1329
|
-
'> depend on each other by version and npm would try to fetch those from the',
|
|
1330
|
-
'> registry too.',
|
|
1331
|
-
''
|
|
1332
|
-
]
|
|
1333
|
-
: []),
|
|
1334
1371
|
'```bash',
|
|
1335
1372
|
'npm install',
|
|
1336
1373
|
'npm run langium:generate # generate the AST from the grammar',
|
|
@@ -1438,6 +1475,24 @@ function readme(composition) {
|
|
|
1438
1475
|
'atomic set and depends on a single physical copy, so a floating range can',
|
|
1439
1476
|
'silently resolve a second one.',
|
|
1440
1477
|
'',
|
|
1478
|
+
'`vscode-languageserver` is pinned to `10.0.1`, and',
|
|
1479
|
+
'`vscode-languageserver-protocol` and `vscode-jsonrpc` to the `3.18.1` and',
|
|
1480
|
+
'`9.0.0` it pins, so Langium, the LSP connection and the framework share one',
|
|
1481
|
+
'copy of each. With two, an error a Langium handler returns, such as a request',
|
|
1482
|
+
'for a document the server does not have, reaches the client as a result, and a',
|
|
1483
|
+
'framework error your own LSP handler throws loses its code. The Langium 4.4',
|
|
1484
|
+
'upgrade',
|
|
1485
|
+
'([eclipse-emfcloud/hydranium#142](https://github.com/eclipse-emfcloud/hydranium/issues/142))',
|
|
1486
|
+
'replaces these pins.',
|
|
1487
|
+
'',
|
|
1488
|
+
...(pinsVitest(composition)
|
|
1489
|
+
? [
|
|
1490
|
+
'`vitest` is held below 4.1, whose peer set crashes the npm every Node 22',
|
|
1491
|
+
'release bundles. Raise it once every npm that installs this project is',
|
|
1492
|
+
'11.6 or later.',
|
|
1493
|
+
''
|
|
1494
|
+
]
|
|
1495
|
+
: []),
|
|
1441
1496
|
'## If your repo gates license headers',
|
|
1442
1497
|
'',
|
|
1443
1498
|
'The emitted `.ts` files carry no copyright header — the scaffold cannot know',
|
|
@@ -1524,8 +1579,11 @@ export class __GRAMMAR__GlspState extends FullTextHydraniumGlspState<__ENTRY_RUL
|
|
|
1524
1579
|
`;
|
|
1525
1580
|
const storage = `// Source-model storage for the __GRAMMAR__ diagram, inheriting both framework
|
|
1526
1581
|
// defaults: \`loadSourceModel\` (open + settle + \`setSourceRoot\`) and
|
|
1527
|
-
// \`saveSourceModel\` (
|
|
1528
|
-
//
|
|
1582
|
+
// \`saveSourceModel\` (flush the store's text for the primary and every tracked
|
|
1583
|
+
// secondary through the diagram's client session).
|
|
1584
|
+
//
|
|
1585
|
+
// No serializer runs on the save path — a save persists what the diagram's
|
|
1586
|
+
// operations already wrote to the store.
|
|
1529
1587
|
//
|
|
1530
1588
|
// The subclass exists so a bespoke load or save has a stable place to land.
|
|
1531
1589
|
|
|
@@ -1681,27 +1739,28 @@ export class __GRAMMAR__DiagramConfiguration implements DiagramConfiguration {
|
|
|
1681
1739
|
// read-only is a deletion the compiler checks, while read-only → editable is
|
|
1682
1740
|
// authoring against a seam you have not used yet.
|
|
1683
1741
|
//
|
|
1684
|
-
// **It
|
|
1685
|
-
// \`
|
|
1686
|
-
//
|
|
1687
|
-
//
|
|
1688
|
-
//
|
|
1689
|
-
//
|
|
1690
|
-
//
|
|
1691
|
-
// \`HydraniumGlspRecordingCommand\` over \`state.sourceModel\` instead — the same
|
|
1692
|
-
// binding the diagram's own save action needs.
|
|
1742
|
+
// **It edits the operation's working copy.** A handler runs inside an operation,
|
|
1743
|
+
// where \`sourceRoot\` is a copy of the built root: the recording command's
|
|
1744
|
+
// runnable appends to the copy, and the operation writes the copy's text through
|
|
1745
|
+
// the grammar's \`Serializer\` once, gated on the version the copy was made at,
|
|
1746
|
+
// with undo and redo applying that one change. A command that writes the
|
|
1747
|
+
// document itself instead reads no text off the copy, which has no \`$document\`,
|
|
1748
|
+
// and the operation's own write then overwrites it.
|
|
1693
1749
|
//
|
|
1694
1750
|
// **The drop location is discarded.** \`needsClientLayout\` is \`true\` and the
|
|
1695
1751
|
// starter grammar persists no bounds, so there is nowhere to put a coordinate: the
|
|
1696
1752
|
// node is appended at the end of the document and the client places it.
|
|
1697
1753
|
//
|
|
1698
1754
|
// Like \`types.ts\`, \`gmodel-factory.ts\` and \`diagram-configuration.ts\`, this file
|
|
1699
|
-
// knows the starter grammar's
|
|
1700
|
-
//
|
|
1755
|
+
// knows the starter grammar's shape — the \`nodes\` list and the \`__NODE_RULE__\`
|
|
1756
|
+
// it holds. Replacing the grammar means replacing these four together.
|
|
1701
1757
|
|
|
1702
1758
|
import { type Command, type CreateNodeOperation, JsonCreateNodeOperationHandler, type MaybePromise } from '@eclipse-glsp/server';
|
|
1759
|
+
import { appendChild } from '@hydranium/core';
|
|
1760
|
+
import { HydraniumGlspRecordingCommand } from '@hydranium/glsp-server';
|
|
1703
1761
|
import { findNextUnique } from '@hydranium/protocol';
|
|
1704
1762
|
import { injectable } from 'inversify';
|
|
1763
|
+
import type { __NODE_RULE__ } from '../../language-server/ast.js';
|
|
1705
1764
|
import { ${upper}_NODE_TYPE } from './types.js';
|
|
1706
1765
|
import type { __GRAMMAR__GlspState } from './state.js';
|
|
1707
1766
|
|
|
@@ -1720,34 +1779,14 @@ export class __GRAMMAR__CreateNodeOperationHandler extends JsonCreateNodeOperati
|
|
|
1720
1779
|
if (!this.elementTypeIds.includes(operation.elementTypeId)) {
|
|
1721
1780
|
return undefined;
|
|
1722
1781
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
before,
|
|
1727
|
-
findNextUnique(
|
|
1782
|
+
return new HydraniumGlspRecordingCommand(this.modelState, this.label, () => {
|
|
1783
|
+
const root = this.modelState.sourceRoot;
|
|
1784
|
+
const name = findNextUnique(
|
|
1728
1785
|
NODE_NAME_STEM,
|
|
1729
|
-
|
|
1730
|
-
)
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
// has exactly one field, so there is nothing to merge a concurrent edit into.
|
|
1734
|
-
return {
|
|
1735
|
-
execute: () => state.updateSourceModel({ text: after }),
|
|
1736
|
-
undo: () => state.updateSourceModel({ text: before }),
|
|
1737
|
-
redo: () => state.updateSourceModel({ text: after })
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
/** The text the captured source root was parsed from — the baseline an edit appends to. */
|
|
1742
|
-
protected documentText(): string {
|
|
1743
|
-
return this.modelState.sourceRoot.$document?.textDocument.getText() ?? '';
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
/** \`text\` with one more node declaration, under exactly one trailing newline. */
|
|
1747
|
-
protected withNode(text: string, name: string): string {
|
|
1748
|
-
const body = text.trimEnd();
|
|
1749
|
-
const declaration = \`node \${name}\`;
|
|
1750
|
-
return body.length === 0 ? \`\${declaration}\\n\` : \`\${body}\\n\${declaration}\\n\`;
|
|
1786
|
+
root.nodes.map(node => node.name)
|
|
1787
|
+
);
|
|
1788
|
+
appendChild(root, 'nodes', root.nodes, { $type: '__NODE_RULE__', name } as __NODE_RULE__);
|
|
1789
|
+
});
|
|
1751
1790
|
}
|
|
1752
1791
|
}
|
|
1753
1792
|
`;
|
|
@@ -1833,6 +1872,75 @@ export class __GRAMMAR__DiagramModule extends AbstractHydraniumGlspDiagramModule
|
|
|
1833
1872
|
binding.add(__GRAMMAR__CreateNodeOperationHandler);
|
|
1834
1873
|
}
|
|
1835
1874
|
}
|
|
1875
|
+
`;
|
|
1876
|
+
const diagramTest = `// The starter create-node handler, run the way a client runs it: an in-process
|
|
1877
|
+
// GLSP server opens a document, a create-node operation adds a node, and an undo
|
|
1878
|
+
// and a redo take it out and put it back. Every step must keep the nodes the
|
|
1879
|
+
// document already had, and its comments: the diagram writes the document from
|
|
1880
|
+
// its AST, which carries none.
|
|
1881
|
+
|
|
1882
|
+
import 'reflect-metadata';
|
|
1883
|
+
import { type Action, CreateNodeOperation, RedoAction, ServerModule, SetDirtyStateAction, UndoAction } from '@eclipse-glsp/server';
|
|
1884
|
+
import { initializeWorkspaceProgrammatically } from '@hydranium/core';
|
|
1885
|
+
import { makeScratchWorkspace, type ScratchWorkspace } from '@hydranium/core/testing/node';
|
|
1886
|
+
import { HydraniumGlspAppModule } from '@hydranium/glsp-server';
|
|
1887
|
+
import { type GlspHarness, makeGlspHarness } from '@hydranium/glsp-server/testing';
|
|
1888
|
+
import { waitFor } from '@hydranium/protocol/testing';
|
|
1889
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
1890
|
+
import { __GRAMMAR__DiagramModule } from '../src/glsp/__GRAMMAR_ID__/diagram-module.js';
|
|
1891
|
+
import type { __GRAMMAR__GlspState } from '../src/glsp/__GRAMMAR_ID__/state.js';
|
|
1892
|
+
import { ${upper}_DIAGRAM_TYPE, ${upper}_NODE_TYPE } from '../src/glsp/__GRAMMAR_ID__/types.js';
|
|
1893
|
+
import { createServices } from '../src/services.js';
|
|
1894
|
+
|
|
1895
|
+
let workspace: ScratchWorkspace | undefined;
|
|
1896
|
+
let diagram: GlspHarness<__GRAMMAR__GlspState> | undefined;
|
|
1897
|
+
|
|
1898
|
+
afterEach(() => {
|
|
1899
|
+
diagram?.dispose();
|
|
1900
|
+
workspace?.dispose();
|
|
1901
|
+
diagram = undefined;
|
|
1902
|
+
workspace = undefined;
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1905
|
+
/** Send \`action\` and wait for the dirty state the server answers it with, which it sends once the edit is written. */
|
|
1906
|
+
async function send(harness: GlspHarness<__GRAMMAR__GlspState>, action: Action, reason: string): Promise<void> {
|
|
1907
|
+
const before = harness.actions.length;
|
|
1908
|
+
harness.dispatch(action);
|
|
1909
|
+
await waitFor(() => harness.actions.slice(before).some(sent => SetDirtyStateAction.is(sent) && sent.reason === reason), {
|
|
1910
|
+
message: \`no '\${reason}' dirty state\`
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
describe('__GRAMMAR__ diagram', () => {
|
|
1915
|
+
it('creates a node after the existing ones, keeping their comments, and undoes and redoes it', async () => {
|
|
1916
|
+
workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
|
|
1917
|
+
const original = '// keep this comment\\nnode first -> second\\n// about second\\nnode second';
|
|
1918
|
+
const file = workspace.write('model.__EXTENSION__', original);
|
|
1919
|
+
const { shared } = createServices();
|
|
1920
|
+
await initializeWorkspaceProgrammatically(shared, workspace.root);
|
|
1921
|
+
diagram = makeGlspHarness<__GRAMMAR__GlspState>({
|
|
1922
|
+
serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
|
|
1923
|
+
diagramType: ${upper}_DIAGRAM_TYPE,
|
|
1924
|
+
appModules: [new HydraniumGlspAppModule({ shared })]
|
|
1925
|
+
});
|
|
1926
|
+
await diagram.start();
|
|
1927
|
+
await diagram.openDocument(file);
|
|
1928
|
+
const uri = workspace.uri('model.__EXTENSION__');
|
|
1929
|
+
const text = (): string | undefined => shared.workspace.TextDocuments.get(uri)?.getText();
|
|
1930
|
+
|
|
1931
|
+
await send(diagram, CreateNodeOperation.create(${upper}_NODE_TYPE), 'operation');
|
|
1932
|
+
const created = text();
|
|
1933
|
+
await send(diagram, UndoAction.create(), 'undo');
|
|
1934
|
+
const undone = text();
|
|
1935
|
+
await send(diagram, RedoAction.create(), 'redo');
|
|
1936
|
+
|
|
1937
|
+
expect({ created, undone, redone: text() }).toEqual({
|
|
1938
|
+
created: \`\${original}\\nnode Node\`,
|
|
1939
|
+
undone: original,
|
|
1940
|
+
redone: \`\${original}\\nnode Node\`
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1943
|
+
});
|
|
1836
1944
|
`;
|
|
1837
1945
|
return [
|
|
1838
1946
|
{ path: `${dir}/types.ts`, content: render(types) },
|
|
@@ -1842,7 +1950,8 @@ export class __GRAMMAR__DiagramModule extends AbstractHydraniumGlspDiagramModule
|
|
|
1842
1950
|
{ path: `${dir}/gmodel-factory.ts`, content: render(factory) },
|
|
1843
1951
|
{ path: `${dir}/diagram-configuration.ts`, content: render(configuration) },
|
|
1844
1952
|
{ path: `${dir}/create-node-operation-handler.ts`, content: render(createHandler) },
|
|
1845
|
-
{ path: `${dir}/diagram-module.ts`, content: render(module) }
|
|
1953
|
+
{ path: `${dir}/diagram-module.ts`, content: render(module) },
|
|
1954
|
+
{ path: `test/${grammar.grammarId}-diagram.test.ts`, content: render(diagramTest) }
|
|
1846
1955
|
];
|
|
1847
1956
|
}
|
|
1848
1957
|
/**
|
|
@@ -1868,10 +1977,7 @@ export function buildInitTemplates(composition) {
|
|
|
1868
1977
|
{ path: 'tsconfig.json', content: tsconfigJson(composition) },
|
|
1869
1978
|
{ path: 'tsconfig.test.json', content: TSCONFIG_TEST },
|
|
1870
1979
|
{ path: 'vitest.config.ts', content: VITEST_CONFIG },
|
|
1871
|
-
{
|
|
1872
|
-
path: '.gitignore',
|
|
1873
|
-
content: composition.packaging.workspace === undefined ? GITIGNORE : GITIGNORE_WORKSPACE_MEMBER
|
|
1874
|
-
},
|
|
1980
|
+
{ path: '.gitignore', content: GITIGNORE },
|
|
1875
1981
|
{ path: 'README.md', content: readme(composition) },
|
|
1876
1982
|
...grammarFiles,
|
|
1877
1983
|
{ path: `src/language-server/${composition.projectId}-module.ts`, content: moduleFile(composition) },
|