seroval 0.4.0-alpha.13 → 0.4.0-alpha.15

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.
@@ -7,7 +7,6 @@ import {
7
7
  markRef,
8
8
  } from '../context';
9
9
  import quote from '../quote';
10
- import { isReferenceInStack } from './shared';
11
10
  import {
12
11
  SerovalAggregateErrorNode,
13
12
  SerovalArrayNode,
@@ -20,18 +19,19 @@ import {
20
19
  SerovalNullConstructorNode,
21
20
  SerovalObjectRecordNode,
22
21
  SerovalPromiseNode,
22
+ SerovalReferenceNode,
23
23
  SerovalSetNode,
24
24
  SerovalTypedArrayNode,
25
25
  } from './types';
26
26
 
27
27
  function getAssignmentExpression(assignment: Assignment): string {
28
- switch (assignment.type) {
28
+ switch (assignment.t) {
29
29
  case 'index':
30
- return `${assignment.source}=${assignment.value}`;
30
+ return assignment.s + '=' + assignment.v;
31
31
  case 'map':
32
- return `${assignment.source}.set(${assignment.key},${assignment.value})`;
32
+ return assignment.s + '.set(' + assignment.k + ',' + assignment.v + ')';
33
33
  case 'set':
34
- return `${assignment.source}.add(${assignment.value})`;
34
+ return assignment.s + '.add(' + assignment.v + ')';
35
35
  default:
36
36
  return '';
37
37
  }
@@ -41,31 +41,34 @@ function mergeAssignments(assignments: Assignment[]) {
41
41
  const newAssignments = [];
42
42
  let current = assignments[0];
43
43
  let prev = current;
44
+ let item: Assignment;
44
45
  for (let i = 1, len = assignments.length; i < len; i++) {
45
- const item = assignments[i];
46
- if (item.type === prev.type) {
47
- if (item.type === 'index' && item.value === prev.value) {
46
+ item = assignments[i];
47
+ if (item.t === prev.t) {
48
+ if (item.t === 'index' && item.v === prev.v) {
48
49
  // Merge if the right-hand value is the same
49
50
  // saves at least 2 chars
50
51
  current = {
51
- type: 'index',
52
- source: item.source,
53
- value: getAssignmentExpression(current),
52
+ t: 'index',
53
+ s: item.s,
54
+ k: undefined,
55
+ v: getAssignmentExpression(current),
54
56
  };
55
- } else if (item.type === 'map' && item.source === prev.source) {
57
+ } else if (item.t === 'map' && item.s === prev.s) {
56
58
  // Maps has chaining methods, merge if source is the same
57
59
  current = {
58
- type: 'map',
59
- source: getAssignmentExpression(current),
60
- key: item.key,
61
- value: item.value,
60
+ t: 'map',
61
+ s: getAssignmentExpression(current),
62
+ k: item.k,
63
+ v: item.v,
62
64
  };
63
- } else if (item.type === 'set' && item.source === prev.source) {
65
+ } else if (item.t === 'set' && item.s === prev.s) {
64
66
  // Sets has chaining methods too
65
67
  current = {
66
- type: 'set',
67
- source: getAssignmentExpression(current),
68
- value: item.value,
68
+ t: 'set',
69
+ s: getAssignmentExpression(current),
70
+ k: undefined,
71
+ v: item.v,
69
72
  };
70
73
  } else {
71
74
  // Different assignment, push current
@@ -89,7 +92,7 @@ function resolveAssignments(assignments: Assignment[]) {
89
92
  let result = '';
90
93
  const merged = mergeAssignments(assignments);
91
94
  for (let i = 0, len = merged.length; i < len; i++) {
92
- result += `${getAssignmentExpression(merged[i])},`;
95
+ result += getAssignmentExpression(merged[i]) + ',';
93
96
  }
94
97
  return result;
95
98
  }
@@ -105,16 +108,6 @@ export function resolvePatches(ctx: SerializationContext) {
105
108
  * This is different from the assignments array as this one
106
109
  * signifies creation rather than mutation
107
110
  */
108
- function assignRef(
109
- ctx: SerializationContext,
110
- index: number,
111
- value: string,
112
- ) {
113
- if (ctx.markedRefs[index]) {
114
- return `${getRefParam(ctx, index)}=${value}`;
115
- }
116
- return value;
117
- }
118
111
 
119
112
  function createAssignment(
120
113
  ctx: SerializationContext,
@@ -122,9 +115,10 @@ function createAssignment(
122
115
  value: string,
123
116
  ) {
124
117
  ctx.assignments.push({
125
- type: 'index',
126
- source,
127
- value,
118
+ t: 'index',
119
+ s: source,
120
+ k: undefined,
121
+ v: value,
128
122
  });
129
123
  }
130
124
 
@@ -135,9 +129,10 @@ function createSetAdd(
135
129
  ) {
136
130
  markRef(ctx, ref);
137
131
  ctx.assignments.push({
138
- type: 'set',
139
- source: getRefParam(ctx, ref),
140
- value,
132
+ t: 'set',
133
+ s: getRefParam(ctx, ref),
134
+ k: undefined,
135
+ v: value,
141
136
  });
142
137
  }
143
138
 
@@ -149,10 +144,10 @@ function createMapSet(
149
144
  ) {
150
145
  markRef(ctx, ref);
151
146
  ctx.assignments.push({
152
- type: 'map',
153
- source: getRefParam(ctx, ref),
154
- key,
155
- value,
147
+ t: 'map',
148
+ s: getRefParam(ctx, ref),
149
+ k: key,
150
+ v: value,
156
151
  });
157
152
  }
158
153
 
@@ -163,7 +158,7 @@ function createArrayAssign(
163
158
  value: string,
164
159
  ) {
165
160
  markRef(ctx, ref);
166
- createAssignment(ctx, `${getRefParam(ctx, ref)}[${index}]`, value);
161
+ createAssignment(ctx, getRefParam(ctx, ref) + '[' + index.toString() + ']', value);
167
162
  }
168
163
 
169
164
  function createObjectAssign(
@@ -173,379 +168,412 @@ function createObjectAssign(
173
168
  value: string,
174
169
  ) {
175
170
  markRef(ctx, ref);
176
- createAssignment(ctx, `${getRefParam(ctx, ref)}.${key}`, value);
171
+ createAssignment(ctx, getRefParam(ctx, ref) + '.' + key, value);
177
172
  }
178
173
 
179
- const IDENTIFIER_CHECK = /^([$A-Z_][0-9A-Z_$]*)$/i;
180
-
181
- function serializeAssignments(
174
+ function assignRef(
182
175
  ctx: SerializationContext,
183
- targetRef: number,
184
- node: SerovalObjectRecordNode,
176
+ index: number,
177
+ value: string,
185
178
  ) {
186
- ctx.stack.push(targetRef);
187
- const mainAssignments: Assignment[] = [];
188
- for (let i = 0; i < node.s; i++) {
189
- const parentStack = ctx.stack;
190
- ctx.stack = [];
191
- const refParam = serializeTree(ctx, node.v[i]);
192
- ctx.stack = parentStack;
193
- const key = node.k[i];
194
- const check = Number(key);
195
- const parentAssignment = ctx.assignments;
196
- ctx.assignments = mainAssignments;
197
- // Test if key is a valid number or JS identifier
198
- // so that we don't have to serialize the key and wrap with brackets
199
- const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
200
- if (isIdentifier && Number.isNaN(check)) {
201
- createObjectAssign(ctx, targetRef, key, refParam);
202
- } else {
203
- createArrayAssign(ctx, targetRef, isIdentifier ? key : quote(key), refParam);
204
- }
205
- ctx.assignments = parentAssignment;
179
+ if (ctx.markedRefs.has(index)) {
180
+ return getRefParam(ctx, index) + '=' + value;
206
181
  }
207
- ctx.stack.pop();
208
- return resolveAssignments(mainAssignments);
182
+ return value;
209
183
  }
210
184
 
211
- function serializeObject(
185
+ function isReferenceInStack(
212
186
  ctx: SerializationContext,
213
- sourceID: number,
214
- node: SerovalObjectRecordNode,
215
- ) {
216
- if (node.s === 0) {
217
- return '{}';
218
- }
219
- let result = '';
220
- ctx.stack.push(sourceID);
221
- for (let i = 0; i < node.s; i++) {
222
- const key = node.k[i];
223
- const val = node.v[i];
224
- const check = Number(key);
225
- // Test if key is a valid number or JS identifier
226
- // so that we don't have to serialize the key and wrap with brackets
227
- const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
228
- if (isReferenceInStack(ctx, val)) {
229
- const refParam = getRefParam(ctx, val.i);
230
- if (isIdentifier && Number.isNaN(check)) {
231
- createObjectAssign(ctx, sourceID, key, refParam);
187
+ node: SerovalNode,
188
+ ): node is SerovalReferenceNode {
189
+ return node.t === SerovalNodeType.Reference && ctx.stack.includes(node.i);
190
+ }
191
+
192
+ const IDENTIFIER_CHECK = /^([$A-Z_][0-9A-Z_$]*)$/i;
193
+
194
+ export default class SerovalSerializer {
195
+ static serializeNodeList(
196
+ ctx: SerializationContext,
197
+ node: SerovalArrayNode | SerovalIterableNode,
198
+ ) {
199
+ // This is different than Map and Set
200
+ // because we also need to serialize
201
+ // the holes of the Array
202
+ const size = node.a.length;
203
+ let values = '';
204
+ let item: SerovalNode;
205
+ let isHoley = false;
206
+ for (let i = 0; i < size; i++) {
207
+ if (i !== 0) {
208
+ // Add an empty item
209
+ values += ',';
210
+ }
211
+ item = node.a[i];
212
+ // Check if index is a hole
213
+ if (item) {
214
+ // Check if item is a parent
215
+ if (isReferenceInStack(ctx, item)) {
216
+ createArrayAssign(ctx, node.i, i, getRefParam(ctx, item.i));
217
+ isHoley = true;
218
+ } else {
219
+ values += this.serialize(ctx, item);
220
+ isHoley = false;
221
+ }
232
222
  } else {
233
- createArrayAssign(ctx, sourceID, isIdentifier ? key : quote(key), refParam);
223
+ isHoley = true;
234
224
  }
235
- } else {
236
- result += `${isIdentifier ? key : quote(key)}:${serializeTree(ctx, val)},`;
237
225
  }
226
+ if (isHoley) {
227
+ values += ',';
228
+ }
229
+ return '[' + values + ']';
238
230
  }
239
- ctx.stack.pop();
240
- return `{${result.substring(0, result.length - 1)}}`;
241
- }
242
231
 
243
- function serializePromise(
244
- ctx: SerializationContext,
245
- node: SerovalPromiseNode,
246
- ) {
247
- let serialized: string;
248
- // Check if resolved value is a parent expression
249
- if (isReferenceInStack(ctx, node.n)) {
250
- // A Promise trick, reference the value
251
- // inside the `then` expression so that
252
- // the Promise evaluates after the parent
253
- // has initialized
254
- serialized = `Promise.resolve().then(()=>${getRefParam(ctx, node.n.i)})`;
255
- } else {
232
+ static serializeArray(
233
+ ctx: SerializationContext,
234
+ node: SerovalArrayNode,
235
+ ) {
256
236
  ctx.stack.push(node.i);
257
- const result = serializeTree(ctx, node.n);
237
+ const result = this.serializeNodeList(ctx, node);
258
238
  ctx.stack.pop();
259
- // just inline the value/reference here
260
- serialized = `Promise.resolve(${result})`;
239
+ return assignRef(ctx, node.i, result);
261
240
  }
262
- return assignRef(ctx, node.i, serialized);
263
- }
264
241
 
265
- function serializeTypedArray(
266
- ctx: SerializationContext,
267
- node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
268
- ) {
269
- let args = `[${node.s}]`;
270
- if (node.l !== 0) {
271
- args += `,${node.l}`;
272
- }
273
- return assignRef(ctx, node.i, `new ${node.c}(${args})`);
274
- }
275
-
276
- function serializeSet(
277
- ctx: SerializationContext,
278
- node: SerovalSetNode,
279
- ) {
280
- let serialized = 'new Set';
281
- const size = node.a.length;
282
- if (size) {
242
+ static serializeObject(
243
+ ctx: SerializationContext,
244
+ sourceID: number,
245
+ node: SerovalObjectRecordNode,
246
+ ) {
247
+ if (node.s === 0) {
248
+ return '{}';
249
+ }
283
250
  let result = '';
284
- ctx.stack.push(node.i);
285
- for (let i = 0; i < size; i++) {
286
- const item = node.a[i];
287
- if (isReferenceInStack(ctx, item)) {
288
- createSetAdd(ctx, node.i, getRefParam(ctx, item.i));
251
+ ctx.stack.push(sourceID);
252
+ let key: string;
253
+ let val: SerovalNode;
254
+ let check: number;
255
+ let isIdentifier: boolean;
256
+ let refParam: string;
257
+ let hasPrev = false;
258
+ for (let i = 0; i < node.s; i++) {
259
+ key = node.k[i];
260
+ val = node.v[i];
261
+ check = Number(key);
262
+ // Test if key is a valid number or JS identifier
263
+ // so that we don't have to serialize the key and wrap with brackets
264
+ isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
265
+ if (isReferenceInStack(ctx, val)) {
266
+ refParam = getRefParam(ctx, val.i);
267
+ if (isIdentifier && Number.isNaN(check)) {
268
+ createObjectAssign(ctx, sourceID, key, refParam);
269
+ } else {
270
+ createArrayAssign(ctx, sourceID, isIdentifier ? key : quote(key), refParam);
271
+ }
289
272
  } else {
290
- // Push directly
291
- result += `${serializeTree(ctx, item)},`;
273
+ if (hasPrev) {
274
+ result += ',';
275
+ }
276
+ result += isIdentifier ? key : quote(key);
277
+ result += ':' + this.serialize(ctx, val);
278
+ hasPrev = true;
292
279
  }
293
280
  }
294
281
  ctx.stack.pop();
295
- if (result) {
296
- serialized += `([${result.substring(0, result.length - 1)}])`;
282
+ return '{' + result + '}';
283
+ }
284
+
285
+ static serializeWithObjectAssign(
286
+ ctx: SerializationContext,
287
+ value: SerovalObjectRecordNode,
288
+ id: number,
289
+ serialized: string,
290
+ ) {
291
+ const fields = this.serializeObject(ctx, id, value);
292
+ if (fields !== '{}') {
293
+ return 'Object.assign(' + serialized + ',' + fields + ')';
297
294
  }
295
+ return serialized;
298
296
  }
299
- return assignRef(ctx, node.i, serialized);
300
- }
301
297
 
302
- function serializeMap(
303
- ctx: SerializationContext,
304
- node: SerovalMapNode,
305
- ) {
306
- let serialized = 'new Map';
307
- if (node.d.s) {
308
- let result = '';
309
- ctx.stack.push(node.i);
310
- for (let i = 0; i < node.d.s; i++) {
311
- // Check if key is a parent
312
- const key = node.d.k[i];
313
- const val = node.d.v[i];
314
- if (isReferenceInStack(ctx, key)) {
315
- // Create reference for the map instance
316
- const keyRef = getRefParam(ctx, key.i);
317
- // Check if value is a parent
318
- if (isReferenceInStack(ctx, val)) {
319
- const valueRef = getRefParam(ctx, val.i);
320
- // Register an assignment since
321
- // both key and value are a parent of this
322
- // Map instance
323
- createMapSet(ctx, node.i, keyRef, valueRef);
324
- } else {
325
- // Reset the stack
326
- // This is required because the serialized
327
- // value is no longer part of the expression
328
- // tree and has been moved to the deferred
329
- // assignment
330
- const parent = ctx.stack;
331
- ctx.stack = [];
332
- createMapSet(ctx, node.i, keyRef, serializeTree(ctx, val));
333
- ctx.stack = parent;
334
- }
335
- } else if (isReferenceInStack(ctx, val)) {
336
- // Create ref for the Map instance
337
- const valueRef = getRefParam(ctx, val.i);
338
- // Reset stack for the key serialization
339
- const parent = ctx.stack;
340
- ctx.stack = [];
341
- createMapSet(ctx, node.i, serializeTree(ctx, key), valueRef);
342
- ctx.stack = parent;
298
+ static serializeAssignments(
299
+ ctx: SerializationContext,
300
+ sourceID: number,
301
+ node: SerovalObjectRecordNode,
302
+ ) {
303
+ ctx.stack.push(sourceID);
304
+ const mainAssignments: Assignment[] = [];
305
+ let parentStack: number[];
306
+ let refParam: string;
307
+ let key: string;
308
+ let check: number;
309
+ let parentAssignment: Assignment[];
310
+ let isIdentifier: boolean;
311
+ for (let i = 0; i < node.s; i++) {
312
+ parentStack = ctx.stack;
313
+ ctx.stack = [];
314
+ refParam = this.serialize(ctx, node.v[i]);
315
+ ctx.stack = parentStack;
316
+ key = node.k[i];
317
+ check = Number(key);
318
+ parentAssignment = ctx.assignments;
319
+ ctx.assignments = mainAssignments;
320
+ // Test if key is a valid number or JS identifier
321
+ // so that we don't have to serialize the key and wrap with brackets
322
+ isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
323
+ if (isIdentifier && Number.isNaN(check)) {
324
+ createObjectAssign(ctx, sourceID, key, refParam);
343
325
  } else {
344
- result += `[${serializeTree(ctx, key)},${serializeTree(ctx, val)}],`;
326
+ createArrayAssign(ctx, sourceID, isIdentifier ? key : quote(key), refParam);
345
327
  }
328
+ ctx.assignments = parentAssignment;
346
329
  }
347
330
  ctx.stack.pop();
348
- // Check if there are any values
349
- // so that the empty Map constructor
350
- // can be used instead
351
- if (result) {
352
- serialized += `([${result.substring(0, result.length - 1)}])`;
353
- }
331
+ return resolveAssignments(mainAssignments);
354
332
  }
355
- return assignRef(ctx, node.i, serialized);
356
- }
357
333
 
358
- function serializeNodeList(
359
- ctx: SerializationContext,
360
- node: SerovalArrayNode | SerovalIterableNode,
361
- ) {
362
- // This is different than Map and Set
363
- // because we also need to serialize
364
- // the holes of the Array
365
- const size = node.a.length;
366
- let values = '';
367
- for (let i = 0; i < size; i++) {
368
- const item = node.a[i];
369
- // Check if index is a hole
370
- if (item) {
371
- // Check if item is a parent
372
- if (isReferenceInStack(ctx, item)) {
373
- createArrayAssign(ctx, node.i, i, getRefParam(ctx, item.i));
374
- values += ',';
334
+ static serializeDictionary(
335
+ ctx: SerializationContext,
336
+ i: number,
337
+ d: SerovalObjectRecordNode | undefined,
338
+ init: string,
339
+ ) {
340
+ if (d) {
341
+ if (ctx.features & Feature.ObjectAssign) {
342
+ init = this.serializeWithObjectAssign(ctx, d, i, init);
375
343
  } else {
376
- values += serializeTree(ctx, item);
377
- if (i < size - 1) {
378
- values += ',';
344
+ markRef(ctx, i);
345
+ const assignments = this.serializeAssignments(ctx, i, d);
346
+ if (assignments) {
347
+ const ref = getRefParam(ctx, i);
348
+ return '(' + assignRef(ctx, i, init) + ',' + assignments + ref + ')';
379
349
  }
380
350
  }
381
- } else {
382
- // Add an empty item
383
- values += ',';
384
351
  }
352
+ return assignRef(ctx, i, init);
385
353
  }
386
- return `[${values}]`;
387
- }
388
-
389
- function serializeArray(
390
- ctx: SerializationContext,
391
- node: SerovalArrayNode,
392
- ) {
393
- ctx.stack.push(node.i);
394
- const result = serializeNodeList(ctx, node);
395
- ctx.stack.pop();
396
- return assignRef(ctx, node.i, result);
397
- }
398
354
 
399
- function serializeWithObjectAssign(
400
- ctx: SerializationContext,
401
- value: SerovalObjectRecordNode,
402
- id: number,
403
- serialized: string,
404
- ) {
405
- const fields = serializeObject(ctx, id, value);
406
- if (fields !== '{}') {
407
- return `Object.assign(${serialized},${fields})`;
355
+ static serializeNullConstructor(
356
+ ctx: SerializationContext,
357
+ node: SerovalNullConstructorNode,
358
+ ) {
359
+ return this.serializeDictionary(ctx, node.i, node.d, 'Object.create(null)');
408
360
  }
409
- return serialized;
410
- }
411
361
 
412
- function serializeAggregateError(
413
- ctx: SerializationContext,
414
- node: SerovalAggregateErrorNode,
415
- ) {
416
- // Serialize the required arguments
417
- ctx.stack.push(node.i);
418
- let serialized = `new AggregateError(${serializeTree(ctx, node.n)},${quote(node.m)})`;
419
- ctx.stack.pop();
420
- // `AggregateError` might've been extended
421
- // either through class or custom properties
422
- // Make sure to assign extra properties
423
- if (node.d) {
424
- if (ctx.features & Feature.ObjectAssign) {
425
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
426
- } else {
427
- markRef(ctx, node.i);
428
- const assignments = serializeAssignments(ctx, node.i, node.d);
429
- if (assignments) {
430
- const ref = getRefParam(ctx, node.i);
431
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
362
+ static serializeSet(
363
+ ctx: SerializationContext,
364
+ node: SerovalSetNode,
365
+ ) {
366
+ let serialized = 'new Set';
367
+ const size = node.a.length;
368
+ if (size) {
369
+ let result = '';
370
+ ctx.stack.push(node.i);
371
+ let item: SerovalNode;
372
+ let hasPrev = false;
373
+ for (let i = 0; i < size; i++) {
374
+ item = node.a[i];
375
+ if (isReferenceInStack(ctx, item)) {
376
+ createSetAdd(ctx, node.i, getRefParam(ctx, item.i));
377
+ } else {
378
+ // Push directly
379
+ if (hasPrev) {
380
+ result += ',';
381
+ }
382
+ result += this.serialize(ctx, item);
383
+ hasPrev = true;
384
+ }
385
+ }
386
+ ctx.stack.pop();
387
+ if (result) {
388
+ serialized += '([' + result + '])';
432
389
  }
433
390
  }
391
+ return assignRef(ctx, node.i, serialized);
434
392
  }
435
- return assignRef(ctx, node.i, serialized);
436
- }
437
393
 
438
- function serializeError(
439
- ctx: SerializationContext,
440
- node: SerovalErrorNode,
441
- ) {
442
- let serialized = `new ${node.c}(${quote(node.m)})`;
443
- if (node.d) {
444
- if (ctx.features & Feature.ObjectAssign) {
445
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
446
- } else {
447
- markRef(ctx, node.i);
448
- const assignments = serializeAssignments(ctx, node.i, node.d);
449
- if (assignments) {
450
- const ref = getRefParam(ctx, node.i);
451
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
394
+ static serializeMap(
395
+ ctx: SerializationContext,
396
+ node: SerovalMapNode,
397
+ ) {
398
+ let serialized = 'new Map';
399
+ if (node.d.s) {
400
+ let result = '';
401
+ ctx.stack.push(node.i);
402
+ let key: SerovalNode;
403
+ let val: SerovalNode;
404
+ let keyRef: string;
405
+ let valueRef: string;
406
+ let parent: number[];
407
+ let hasPrev = false;
408
+ for (let i = 0; i < node.d.s; i++) {
409
+ // Check if key is a parent
410
+ key = node.d.k[i];
411
+ val = node.d.v[i];
412
+ if (isReferenceInStack(ctx, key)) {
413
+ // Create reference for the map instance
414
+ keyRef = getRefParam(ctx, key.i);
415
+ // Check if value is a parent
416
+ if (isReferenceInStack(ctx, val)) {
417
+ valueRef = getRefParam(ctx, val.i);
418
+ // Register an assignment since
419
+ // both key and value are a parent of this
420
+ // Map instance
421
+ createMapSet(ctx, node.i, keyRef, valueRef);
422
+ } else {
423
+ // Reset the stack
424
+ // This is required because the serialized
425
+ // value is no longer part of the expression
426
+ // tree and has been moved to the deferred
427
+ // assignment
428
+ parent = ctx.stack;
429
+ ctx.stack = [];
430
+ createMapSet(ctx, node.i, keyRef, this.serialize(ctx, val));
431
+ ctx.stack = parent;
432
+ }
433
+ } else if (isReferenceInStack(ctx, val)) {
434
+ // Create ref for the Map instance
435
+ valueRef = getRefParam(ctx, val.i);
436
+ // Reset stack for the key serialization
437
+ parent = ctx.stack;
438
+ ctx.stack = [];
439
+ createMapSet(ctx, node.i, this.serialize(ctx, key), valueRef);
440
+ ctx.stack = parent;
441
+ } else {
442
+ if (hasPrev) {
443
+ result += ',';
444
+ }
445
+ result += '[' + this.serialize(ctx, key) + ',' + this.serialize(ctx, val) + ']';
446
+ hasPrev = true;
447
+ }
448
+ }
449
+ ctx.stack.pop();
450
+ // Check if there are any values
451
+ // so that the empty Map constructor
452
+ // can be used instead
453
+ if (result) {
454
+ serialized += '([' + result + '])';
452
455
  }
453
456
  }
457
+ return assignRef(ctx, node.i, serialized);
454
458
  }
455
- return assignRef(ctx, node.i, serialized);
456
- }
457
459
 
458
- function serializeIterable(
459
- ctx: SerializationContext,
460
- node: SerovalIterableNode,
461
- ) {
462
- const parent = ctx.stack;
463
- ctx.stack = [];
464
- const values = serializeNodeList(ctx, node);
465
- ctx.stack = parent;
466
- let serialized: string;
467
- if (ctx.features & Feature.ArrayPrototypeValues) {
468
- serialized = `${values}.values()`;
469
- } else {
470
- serialized = `${values}[Symbol.iterator]()`;
460
+ static serializeAggregateError(
461
+ ctx: SerializationContext,
462
+ node: SerovalAggregateErrorNode,
463
+ ) {
464
+ // Serialize the required arguments
465
+ ctx.stack.push(node.i);
466
+ const serialized = 'new AggregateError(' + this.serialize(ctx, node.n) + ',' + quote(node.m) + ')';
467
+ ctx.stack.pop();
468
+ // `AggregateError` might've been extended
469
+ // either through class or custom properties
470
+ // Make sure to assign extra properties
471
+ return this.serializeDictionary(ctx, node.i, node.d, serialized);
471
472
  }
472
- if (ctx.features & Feature.ArrowFunction) {
473
- serialized = `{[Symbol.iterator]:()=>${serialized}}`;
474
- } else if (ctx.features & Feature.MethodShorthand) {
475
- serialized = `{[Symbol.iterator](){return ${serialized}}}`;
476
- } else {
477
- serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
473
+
474
+ static serializeError(
475
+ ctx: SerializationContext,
476
+ node: SerovalErrorNode,
477
+ ) {
478
+ const serialized = 'new ' + node.c + '(' + quote(node.m) + ')';
479
+ return this.serializeDictionary(ctx, node.i, node.d, serialized);
478
480
  }
479
- if (node.d) {
480
- if (ctx.features & Feature.ObjectAssign) {
481
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
481
+
482
+ static serializePromise(
483
+ ctx: SerializationContext,
484
+ node: SerovalPromiseNode,
485
+ ) {
486
+ let serialized: string;
487
+ // Check if resolved value is a parent expression
488
+ if (isReferenceInStack(ctx, node.n)) {
489
+ // A Promise trick, reference the value
490
+ // inside the `then` expression so that
491
+ // the Promise evaluates after the parent
492
+ // has initialized
493
+ serialized = 'Promise.resolve().then(()=>' + getRefParam(ctx, node.n.i) + ')';
482
494
  } else {
483
- markRef(ctx, node.i);
484
- const assignments = serializeAssignments(ctx, node.i, node.d);
485
- if (assignments) {
486
- const ref = getRefParam(ctx, node.i);
487
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
488
- }
495
+ ctx.stack.push(node.i);
496
+ const result = this.serialize(ctx, node.n);
497
+ ctx.stack.pop();
498
+ // just inline the value/reference here
499
+ serialized = 'Promise.resolve(' + result + ')';
489
500
  }
501
+ return assignRef(ctx, node.i, serialized);
490
502
  }
491
- return assignRef(ctx, node.i, serialized);
492
- }
493
503
 
494
- function serializeNullConstructor(
495
- ctx: SerializationContext,
496
- node: SerovalNullConstructorNode,
497
- ) {
498
- let serialized = 'Object.create(null)';
499
- if (ctx.features & Feature.ObjectAssign) {
500
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
501
- } else {
502
- markRef(ctx, node.i);
503
- const assignments = serializeAssignments(ctx, node.i, node.d);
504
- if (assignments) {
505
- const ref = getRefParam(ctx, node.i);
506
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
504
+ static serializeTypedArray(
505
+ ctx: SerializationContext,
506
+ node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
507
+ ) {
508
+ let args = '[' + node.s + ']';
509
+ if (node.l !== 0) {
510
+ args += ',' + node.l.toString();
507
511
  }
512
+ return assignRef(ctx, node.i, 'new ' + node.c + '(' + args + ')');
508
513
  }
509
- return assignRef(ctx, node.i, serialized);
510
- }
511
514
 
512
- export default function serializeTree(
513
- ctx: SerializationContext,
514
- node: SerovalNode,
515
- ): string {
516
- switch (node.t) {
517
- case SerovalNodeType.Primitive:
518
- return String(node.s);
519
- case SerovalNodeType.Reference:
520
- return getRefParam(ctx, node.i);
521
- case SerovalNodeType.Promise:
522
- return serializePromise(ctx, node);
523
- case SerovalNodeType.BigInt:
524
- return node.s;
525
- case SerovalNodeType.Date:
526
- return assignRef(ctx, node.i, `new Date("${node.s}")`);
527
- case SerovalNodeType.RegExp:
528
- return assignRef(ctx, node.i, node.s);
529
- case SerovalNodeType.BigIntTypedArray:
530
- case SerovalNodeType.TypedArray:
531
- return serializeTypedArray(ctx, node);
532
- case SerovalNodeType.Set:
533
- return serializeSet(ctx, node);
534
- case SerovalNodeType.Map:
535
- return serializeMap(ctx, node);
536
- case SerovalNodeType.Array:
537
- return serializeArray(ctx, node);
538
- case SerovalNodeType.AggregateError:
539
- return serializeAggregateError(ctx, node);
540
- case SerovalNodeType.Error:
541
- return serializeError(ctx, node);
542
- case SerovalNodeType.Iterable:
543
- return serializeIterable(ctx, node);
544
- case SerovalNodeType.NullConstructor:
545
- return serializeNullConstructor(ctx, node);
546
- case SerovalNodeType.Object:
547
- return assignRef(ctx, node.i, serializeObject(ctx, node.i, node.d));
548
- default:
549
- throw new Error('Unsupported type');
515
+ static serializeIterable(
516
+ ctx: SerializationContext,
517
+ node: SerovalIterableNode,
518
+ ) {
519
+ const parent = ctx.stack;
520
+ ctx.stack = [];
521
+ const values = this.serializeNodeList(ctx, node);
522
+ ctx.stack = parent;
523
+ let serialized = values;
524
+ if (ctx.features & Feature.ArrayPrototypeValues) {
525
+ serialized += '.values()';
526
+ } else {
527
+ serialized += '[Symbol.iterator]()';
528
+ }
529
+ if (ctx.features & Feature.ArrowFunction) {
530
+ serialized = '{[Symbol.iterator]:()=>' + serialized + '}';
531
+ } else if (ctx.features & Feature.MethodShorthand) {
532
+ serialized = '{[Symbol.iterator](){return ' + serialized + '}}';
533
+ } else {
534
+ serialized = '{[Symbol.iterator]:function(){return ' + serialized + '}}';
535
+ }
536
+ return this.serializeDictionary(ctx, node.i, node.d, serialized);
537
+ }
538
+
539
+ static serialize(
540
+ ctx: SerializationContext,
541
+ node: SerovalNode,
542
+ ): string {
543
+ switch (node.t) {
544
+ case SerovalNodeType.Primitive:
545
+ return String(node.s);
546
+ case SerovalNodeType.BigInt:
547
+ return node.s;
548
+ case SerovalNodeType.Reference:
549
+ return getRefParam(ctx, node.i);
550
+ case SerovalNodeType.Array:
551
+ return this.serializeArray(ctx, node);
552
+ case SerovalNodeType.Object:
553
+ return assignRef(ctx, node.i, this.serializeObject(ctx, node.i, node.d));
554
+ case SerovalNodeType.NullConstructor:
555
+ return this.serializeNullConstructor(ctx, node);
556
+ case SerovalNodeType.Date:
557
+ return assignRef(ctx, node.i, 'new Date("' + node.s + '")');
558
+ case SerovalNodeType.RegExp:
559
+ return assignRef(ctx, node.i, node.s);
560
+ case SerovalNodeType.Set:
561
+ return this.serializeSet(ctx, node);
562
+ case SerovalNodeType.Map:
563
+ return this.serializeMap(ctx, node);
564
+ case SerovalNodeType.BigIntTypedArray:
565
+ case SerovalNodeType.TypedArray:
566
+ return this.serializeTypedArray(ctx, node);
567
+ case SerovalNodeType.AggregateError:
568
+ return this.serializeAggregateError(ctx, node);
569
+ case SerovalNodeType.Error:
570
+ return this.serializeError(ctx, node);
571
+ case SerovalNodeType.Iterable:
572
+ return this.serializeIterable(ctx, node);
573
+ case SerovalNodeType.Promise:
574
+ return this.serializePromise(ctx, node);
575
+ default:
576
+ throw new Error('Unsupported type');
577
+ }
550
578
  }
551
579
  }