@hydranium/cli 1.0.0-next.4 → 1.0.0-next.400
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 +49 -0
- package/lib/commands/generate-ast-builder.d.ts.map +1 -0
- package/lib/commands/generate-ast-builder.js +191 -0
- package/lib/commands/generate-ast-builder.js.map +1 -0
- 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 +30 -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 +10 -3
- package/lib/commands/generate-transfer-model-config.js.map +1 -1
- package/lib/commands/generate-transfer-model.d.ts +49 -9
- package/lib/commands/generate-transfer-model.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model.js +235 -70
- 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 +20 -14
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +3015 -750
- 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 +203 -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 +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 +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -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 -38
- 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 +236 -0
- 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 +30 -5
- package/src/commands/generate-transfer-model-config.ts +10 -3
- package/src/commands/generate-transfer-model.ts +316 -68
- 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 +3183 -760
- package/src/commands/init-wizard.ts +55 -31
- package/src/commands/init-workspace.ts +31 -16
- package/src/commands/init.ts +261 -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 +28 -7
- package/src/driver-heap.ts +65 -0
- package/src/index.ts +3 -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
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
|
+
import { emitAstBuilder } from './generate-ast-builder.js';
|
|
13
|
+
import { emitLanguages } from './generate-languages.js';
|
|
14
|
+
import { emitTransferBuilder } from './generate-transfer-builder.js';
|
|
12
15
|
import {
|
|
13
16
|
type Node,
|
|
14
17
|
type ObjectLiteralExpression,
|
|
15
18
|
Project,
|
|
16
19
|
SyntaxKind,
|
|
20
|
+
ts,
|
|
17
21
|
type ClassDeclaration,
|
|
18
22
|
type InterfaceDeclaration,
|
|
19
23
|
type SourceFile
|
|
20
24
|
} from 'ts-morph';
|
|
25
|
+
import { describeError } from '@hydranium/protocol';
|
|
21
26
|
|
|
22
27
|
/**
|
|
23
28
|
* Options for the transfer-model generator.
|
|
@@ -35,6 +40,37 @@ export interface GenerateTransferModelOptions {
|
|
|
35
40
|
augmentationFile: string;
|
|
36
41
|
/** Destination path for the generated transfer model. */
|
|
37
42
|
outFile: string;
|
|
43
|
+
/**
|
|
44
|
+
* Destination for a generated AST-node builder module. Omitted by consumers
|
|
45
|
+
* that construct no AST nodes; the transfer model is emitted either way.
|
|
46
|
+
*
|
|
47
|
+
* Emitted from this command rather than its own because the AST source is
|
|
48
|
+
* already parsed here, and because a consumer that regenerates one artefact
|
|
49
|
+
* and not the other has the two disagreeing about the same grammar.
|
|
50
|
+
*/
|
|
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;
|
|
38
74
|
/** Name used for the base element type in the output. Defaults to `TransferElement`. */
|
|
39
75
|
elementTypeName?: string;
|
|
40
76
|
/** Name used for the terminal-patterns const in the output. Defaults to `ModelTerminals`. */
|
|
@@ -76,6 +112,15 @@ interface AugmentedProperty {
|
|
|
76
112
|
|
|
77
113
|
interface ReflectionEntry {
|
|
78
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;
|
|
79
124
|
}
|
|
80
125
|
|
|
81
126
|
const DEFAULT_ELEMENT_TYPE_NAME = 'TransferElement';
|
|
@@ -88,38 +133,67 @@ const DEFAULT_REGEN_COMMAND = 'Run the hydranium-cli generate-transfer-model com
|
|
|
88
133
|
*/
|
|
89
134
|
const NAMESPACED_TERMINALS_NAME = 'Terminals';
|
|
90
135
|
|
|
136
|
+
/** How often `--watch` polls its input files. */
|
|
137
|
+
const WATCH_POLL_INTERVAL_MS = 250;
|
|
138
|
+
|
|
91
139
|
/**
|
|
92
|
-
* 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),
|
|
93
142
|
* until `signal` aborts (the CLI wires this to SIGINT). Regeneration is debounced
|
|
94
143
|
* so an editor's multi-event save fires a single run, and a generation error is
|
|
95
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.
|
|
96
151
|
*/
|
|
97
152
|
export function watchTransferModel(options: GenerateTransferModelOptions, context: { signal: AbortSignal }): Promise<void> {
|
|
98
153
|
const runSafely = (): void => {
|
|
99
154
|
try {
|
|
100
155
|
generateTransferModel(options);
|
|
101
156
|
} catch (error: unknown) {
|
|
102
|
-
console.error(`Transfer-model generation failed: ${
|
|
157
|
+
console.error(`Transfer-model generation failed: ${describeError(error)}`);
|
|
103
158
|
}
|
|
104
159
|
};
|
|
105
160
|
runSafely();
|
|
106
|
-
|
|
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
|
+
];
|
|
107
168
|
|
|
108
169
|
let timer: NodeJS.Timeout | undefined;
|
|
109
|
-
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
|
+
}
|
|
110
181
|
if (timer) {
|
|
111
182
|
clearTimeout(timer);
|
|
112
183
|
}
|
|
113
|
-
timer = setTimeout(
|
|
184
|
+
timer = setTimeout(rerun, 150);
|
|
114
185
|
};
|
|
115
|
-
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.`);
|
|
116
190
|
|
|
117
191
|
return new Promise<void>(resolve => {
|
|
118
192
|
context.signal.addEventListener(
|
|
119
193
|
'abort',
|
|
120
194
|
() => {
|
|
121
|
-
for (const
|
|
122
|
-
|
|
195
|
+
for (const file of watched) {
|
|
196
|
+
fs.unwatchFile(file, onPoll);
|
|
123
197
|
}
|
|
124
198
|
if (timer) {
|
|
125
199
|
clearTimeout(timer);
|
|
@@ -131,6 +205,11 @@ export function watchTransferModel(options: GenerateTransferModelOptions, contex
|
|
|
131
205
|
});
|
|
132
206
|
}
|
|
133
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
|
+
|
|
134
213
|
/** Run the transfer-model generator. */
|
|
135
214
|
export function generateTransferModel(options: GenerateTransferModelOptions): void {
|
|
136
215
|
const elementTypeName = options.elementTypeName ?? DEFAULT_ELEMENT_TYPE_NAME;
|
|
@@ -168,15 +247,19 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
168
247
|
astInterfaces.set(iface.getName(), iface);
|
|
169
248
|
}
|
|
170
249
|
|
|
171
|
-
const typeAliases: Array<{ name: string; definition: string }> = [];
|
|
250
|
+
const typeAliases: Array<{ name: string; definition: string; members: string[] }> = [];
|
|
172
251
|
for (const alias of astSource.getTypeAliases()) {
|
|
173
252
|
const name = alias.getName();
|
|
174
253
|
if (skipTypeAliases.has(name)) {
|
|
175
254
|
continue;
|
|
176
255
|
}
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
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
|
+
});
|
|
180
263
|
}
|
|
181
264
|
}
|
|
182
265
|
|
|
@@ -193,7 +276,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
193
276
|
// 7. Classify interfaces: abstract (union $type) vs concrete (literal $type).
|
|
194
277
|
const interfaceNames: string[] = [];
|
|
195
278
|
const abstractTypes = new Set<string>();
|
|
196
|
-
const concreteTypes = new Set<string>();
|
|
197
279
|
|
|
198
280
|
for (const name of Object.keys(reflectionTypes)) {
|
|
199
281
|
const iface = astInterfaces.get(name);
|
|
@@ -205,8 +287,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
205
287
|
const typeText = iface.getProperty('$type')?.getTypeNode()?.getText() ?? '';
|
|
206
288
|
if (typeText.includes('|')) {
|
|
207
289
|
abstractTypes.add(name);
|
|
208
|
-
} else {
|
|
209
|
-
concreteTypes.add(name);
|
|
210
290
|
}
|
|
211
291
|
}
|
|
212
292
|
|
|
@@ -248,12 +328,13 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
248
328
|
out.push('}');
|
|
249
329
|
out.push('');
|
|
250
330
|
|
|
251
|
-
//
|
|
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));
|
|
252
334
|
out.push('// --- Type Constants ---');
|
|
253
|
-
for (const name of
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
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;`);
|
|
257
338
|
}
|
|
258
339
|
out.push('');
|
|
259
340
|
|
|
@@ -264,7 +345,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
264
345
|
out.push(`export type ${name} = ${definition};`);
|
|
265
346
|
const literals = extractStringLiterals(definition);
|
|
266
347
|
if (literals) {
|
|
267
|
-
const items = literals.map(
|
|
348
|
+
const items = literals.map(quote).join(', ');
|
|
268
349
|
out.push(`export const ${name}Values = [${items}] as const;`);
|
|
269
350
|
}
|
|
270
351
|
}
|
|
@@ -295,7 +376,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
295
376
|
const typeValue = iface.getProperty('$type')?.getTypeNode()?.getText() ?? `'${name}'`;
|
|
296
377
|
out.push(` readonly $type: ${typeValue};`);
|
|
297
378
|
} else {
|
|
298
|
-
out.push(` readonly $type:
|
|
379
|
+
out.push(` readonly $type: '${name}';`);
|
|
299
380
|
}
|
|
300
381
|
|
|
301
382
|
const emitted = new Set<string>(['$type']);
|
|
@@ -357,24 +438,24 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
357
438
|
const checks = members.map(m => `item.$type === '${m}'`).join(' || ');
|
|
358
439
|
out.push(` return is${elementTypeName}(item) && (${checks});`);
|
|
359
440
|
} else {
|
|
360
|
-
out.push(` return is${elementTypeName}(item) && item.$type === ${name}
|
|
441
|
+
out.push(` return is${elementTypeName}(item) && item.$type === '${name}';`);
|
|
361
442
|
}
|
|
362
443
|
out.push('}');
|
|
363
444
|
out.push('');
|
|
364
445
|
}
|
|
365
446
|
|
|
366
|
-
for (const { name, definition } of typeAliases) {
|
|
447
|
+
for (const { name, definition, members } of typeAliases) {
|
|
367
448
|
const literals = extractStringLiterals(definition);
|
|
368
449
|
if (literals) {
|
|
369
|
-
const checks = literals.map(l => `item ===
|
|
450
|
+
const checks = literals.map(l => `item === ${quote(l)}`).join(' || ') || 'false';
|
|
370
451
|
out.push(`export function is${name}(item: unknown): item is ${name} {`);
|
|
371
452
|
out.push(` return ${checks};`);
|
|
372
453
|
out.push('}');
|
|
373
454
|
out.push('');
|
|
374
455
|
} else {
|
|
375
|
-
// Union (or single) of named members
|
|
376
|
-
//
|
|
377
|
-
|
|
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.
|
|
378
459
|
const checks = members.map(typeAliasMemberGuard).join(' || ');
|
|
379
460
|
out.push(`export function is${name}(item: unknown): item is ${name} {`);
|
|
380
461
|
out.push(` return ${checks};`);
|
|
@@ -383,6 +464,9 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
383
464
|
}
|
|
384
465
|
}
|
|
385
466
|
|
|
467
|
+
out.push(...renderTypeMap(languageId, reflectedTypes));
|
|
468
|
+
out.push(...renderReflection(languageId, elementTypeName, reflectedTypes, reflectionTypes));
|
|
469
|
+
|
|
386
470
|
// Coverage check: warn on reflection types not emitted.
|
|
387
471
|
const processedSet = new Set(sorted);
|
|
388
472
|
for (const name of Object.keys(reflectionTypes)) {
|
|
@@ -396,10 +480,123 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
396
480
|
fs.mkdirSync(path.dirname(options.outFile), { recursive: true });
|
|
397
481
|
if (fs.existsSync(options.outFile) && fs.readFileSync(options.outFile, 'utf-8') === content) {
|
|
398
482
|
console.log('Transfer model is up to date.');
|
|
399
|
-
|
|
483
|
+
} else {
|
|
484
|
+
fs.writeFileSync(options.outFile, content, 'utf-8');
|
|
485
|
+
console.log(`Generated: ${options.outFile}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (options.astBuilderFile !== undefined) {
|
|
489
|
+
const emitted = emitAstBuilder(astSource, {
|
|
490
|
+
outFile: options.astBuilderFile,
|
|
491
|
+
astFile: options.astFile,
|
|
492
|
+
languageId,
|
|
493
|
+
regenCommand
|
|
494
|
+
});
|
|
495
|
+
if (!emitted) {
|
|
496
|
+
console.warn(`Warning: no '${languageId}AstType' alias in ${options.astFile}; skipping the AST builder.`);
|
|
497
|
+
}
|
|
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 });
|
|
400
507
|
}
|
|
401
|
-
|
|
402
|
-
|
|
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;
|
|
403
600
|
}
|
|
404
601
|
|
|
405
602
|
// ---------------------------------------------------------------------------
|
|
@@ -440,10 +637,11 @@ function findAstReflectionClass(source: SourceFile): ClassDeclaration {
|
|
|
440
637
|
}
|
|
441
638
|
|
|
442
639
|
/**
|
|
443
|
-
* Extract `superTypes` for each entry of the
|
|
444
|
-
* initializer. We rely on the Langium
|
|
445
|
-
*
|
|
446
|
-
* `.$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.
|
|
447
645
|
*/
|
|
448
646
|
function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string, ReflectionEntry> {
|
|
449
647
|
const result: Record<string, ReflectionEntry> = {};
|
|
@@ -465,29 +663,35 @@ function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string,
|
|
|
465
663
|
const value = entry.getLastChildByKind(SyntaxKind.ObjectLiteralExpression);
|
|
466
664
|
const supersProp = value?.getProperty('superTypes');
|
|
467
665
|
const supersInit = supersProp?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
|
|
468
|
-
const superTypes
|
|
469
|
-
|
|
470
|
-
for (const element of supersInit.getElements()) {
|
|
471
|
-
const text = element.getText();
|
|
472
|
-
const dotMatch = /^([A-Za-z_$][\w$]*)\.\$type$/.exec(text);
|
|
473
|
-
if (dotMatch) {
|
|
474
|
-
superTypes.push(dotMatch[1]);
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
// Bare identifier or quoted string fall-back.
|
|
478
|
-
const stringMatch = /^['"](.+)['"]$/.exec(text);
|
|
479
|
-
if (stringMatch) {
|
|
480
|
-
superTypes.push(stringMatch[1]);
|
|
481
|
-
} else {
|
|
482
|
-
superTypes.push(text);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
result[name] = { superTypes };
|
|
666
|
+
const superTypes = supersInit?.getElements().map(element => typeNameOf(element.getText())) ?? [];
|
|
667
|
+
result[name] = { superTypes, properties: parseReflectionProperties(value) };
|
|
487
668
|
}
|
|
488
669
|
return result;
|
|
489
670
|
}
|
|
490
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
|
+
|
|
491
695
|
/**
|
|
492
696
|
* Collect terminal regex literals from the AST file.
|
|
493
697
|
*
|
|
@@ -625,12 +829,16 @@ function mapType(text: string, elementTypeName: string): string {
|
|
|
625
829
|
|
|
626
830
|
/**
|
|
627
831
|
* Type-guard expression (against an `item: unknown`) for one member of a non-literal type-alias
|
|
628
|
-
* union.
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* `
|
|
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.
|
|
632
837
|
*/
|
|
633
838
|
export function typeAliasMemberGuard(member: string): string {
|
|
839
|
+
if (member.startsWith("'") || member.startsWith('"')) {
|
|
840
|
+
return `item === ${member}`;
|
|
841
|
+
}
|
|
634
842
|
switch (member) {
|
|
635
843
|
case 'string':
|
|
636
844
|
case 'number':
|
|
@@ -654,10 +862,11 @@ export function typeAliasMemberGuard(member: string): string {
|
|
|
654
862
|
* name.
|
|
655
863
|
*
|
|
656
864
|
* Each referenced member is inlined and de-duplicated, preserving first-seen
|
|
657
|
-
* order
|
|
658
|
-
* namespace-qualified reference (the
|
|
659
|
-
*
|
|
660
|
-
* 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.
|
|
661
870
|
*/
|
|
662
871
|
export function resolveNamespacedStringUnion(astSource: SourceFile, definition: string): string {
|
|
663
872
|
const references = [...definition.matchAll(/\b([A-Za-z_]\w*)\.([A-Za-z_]\w*)\b/g)];
|
|
@@ -679,16 +888,55 @@ export function resolveNamespacedStringUnion(astSource: SourceFile, definition:
|
|
|
679
888
|
}
|
|
680
889
|
}
|
|
681
890
|
}
|
|
682
|
-
return literals.map(
|
|
891
|
+
return literals.length > 0 ? literals.map(quote).join(' | ') : 'never';
|
|
683
892
|
}
|
|
684
893
|
|
|
685
|
-
/**
|
|
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()];
|
|
909
|
+
}
|
|
910
|
+
|
|
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
|
+
*/
|
|
686
917
|
function extractStringLiterals(definition: string): string[] | undefined {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
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
|
+
}
|
|
690
930
|
}
|
|
691
|
-
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("'", "\\'")}'`;
|
|
692
940
|
}
|
|
693
941
|
|
|
694
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
|
+
}
|