@flowgram-vue/variable-core 0.2.0

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.
Files changed (67) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +2630 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +2293 -0
  5. package/dist/index.js +2592 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +68 -0
  8. package/src/ast/ast-node.ts +374 -0
  9. package/src/ast/ast-registers.ts +129 -0
  10. package/src/ast/common/data-node.ts +63 -0
  11. package/src/ast/common/index.ts +8 -0
  12. package/src/ast/common/list-node.ts +70 -0
  13. package/src/ast/common/map-node.ts +89 -0
  14. package/src/ast/declaration/base-variable-field.ts +184 -0
  15. package/src/ast/declaration/index.ts +13 -0
  16. package/src/ast/declaration/property.ts +19 -0
  17. package/src/ast/declaration/variable-declaration-list.ts +112 -0
  18. package/src/ast/declaration/variable-declaration.ts +78 -0
  19. package/src/ast/expression/base-expression.ts +117 -0
  20. package/src/ast/expression/enumerate-expression.ts +77 -0
  21. package/src/ast/expression/index.ts +10 -0
  22. package/src/ast/expression/keypath-expression.ts +157 -0
  23. package/src/ast/expression/legacy-keypath-expression.ts +119 -0
  24. package/src/ast/expression/wrap-array-expression.ts +96 -0
  25. package/src/ast/factory.ts +163 -0
  26. package/src/ast/flags.ts +50 -0
  27. package/src/ast/index.ts +26 -0
  28. package/src/ast/match.ts +146 -0
  29. package/src/ast/type/array.ts +109 -0
  30. package/src/ast/type/base-type.ts +49 -0
  31. package/src/ast/type/boolean.ts +26 -0
  32. package/src/ast/type/custom-type.ts +70 -0
  33. package/src/ast/type/index.ts +19 -0
  34. package/src/ast/type/integer.ts +29 -0
  35. package/src/ast/type/map.ts +96 -0
  36. package/src/ast/type/number.ts +26 -0
  37. package/src/ast/type/object.ts +185 -0
  38. package/src/ast/type/string.ts +55 -0
  39. package/src/ast/type/union.ts +13 -0
  40. package/src/ast/types.ts +188 -0
  41. package/src/ast/utils/expression.ts +61 -0
  42. package/src/ast/utils/helpers.ts +73 -0
  43. package/src/ast/utils/inversify.ts +42 -0
  44. package/src/ast/utils/observable.ts +5 -0
  45. package/src/ast/utils/variable-field.ts +25 -0
  46. package/src/composables/index.ts +9 -0
  47. package/src/composables/scope-provider.ts +78 -0
  48. package/src/composables/use-available-variables.ts +39 -0
  49. package/src/composables/use-output-variables.ts +36 -0
  50. package/src/composables/use-scope-available.ts +32 -0
  51. package/src/index.ts +14 -0
  52. package/src/providers.ts +22 -0
  53. package/src/scope/datas/index.ts +8 -0
  54. package/src/scope/datas/scope-available-data.ts +234 -0
  55. package/src/scope/datas/scope-event-data.ts +67 -0
  56. package/src/scope/datas/scope-output-data.ts +151 -0
  57. package/src/scope/index.ts +9 -0
  58. package/src/scope/scope-chain.ts +69 -0
  59. package/src/scope/scope.ts +200 -0
  60. package/src/scope/types.ts +102 -0
  61. package/src/scope/variable-table.ts +203 -0
  62. package/src/services/index.ts +6 -0
  63. package/src/services/variable-field-key-rename-service.ts +131 -0
  64. package/src/utils/memo.ts +38 -0
  65. package/src/utils/toDisposable.ts +16 -0
  66. package/src/variable-container-module.ts +28 -0
  67. package/src/variable-engine.ts +197 -0
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import {
7
+ BehaviorSubject,
8
+ animationFrameScheduler,
9
+ debounceTime,
10
+ distinctUntilChanged,
11
+ map,
12
+ skip,
13
+ tap,
14
+ } from 'rxjs';
15
+ import { nanoid } from 'nanoid';
16
+ import { isNil, omitBy } from 'lodash-es';
17
+ import { shallowEqual } from 'fast-equals';
18
+ import { Disposable, DisposableCollection } from '@flowgram-vue/utils';
19
+
20
+ import { subsToDisposable } from '../utils/toDisposable';
21
+ import { updateChildNodeHelper } from './utils/helpers';
22
+ import { type Scope } from '../scope';
23
+ import {
24
+ type ASTNodeJSON,
25
+ type ObserverOrNext,
26
+ type ASTKindType,
27
+ type CreateASTParams,
28
+ type Identifier,
29
+ SubscribeConfig,
30
+ GlobalEventActionType,
31
+ DisposeASTAction,
32
+ UpdateASTAction,
33
+ } from './types';
34
+ import { ASTNodeFlags } from './flags';
35
+
36
+ export interface ASTNodeRegistry<JSON extends ASTNodeJSON = any> {
37
+ kind: string;
38
+ new (params: CreateASTParams, injectOpts: any): ASTNode<JSON>;
39
+ }
40
+
41
+ /**
42
+ * An `ASTNode` represents a fundamental unit of variable information within the system's Abstract Syntax Tree.
43
+ * It can model various constructs, for example:
44
+ * - **Declarations**: `const a = 1`
45
+ * - **Expressions**: `a.b.c`
46
+ * - **Types**: `number`, `string`, `boolean`
47
+ *
48
+ * Here is some characteristic of ASTNode:
49
+ * - **Tree-like Structure**: ASTNodes can be nested to form a tree, representing complex variable structures.
50
+ * - **Extendable**: New features can be added by extending the base ASTNode class.
51
+ * - **Reactive**: Changes in an ASTNode's value trigger events, enabling reactive programming patterns.
52
+ * - **Serializable**: ASTNodes can be converted to and from a JSON format (ASTNodeJSON) for storage or transmission.
53
+ */
54
+ export abstract class ASTNode<JSON extends ASTNodeJSON = any> implements Disposable {
55
+ /**
56
+ * @deprecated
57
+ * Get the injected options for the ASTNode.
58
+ *
59
+ * Please use `@injectToAst(XXXService) declare xxxService: XXXService` to achieve external dependency injection.
60
+ */
61
+ public readonly opts?: any;
62
+
63
+ /**
64
+ * The unique identifier of the ASTNode, which is **immutable**.
65
+ * - Immutable: Once assigned, the key cannot be changed.
66
+ * - Automatically generated if not specified, and cannot be changed as well.
67
+ * - If a new key needs to be generated, the current ASTNode should be destroyed and a new ASTNode should be generated.
68
+ */
69
+ public readonly key: Identifier;
70
+
71
+ /**
72
+ * The kind of the ASTNode.
73
+ */
74
+ static readonly kind: ASTKindType;
75
+
76
+ /**
77
+ * Node flags, used to record some flag information.
78
+ */
79
+ public readonly flags: number = ASTNodeFlags.None;
80
+
81
+ /**
82
+ * The scope in which the ASTNode is located.
83
+ */
84
+ public readonly scope: Scope;
85
+
86
+ /**
87
+ * The parent ASTNode.
88
+ */
89
+ public readonly parent: ASTNode | undefined;
90
+
91
+ /**
92
+ * The version number of the ASTNode, which increments by 1 each time `fireChange` is called.
93
+ */
94
+ protected _version: number = 0;
95
+
96
+ /**
97
+ * Update lock.
98
+ * - When set to `true`, `fireChange` will not trigger any events.
99
+ * - This is useful when multiple updates are needed, and you want to avoid multiple triggers.
100
+ */
101
+ public changeLocked = false;
102
+
103
+ /**
104
+ * Parameters related to batch updates.
105
+ */
106
+ private _batch: {
107
+ batching: boolean;
108
+ hasChangesInBatch: boolean;
109
+ } = {
110
+ batching: false,
111
+ hasChangesInBatch: false,
112
+ };
113
+
114
+ /**
115
+ * AST node change Observable events, implemented based on RxJS.
116
+ * - Emits the current ASTNode value upon subscription.
117
+ * - Emits a new value whenever `fireChange` is called.
118
+ */
119
+ public readonly value$: BehaviorSubject<ASTNode> = new BehaviorSubject<ASTNode>(this as ASTNode);
120
+
121
+ /**
122
+ * Child ASTNodes.
123
+ */
124
+ protected _children = new Set<ASTNode>();
125
+
126
+ /**
127
+ * List of disposal handlers for the ASTNode.
128
+ */
129
+ public readonly toDispose: DisposableCollection = new DisposableCollection(
130
+ Disposable.create(() => {
131
+ // When a child element is deleted, the parent element triggers an update.
132
+ this.parent?.fireChange();
133
+ this.children.forEach((child) => child.dispose());
134
+ })
135
+ );
136
+
137
+ /**
138
+ * Callback triggered upon disposal.
139
+ */
140
+ onDispose = this.toDispose.onDispose;
141
+
142
+ /**
143
+ * Constructor.
144
+ * @param createParams Necessary parameters for creating an ASTNode.
145
+ * @param injectOptions Dependency injection for various modules.
146
+ */
147
+ constructor({ key, parent, scope }: CreateASTParams, opts?: any) {
148
+ this.scope = scope;
149
+ this.parent = parent;
150
+ this.opts = opts;
151
+
152
+ // Initialize the key value. If a key is passed in, use it; otherwise, generate a random one using nanoid.
153
+ this.key = key || nanoid();
154
+
155
+ // All `fireChange` calls within the subsequent `fromJSON` will be merged into one.
156
+ this.fromJSON = this.withBatchUpdate(this.fromJSON.bind(this));
157
+
158
+ // Add the kind field to the JSON output.
159
+ const rawToJSON = this.toJSON?.bind(this);
160
+ this.toJSON = () =>
161
+ omitBy(
162
+ {
163
+ // always include kind
164
+ kind: this.kind,
165
+ ...(rawToJSON?.() || {}),
166
+ },
167
+ // remove undefined fields
168
+ isNil
169
+ ) as JSON;
170
+ }
171
+
172
+ /**
173
+ * The type of the ASTNode.
174
+ */
175
+ get kind(): string {
176
+ if (!(this.constructor as any).kind) {
177
+ throw new Error(`ASTNode Registry need a kind: ${this.constructor.name}`);
178
+ }
179
+ return (this.constructor as any).kind;
180
+ }
181
+
182
+ /**
183
+ * Parses AST JSON data.
184
+ * @param json AST JSON data.
185
+ */
186
+ abstract fromJSON(json: JSON): void;
187
+
188
+ /**
189
+ * Gets all child ASTNodes of the current ASTNode.
190
+ */
191
+ get children(): ASTNode[] {
192
+ return Array.from(this._children);
193
+ }
194
+
195
+ /**
196
+ * Serializes the current ASTNode to ASTNodeJSON.
197
+ * @returns
198
+ */
199
+ abstract toJSON(): JSON;
200
+
201
+ /**
202
+ * Creates a child ASTNode.
203
+ * @param json The AST JSON of the child ASTNode.
204
+ * @returns
205
+ */
206
+ protected createChildNode<ChildNode extends ASTNode = ASTNode>(json: ASTNodeJSON): ChildNode {
207
+ const astRegisters = this.scope.variableEngine.astRegisters;
208
+
209
+ const child = astRegisters.createAST(json, {
210
+ parent: this,
211
+ scope: this.scope,
212
+ }) as ChildNode;
213
+
214
+ // Add to the _children set.
215
+ this._children.add(child);
216
+ child.toDispose.push(
217
+ Disposable.create(() => {
218
+ this._children.delete(child);
219
+ })
220
+ );
221
+
222
+ return child;
223
+ }
224
+
225
+ /**
226
+ * Updates a child ASTNode, quickly implementing the consumption logic for child ASTNode updates.
227
+ * @param keyInThis The specified key on the current object.
228
+ */
229
+ protected updateChildNodeByKey(keyInThis: keyof this, nextJSON?: ASTNodeJSON) {
230
+ this.withBatchUpdate(updateChildNodeHelper).call(this, {
231
+ getChildNode: () => this[keyInThis] as ASTNode,
232
+ updateChildNode: (_node) => ((this as any)[keyInThis] = _node),
233
+ removeChildNode: () => ((this as any)[keyInThis] = undefined),
234
+ nextJSON,
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Batch updates the ASTNode, merging all `fireChange` calls within the batch function into one.
240
+ * @param updater The batch function.
241
+ * @returns
242
+ */
243
+ protected withBatchUpdate<ParamTypes extends any[], ReturnType>(
244
+ updater: (...args: ParamTypes) => ReturnType
245
+ ) {
246
+ return (...args: ParamTypes) => {
247
+ // Nested batchUpdate can only take effect once.
248
+ if (this._batch.batching) {
249
+ return updater.call(this, ...args);
250
+ }
251
+
252
+ this._batch.hasChangesInBatch = false;
253
+
254
+ this._batch.batching = true;
255
+ const res = updater.call(this, ...args);
256
+ this._batch.batching = false;
257
+
258
+ if (this._batch.hasChangesInBatch) {
259
+ this.fireChange();
260
+ }
261
+ this._batch.hasChangesInBatch = false;
262
+
263
+ return res;
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Triggers an update for the current node.
269
+ */
270
+ fireChange(): void {
271
+ if (this.changeLocked || this.disposed) {
272
+ return;
273
+ }
274
+
275
+ if (this._batch.batching) {
276
+ this._batch.hasChangesInBatch = true;
277
+ return;
278
+ }
279
+
280
+ this._version++;
281
+ this.value$.next(this);
282
+ this.dispatchGlobalEvent<UpdateASTAction>({ type: 'UpdateAST' });
283
+ this.parent?.fireChange();
284
+ }
285
+
286
+ /**
287
+ * The version value of the ASTNode.
288
+ * - You can used to check whether ASTNode are updated.
289
+ */
290
+ get version(): number {
291
+ return this._version;
292
+ }
293
+
294
+ /**
295
+ * The unique hash value of the ASTNode.
296
+ * - It will update when the ASTNode is updated.
297
+ * - You can used to check two ASTNode are equal.
298
+ */
299
+ get hash(): string {
300
+ return `${this._version}${this.kind}${this.key}`;
301
+ }
302
+
303
+ /**
304
+ * Listens for changes to the ASTNode.
305
+ * @param observer The listener callback.
306
+ * @param selector Listens for specified data.
307
+ * @returns
308
+ */
309
+ subscribe<Data = this>(
310
+ observer: ObserverOrNext<Data>,
311
+ { selector, debounceAnimation, triggerOnInit }: SubscribeConfig<this, Data> = {}
312
+ ): Disposable {
313
+ return subsToDisposable(
314
+ this.value$
315
+ .pipe(
316
+ map(() => (selector ? selector(this) : (this as any))),
317
+ distinctUntilChanged(
318
+ (a, b) => shallowEqual(a, b),
319
+ (value) => {
320
+ if (value instanceof ASTNode) {
321
+ // If the value is an ASTNode, compare its hash.
322
+ return value.hash;
323
+ }
324
+ return value;
325
+ }
326
+ ),
327
+ // By default, skip the first trigger of BehaviorSubject.
328
+ triggerOnInit ? tap(() => null) : skip(1),
329
+ // All updates within each animationFrame are merged into one.
330
+ debounceAnimation ? debounceTime(0, animationFrameScheduler) : tap(() => null)
331
+ )
332
+ .subscribe(observer)
333
+ );
334
+ }
335
+
336
+ /**
337
+ * Dispatches a global event for the current ASTNode.
338
+ * @param event The global event.
339
+ */
340
+ dispatchGlobalEvent<ActionType extends GlobalEventActionType = GlobalEventActionType>(
341
+ event: Omit<ActionType, 'ast'>
342
+ ) {
343
+ this.scope.event.dispatch({
344
+ ...event,
345
+ ast: this,
346
+ });
347
+ }
348
+
349
+ /**
350
+ * Disposes the ASTNode.
351
+ */
352
+ dispose(): void {
353
+ // Prevent multiple disposals.
354
+ if (this.toDispose.disposed) {
355
+ return;
356
+ }
357
+
358
+ this.toDispose.dispose();
359
+ this.dispatchGlobalEvent<DisposeASTAction>({ type: 'DisposeAST' });
360
+
361
+ // When the complete event is emitted, ensure that the current ASTNode is in a disposed state.
362
+ this.value$.complete();
363
+ this.value$.unsubscribe();
364
+ }
365
+
366
+ get disposed(): boolean {
367
+ return this.toDispose.disposed;
368
+ }
369
+
370
+ /**
371
+ * Extended information of the ASTNode.
372
+ */
373
+ [key: string]: unknown;
374
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { omit } from 'lodash-es';
7
+ import { injectable } from 'inversify';
8
+
9
+ import { POST_CONSTRUCT_AST_SYMBOL } from './utils/inversify';
10
+ import { ASTKindType, ASTNodeJSON, CreateASTParams, NewASTAction } from './types';
11
+ import { ArrayType } from './type/array';
12
+ import {
13
+ BooleanType,
14
+ CustomType,
15
+ IntegerType,
16
+ MapType,
17
+ NumberType,
18
+ ObjectType,
19
+ StringType,
20
+ } from './type';
21
+ import { EnumerateExpression, KeyPathExpression, WrapArrayExpression } from './expression';
22
+ import { Property, VariableDeclaration, VariableDeclarationList } from './declaration';
23
+ import { DataNode, MapNode } from './common';
24
+ import { ASTNode, ASTNodeRegistry } from './ast-node';
25
+
26
+ type DataInjector = () => Record<string, any>;
27
+
28
+ /**
29
+ * Register the AST node to the engine.
30
+ */
31
+ @injectable()
32
+ export class ASTRegisters {
33
+ /**
34
+ * @deprecated Please use `@injectToAst(XXXService) declare xxxService: XXXService` to achieve external dependency injection.
35
+ */
36
+ protected injectors: Map<ASTKindType, DataInjector> = new Map();
37
+
38
+ protected astMap: Map<ASTKindType, ASTNodeRegistry> = new Map();
39
+
40
+ /**
41
+ * Core AST node registration.
42
+ */
43
+ constructor() {
44
+ this.registerAST(StringType);
45
+ this.registerAST(NumberType);
46
+ this.registerAST(BooleanType);
47
+ this.registerAST(IntegerType);
48
+ this.registerAST(ObjectType);
49
+ this.registerAST(ArrayType);
50
+ this.registerAST(MapType);
51
+ this.registerAST(CustomType);
52
+ this.registerAST(Property);
53
+ this.registerAST(VariableDeclaration);
54
+ this.registerAST(VariableDeclarationList);
55
+ this.registerAST(KeyPathExpression);
56
+
57
+ this.registerAST(EnumerateExpression);
58
+ this.registerAST(WrapArrayExpression);
59
+ this.registerAST(MapNode);
60
+ this.registerAST(DataNode);
61
+ }
62
+
63
+ /**
64
+ * Creates an AST node.
65
+ * @param param Creation parameters.
66
+ * @returns
67
+ */
68
+ createAST<ReturnNode extends ASTNode = ASTNode>(
69
+ json: ASTNodeJSON,
70
+ { parent, scope }: CreateASTParams
71
+ ): ReturnNode {
72
+ const Registry = this.astMap.get(json.kind!);
73
+
74
+ if (!Registry) {
75
+ throw Error(`ASTKind: ${String(json.kind)} can not find its ASTNode Registry`);
76
+ }
77
+
78
+ const injector = this.injectors.get(json.kind!);
79
+
80
+ const node = new Registry(
81
+ {
82
+ key: json.key,
83
+ scope,
84
+ parent,
85
+ },
86
+ injector?.() || {}
87
+ ) as ReturnNode;
88
+
89
+ // Do not trigger fireChange during initial creation.
90
+ node.changeLocked = true;
91
+ node.fromJSON(omit(json, ['key', 'kind']));
92
+ node.changeLocked = false;
93
+
94
+ node.dispatchGlobalEvent<NewASTAction>({ type: 'NewAST' });
95
+
96
+ if (Reflect.hasMetadata(POST_CONSTRUCT_AST_SYMBOL, node)) {
97
+ const postConstructKey = Reflect.getMetadata(POST_CONSTRUCT_AST_SYMBOL, node);
98
+ (node[postConstructKey] as () => void)?.();
99
+ }
100
+
101
+ return node;
102
+ }
103
+
104
+ /**
105
+ * Gets the node Registry by AST node type.
106
+ * @param kind
107
+ * @returns
108
+ */
109
+ getASTRegistryByKind(kind: ASTKindType) {
110
+ return this.astMap.get(kind);
111
+ }
112
+
113
+ /**
114
+ * Registers an AST node.
115
+ * @param ASTNode
116
+ */
117
+ registerAST(
118
+ ASTNode: ASTNodeRegistry,
119
+ /**
120
+ * @deprecated Please use `@injectToAst(XXXService) declare xxxService: XXXService` to achieve external dependency injection.
121
+ */
122
+ injector?: DataInjector
123
+ ) {
124
+ this.astMap.set(ASTNode.kind, ASTNode);
125
+ if (injector) {
126
+ this.injectors.set(ASTNode.kind, injector);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { shallowEqual } from 'fast-equals';
7
+
8
+ import { ASTKind, ASTNodeJSON } from '../types';
9
+ import { ASTNode } from '../ast-node';
10
+
11
+ /**
12
+ * Represents a general data node with no child nodes.
13
+ */
14
+ export class DataNode<Data = any> extends ASTNode {
15
+ static kind: string = ASTKind.DataNode;
16
+
17
+ protected _data: Data;
18
+
19
+ /**
20
+ * The data of the node.
21
+ */
22
+ get data(): Data {
23
+ return this._data;
24
+ }
25
+
26
+ /**
27
+ * Deserializes the `DataNodeJSON` to the `DataNode`.
28
+ * @param json The `DataNodeJSON` to deserialize.
29
+ */
30
+ fromJSON(json: Data): void {
31
+ const { kind, ...restData } = json as ASTNodeJSON;
32
+
33
+ if (!shallowEqual(restData, this._data)) {
34
+ this._data = restData as unknown as Data;
35
+ this.fireChange();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Serialize the `DataNode` to `DataNodeJSON`.
41
+ * @returns The JSON representation of `DataNode`.
42
+ */
43
+ toJSON() {
44
+ return {
45
+ kind: ASTKind.DataNode,
46
+ ...this._data,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Partially update the data of the node.
52
+ * @param nextData The data to be updated.
53
+ */
54
+ partialUpdate(nextData: Data) {
55
+ if (!shallowEqual(nextData, this._data)) {
56
+ this._data = {
57
+ ...this._data,
58
+ ...nextData,
59
+ };
60
+ this.fireChange();
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export { DataNode } from './data-node';
7
+ export { ListNode, type ListNodeJSON } from './list-node';
8
+ export { MapNode, type MapNodeJSON } from './map-node';
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { ASTKind, ASTNodeJSON } from '../types';
7
+ import { ASTNode } from '../ast-node';
8
+
9
+ /**
10
+ * ASTNodeJSON representation of `ListNode`
11
+ */
12
+ export interface ListNodeJSON {
13
+ /**
14
+ * The list of nodes.
15
+ */
16
+ list: ASTNodeJSON[];
17
+ }
18
+
19
+ /**
20
+ * Represents a list of nodes.
21
+ */
22
+ export class ListNode extends ASTNode<ListNodeJSON> {
23
+ static kind: string = ASTKind.ListNode;
24
+
25
+ protected _list: ASTNode[];
26
+
27
+ /**
28
+ * The list of nodes.
29
+ */
30
+ get list(): ASTNode[] {
31
+ return this._list;
32
+ }
33
+
34
+ /**
35
+ * Deserializes the `ListNodeJSON` to the `ListNode`.
36
+ * @param json The `ListNodeJSON` to deserialize.
37
+ */
38
+ fromJSON({ list }: ListNodeJSON): void {
39
+ // Children that exceed the length need to be destroyed.
40
+ this._list.slice(list.length).forEach((_item) => {
41
+ _item.dispose();
42
+ this.fireChange();
43
+ });
44
+
45
+ // Processing of remaining children.
46
+ this._list = list.map((_item, idx) => {
47
+ const prevItem = this._list[idx];
48
+
49
+ if (prevItem.kind !== _item.kind) {
50
+ prevItem.dispose();
51
+ this.fireChange();
52
+ return this.createChildNode(_item);
53
+ }
54
+
55
+ prevItem.fromJSON(_item);
56
+ return prevItem;
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Serialize the `ListNode` to `ListNodeJSON`.
62
+ * @returns The JSON representation of `ListNode`.
63
+ */
64
+ toJSON() {
65
+ return {
66
+ kind: ASTKind.ListNode,
67
+ list: this._list.map((item) => item.toJSON()),
68
+ };
69
+ }
70
+ }