@hydranium/core 1.0.0-next.315 → 1.0.0-next.317
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/lib/langium/ast-extension/ast-node-builder.d.ts +12 -7
- package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
- package/lib/langium/ast-extension/ast-node-builder.js +25 -9
- package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
- package/lib/langium/transfer/transfer-encoder.d.ts +2 -2
- package/lib/langium/transfer/transfer-encoder.js +2 -2
- package/package.json +6 -6
- package/src/langium/ast-extension/ast-node-builder.ts +35 -13
- package/src/langium/transfer/transfer-encoder.ts +2 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
import type { AstNode, AstReflection } from '@hydranium/langium';
|
|
10
|
+
import type { ConcreteTypeName, SingleTypeName } from '@hydranium/protocol';
|
|
10
11
|
/**
|
|
11
12
|
* Shape of the per-type constants Langium emits alongside each
|
|
12
13
|
* grammar-generated interface — a `const` of the same name whose `$type` field
|
|
@@ -50,13 +51,17 @@ export type AstNodeInit<T extends AstNode> = Omit<T, keyof AstNode | ArrayKeys<T
|
|
|
50
51
|
* the AST interface from a constant's `$type` literal alone, so call sites do
|
|
51
52
|
* not have to write the type name twice.
|
|
52
53
|
*
|
|
54
|
+
* The constant must name one type its own `$type` lists: a union's or an
|
|
55
|
+
* abstract type's constant, or one typed by a union of type names, would let
|
|
56
|
+
* an incomplete node through.
|
|
57
|
+
*
|
|
53
58
|
* @param typeConstant the per-type constant Langium emits (drives `$type`).
|
|
54
59
|
* @param init typed fields — TS enforces mandatory non-array fields here.
|
|
55
60
|
* @param extras explicitly-unchecked escape hatch for properties not declared
|
|
56
61
|
* on the AST interface. Use sparingly; lives in a separate
|
|
57
62
|
* argument so typos can't silently pass into the typed slot.
|
|
58
63
|
*/
|
|
59
|
-
export type AstNodeBuilder<TMap> = <TType extends
|
|
64
|
+
export type AstNodeBuilder<TMap> = <TType extends ConcreteTypeName<TMap>>(typeConstant: AstTypeConstant<TType> & SingleTypeName<TType>, init: AstNodeInit<Extract<TMap[TType], AstNode>>, extras?: Record<string, unknown>) => Extract<TMap[TType], AstNode>;
|
|
60
65
|
/**
|
|
61
66
|
* Build an {@link AstNodeBuilder} pre-bound to a language's `AstReflection`
|
|
62
67
|
* and its grammar `AstType` registry.
|
|
@@ -101,12 +106,12 @@ export declare function makeAstNodeBuilder<TMap>(reflection: AstReflection): Ast
|
|
|
101
106
|
* permissive lookup into a throw at a call site that can only guess.
|
|
102
107
|
*
|
|
103
108
|
* **The `type` argument is authoritative: `init` cannot rebind `$type`.** The
|
|
104
|
-
* typed builder
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
109
|
+
* typed builder holds the same rule, since its init type keeps out only a
|
|
110
|
+
* `$type` written in the call. The case that needs the guarantee is RETYPING:
|
|
111
|
+
* a caller spreading an existing node to produce one of a different type
|
|
112
|
+
* (`{ ...source }`) carries the SOURCE's `$type` in, which would silently win
|
|
113
|
+
* over the type actually requested and hand back a node of the wrong type with
|
|
114
|
+
* no error anywhere.
|
|
110
115
|
*/
|
|
111
116
|
export declare function buildAstNode<TAst extends AstNode = AstNode>(reflection: AstReflection, type: string, init?: Partial<AstNode> & Record<string, unknown>): TAst;
|
|
112
117
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-node-builder.d.ts","sourceRoot":"","sources":["../../../src/langium/ast-extension/ast-node-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ast-node-builder.d.ts","sourceRoot":"","sources":["../../../src/langium/ast-extension/ast-node-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC3D,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACxB;AAED;;;;;GAKG;AACH,KAAK,SAAS,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK;CAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AAEnG;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,GAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,oBAAoB,GAAG,iBAAiB,CAAC,CAAC,GAAG;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,IAAI,CAAC,KAAK,SAAS,gBAAgB,CAAC,IAAI,CAAC,EACrE,YAAY,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,EAC5D,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,EAChD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAkBxF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO,EACxD,UAAU,EAAE,aAAa,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjD,IAAI,CAON"}
|
|
@@ -35,12 +35,15 @@
|
|
|
35
35
|
export function makeAstNodeBuilder(reflection) {
|
|
36
36
|
return function astNode(typeConstant, init, extras) {
|
|
37
37
|
const result = withTypeDefaults(reflection, typeConstant.$type);
|
|
38
|
-
|
|
38
|
+
assignOverDefaults(result, init);
|
|
39
39
|
if (extras) {
|
|
40
40
|
// Extras win over init: this is the explicit escape hatch, so when a
|
|
41
41
|
// call site uses it, treat that as deliberate override intent.
|
|
42
|
-
|
|
42
|
+
assignOverDefaults(result, extras);
|
|
43
43
|
}
|
|
44
|
+
// Last, so neither can rebind it: `AstNodeInit` keeps out only a `$type`
|
|
45
|
+
// written in the call, and a spread of another node carries its own.
|
|
46
|
+
result.$type = typeConstant.$type;
|
|
44
47
|
return result;
|
|
45
48
|
};
|
|
46
49
|
}
|
|
@@ -61,17 +64,17 @@ export function makeAstNodeBuilder(reflection) {
|
|
|
61
64
|
* permissive lookup into a throw at a call site that can only guess.
|
|
62
65
|
*
|
|
63
66
|
* **The `type` argument is authoritative: `init` cannot rebind `$type`.** The
|
|
64
|
-
* typed builder
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* typed builder holds the same rule, since its init type keeps out only a
|
|
68
|
+
* `$type` written in the call. The case that needs the guarantee is RETYPING:
|
|
69
|
+
* a caller spreading an existing node to produce one of a different type
|
|
70
|
+
* (`{ ...source }`) carries the SOURCE's `$type` in, which would silently win
|
|
71
|
+
* over the type actually requested and hand back a node of the wrong type with
|
|
72
|
+
* no error anywhere.
|
|
70
73
|
*/
|
|
71
74
|
export function buildAstNode(reflection, type, init) {
|
|
72
75
|
const result = withTypeDefaults(reflection, type);
|
|
73
76
|
if (init) {
|
|
74
|
-
|
|
77
|
+
assignOverDefaults(result, init);
|
|
75
78
|
result.$type = type;
|
|
76
79
|
}
|
|
77
80
|
return result;
|
|
@@ -96,4 +99,17 @@ function withTypeDefaults(reflection, type) {
|
|
|
96
99
|
}
|
|
97
100
|
return result;
|
|
98
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* `Object.assign`, except that an `undefined` does not replace a value already
|
|
104
|
+
* there: an optional field the caller names as `undefined` keeps its grammar
|
|
105
|
+
* default, where a plain assign would leave a node typed complete without it.
|
|
106
|
+
* Any other `undefined` is written, so the key stays present.
|
|
107
|
+
*/
|
|
108
|
+
function assignOverDefaults(target, source) {
|
|
109
|
+
for (const [key, value] of Object.entries(source)) {
|
|
110
|
+
if (value !== undefined || !Object.hasOwn(target, key)) {
|
|
111
|
+
target[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
99
115
|
//# sourceMappingURL=ast-node-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-node-builder.js","sourceRoot":"","sources":["../../../src/langium/ast-extension/ast-node-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;
|
|
1
|
+
{"version":3,"file":"ast-node-builder.js","sourceRoot":"","sources":["../../../src/langium/ast-extension/ast-node-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAmElF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,kBAAkB,CAAO,UAAyB;IAC/D,OAAO,SAAS,OAAO,CACpB,YAA4D,EAC5D,IAAgD,EAChD,MAAgC;QAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAChE,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM,EAAE,CAAC;YACV,qEAAqE;YACrE,+DAA+D;YAC/D,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,yEAAyE;QACzE,qEAAqE;QACrE,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC;QAClC,OAAO,MAAkD,CAAC;IAC7D,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,YAAY,CACzB,UAAyB,EACzB,IAAY,EACZ,IAAiD;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,IAAI,EAAE,CAAC;QACR,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;IACvB,CAAC;IACD,OAAO,MAAyB,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,UAAyB,EAAE,IAAY;IAC9D,MAAM,MAAM,GAA4B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,IAAI,EAAE,UAAU,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAClE,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;YAC3C,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC9B,SAAS;YACZ,CAAC;YACD,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QACrF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,MAA+B,EAAE,MAAc;IACxE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACtD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACJ,CAAC;AACJ,CAAC"}
|
|
@@ -125,8 +125,8 @@ export interface TransferEncoder<TDiagnostic extends TransferDiagnostic = Transf
|
|
|
125
125
|
* resolving each AST type to its declared wire shape. Default is
|
|
126
126
|
* `Record<string, TransferElement>`, which collapses every encoded node
|
|
127
127
|
* to the structural base — adequate for adopters without a generated
|
|
128
|
-
* overlay;
|
|
129
|
-
* encoder's
|
|
128
|
+
* overlay; an adopter passes the generated `<LanguageId>TransferTypes` to
|
|
129
|
+
* type the encoder's returns by the grammar.
|
|
130
130
|
* - `TDiagnostic` — wire-diagnostic shape produced by
|
|
131
131
|
* {@link toTransferDiagnostic}. Constrained to extend
|
|
132
132
|
* {@link TransferDiagnostic} so the framework default (which constructs a
|
|
@@ -26,8 +26,8 @@ import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol';
|
|
|
26
26
|
* resolving each AST type to its declared wire shape. Default is
|
|
27
27
|
* `Record<string, TransferElement>`, which collapses every encoded node
|
|
28
28
|
* to the structural base — adequate for adopters without a generated
|
|
29
|
-
* overlay;
|
|
30
|
-
* encoder's
|
|
29
|
+
* overlay; an adopter passes the generated `<LanguageId>TransferTypes` to
|
|
30
|
+
* type the encoder's returns by the grammar.
|
|
31
31
|
* - `TDiagnostic` — wire-diagnostic shape produced by
|
|
32
32
|
* {@link toTransferDiagnostic}. Constrained to extend
|
|
33
33
|
* {@link TransferDiagnostic} so the framework default (which constructs a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/core",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.317",
|
|
4
4
|
"description": "Foundational runtime of the hydranium framework: AST coordination layer (services, integrity, AST extension, serialization, multi-client documents) plus the LSP textual head at the /lsp subpath. Consumed by protocol-head packages (@hydranium/data-server, @hydranium/glsp-server).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
@@ -87,9 +87,9 @@
|
|
|
87
87
|
"diff": "^5.2.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@hydranium/langium": "1.0.0-next.
|
|
91
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
92
|
-
"@playwright/test": "^1.
|
|
90
|
+
"@hydranium/langium": "1.0.0-next.317",
|
|
91
|
+
"@hydranium/protocol": "1.0.0-next.317",
|
|
92
|
+
"@playwright/test": "^1.55.0",
|
|
93
93
|
"@types/diff": "^5.2.0",
|
|
94
94
|
"fake-indexeddb": "^6.2.5",
|
|
95
95
|
"rimraf": "^5.0.0",
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"vscode-languageserver-types": "^3.17.5"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@hydranium/langium": "1.0.0-next.
|
|
105
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
104
|
+
"@hydranium/langium": "1.0.0-next.317",
|
|
105
|
+
"@hydranium/protocol": "1.0.0-next.317",
|
|
106
106
|
"@playwright/test": "^1.40.0",
|
|
107
107
|
"vscode-jsonrpc": "^9.0.0",
|
|
108
108
|
"vscode-languageserver": "~10.0.1",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import type { AstNode, AstReflection } from '@hydranium/langium';
|
|
11
|
+
import type { ConcreteTypeName, SingleTypeName } from '@hydranium/protocol';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Shape of the per-type constants Langium emits alongside each
|
|
@@ -55,14 +56,18 @@ export type AstNodeInit<T extends AstNode> = Omit<T, keyof AstNode | ArrayKeys<T
|
|
|
55
56
|
* the AST interface from a constant's `$type` literal alone, so call sites do
|
|
56
57
|
* not have to write the type name twice.
|
|
57
58
|
*
|
|
59
|
+
* The constant must name one type its own `$type` lists: a union's or an
|
|
60
|
+
* abstract type's constant, or one typed by a union of type names, would let
|
|
61
|
+
* an incomplete node through.
|
|
62
|
+
*
|
|
58
63
|
* @param typeConstant the per-type constant Langium emits (drives `$type`).
|
|
59
64
|
* @param init typed fields — TS enforces mandatory non-array fields here.
|
|
60
65
|
* @param extras explicitly-unchecked escape hatch for properties not declared
|
|
61
66
|
* on the AST interface. Use sparingly; lives in a separate
|
|
62
67
|
* argument so typos can't silently pass into the typed slot.
|
|
63
68
|
*/
|
|
64
|
-
export type AstNodeBuilder<TMap> = <TType extends
|
|
65
|
-
typeConstant: AstTypeConstant<TType>,
|
|
69
|
+
export type AstNodeBuilder<TMap> = <TType extends ConcreteTypeName<TMap>>(
|
|
70
|
+
typeConstant: AstTypeConstant<TType> & SingleTypeName<TType>,
|
|
66
71
|
init: AstNodeInit<Extract<TMap[TType], AstNode>>,
|
|
67
72
|
extras?: Record<string, unknown>
|
|
68
73
|
) => Extract<TMap[TType], AstNode>;
|
|
@@ -94,18 +99,21 @@ export type AstNodeBuilder<TMap> = <TType extends string & keyof TMap>(
|
|
|
94
99
|
* rather than a way to drop fields from the typed call site.
|
|
95
100
|
*/
|
|
96
101
|
export function makeAstNodeBuilder<TMap>(reflection: AstReflection): AstNodeBuilder<TMap> {
|
|
97
|
-
return function astNode<TType extends
|
|
98
|
-
typeConstant: AstTypeConstant<TType>,
|
|
102
|
+
return function astNode<TType extends ConcreteTypeName<TMap>>(
|
|
103
|
+
typeConstant: AstTypeConstant<TType> & SingleTypeName<TType>,
|
|
99
104
|
init: AstNodeInit<Extract<TMap[TType], AstNode>>,
|
|
100
105
|
extras?: Record<string, unknown>
|
|
101
106
|
): Extract<TMap[TType], AstNode> {
|
|
102
107
|
const result = withTypeDefaults(reflection, typeConstant.$type);
|
|
103
|
-
|
|
108
|
+
assignOverDefaults(result, init);
|
|
104
109
|
if (extras) {
|
|
105
110
|
// Extras win over init: this is the explicit escape hatch, so when a
|
|
106
111
|
// call site uses it, treat that as deliberate override intent.
|
|
107
|
-
|
|
112
|
+
assignOverDefaults(result, extras);
|
|
108
113
|
}
|
|
114
|
+
// Last, so neither can rebind it: `AstNodeInit` keeps out only a `$type`
|
|
115
|
+
// written in the call, and a spread of another node carries its own.
|
|
116
|
+
result.$type = typeConstant.$type;
|
|
109
117
|
return result as unknown as Extract<TMap[TType], AstNode>;
|
|
110
118
|
};
|
|
111
119
|
}
|
|
@@ -127,12 +135,12 @@ export function makeAstNodeBuilder<TMap>(reflection: AstReflection): AstNodeBuil
|
|
|
127
135
|
* permissive lookup into a throw at a call site that can only guess.
|
|
128
136
|
*
|
|
129
137
|
* **The `type` argument is authoritative: `init` cannot rebind `$type`.** The
|
|
130
|
-
* typed builder
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
138
|
+
* typed builder holds the same rule, since its init type keeps out only a
|
|
139
|
+
* `$type` written in the call. The case that needs the guarantee is RETYPING:
|
|
140
|
+
* a caller spreading an existing node to produce one of a different type
|
|
141
|
+
* (`{ ...source }`) carries the SOURCE's `$type` in, which would silently win
|
|
142
|
+
* over the type actually requested and hand back a node of the wrong type with
|
|
143
|
+
* no error anywhere.
|
|
136
144
|
*/
|
|
137
145
|
export function buildAstNode<TAst extends AstNode = AstNode>(
|
|
138
146
|
reflection: AstReflection,
|
|
@@ -141,7 +149,7 @@ export function buildAstNode<TAst extends AstNode = AstNode>(
|
|
|
141
149
|
): TAst {
|
|
142
150
|
const result = withTypeDefaults(reflection, type);
|
|
143
151
|
if (init) {
|
|
144
|
-
|
|
152
|
+
assignOverDefaults(result, init);
|
|
145
153
|
result.$type = type;
|
|
146
154
|
}
|
|
147
155
|
return result as unknown as TAst;
|
|
@@ -167,3 +175,17 @@ function withTypeDefaults(reflection: AstReflection, type: string): Record<strin
|
|
|
167
175
|
}
|
|
168
176
|
return result;
|
|
169
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* `Object.assign`, except that an `undefined` does not replace a value already
|
|
181
|
+
* there: an optional field the caller names as `undefined` keeps its grammar
|
|
182
|
+
* default, where a plain assign would leave a node typed complete without it.
|
|
183
|
+
* Any other `undefined` is written, so the key stays present.
|
|
184
|
+
*/
|
|
185
|
+
function assignOverDefaults(target: Record<string, unknown>, source: object): void {
|
|
186
|
+
for (const [key, value] of Object.entries(source)) {
|
|
187
|
+
if (value !== undefined || !Object.hasOwn(target, key)) {
|
|
188
|
+
target[key] = value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -142,8 +142,8 @@ export interface TransferEncoder<TDiagnostic extends TransferDiagnostic = Transf
|
|
|
142
142
|
* resolving each AST type to its declared wire shape. Default is
|
|
143
143
|
* `Record<string, TransferElement>`, which collapses every encoded node
|
|
144
144
|
* to the structural base — adequate for adopters without a generated
|
|
145
|
-
* overlay;
|
|
146
|
-
* encoder's
|
|
145
|
+
* overlay; an adopter passes the generated `<LanguageId>TransferTypes` to
|
|
146
|
+
* type the encoder's returns by the grammar.
|
|
147
147
|
* - `TDiagnostic` — wire-diagnostic shape produced by
|
|
148
148
|
* {@link toTransferDiagnostic}. Constrained to extend
|
|
149
149
|
* {@link TransferDiagnostic} so the framework default (which constructs a
|