@@ -13,18 +13,26 @@ namespace ts {
13
13
/**
14
14
* A mapping of private names to information needed for transformation.
15
15
*/
16
- type PrivateNameEnvironment = UnderscoreEscapedMap < PrivateNamedInstanceField > ;
16
+ type PrivateNameEnvironment = UnderscoreEscapedMap < PrivateNamedInstanceField | PrivateNamedInstanceMethod > ;
17
17
18
18
/**
19
19
* Identifies the type of private name.
20
20
*/
21
- const enum PrivateNameType {
22
- InstanceField
21
+ const enum PrivateNamePlacement {
22
+ InstanceField ,
23
+ InstanceMethod
23
24
}
24
25
25
26
interface PrivateNamedInstanceField {
26
- type : PrivateNameType . InstanceField ;
27
- weakMapName : Identifier ;
27
+ placement : PrivateNamePlacement . InstanceField ;
28
+ accumulator : Identifier ;
29
+ }
30
+
31
+ interface PrivateNamedInstanceMethod {
32
+ placement : PrivateNamePlacement . InstanceMethod ;
33
+ accumulator : Identifier ;
34
+ origFunc : MethodDeclaration ;
35
+ funcName : Identifier ;
28
36
}
29
37
30
38
export function transformESNext ( context : TransformationContext ) {
@@ -365,11 +373,30 @@ namespace ts {
365
373
366
374
function transformClassMembers ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
367
375
// Declare private names.
368
- const privateProperties = filter ( node . members , isPrivatePropertyDeclaration ) ;
369
- privateProperties . forEach ( property => addPrivateNameToEnvironment ( property . name ) ) ;
370
-
376
+ const privateNamedMembers = node . members
377
+ . filter ( element => isNamedDeclaration ( element ) && isPrivateName ( element . name ) ) ;
378
+ privateNamedMembers . forEach ( addPrivateName ) ;
379
+
380
+ pendingExpressions = pendingExpressions || [ ] ;
381
+ last ( privateNameEnvironmentStack ) . forEach ( entry => {
382
+ const { placement } = entry ;
383
+ switch ( placement ) {
384
+ case PrivateNamePlacement . InstanceField :
385
+ break ;
386
+ case PrivateNamePlacement . InstanceMethod :
387
+ entry = entry as PrivateNamedInstanceMethod ;
388
+ const func = privateNamedMethodToFunction ( entry . origFunc , entry . funcName , entry . accumulator ) ;
389
+ ( pendingExpressions = pendingExpressions || [ ] ) . push ( createAssignment (
390
+ entry . funcName ,
391
+ func
392
+ ) ) ;
393
+ break ;
394
+ default :
395
+ Debug . assertNever ( placement , "unexpected Private Name Placement" ) ;
396
+ }
397
+ } ) ;
371
398
const members : ClassElement [ ] = [ ] ;
372
- const constructor = transformConstructor ( node , isDerivedClass ) ;
399
+ const constructor = transformConstructor ( node , isDerivedClass , ! ! privateNamedMembers . length ) ;
373
400
if ( constructor ) {
374
401
members . push ( constructor ) ;
375
402
}
@@ -378,14 +405,24 @@ namespace ts {
378
405
return setTextRange ( createNodeArray ( members ) , /*location*/ node . members ) ;
379
406
}
380
407
381
- function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
408
+ function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean , declaresPrivateNames : boolean ) {
382
409
const constructor = visitNode ( getFirstConstructorWithBody ( node ) , visitor , isConstructorDeclaration ) ;
383
410
const containsPropertyInitializer = forEach ( node . members , isInitializedProperty ) ;
384
- if ( ! containsPropertyInitializer ) {
385
- return constructor ;
411
+ let body = constructor ? constructor . body : undefined ;
412
+ let parameters = constructor ? constructor . parameters : undefined ;
413
+ const shouldTransformConstructorBody = containsPropertyInitializer || declaresPrivateNames ;
414
+ if ( shouldTransformConstructorBody ) {
415
+ if ( containsPropertyInitializer ) {
416
+ parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
417
+ }
418
+ else {
419
+ // provide a scope for hoisted declarations for WeakSet or WeakMap for private name brand checks
420
+ // not needed if `visitParameterList` was called, because that starts/suspends the lexical environment
421
+ context . startLexicalEnvironment ( ) ;
422
+ context . suspendLexicalEnvironment ( ) ;
423
+ }
424
+ body = transformConstructorBody ( node , constructor , isDerivedClass , declaresPrivateNames ) ;
386
425
}
387
- const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
388
- const body = transformConstructorBody ( node , constructor , isDerivedClass ) ;
389
426
if ( ! body ) {
390
427
return undefined ;
391
428
}
@@ -395,7 +432,7 @@ namespace ts {
395
432
createConstructor (
396
433
/*decorators*/ undefined ,
397
434
/*modifiers*/ undefined ,
398
- parameters ,
435
+ parameters || [ ] ,
399
436
body
400
437
) ,
401
438
constructor || node
@@ -405,18 +442,25 @@ namespace ts {
405
442
) ;
406
443
}
407
444
408
- function transformConstructorBody ( node : ClassDeclaration | ClassExpression , constructor : ConstructorDeclaration | undefined , isDerivedClass : boolean ) {
445
+ function transformConstructorBody (
446
+ node : ClassDeclaration | ClassExpression ,
447
+ constructor : ConstructorDeclaration | undefined ,
448
+ isDerivedClass : boolean ,
449
+ classDeclaresPrivateNames : boolean ,
450
+ ) {
409
451
const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
410
452
411
- // Only generate synthetic constructor when there are property declarations to move.
412
- if ( ! constructor && ! some ( properties ) ) {
453
+ // Only generate synthetic constructor when there are property or private name declarations to move
454
+ if ( ! constructor && ! some ( properties ) && ! classDeclaresPrivateNames ) {
413
455
return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
414
456
}
415
457
458
+ let statements : Statement [ ] = [ ] ;
459
+
460
+ // was suspended to provide a scope for parameter properties and/or private names
416
461
resumeLexicalEnvironment ( ) ;
417
462
418
463
let indexOfFirstStatement = 0 ;
419
- let statements : Statement [ ] = [ ] ;
420
464
421
465
if ( ! constructor && isDerivedClass ) {
422
466
// Add a synthetic `super` call:
@@ -449,6 +493,26 @@ namespace ts {
449
493
// }
450
494
//
451
495
addInitializedPropertyStatements ( statements , properties , createThis ( ) ) ;
496
+ if ( classDeclaresPrivateNames ) {
497
+ last ( privateNameEnvironmentStack ) . forEach ( ( { placement, accumulator } ) => {
498
+ switch ( placement ) {
499
+ case PrivateNamePlacement . InstanceField :
500
+ // TODO: instance field add accumulator
501
+ break ;
502
+ case PrivateNamePlacement . InstanceMethod :
503
+ statements . push (
504
+ createExpressionStatement (
505
+ createCall (
506
+ createPropertyAccess ( accumulator , "add" ) ,
507
+ /* typeArguments */ undefined ,
508
+ [ createThis ( ) ]
509
+ )
510
+ )
511
+ ) ;
512
+ break ;
513
+ }
514
+ } ) ;
515
+ }
452
516
453
517
// Add existing statements, skipping the initial super call.
454
518
if ( constructor ) {
@@ -528,10 +592,10 @@ namespace ts {
528
592
if ( isPrivateName ( propertyName ) ) {
529
593
const privateNameInfo = accessPrivateName ( propertyName ) ;
530
594
if ( privateNameInfo ) {
531
- switch ( privateNameInfo . type ) {
532
- case PrivateNameType . InstanceField : {
595
+ switch ( privateNameInfo . placement ) {
596
+ case PrivateNamePlacement . InstanceField : {
533
597
return createCall (
534
- createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
598
+ createPropertyAccess ( privateNameInfo . accumulator , "set" ) ,
535
599
/*typeArguments*/ undefined ,
536
600
[ receiver , initializer || createVoidZero ( ) ]
537
601
) ;
@@ -557,17 +621,66 @@ namespace ts {
557
621
privateNameEnvironmentStack . pop ( ) ;
558
622
}
559
623
560
- function addPrivateNameToEnvironment ( name : PrivateName ) {
624
+ function privateNamedMethodToFunction ( declaration : MethodDeclaration , funcName : Identifier , accumulator : Identifier ) : FunctionExpression {
625
+ const params = declaration . parameters ;
626
+ let body = getMutableClone ( declaration . body || createBlock ( [ ] , true ) ) ;
627
+ body = visitEachChild ( body , visitor , context ) ;
628
+ const toPrepend = startOnNewLine (
629
+ createStatement (
630
+ createClassPrivateNamedCallCheckHelper ( context , accumulator )
631
+ )
632
+ ) ;
633
+ body . statements = setTextRange (
634
+ createNodeArray ( [
635
+ toPrepend ,
636
+ ...body . statements
637
+ ] ) ,
638
+ body . statements
639
+ ) ;
640
+ const func = createFunctionExpression (
641
+ /* modifiers */ undefined ,
642
+ /* asteriskToken */ undefined ,
643
+ funcName ,
644
+ /* typeParameters */ undefined ,
645
+ params ,
646
+ /* type */ undefined ,
647
+ body ) ;
648
+ return func ;
649
+ }
650
+
651
+
652
+ function addPrivateName ( element : ClassElement & { name : PrivateName } ) {
561
653
const env = last ( privateNameEnvironmentStack ) ;
562
- const text = getTextOfPropertyName ( name ) as string ;
563
- const weakMapName = createFileLevelUniqueName ( "_" + text . substring ( 1 ) ) ;
564
- hoistVariableDeclaration ( weakMapName ) ;
565
- env . set ( name . escapedText , { type : PrivateNameType . InstanceField , weakMapName } ) ;
566
- ( pendingExpressions || ( pendingExpressions = [ ] ) ) . push (
654
+ const text = getTextOfPropertyName ( element . name ) as string ;
655
+ const accumulator = createFileLevelUniqueName ( `_${ text . substring ( 1 ) } Private` ) ;
656
+ const { escapedText } = element . name ;
657
+ hoistVariableDeclaration ( accumulator ) ;
658
+
659
+ let identifierName : string ;
660
+ if ( hasModifier ( element , ModifierFlags . Static ) ) {
661
+ // statics not supported yet
662
+ return ;
663
+ }
664
+ if ( isPropertyDeclaration ( element ) ) {
665
+ identifierName = "WeakMap" ;
666
+ env . set ( escapedText , { placement : PrivateNamePlacement . InstanceField , accumulator } ) ;
667
+ }
668
+ else if ( isMethodDeclaration ( element ) ) {
669
+ identifierName = "WeakSet" ;
670
+ const escapedText = element . name . escapedText ;
671
+ const escapedTextNoHash = `_${ `${ escapedText } ` . slice ( 1 ) } ` ;
672
+ const funcName : Identifier = createFileLevelUniqueName ( escapedTextNoHash ) ;
673
+ env . set ( escapedText , { placement : PrivateNamePlacement . InstanceMethod , accumulator, funcName, origFunc : element } ) ;
674
+ hoistVariableDeclaration ( funcName ) ; // todo: hoist in lexical, not func scope
675
+ }
676
+ else {
677
+ return ;
678
+ }
679
+ ( pendingExpressions = pendingExpressions || [ ] ) . push (
567
680
createAssignment (
568
- weakMapName ,
681
+ accumulator ,
569
682
createNew (
570
- createIdentifier ( "WeakMap" ) ,
683
+ createIdentifier ( identifierName ) ,
571
684
/*typeArguments*/ undefined ,
572
685
[ ]
573
686
)
@@ -589,14 +702,14 @@ namespace ts {
589
702
if ( isPrivateName ( node . name ) ) {
590
703
const privateNameInfo = accessPrivateName ( node . name ) ;
591
704
if ( privateNameInfo ) {
592
- switch ( privateNameInfo . type ) {
593
- case PrivateNameType . InstanceField :
705
+ switch ( privateNameInfo . placement ) {
706
+ case PrivateNamePlacement . InstanceField :
594
707
return setOriginalNode (
595
708
setTextRange (
596
709
createClassPrivateFieldGetHelper (
597
710
context ,
598
711
visitNode ( node . expression , visitor , isExpression ) ,
599
- privateNameInfo . weakMapName
712
+ privateNameInfo . accumulator
600
713
) ,
601
714
node
602
715
) ,
@@ -622,6 +735,23 @@ namespace ts {
622
735
) ;
623
736
receiver = generatedName ;
624
737
}
738
+ const privateNameEntry = last ( privateNameEnvironmentStack ) . get ( node . expression . name . escapedText ) ;
739
+ if ( privateNameEntry && privateNameEntry . placement === PrivateNamePlacement . InstanceMethod ) {
740
+ return setOriginalNode (
741
+ setTextRange (
742
+ createCall (
743
+ createPropertyAccess (
744
+ privateNameEntry . funcName ,
745
+ "call"
746
+ ) ,
747
+ /*typeArguments*/ undefined ,
748
+ [ createThis ( ) , ...node . arguments ]
749
+ ) ,
750
+ /* location */ node
751
+ ) ,
752
+ node
753
+ ) ;
754
+ }
625
755
return visitNode (
626
756
updateCall (
627
757
node ,
@@ -903,7 +1033,7 @@ namespace ts {
903
1033
}
904
1034
else if ( isAssignmentExpression ( node ) && isPropertyAccessExpression ( node . left ) && isPrivateName ( node . left . name ) ) {
905
1035
const privateNameInfo = accessPrivateName ( node . left . name ) ;
906
- if ( privateNameInfo && privateNameInfo . type === PrivateNameType . InstanceField ) {
1036
+ if ( privateNameInfo && privateNameInfo . placement === PrivateNamePlacement . InstanceField ) {
907
1037
if ( isCompoundAssignment ( node . operatorToken . kind ) ) {
908
1038
const isReceiverInlineable = isSimpleInlineableExpression ( node . left . expression ) ;
909
1039
const getReceiver = isReceiverInlineable ? node . left . expression : createTempVariable ( hoistVariableDeclaration ) ;
@@ -914,12 +1044,12 @@ namespace ts {
914
1044
createClassPrivateFieldSetHelper (
915
1045
context ,
916
1046
setReceiver ,
917
- privateNameInfo . weakMapName ,
1047
+ privateNameInfo . accumulator ,
918
1048
createBinary (
919
1049
createClassPrivateFieldGetHelper (
920
1050
context ,
921
1051
getReceiver ,
922
- privateNameInfo . weakMapName
1052
+ privateNameInfo . accumulator
923
1053
) ,
924
1054
getOperatorForCompoundAssignment ( node . operatorToken . kind ) ,
925
1055
visitNode ( node . right , visitor )
@@ -933,7 +1063,7 @@ namespace ts {
933
1063
createClassPrivateFieldSetHelper (
934
1064
context ,
935
1065
node . left . expression ,
936
- privateNameInfo . weakMapName ,
1066
+ privateNameInfo . accumulator ,
937
1067
visitNode ( node . right , visitor )
938
1068
) ,
939
1069
node
@@ -1252,6 +1382,9 @@ namespace ts {
1252
1382
function visitMethodDeclaration ( node : MethodDeclaration ) {
1253
1383
const savedEnclosingFunctionFlags = enclosingFunctionFlags ;
1254
1384
enclosingFunctionFlags = getFunctionFlags ( node ) ;
1385
+ if ( isPrivateName ( node . name ) ) {
1386
+ return [ ] ;
1387
+ }
1255
1388
const updated = updateMethod (
1256
1389
node ,
1257
1390
/*decorators*/ undefined ,
@@ -1789,4 +1922,15 @@ namespace ts {
1789
1922
context . requestEmitHelper ( classPrivateFieldSetHelper ) ;
1790
1923
return createCall ( getHelperName ( "_classPrivateFieldSet" ) , /* typeArguments */ undefined , [ receiver , privateField , value ] ) ;
1791
1924
}
1925
+ const classPrivateNamedCallCheckHelper : EmitHelper = {
1926
+ name : "typescript:classPrivateNamedCallCheck" ,
1927
+ scoped : false ,
1928
+ text : `var _classPrivateNamedCallCheck = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};`
1929
+ } ;
1930
+
1931
+ function createClassPrivateNamedCallCheckHelper ( context : TransformationContext , weakSet : Identifier ) {
1932
+ context . requestEmitHelper ( classPrivateNamedCallCheckHelper ) ;
1933
+ return createCall ( getHelperName ( "_classPrivateNamedCallCheck" ) , /* typeArguments */ undefined , [ createThis ( ) , weakSet ] ) ;
1934
+ }
1935
+
1792
1936
}
0 commit comments