seroval 1.0.0 → 1.0.3

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.
@@ -48,29 +48,36 @@ import type {
48
48
  } from '../types';
49
49
  import { isValidIdentifier } from '../utils/is-valid-identifier';
50
50
 
51
+ const enum AssignmentType {
52
+ Index = 0,
53
+ Add = 1,
54
+ Set = 2,
55
+ Delete = 3,
56
+ }
57
+
51
58
  interface IndexAssignment {
52
- t: 'index';
59
+ t: AssignmentType.Index;
53
60
  s: string;
54
61
  k: undefined;
55
62
  v: string;
56
63
  }
57
64
 
58
65
  interface SetAssignment {
59
- t: 'set';
66
+ t: AssignmentType.Set;
60
67
  s: string;
61
68
  k: string;
62
69
  v: string;
63
70
  }
64
71
 
65
72
  interface AddAssignment {
66
- t: 'add';
73
+ t: AssignmentType.Add;
67
74
  s: string;
68
75
  k: undefined;
69
76
  v: string;
70
77
  }
71
78
 
72
79
  interface DeleteAssignment {
73
- t: 'delete';
80
+ t: AssignmentType.Delete;
74
81
  s: string;
75
82
  k: string;
76
83
  v: undefined;
@@ -90,16 +97,14 @@ export interface FlaggedObject {
90
97
 
91
98
  function getAssignmentExpression(assignment: Assignment): string {
92
99
  switch (assignment.t) {
93
- case 'index':
100
+ case AssignmentType.Index:
94
101
  return assignment.s + '=' + assignment.v;
95
- case 'set':
102
+ case AssignmentType.Set:
96
103
  return assignment.s + '.set(' + assignment.k + ',' + assignment.v + ')';
97
- case 'add':
104
+ case AssignmentType.Add:
98
105
  return assignment.s + '.add(' + assignment.v + ')';
99
- case 'delete':
106
+ case AssignmentType.Delete:
100
107
  return assignment.s + '.delete(' + assignment.k + ')';
101
- default:
102
- return '';
103
108
  }
104
109
  }
105
110
 
@@ -112,35 +117,35 @@ function mergeAssignments(assignments: Assignment[]): Assignment[] {
112
117
  i++
113
118
  ) {
114
119
  item = assignments[i];
115
- if (item.t === 'index' && item.v === prev.v) {
120
+ if (item.t === AssignmentType.Index && item.v === prev.v) {
116
121
  // Merge if the right-hand value is the same
117
122
  // saves at least 2 chars
118
123
  current = {
119
- t: 'index',
124
+ t: AssignmentType.Index,
120
125
  s: item.s,
121
126
  k: undefined,
122
127
  v: getAssignmentExpression(current),
123
128
  } as IndexAssignment;
124
- } else if (item.t === 'set' && item.s === prev.s) {
129
+ } else if (item.t === AssignmentType.Set && item.s === prev.s) {
125
130
  // Maps has chaining methods, merge if source is the same
126
131
  current = {
127
- t: 'set',
132
+ t: AssignmentType.Set,
128
133
  s: getAssignmentExpression(current),
129
134
  k: item.k,
130
135
  v: item.v,
131
136
  } as SetAssignment;
132
- } else if (item.t === 'add' && item.s === prev.s) {
137
+ } else if (item.t === AssignmentType.Add && item.s === prev.s) {
133
138
  // Sets has chaining methods too
134
139
  current = {
135
- t: 'add',
140
+ t: AssignmentType.Add,
136
141
  s: getAssignmentExpression(current),
137
142
  k: undefined,
138
143
  v: item.v,
139
144
  } as AddAssignment;
140
- } else if (item.t === 'delete' && item.s === prev.s) {
145
+ } else if (item.t === AssignmentType.Delete && item.s === prev.s) {
141
146
  // Maps has chaining methods, merge if source is the same
142
147
  current = {
143
- t: 'delete',
148
+ t: AssignmentType.Delete,
144
149
  s: getAssignmentExpression(current),
145
150
  k: item.k,
146
151
  v: undefined,
@@ -319,7 +324,7 @@ export default abstract class BaseSerializerContext
319
324
  */
320
325
  protected createAssignment(source: string, value: string): void {
321
326
  this.assignments.push({
322
- t: 'index',
327
+ t: AssignmentType.Index,
323
328
  s: source,
324
329
  k: undefined,
325
330
  v: value,
@@ -328,7 +333,7 @@ export default abstract class BaseSerializerContext
328
333
 
329
334
  protected createAddAssignment(ref: number, value: string): void {
330
335
  this.assignments.push({
331
- t: 'add',
336
+ t: AssignmentType.Add,
332
337
  s: this.getRefParam(ref),
333
338
  k: undefined,
334
339
  v: value,
@@ -337,7 +342,7 @@ export default abstract class BaseSerializerContext
337
342
 
338
343
  protected createSetAssignment(ref: number, key: string, value: string): void {
339
344
  this.assignments.push({
340
- t: 'set',
345
+ t: AssignmentType.Set,
341
346
  s: this.getRefParam(ref),
342
347
  k: key,
343
348
  v: value,
@@ -346,7 +351,7 @@ export default abstract class BaseSerializerContext
346
351
 
347
352
  protected createDeleteAssignment(ref: number, key: string): void {
348
353
  this.assignments.push({
349
- t: 'delete',
354
+ t: AssignmentType.Delete,
350
355
  s: this.getRefParam(ref),
351
356
  k: key,
352
357
  v: undefined,
@@ -416,11 +421,11 @@ export default abstract class BaseSerializerContext
416
421
  const id = node.i;
417
422
  if (node.l) {
418
423
  this.stack.push(id);
424
+ const list = node.a;
425
+ let values = this.serializeArrayItem(id, list[0], 0);
419
426
  // This is different than Map and Set
420
427
  // because we also need to serialize
421
428
  // the holes of the Array
422
- const list = node.a;
423
- let values = this.serializeArrayItem(id, list[0], 0);
424
429
  let isHoley = values === '';
425
430
  for (let i = 1, len = node.l, item: string; i < len; i++) {
426
431
  item = this.serializeArrayItem(id, list[i], i);
@@ -441,12 +446,24 @@ export default abstract class BaseSerializerContext
441
446
  ): string {
442
447
  if (typeof key === 'string') {
443
448
  const check = Number(key);
444
- // Test if key is a valid number or JS identifier
445
- // so that we don't have to serialize the key and wrap with brackets
446
- const isIdentifier = check >= 0 || isValidIdentifier(key);
449
+ const isIdentifier = (
450
+ // Test if key is a valid positive number or JS identifier
451
+ // so that we don't have to serialize the key and wrap with brackets
452
+ check >= 0
453
+ // It's also important to consider that if the key is
454
+ // indeed numeric, we need to make sure that when
455
+ // converted back into a string, it's still the same
456
+ // to the original key. This allows us to differentiate
457
+ // keys that has numeric formats but in a different
458
+ // format, which can cause unintentional key declaration
459
+ // Example: { 0x1: 1 } vs { '0x1': 1 }
460
+ && check.toString() === key
461
+ ) || isValidIdentifier(key);
447
462
  if (this.isIndexedValueInStack(val)) {
448
463
  const refParam = this.getRefParam((val as SerovalIndexedValueNode).i);
449
464
  this.markRef(source.i);
465
+ // Strict identifier check, make sure
466
+ // that it isn't numeric (except NaN)
450
467
  if (isIdentifier && check !== check) {
451
468
  this.createObjectAssign(source.i, key, refParam);
452
469
  } else {
@@ -469,9 +486,9 @@ export default abstract class BaseSerializerContext
469
486
  ): string {
470
487
  const len = record.s;
471
488
  if (len) {
472
- this.stack.push(source.i);
473
489
  const keys = record.k;
474
490
  const values = record.v;
491
+ this.stack.push(source.i);
475
492
  let result = this.serializeProperty(source, keys[0], values[0]);
476
493
  for (let i = 1, item = result; i < len; i++) {
477
494
  item = this.serializeProperty(source, keys[i], values[i]);
@@ -511,10 +528,22 @@ export default abstract class BaseSerializerContext
511
528
  ): void {
512
529
  const serialized = this.serialize(value);
513
530
  const check = Number(key);
514
- // Test if key is a valid number or JS identifier
515
- // so that we don't have to serialize the key and wrap with brackets
516
- const isIdentifier = check >= 0 || isValidIdentifier(key);
531
+ const isIdentifier = (
532
+ // Test if key is a valid positive number or JS identifier
533
+ // so that we don't have to serialize the key and wrap with brackets
534
+ check >= 0
535
+ // It's also important to consider that if the key is
536
+ // indeed numeric, we need to make sure that when
537
+ // converted back into a string, it's still the same
538
+ // to the original key. This allows us to differentiate
539
+ // keys that has numeric formats but in a different
540
+ // format, which can cause unintentional key declaration
541
+ // Example: { 0x1: 1 } vs { '0x1': 1 }
542
+ && check.toString() === key
543
+ ) || isValidIdentifier(key);
517
544
  if (this.isIndexedValueInStack(value)) {
545
+ // Strict identifier check, make sure
546
+ // that it isn't numeric (except NaN)
518
547
  if (isIdentifier && check !== check) {
519
548
  this.createObjectAssign(source.i, key, serialized);
520
549
  } else {
@@ -527,7 +556,7 @@ export default abstract class BaseSerializerContext
527
556
  } else {
528
557
  const parentAssignment = this.assignments;
529
558
  this.assignments = mainAssignments;
530
- if (isIdentifier) {
559
+ if (isIdentifier && check !== check) {
531
560
  this.createObjectAssign(source.i, key, serialized);
532
561
  } else {
533
562
  this.createArrayAssign(
@@ -566,10 +595,10 @@ export default abstract class BaseSerializerContext
566
595
  ): string | undefined {
567
596
  const len = node.s;
568
597
  if (len) {
569
- this.stack.push(source.i);
570
598
  const mainAssignments: Assignment[] = [];
571
599
  const keys = node.k;
572
600
  const values = node.v;
601
+ this.stack.push(source.i);
573
602
  for (let i = 0; i < len; i++) {
574
603
  this.serializeAssignment(source, mainAssignments, keys[i], values[i]);
575
604
  }
@@ -795,15 +824,17 @@ export default abstract class BaseSerializerContext
795
824
  }
796
825
 
797
826
  protected serializeAggregateError(node: SerovalAggregateErrorNode): string {
798
- // Serialize the required arguments
799
827
  const id = node.i;
800
- this.stack.push(id);
801
- const serialized = 'new AggregateError([],"' + node.m + '")';
802
- this.stack.pop();
803
828
  // `AggregateError` might've been extended
804
829
  // either through class or custom properties
805
830
  // Make sure to assign extra properties
806
- return this.serializeDictionary(node, serialized);
831
+ this.stack.push(id);
832
+ const serialized = this.serializeDictionary(
833
+ node,
834
+ 'new AggregateError([],"' + node.m + '")',
835
+ );
836
+ this.stack.pop();
837
+ return serialized;
807
838
  }
808
839
 
809
840
  protected serializeError(node: SerovalErrorNode): string {
@@ -846,21 +877,6 @@ export default abstract class BaseSerializerContext
846
877
  return this.assignIndexedValue(node.i, SYMBOL_STRING[node.s]);
847
878
  }
848
879
 
849
- protected serializeFormDataEntry(
850
- id: number,
851
- key: string,
852
- value: SerovalNode,
853
- ): string {
854
- return (
855
- this.getRefParam(id) +
856
- '.append("' +
857
- key +
858
- '",' +
859
- this.serialize(value) +
860
- ')'
861
- );
862
- }
863
-
864
880
  protected serializeBoxed(node: SerovalBoxedNode): string {
865
881
  return this.assignIndexedValue(
866
882
  node.i,
@@ -943,10 +959,10 @@ export default abstract class BaseSerializerContext
943
959
  case SpecialReference.StreamConstructor:
944
960
  return this.createFunction(
945
961
  ['b', 'a', 's', 'l', 'p', 'f', 'e', 'n'],
946
- '(b=[],a=!0,s=!1,l=[],s=0,f=' +
962
+ '(b=[],a=!0,s=!1,l=[],p=0,f=' +
947
963
  this.createEffectfulFunction(
948
964
  ['v', 'm', 'x'],
949
- 'for(x=0;x<s;x++)l[x]&&l[x][m](v)',
965
+ 'for(x=0;x<p;x++)l[x]&&l[x][m](v)',
950
966
  ) +
951
967
  ',n=' +
952
968
  this.createEffectfulFunction(