2
2
type ArrayExpression ,
3
3
type Expression ,
4
4
type Identifier ,
5
+ type ObjectExpression ,
5
6
type Property ,
6
7
type SimpleLiteral ,
7
8
type VariableDeclarator
@@ -36,14 +37,14 @@ function literal(value: SimpleLiteral['value']): SimpleLiteral {
36
37
*
37
38
* @param object
38
39
* The object to call the method on.
39
- * @param property
40
+ * @param name
40
41
* The name of the method to call.
41
42
* @param args
42
43
* Arguments to pass to the function call
43
44
* @returns
44
45
* The call expression node.
45
46
*/
46
- function methodCall ( object : Expression , property : string , args : Expression [ ] ) : Expression {
47
+ function methodCall ( object : Expression , name : string , args : Expression [ ] ) : Expression {
47
48
return {
48
49
type : 'CallExpression' ,
49
50
optional : false ,
@@ -52,7 +53,7 @@ function methodCall(object: Expression, property: string, args: Expression[]): E
52
53
computed : false ,
53
54
optional : false ,
54
55
object,
55
- property : identifier ( property )
56
+ property : identifier ( name )
56
57
} ,
57
58
arguments : args
58
59
}
@@ -284,6 +285,59 @@ function replaceAssignment(expression: Expression, assignment: Expression | unde
284
285
return assignment
285
286
}
286
287
288
+ /**
289
+ * Create an ESTree epxression to represent a symbol. Global and well-known symbols are supported.
290
+ *
291
+ * @param symbol
292
+ * THe symbol to represent.
293
+ * @returns
294
+ * An ESTree expression to represent the symbol.
295
+ */
296
+ function symbolToEstree ( symbol : symbol ) : Expression {
297
+ const name = wellKnownSymbols . get ( symbol )
298
+ if ( name ) {
299
+ return {
300
+ type : 'MemberExpression' ,
301
+ computed : false ,
302
+ optional : false ,
303
+ object : identifier ( 'Symbol' ) ,
304
+ property : identifier ( name )
305
+ }
306
+ }
307
+
308
+ if ( symbol . description && symbol === Symbol . for ( symbol . description ) ) {
309
+ return methodCall ( identifier ( 'Symbol' ) , 'for' , [ literal ( symbol . description ) ] )
310
+ }
311
+
312
+ throw new TypeError ( `Only global symbols are supported, got: ${ String ( symbol ) } ` , {
313
+ cause : symbol
314
+ } )
315
+ }
316
+
317
+ /**
318
+ * Create an ESTree property from a key and a value expression.
319
+ *
320
+ * @param key
321
+ * The property key value
322
+ * @param value
323
+ * The property value as an ESTree expression.
324
+ * @returns
325
+ * The ESTree properry node.
326
+ */
327
+ function property ( key : string | symbol , value : Expression ) : Property {
328
+ const computed = typeof key !== 'string'
329
+
330
+ return {
331
+ type : 'Property' ,
332
+ method : false ,
333
+ shorthand : false ,
334
+ computed,
335
+ kind : 'init' ,
336
+ key : computed ? symbolToEstree ( key ) : literal ( key ) ,
337
+ value
338
+ }
339
+ }
340
+
287
341
/**
288
342
* Convert a value to an ESTree node.
289
343
*
@@ -408,22 +462,7 @@ export function valueToEstree(value: unknown, options: Options = {}): Expression
408
462
}
409
463
410
464
if ( typeof val === 'symbol' ) {
411
- const name = wellKnownSymbols . get ( val )
412
- if ( name ) {
413
- return {
414
- type : 'MemberExpression' ,
415
- computed : false ,
416
- optional : false ,
417
- object : identifier ( 'Symbol' ) ,
418
- property : identifier ( name )
419
- }
420
- }
421
-
422
- if ( val . description && val === Symbol . for ( val . description ) ) {
423
- return methodCall ( identifier ( 'Symbol' ) , 'for' , [ literal ( val . description ) ] )
424
- }
425
-
426
- throw new TypeError ( `Only global symbols are supported, got: ${ String ( val ) } ` , { cause : val } )
465
+ return symbolToEstree ( val )
427
466
}
428
467
429
468
const context = collectedContexts . get ( val )
@@ -588,24 +627,34 @@ export function valueToEstree(value: unknown, options: Options = {}): Expression
588
627
589
628
const properties : Property [ ] = [ ]
590
629
if ( Object . getPrototypeOf ( val ) == null ) {
591
- properties . push ( {
592
- type : 'Property' ,
593
- method : false ,
594
- shorthand : false ,
595
- computed : false ,
596
- kind : 'init' ,
597
- key : identifier ( '__proto__' ) ,
598
- value : literal ( null )
599
- } )
630
+ properties . push ( property ( '__proto__' , literal ( null ) ) )
600
631
}
601
632
602
633
const object = val as Record < string | symbol , unknown >
634
+ const propertyDescriptors : Property [ ] = [ ]
603
635
for ( const key of Reflect . ownKeys ( val ) ) {
604
- const computed = typeof key !== 'string'
605
- const keyExpression = generate ( key )
636
+ // TODO [>=4] Throw an error for getters.
606
637
const child = object [ key ]
638
+ const { configurable, enumerable, writable } = Object . getOwnPropertyDescriptor ( val , key ) !
607
639
const childContext = collectedContexts . get ( child )
608
- if (
640
+ if ( ! configurable || ! enumerable || ! writable ) {
641
+ const propertyDescriptor = [ property ( 'value' , generate ( child ) ) ]
642
+ if ( configurable ) {
643
+ propertyDescriptor . push ( property ( 'configurable' , literal ( true ) ) )
644
+ }
645
+ if ( enumerable ) {
646
+ propertyDescriptor . push ( property ( 'enumerable' , literal ( true ) ) )
647
+ }
648
+ if ( writable ) {
649
+ propertyDescriptor . push ( property ( 'writable' , literal ( true ) ) )
650
+ }
651
+ propertyDescriptors . push (
652
+ property ( key , {
653
+ type : 'ObjectExpression' ,
654
+ properties : propertyDescriptor
655
+ } )
656
+ )
657
+ } else if (
609
658
context &&
610
659
childContext &&
611
660
namedContexts . indexOf ( childContext ) >= namedContexts . indexOf ( context )
@@ -618,27 +667,44 @@ export function valueToEstree(value: unknown, options: Options = {}): Expression
618
667
computed : true ,
619
668
optional : false ,
620
669
object : identifier ( context . name ! ) ,
621
- property : keyExpression
670
+ property : generate ( key )
622
671
} ,
623
672
right : childContext . assignment || generate ( child )
624
673
}
625
674
} else {
626
- properties . push ( {
627
- type : 'Property' ,
628
- method : false ,
629
- shorthand : false ,
630
- computed,
631
- kind : 'init' ,
632
- key : keyExpression ,
633
- value : generate ( child )
634
- } )
675
+ properties . push ( property ( key , generate ( child ) ) )
635
676
}
636
677
}
637
678
638
- return {
679
+ const objectExpression : ObjectExpression = {
639
680
type : 'ObjectExpression' ,
640
681
properties
641
682
}
683
+
684
+ if ( propertyDescriptors . length ) {
685
+ if ( ! context ) {
686
+ return methodCall ( identifier ( 'Object' ) , 'defineProperties' , [
687
+ objectExpression ,
688
+ {
689
+ type : 'ObjectExpression' ,
690
+ properties : propertyDescriptors
691
+ }
692
+ ] )
693
+ }
694
+
695
+ context . assignment = replaceAssignment (
696
+ methodCall ( identifier ( 'Object' ) , 'defineProperties' , [
697
+ identifier ( context . name ! ) ,
698
+ {
699
+ type : 'ObjectExpression' ,
700
+ properties : propertyDescriptors
701
+ }
702
+ ] ) ,
703
+ context . assignment
704
+ )
705
+ }
706
+
707
+ return objectExpression
642
708
}
643
709
644
710
analyze ( value )
0 commit comments