@hydranium/cli 1.0.0-next.39 → 1.0.0-next.390
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 +102 -204
- package/bin/hydranium-cli.js +13 -0
- package/heap-analysis/heap/core/load.mjs +2 -2
- package/heap-analysis/heap/langium/langium.classifier.mjs +22 -11
- package/lib/cli-version.js.map +1 -1
- package/lib/cli.js +7 -2
- package/lib/cli.js.map +1 -1
- package/lib/commands/analyze-heap-args.d.ts.map +1 -1
- package/lib/commands/analyze-heap-args.js +1 -2
- 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-driver.js +4 -2
- package/lib/commands/ast-ground-truth-driver.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/client-session.d.ts +17 -0
- package/lib/commands/client-session.d.ts.map +1 -0
- package/lib/commands/client-session.js +27 -0
- package/lib/commands/client-session.js.map +1 -0
- package/lib/commands/create-args.d.ts +17 -0
- package/lib/commands/create-args.d.ts.map +1 -0
- package/lib/commands/create-args.js +53 -0
- package/lib/commands/create-args.js.map +1 -0
- package/lib/commands/create.d.ts +38 -0
- package/lib/commands/create.d.ts.map +1 -0
- package/lib/commands/create.js +43 -0
- package/lib/commands/create.js.map +1 -0
- package/lib/commands/delete-args.d.ts +17 -0
- package/lib/commands/delete-args.d.ts.map +1 -0
- package/lib/commands/delete-args.js +49 -0
- package/lib/commands/delete-args.js.map +1 -0
- package/lib/commands/delete.d.ts +30 -0
- package/lib/commands/delete.d.ts.map +1 -0
- package/lib/commands/delete.js +32 -0
- package/lib/commands/delete.js.map +1 -0
- package/lib/commands/generate-ast-builder.d.ts.map +1 -1
- package/lib/commands/generate-ast-builder.js +1 -10
- package/lib/commands/generate-ast-builder.js.map +1 -1
- package/lib/commands/generate-languages.d.ts +35 -0
- package/lib/commands/generate-languages.d.ts.map +1 -0
- package/lib/commands/generate-languages.js +105 -0
- package/lib/commands/generate-languages.js.map +1 -0
- package/lib/commands/generate-transfer-builder.d.ts +33 -0
- package/lib/commands/generate-transfer-builder.d.ts.map +1 -0
- package/lib/commands/generate-transfer-builder.js +86 -0
- package/lib/commands/generate-transfer-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 +20 -5
- 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 +8 -3
- package/lib/commands/generate-transfer-model-config.js.map +1 -1
- package/lib/commands/generate-transfer-model.d.ts +40 -9
- package/lib/commands/generate-transfer-model.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model.js +219 -67
- package/lib/commands/generate-transfer-model.js.map +1 -1
- package/lib/commands/generated-module-path.d.ts +16 -0
- package/lib/commands/generated-module-path.d.ts.map +1 -0
- package/lib/commands/generated-module-path.js +21 -0
- package/lib/commands/generated-module-path.js.map +1 -0
- package/lib/commands/harness-args.d.ts +44 -42
- package/lib/commands/harness-args.d.ts.map +1 -1
- package/lib/commands/harness-args.js +109 -27
- 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 +71 -19
- package/lib/commands/headless-harness.js.map +1 -1
- package/lib/commands/init-args.d.ts +2 -0
- package/lib/commands/init-args.d.ts.map +1 -1
- package/lib/commands/init-args.js +74 -31
- package/lib/commands/init-args.js.map +1 -1
- package/lib/commands/init-hosts.d.ts +24 -0
- package/lib/commands/init-hosts.d.ts.map +1 -0
- package/lib/commands/init-hosts.js +37 -0
- package/lib/commands/init-hosts.js.map +1 -0
- package/lib/commands/init-prompt.d.ts +3 -3
- package/lib/commands/init-prompt.d.ts.map +1 -1
- package/lib/commands/init-prompt.js +4 -5
- package/lib/commands/init-prompt.js.map +1 -1
- package/lib/commands/init-templates.d.ts +26 -14
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +2989 -746
- package/lib/commands/init-templates.js.map +1 -1
- package/lib/commands/init-wizard.d.ts +3 -0
- package/lib/commands/init-wizard.d.ts.map +1 -1
- package/lib/commands/init-wizard.js +46 -31
- package/lib/commands/init-wizard.js.map +1 -1
- package/lib/commands/init-workspace.d.ts +13 -11
- package/lib/commands/init-workspace.d.ts.map +1 -1
- package/lib/commands/init-workspace.js +21 -7
- package/lib/commands/init-workspace.js.map +1 -1
- package/lib/commands/init.d.ts +58 -22
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +206 -48
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/kebab.d.ts +15 -0
- package/lib/commands/kebab.d.ts.map +1 -0
- package/lib/commands/kebab.js +17 -0
- package/lib/commands/kebab.js.map +1 -0
- 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-driver.js +2 -1
- package/lib/commands/lint-grammar-driver.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 +22 -11
- package/lib/commands/measure-memory-args.js.map +1 -1
- package/lib/commands/measure-memory-driver.js +5 -3
- 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-driver.js +2 -1
- package/lib/commands/model-docs-driver.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 +6 -5
- package/lib/commands/query-args.js.map +1 -1
- package/lib/commands/query.d.ts +6 -5
- package/lib/commands/query.d.ts.map +1 -1
- package/lib/commands/query.js +5 -4
- 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-driver.js +2 -1
- package/lib/commands/reflect-driver.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 +7 -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 +12 -23
- 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-driver.js +2 -1
- package/lib/commands/validate-driver.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 +5 -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 +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -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 +9 -4
- package/lib/spawn-data-server.d.ts.map +1 -1
- package/lib/spawn-data-server.js +5 -4
- 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 +9 -1
- package/lib/testing/echo-server.js.map +1 -1
- package/package.json +90 -39
- package/src/cli.ts +7 -2
- package/src/commands/analyze-heap-args.ts +1 -2
- package/src/commands/analyze-heap.ts +4 -1
- package/src/commands/ast-ground-truth-args.ts +15 -12
- package/src/commands/ast-ground-truth-driver.ts +4 -2
- package/src/commands/ast-ground-truth.ts +1 -1
- package/src/commands/client-session.ts +33 -0
- package/src/commands/create-args.ts +68 -0
- package/src/commands/create.ts +83 -0
- package/src/commands/delete-args.ts +64 -0
- package/src/commands/delete.ts +61 -0
- package/src/commands/generate-ast-builder.ts +1 -11
- package/src/commands/generate-languages.ts +132 -0
- package/src/commands/generate-transfer-builder.ts +101 -0
- package/src/commands/generate-transfer-model-args.ts +20 -5
- package/src/commands/generate-transfer-model-config.ts +8 -3
- package/src/commands/generate-transfer-model.ts +291 -65
- package/src/commands/generated-module-path.ts +22 -0
- package/src/commands/harness-args.ts +99 -31
- package/src/commands/headless-harness.ts +89 -28
- package/src/commands/init-args.ts +76 -31
- package/src/commands/init-hosts.ts +51 -0
- package/src/commands/init-prompt.ts +5 -6
- package/src/commands/init-templates.ts +3161 -759
- package/src/commands/init-wizard.ts +55 -31
- package/src/commands/init-workspace.ts +31 -16
- package/src/commands/init.ts +266 -63
- package/src/commands/kebab.ts +17 -0
- package/src/commands/lint-grammar-args.ts +17 -13
- package/src/commands/lint-grammar-driver.ts +2 -1
- package/src/commands/lint-grammar.ts +1 -1
- package/src/commands/measure-memory-args.ts +27 -14
- package/src/commands/measure-memory-driver.ts +5 -3
- package/src/commands/measure-memory.ts +1 -1
- package/src/commands/model-docs-args.ts +16 -13
- package/src/commands/model-docs-driver.ts +2 -1
- 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 +6 -4
- package/src/commands/query.ts +9 -9
- package/src/commands/reflect-args.ts +17 -13
- package/src/commands/reflect-driver.ts +2 -1
- package/src/commands/reflect.ts +1 -1
- package/src/commands/save-args.ts +7 -3
- package/src/commands/save.ts +24 -27
- package/src/commands/validate-args.ts +17 -13
- package/src/commands/validate-driver.ts +2 -1
- package/src/commands/validate.ts +1 -1
- package/src/commands/watch-args.ts +6 -3
- package/src/commands/watch.ts +7 -6
- package/src/driver-heap.ts +65 -0
- package/src/index.ts +2 -0
- package/src/log-level.ts +42 -14
- package/src/spawn-data-server.ts +17 -5
- package/src/stderr-connection-logger.ts +18 -0
- package/src/testing/echo-server.ts +11 -2
- package/templates/client/gitignore +3 -0
- package/templates/client/src/__GRAMMAR_ID__-diagram-module.ts +82 -0
- package/templates/editorconfig +15 -0
- package/templates/gitignore +1 -0
- package/templates/host-browser/esbuild.mjs +82 -0
- package/templates/host-browser/gitignore +4 -0
- package/templates/host-browser/hydranium-mark.svg +40 -0
- package/templates/host-browser/index.html +287 -0
- package/templates/host-browser/playwright.config.mts +24 -0
- package/templates/host-browser/src/data-status.ts +15 -0
- package/templates/host-browser/src/diagram-save.ts +60 -0
- package/templates/host-browser/src/diagram.ts +121 -0
- package/templates/host-browser/src/editor.ts +102 -0
- package/templates/host-browser/src/log.ts +14 -0
- package/templates/host-browser/src/monaco-editor-core-worker.d.ts +7 -0
- package/templates/host-browser/src/monaco-editor-worker.ts +10 -0
- package/templates/host-browser/src/posix-path-shim.ts +23 -0
- package/templates/host-browser/src/shown-file.ts +61 -0
- package/templates/host-browser/src/workspace.ts +5 -0
- package/templates/host-browser/tsconfig.e2e.json +16 -0
- package/templates/host-browser/tsconfig.page.json +16 -0
- package/templates/host-browser/tsconfig.worker.json +16 -0
- package/templates/host-theia-browser/esbuild.mjs +24 -0
- package/templates/host-theia-browser/gitignore +9 -0
- package/templates/host-theia-browser/playwright.config.mts +42 -0
- package/templates/host-theia-browser/start.mjs +50 -0
- package/templates/host-theia-browser/tsconfig.e2e.json +16 -0
- package/templates/host-theia-electron/esbuild.mjs +27 -0
- package/templates/host-theia-electron/gitignore +8 -0
- package/templates/host-theia-electron/playwright.config.mts +12 -0
- package/templates/host-theia-electron/start.mjs +47 -0
- package/templates/host-theia-electron/tsconfig.e2e.json +16 -0
- package/templates/host-vscode/esbuild.mjs +38 -0
- package/templates/host-vscode/gitignore +7 -0
- package/templates/host-vscode/playwright.config.mts +14 -0
- package/templates/host-vscode/src/__GRAMMAR_ID__-diagram-editor.ts +19 -0
- package/templates/host-vscode/src/show-model-command.ts +24 -0
- package/templates/host-vscode/src/webview/__GRAMMAR_ID__-diagram.ts +18 -0
- package/templates/host-vscode/src/webview/diagram.css +40 -0
- package/templates/host-vscode/start.mjs +22 -0
- package/templates/host-vscode/test/install-vscode.mts +21 -0
- package/templates/host-vscode/test/vscode-version.mts +10 -0
- package/templates/host-vscode/tsconfig.e2e.json +17 -0
- package/templates/host-vscode/tsconfig.json +19 -0
- package/templates/host-vscode/tsconfig.webview.json +17 -0
- package/templates/oxlintrc.json +4 -0
- package/templates/protocol/gitignore +3 -0
- package/templates/protocol/src/__GRAMMAR_ID__-diagram-types.ts +47 -0
- package/templates/server/gitignore +6 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/create-node-operation-handler.ts +62 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/delete-operation-handler.ts +63 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/gmodel-factory.ts +144 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/state.ts +18 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/storage.ts +16 -0
- package/templates/server/src/glsp/__GRAMMAR_ID__/submission-handler.ts +24 -0
- package/templates/server/src/grammar/common.langium +12 -0
- package/templates/server/src/language-server/__GRAMMAR_ID__-serializer.ts +53 -0
- package/templates/server/src/language-server/__GRAMMAR_ID__-validation.ts +30 -0
- package/templates/server/src/language-server/ast.ts +23 -0
- package/templates/server/src/node/services.ts +16 -0
- package/templates/server/test/__GRAMMAR_ID__-diagram.test.ts +403 -0
- package/templates/server/test/tsconfig.json +4 -0
- package/templates/server/tsconfig.test.json +10 -0
- package/templates/server/vitest.config.ts +14 -0
- package/templates/theia/gitignore +3 -0
- package/templates/theia/src/browser/__GRAMMAR_ID__-diagram-frontend-module.ts +65 -0
- package/templates/theia/src/browser/__PROJECT_ID__-connection-preload-module.ts +15 -0
- package/templates/theia/src/browser/__PROJECT_ID__-data-connection.ts +15 -0
- package/templates/theia/src/browser/__PROJECT_ID__-data-port.ts +11 -0
- package/templates/theia/src/browser/__PROJECT_ID__-glsp-client-contribution.ts +13 -0
- package/templates/theia/src/browser/__PROJECT_ID__-show-model-command.ts +51 -0
- package/templates/theia/src/common/__PROJECT_ID__-glsp-contribution.ts +3 -0
- package/templates/theia/src/node/__PROJECT_ID__-connection-backend-module.ts +12 -0
- package/templates/theia/src/node/__PROJECT_ID__-data-backend-module.ts +15 -0
- package/templates/theia/src/node/__PROJECT_ID__-glsp-backend-module.ts +16 -0
- package/templates/theia/style/diagram.css +20 -0
- package/templates/theia/tsconfig.json +24 -0
- package/templates/vscode-servers/gitignore +4 -0
- package/templates/vscode-servers/tsconfig.json +19 -0
- package/templates/workspace/nodes.__EXTENSION__ +5 -0
- package/templates/workspace/references.__EXTENSION__ +2 -0
|
@@ -10,15 +10,19 @@
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { emitAstBuilder } from './generate-ast-builder.js';
|
|
13
|
+
import { emitLanguages } from './generate-languages.js';
|
|
14
|
+
import { emitTransferBuilder } from './generate-transfer-builder.js';
|
|
13
15
|
import {
|
|
14
16
|
type Node,
|
|
15
17
|
type ObjectLiteralExpression,
|
|
16
18
|
Project,
|
|
17
19
|
SyntaxKind,
|
|
20
|
+
ts,
|
|
18
21
|
type ClassDeclaration,
|
|
19
22
|
type InterfaceDeclaration,
|
|
20
23
|
type SourceFile
|
|
21
24
|
} from 'ts-morph';
|
|
25
|
+
import { describeError } from '@hydranium/protocol';
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Options for the transfer-model generator.
|
|
@@ -45,6 +49,28 @@ export interface GenerateTransferModelOptions {
|
|
|
45
49
|
* and not the other has the two disagreeing about the same grammar.
|
|
46
50
|
*/
|
|
47
51
|
astBuilderFile?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Destination for a generated test module binding `makeTransferNodeBuilder`
|
|
54
|
+
* from `@hydranium/protocol/testing` to the transfer model, as `transferNode`,
|
|
55
|
+
* the way {@link astBuilderFile} binds `astNode`. Belongs in a test tree:
|
|
56
|
+
* production code has no use for a builder that fills grammar defaults.
|
|
57
|
+
*
|
|
58
|
+
* When it sits in another package than {@link outFile}, it imports the model
|
|
59
|
+
* by that package's name, so the package's main entry must re-export the
|
|
60
|
+
* generated model, and with it each type's constant: an interface the entry
|
|
61
|
+
* narrows hides the generated constant of the same name unless the entry
|
|
62
|
+
* re-exports that constant too, and the builder keeps the generated
|
|
63
|
+
* interface unless the entry declares `<LanguageId>TransferTypes` with the
|
|
64
|
+
* narrowed entry.
|
|
65
|
+
*/
|
|
66
|
+
transferBuilderFile?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Destination for a generated module stating each grammar's language id and
|
|
69
|
+
* file extensions, read from the `module.ts` beside {@link astFile}. Lets a
|
|
70
|
+
* package without Langium, such as one shared with clients, name the ids
|
|
71
|
+
* without copying them by hand.
|
|
72
|
+
*/
|
|
73
|
+
languagesFile?: string;
|
|
48
74
|
/** Name used for the base element type in the output. Defaults to `TransferElement`. */
|
|
49
75
|
elementTypeName?: string;
|
|
50
76
|
/** Name used for the terminal-patterns const in the output. Defaults to `ModelTerminals`. */
|
|
@@ -86,6 +112,15 @@ interface AugmentedProperty {
|
|
|
86
112
|
|
|
87
113
|
interface ReflectionEntry {
|
|
88
114
|
superTypes: string[];
|
|
115
|
+
properties: ReflectionProperty[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One grammar property of a reflection entry; `defaultValue` keeps the source text of the literal. */
|
|
119
|
+
interface ReflectionProperty {
|
|
120
|
+
name: string;
|
|
121
|
+
defaultValue?: string;
|
|
122
|
+
optional: boolean;
|
|
123
|
+
referenceType?: string;
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
const DEFAULT_ELEMENT_TYPE_NAME = 'TransferElement';
|
|
@@ -98,38 +133,67 @@ const DEFAULT_REGEN_COMMAND = 'Run the hydranium-cli generate-transfer-model com
|
|
|
98
133
|
*/
|
|
99
134
|
const NAMESPACED_TERMINALS_NAME = 'Terminals';
|
|
100
135
|
|
|
136
|
+
/** How often `--watch` polls its input files. */
|
|
137
|
+
const WATCH_POLL_INTERVAL_MS = 250;
|
|
138
|
+
|
|
101
139
|
/**
|
|
102
|
-
* Generate once, then re-generate whenever
|
|
140
|
+
* Generate once, then re-generate whenever an input file changes (the AST, the
|
|
141
|
+
* augmentation file, and `module.ts` when a languages file is written),
|
|
103
142
|
* until `signal` aborts (the CLI wires this to SIGINT). Regeneration is debounced
|
|
104
143
|
* so an editor's multi-event save fires a single run, and a generation error is
|
|
105
144
|
* logged without tearing down the watch. Resolves when the signal aborts.
|
|
145
|
+
*
|
|
146
|
+
* Polled by path rather than watched through OS events: langium-cli removes
|
|
147
|
+
* its output directory and writes the files back, which an event watch, Node's
|
|
148
|
+
* and chokidar's alike, stops hearing after the first run. A run that finds an
|
|
149
|
+
* input missing is skipped, since the file's return is a change of its own, so
|
|
150
|
+
* nothing generates from a half-written output.
|
|
106
151
|
*/
|
|
107
152
|
export function watchTransferModel(options: GenerateTransferModelOptions, context: { signal: AbortSignal }): Promise<void> {
|
|
108
153
|
const runSafely = (): void => {
|
|
109
154
|
try {
|
|
110
155
|
generateTransferModel(options);
|
|
111
156
|
} catch (error: unknown) {
|
|
112
|
-
console.error(`Transfer-model generation failed: ${
|
|
157
|
+
console.error(`Transfer-model generation failed: ${describeError(error)}`);
|
|
113
158
|
}
|
|
114
159
|
};
|
|
115
160
|
runSafely();
|
|
116
|
-
|
|
161
|
+
// A grammar's language id and extensions reach only `module.ts`, so the
|
|
162
|
+
// languages module would go stale without it.
|
|
163
|
+
const watched = [
|
|
164
|
+
options.astFile,
|
|
165
|
+
options.augmentationFile,
|
|
166
|
+
...(options.languagesFile !== undefined ? [languagesModuleFile(options)] : [])
|
|
167
|
+
];
|
|
117
168
|
|
|
118
169
|
let timer: NodeJS.Timeout | undefined;
|
|
119
|
-
const
|
|
170
|
+
const rerun = (): void => {
|
|
171
|
+
if (watched.every(file => fs.existsSync(file))) {
|
|
172
|
+
runSafely();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
// The poll reports every stat it takes; a replaced file has a new inode, a
|
|
176
|
+
// rewritten one a new mtime, a removed or returning one zeroed stats.
|
|
177
|
+
const onPoll = (current: fs.Stats, previous: fs.Stats): void => {
|
|
178
|
+
if (current.ino === previous.ino && current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
120
181
|
if (timer) {
|
|
121
182
|
clearTimeout(timer);
|
|
122
183
|
}
|
|
123
|
-
timer = setTimeout(
|
|
184
|
+
timer = setTimeout(rerun, 150);
|
|
124
185
|
};
|
|
125
|
-
const
|
|
186
|
+
for (const file of watched) {
|
|
187
|
+
fs.watchFile(file, { interval: WATCH_POLL_INTERVAL_MS }, onPoll);
|
|
188
|
+
}
|
|
189
|
+
console.log(`Watching ${watched.join(', ')} for changes. Press Ctrl-C to stop.`);
|
|
126
190
|
|
|
127
191
|
return new Promise<void>(resolve => {
|
|
128
192
|
context.signal.addEventListener(
|
|
129
193
|
'abort',
|
|
130
194
|
() => {
|
|
131
|
-
for (const
|
|
132
|
-
|
|
195
|
+
for (const file of watched) {
|
|
196
|
+
fs.unwatchFile(file, onPoll);
|
|
133
197
|
}
|
|
134
198
|
if (timer) {
|
|
135
199
|
clearTimeout(timer);
|
|
@@ -141,6 +205,11 @@ export function watchTransferModel(options: GenerateTransferModelOptions, contex
|
|
|
141
205
|
});
|
|
142
206
|
}
|
|
143
207
|
|
|
208
|
+
/** The langium-cli `module.ts` beside the AST file, which the languages module is read from. */
|
|
209
|
+
function languagesModuleFile(options: GenerateTransferModelOptions): string {
|
|
210
|
+
return path.join(path.dirname(options.astFile), 'module.ts');
|
|
211
|
+
}
|
|
212
|
+
|
|
144
213
|
/** Run the transfer-model generator. */
|
|
145
214
|
export function generateTransferModel(options: GenerateTransferModelOptions): void {
|
|
146
215
|
const elementTypeName = options.elementTypeName ?? DEFAULT_ELEMENT_TYPE_NAME;
|
|
@@ -178,15 +247,19 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
178
247
|
astInterfaces.set(iface.getName(), iface);
|
|
179
248
|
}
|
|
180
249
|
|
|
181
|
-
const typeAliases: Array<{ name: string; definition: string }> = [];
|
|
250
|
+
const typeAliases: Array<{ name: string; definition: string; members: string[] }> = [];
|
|
182
251
|
for (const alias of astSource.getTypeAliases()) {
|
|
183
252
|
const name = alias.getName();
|
|
184
253
|
if (skipTypeAliases.has(name)) {
|
|
185
254
|
continue;
|
|
186
255
|
}
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
typeAliases.push({
|
|
256
|
+
const typeNode = alias.getTypeNode();
|
|
257
|
+
if (typeNode) {
|
|
258
|
+
typeAliases.push({
|
|
259
|
+
name,
|
|
260
|
+
definition: mapType(resolveNamespacedStringUnion(astSource, typeNode.getText()), elementTypeName),
|
|
261
|
+
members: unionMembers(typeNode).map(member => mapType(member, elementTypeName))
|
|
262
|
+
});
|
|
190
263
|
}
|
|
191
264
|
}
|
|
192
265
|
|
|
@@ -203,7 +276,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
203
276
|
// 7. Classify interfaces: abstract (union $type) vs concrete (literal $type).
|
|
204
277
|
const interfaceNames: string[] = [];
|
|
205
278
|
const abstractTypes = new Set<string>();
|
|
206
|
-
const concreteTypes = new Set<string>();
|
|
207
279
|
|
|
208
280
|
for (const name of Object.keys(reflectionTypes)) {
|
|
209
281
|
const iface = astInterfaces.get(name);
|
|
@@ -215,8 +287,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
215
287
|
const typeText = iface.getProperty('$type')?.getTypeNode()?.getText() ?? '';
|
|
216
288
|
if (typeText.includes('|')) {
|
|
217
289
|
abstractTypes.add(name);
|
|
218
|
-
} else {
|
|
219
|
-
concreteTypes.add(name);
|
|
220
290
|
}
|
|
221
291
|
}
|
|
222
292
|
|
|
@@ -258,12 +328,13 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
258
328
|
out.push('}');
|
|
259
329
|
out.push('');
|
|
260
330
|
|
|
261
|
-
//
|
|
331
|
+
// Langium's per-type constant under the same name, for every reflection type
|
|
332
|
+
// the model emits, abstract ones and unions included.
|
|
333
|
+
const reflectedTypes = Object.keys(reflectionTypes).filter(name => astInterfaces.has(name) || typeAliasNames.has(name));
|
|
262
334
|
out.push('// --- Type Constants ---');
|
|
263
|
-
for (const name of
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
335
|
+
for (const name of reflectedTypes) {
|
|
336
|
+
const keys = reflectionTypes[name].properties.map(property => `${property.name}: '${property.name}'`);
|
|
337
|
+
out.push(`export const ${name} = { ${[`$type: '${name}'`, ...keys].join(', ')} } as const;`);
|
|
267
338
|
}
|
|
268
339
|
out.push('');
|
|
269
340
|
|
|
@@ -274,7 +345,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
274
345
|
out.push(`export type ${name} = ${definition};`);
|
|
275
346
|
const literals = extractStringLiterals(definition);
|
|
276
347
|
if (literals) {
|
|
277
|
-
const items = literals.map(
|
|
348
|
+
const items = literals.map(quote).join(', ');
|
|
278
349
|
out.push(`export const ${name}Values = [${items}] as const;`);
|
|
279
350
|
}
|
|
280
351
|
}
|
|
@@ -305,7 +376,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
305
376
|
const typeValue = iface.getProperty('$type')?.getTypeNode()?.getText() ?? `'${name}'`;
|
|
306
377
|
out.push(` readonly $type: ${typeValue};`);
|
|
307
378
|
} else {
|
|
308
|
-
out.push(` readonly $type:
|
|
379
|
+
out.push(` readonly $type: '${name}';`);
|
|
309
380
|
}
|
|
310
381
|
|
|
311
382
|
const emitted = new Set<string>(['$type']);
|
|
@@ -367,24 +438,24 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
367
438
|
const checks = members.map(m => `item.$type === '${m}'`).join(' || ');
|
|
368
439
|
out.push(` return is${elementTypeName}(item) && (${checks});`);
|
|
369
440
|
} else {
|
|
370
|
-
out.push(` return is${elementTypeName}(item) && item.$type === ${name}
|
|
441
|
+
out.push(` return is${elementTypeName}(item) && item.$type === '${name}';`);
|
|
371
442
|
}
|
|
372
443
|
out.push('}');
|
|
373
444
|
out.push('');
|
|
374
445
|
}
|
|
375
446
|
|
|
376
|
-
for (const { name, definition } of typeAliases) {
|
|
447
|
+
for (const { name, definition, members } of typeAliases) {
|
|
377
448
|
const literals = extractStringLiterals(definition);
|
|
378
449
|
if (literals) {
|
|
379
|
-
const checks = literals.map(l => `item ===
|
|
450
|
+
const checks = literals.map(l => `item === ${quote(l)}`).join(' || ') || 'false';
|
|
380
451
|
out.push(`export function is${name}(item: unknown): item is ${name} {`);
|
|
381
452
|
out.push(` return ${checks};`);
|
|
382
453
|
out.push('}');
|
|
383
454
|
out.push('');
|
|
384
455
|
} else {
|
|
385
|
-
// Union (or single) of named members
|
|
386
|
-
//
|
|
387
|
-
|
|
456
|
+
// Union (or single) of named members, Langium data-type primitives and the string
|
|
457
|
+
// literals a data-type rule's keywords add beside them. Each member maps to its own
|
|
458
|
+
// check via typeAliasMemberGuard.
|
|
388
459
|
const checks = members.map(typeAliasMemberGuard).join(' || ');
|
|
389
460
|
out.push(`export function is${name}(item: unknown): item is ${name} {`);
|
|
390
461
|
out.push(` return ${checks};`);
|
|
@@ -393,6 +464,9 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
393
464
|
}
|
|
394
465
|
}
|
|
395
466
|
|
|
467
|
+
out.push(...renderTypeMap(languageId, reflectedTypes));
|
|
468
|
+
out.push(...renderReflection(languageId, elementTypeName, reflectedTypes, reflectionTypes));
|
|
469
|
+
|
|
396
470
|
// Coverage check: warn on reflection types not emitted.
|
|
397
471
|
const processedSet = new Set(sorted);
|
|
398
472
|
for (const name of Object.keys(reflectionTypes)) {
|
|
@@ -422,6 +496,107 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
422
496
|
console.warn(`Warning: no '${languageId}AstType' alias in ${options.astFile}; skipping the AST builder.`);
|
|
423
497
|
}
|
|
424
498
|
}
|
|
499
|
+
|
|
500
|
+
if (options.transferBuilderFile !== undefined) {
|
|
501
|
+
emitTransferBuilder({ outFile: options.transferBuilderFile, transferModelFile: options.outFile, languageId, regenCommand });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (options.languagesFile !== undefined) {
|
|
505
|
+
const moduleSource = project.addSourceFileAtPath(languagesModuleFile(options));
|
|
506
|
+
emitLanguages(moduleSource, { outFile: options.languagesFile, regenCommand });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* `<LanguageId>TransferTypes`, each `$type` to its interface, so an encoder or a
|
|
512
|
+
* builder can type a node by the grammar the way Langium's `<LanguageId>AstType`
|
|
513
|
+
* does on the AST side.
|
|
514
|
+
*/
|
|
515
|
+
function renderTypeMap(languageId: string, types: readonly string[]): string[] {
|
|
516
|
+
return ['// --- Type Map ---', `export type ${languageId}TransferTypes = {`, ...types.map(name => ` ${name}: ${name};`), '};', ''];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* `<LanguageId>TransferReflection`, the grammar metadata with the method names
|
|
521
|
+
* Langium's `AstReflection` uses. A plain object with no framework import, so
|
|
522
|
+
* the file stays self-contained, and nothing references it but its importers:
|
|
523
|
+
* the `is` guards test `$type` directly, so a guard never pulls it in. `types`
|
|
524
|
+
* keeps its literal type, which is how the transfer-node builder tells which
|
|
525
|
+
* fields the grammar defaults.
|
|
526
|
+
*/
|
|
527
|
+
function renderReflection(
|
|
528
|
+
languageId: string,
|
|
529
|
+
elementTypeName: string,
|
|
530
|
+
types: readonly string[],
|
|
531
|
+
reflectionTypes: Record<string, ReflectionEntry>
|
|
532
|
+
): string[] {
|
|
533
|
+
const metaData = `${languageId}TransferTypeMetaData`;
|
|
534
|
+
// Index signatures, not `Readonly<Record<…>>`: a grammar type may be named
|
|
535
|
+
// `Record` or `Readonly`, which Langium does not reserve as it does `Object`.
|
|
536
|
+
const out = [
|
|
537
|
+
'// --- Reflection ---',
|
|
538
|
+
`export interface ${metaData} {`,
|
|
539
|
+
' readonly name: string;',
|
|
540
|
+
' readonly properties: {',
|
|
541
|
+
' readonly [name: string]: { readonly name: string; readonly defaultValue?: unknown; readonly optional?: boolean; readonly referenceType?: string };',
|
|
542
|
+
' };',
|
|
543
|
+
' readonly superTypes: readonly string[];',
|
|
544
|
+
'}',
|
|
545
|
+
'',
|
|
546
|
+
`export const ${languageId}TransferReflection = {`,
|
|
547
|
+
' types: {'
|
|
548
|
+
];
|
|
549
|
+
for (const name of types) {
|
|
550
|
+
const { properties, superTypes } = reflectionTypes[name];
|
|
551
|
+
const propertyEntries = properties.map(property => {
|
|
552
|
+
const fields = [`name: '${property.name}'`];
|
|
553
|
+
if (property.defaultValue !== undefined) {
|
|
554
|
+
fields.push(`defaultValue: ${property.defaultValue}`);
|
|
555
|
+
}
|
|
556
|
+
if (property.optional) {
|
|
557
|
+
fields.push('optional: true');
|
|
558
|
+
}
|
|
559
|
+
if (property.referenceType !== undefined) {
|
|
560
|
+
fields.push(`referenceType: '${property.referenceType}'`);
|
|
561
|
+
}
|
|
562
|
+
return `${property.name}: { ${fields.join(', ')} }`;
|
|
563
|
+
});
|
|
564
|
+
const supers = superTypes.map(superType => `'${superType}'`).join(', ');
|
|
565
|
+
const propertiesText = propertyEntries.length > 0 ? `{ ${propertyEntries.join(', ')} }` : '{}';
|
|
566
|
+
out.push(` ${name}: { name: '${name}', properties: ${propertiesText}, superTypes: [${supers}] },`);
|
|
567
|
+
}
|
|
568
|
+
out.push(
|
|
569
|
+
` } as const satisfies { readonly [type: string]: ${metaData} },`,
|
|
570
|
+
'',
|
|
571
|
+
' getAllTypes(): string[] {',
|
|
572
|
+
' return Object.keys(this.types);',
|
|
573
|
+
' },',
|
|
574
|
+
'',
|
|
575
|
+
// An own-property check, not `types[type] ?? …`: a name the object
|
|
576
|
+
// prototype carries, `toString` say, would read the inherited member and
|
|
577
|
+
// make `isSubtype` throw. `hasOwnProperty.call` rather than `Object.hasOwn`
|
|
578
|
+
// because the adopter's protocol package may target a lib without it.
|
|
579
|
+
` getTypeMetaData(type: string): ${metaData} {`,
|
|
580
|
+
' return Object.prototype.hasOwnProperty.call(this.types, type)',
|
|
581
|
+
` ? (this.types as { readonly [type: string]: ${metaData} })[type]`,
|
|
582
|
+
' : { name: type, properties: {}, superTypes: [] };',
|
|
583
|
+
' },',
|
|
584
|
+
'',
|
|
585
|
+
' isSubtype(subtype: string, supertype: string): boolean {',
|
|
586
|
+
' return subtype === supertype || this.getTypeMetaData(subtype).superTypes.some(type => this.isSubtype(type, supertype));',
|
|
587
|
+
' },',
|
|
588
|
+
'',
|
|
589
|
+
' getAllSubTypes(type: string): string[] {',
|
|
590
|
+
' return this.getAllTypes().filter(candidate => this.isSubtype(candidate, type));',
|
|
591
|
+
' },',
|
|
592
|
+
'',
|
|
593
|
+
' isInstance(item: unknown, type: string): boolean {',
|
|
594
|
+
` return is${elementTypeName}(item) && this.isSubtype(item.$type, type);`,
|
|
595
|
+
' }',
|
|
596
|
+
'};',
|
|
597
|
+
''
|
|
598
|
+
);
|
|
599
|
+
return out;
|
|
425
600
|
}
|
|
426
601
|
|
|
427
602
|
// ---------------------------------------------------------------------------
|
|
@@ -462,10 +637,11 @@ function findAstReflectionClass(source: SourceFile): ClassDeclaration {
|
|
|
462
637
|
}
|
|
463
638
|
|
|
464
639
|
/**
|
|
465
|
-
* Extract `superTypes` for each entry of the
|
|
466
|
-
* initializer. We rely on the Langium
|
|
467
|
-
*
|
|
468
|
-
* `.$type
|
|
640
|
+
* Extract `superTypes` and the grammar properties for each entry of the
|
|
641
|
+
* reflection class's `types` property initializer. We rely on the Langium
|
|
642
|
+
* convention that `X.$type === 'X'` and `X.p === 'p'`, so a type expression
|
|
643
|
+
* resolves to the literal type-name string by stripping `.$type`, and a
|
|
644
|
+
* property's name is its key.
|
|
469
645
|
*/
|
|
470
646
|
function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string, ReflectionEntry> {
|
|
471
647
|
const result: Record<string, ReflectionEntry> = {};
|
|
@@ -487,29 +663,35 @@ function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string,
|
|
|
487
663
|
const value = entry.getLastChildByKind(SyntaxKind.ObjectLiteralExpression);
|
|
488
664
|
const supersProp = value?.getProperty('superTypes');
|
|
489
665
|
const supersInit = supersProp?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
|
|
490
|
-
const superTypes
|
|
491
|
-
|
|
492
|
-
for (const element of supersInit.getElements()) {
|
|
493
|
-
const text = element.getText();
|
|
494
|
-
const dotMatch = /^([A-Za-z_$][\w$]*)\.\$type$/.exec(text);
|
|
495
|
-
if (dotMatch) {
|
|
496
|
-
superTypes.push(dotMatch[1]);
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
// Bare identifier or quoted string fall-back.
|
|
500
|
-
const stringMatch = /^['"](.+)['"]$/.exec(text);
|
|
501
|
-
if (stringMatch) {
|
|
502
|
-
superTypes.push(stringMatch[1]);
|
|
503
|
-
} else {
|
|
504
|
-
superTypes.push(text);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
result[name] = { superTypes };
|
|
666
|
+
const superTypes = supersInit?.getElements().map(element => typeNameOf(element.getText())) ?? [];
|
|
667
|
+
result[name] = { superTypes, properties: parseReflectionProperties(value) };
|
|
509
668
|
}
|
|
510
669
|
return result;
|
|
511
670
|
}
|
|
512
671
|
|
|
672
|
+
function parseReflectionProperties(entry: ObjectLiteralExpression | undefined): ReflectionProperty[] {
|
|
673
|
+
const propertiesInit = entry?.getProperty('properties')?.getFirstChildByKind(SyntaxKind.ObjectLiteralExpression);
|
|
674
|
+
const properties: ReflectionProperty[] = [];
|
|
675
|
+
for (const property of propertiesInit?.getChildrenOfKind(SyntaxKind.PropertyAssignment) ?? []) {
|
|
676
|
+
const meta = property.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
677
|
+
const field = (key: string): string | undefined =>
|
|
678
|
+
meta?.getProperty(key)?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText();
|
|
679
|
+
const referenceType = field('referenceType');
|
|
680
|
+
properties.push({
|
|
681
|
+
name: property.getName(),
|
|
682
|
+
defaultValue: field('defaultValue'),
|
|
683
|
+
optional: field('optional') === 'true',
|
|
684
|
+
referenceType: referenceType === undefined ? undefined : typeNameOf(referenceType)
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return properties;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** `X.$type` or a quoted `'X'` to `X`; any other text passes through. */
|
|
691
|
+
function typeNameOf(text: string): string {
|
|
692
|
+
return /^([A-Za-z_$][\w$]*)\.\$type$/.exec(text)?.[1] ?? /^['"](.+)['"]$/.exec(text)?.[1] ?? text;
|
|
693
|
+
}
|
|
694
|
+
|
|
513
695
|
/**
|
|
514
696
|
* Collect terminal regex literals from the AST file.
|
|
515
697
|
*
|
|
@@ -647,12 +829,16 @@ function mapType(text: string, elementTypeName: string): string {
|
|
|
647
829
|
|
|
648
830
|
/**
|
|
649
831
|
* Type-guard expression (against an `item: unknown`) for one member of a non-literal type-alias
|
|
650
|
-
* union.
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
* `
|
|
832
|
+
* union. A string literal is compared as written, so the escapes Langium wrote stay valid.
|
|
833
|
+
* Covers the full set of Langium data-type primitives. The `typeof`-checkable ones
|
|
834
|
+
* (`string`/`number`/`boolean`/`bigint`) use `typeof`, and `Date`, the one Langium primitive
|
|
835
|
+
* `typeof` reports as `'object'`, uses `instanceof`. Any other member is a named type whose
|
|
836
|
+
* generated `is<Member>` guard is called.
|
|
654
837
|
*/
|
|
655
838
|
export function typeAliasMemberGuard(member: string): string {
|
|
839
|
+
if (member.startsWith("'") || member.startsWith('"')) {
|
|
840
|
+
return `item === ${member}`;
|
|
841
|
+
}
|
|
656
842
|
switch (member) {
|
|
657
843
|
case 'string':
|
|
658
844
|
case 'number':
|
|
@@ -676,10 +862,11 @@ export function typeAliasMemberGuard(member: string): string {
|
|
|
676
862
|
* name.
|
|
677
863
|
*
|
|
678
864
|
* Each referenced member is inlined and de-duplicated, preserving first-seen
|
|
679
|
-
* order
|
|
680
|
-
* namespace-qualified reference (the
|
|
681
|
-
*
|
|
682
|
-
* emitting a
|
|
865
|
+
* order; when no member holds a literal, the union is `never`. The definition
|
|
866
|
+
* is returned UNCHANGED when it holds no namespace-qualified reference (the
|
|
867
|
+
* single-language case) or when any referenced member is not a pure
|
|
868
|
+
* string-literal union — passing it through rather than emitting a
|
|
869
|
+
* half-resolved union.
|
|
683
870
|
*/
|
|
684
871
|
export function resolveNamespacedStringUnion(astSource: SourceFile, definition: string): string {
|
|
685
872
|
const references = [...definition.matchAll(/\b([A-Za-z_]\w*)\.([A-Za-z_]\w*)\b/g)];
|
|
@@ -701,16 +888,55 @@ export function resolveNamespacedStringUnion(astSource: SourceFile, definition:
|
|
|
701
888
|
}
|
|
702
889
|
}
|
|
703
890
|
}
|
|
704
|
-
return literals.map(
|
|
891
|
+
return literals.length > 0 ? literals.map(quote).join(' | ') : 'never';
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* The members of a type-alias union as TypeScript parses them. If a data-type rule groups
|
|
896
|
+
* alternatives in parentheses, the alias Langium generates contains the same group, as in
|
|
897
|
+
* `'a' | ('b' | Keyword)`, so each group is flattened into its members. Splitting the text on
|
|
898
|
+
* `|` breaks such a group and any keyword that contains `|`, such as `'||'`, and the
|
|
899
|
+
* resulting guard does not compile.
|
|
900
|
+
*/
|
|
901
|
+
function unionMembers(typeNode: Node): string[] {
|
|
902
|
+
if (typeNode.isKind(SyntaxKind.UnionType)) {
|
|
903
|
+
return typeNode.getTypeNodes().flatMap(member => unionMembers(member));
|
|
904
|
+
}
|
|
905
|
+
if (typeNode.isKind(SyntaxKind.ParenthesizedType)) {
|
|
906
|
+
return unionMembers(typeNode.getTypeNode());
|
|
907
|
+
}
|
|
908
|
+
return [typeNode.getText()];
|
|
705
909
|
}
|
|
706
910
|
|
|
707
|
-
/**
|
|
911
|
+
/**
|
|
912
|
+
* The values of a union of string literals, or undefined when the definition holds anything
|
|
913
|
+
* else. The TypeScript scanner decodes each literal, so a keyword with a quote in it reads the
|
|
914
|
+
* same whichever quotes and escapes Langium wrote it with. Langium writes the keyword names of
|
|
915
|
+
* a language without keywords as `never`, which adds no value.
|
|
916
|
+
*/
|
|
708
917
|
function extractStringLiterals(definition: string): string[] | undefined {
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
918
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, true, ts.LanguageVariant.Standard, definition);
|
|
919
|
+
const literals: string[] = [];
|
|
920
|
+
let members = 0;
|
|
921
|
+
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
922
|
+
if (token === ts.SyntaxKind.StringLiteral) {
|
|
923
|
+
literals.push(scanner.getTokenValue());
|
|
924
|
+
members++;
|
|
925
|
+
} else if (token === ts.SyntaxKind.NeverKeyword) {
|
|
926
|
+
members++;
|
|
927
|
+
} else if (token !== ts.SyntaxKind.BarToken) {
|
|
928
|
+
return undefined;
|
|
929
|
+
}
|
|
712
930
|
}
|
|
713
|
-
return
|
|
931
|
+
return members > 0 ? literals : undefined;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* `value` as a string literal in the single quotes the generated file uses. A keyword can
|
|
936
|
+
* contain a quote or a backslash, and either one ends or escapes the literal unless escaped.
|
|
937
|
+
*/
|
|
938
|
+
function quote(value: string): string {
|
|
939
|
+
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
|
|
714
940
|
}
|
|
715
941
|
|
|
716
942
|
function topologicalSort(names: string[], getDeps: (n: string) => string[]): string[] {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Module specifier for `targetFile` as seen from the generated module
|
|
14
|
+
* `outFile`'s own directory. Emitted with a `.js` extension and posix
|
|
15
|
+
* separators because the output is ESM TypeScript, where the specifier names
|
|
16
|
+
* the compiled sibling.
|
|
17
|
+
*/
|
|
18
|
+
export function relativeImportPath(outFile: string, targetFile: string): string {
|
|
19
|
+
const relative = path.relative(path.dirname(path.resolve(outFile)), path.resolve(targetFile));
|
|
20
|
+
const posix = relative.split(path.sep).join('/').replace(/\.ts$/, '.js');
|
|
21
|
+
return posix.startsWith('.') ? posix : `./${posix}`;
|
|
22
|
+
}
|