@@ -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 ) {
@@ -369,11 +377,30 @@ namespace ts {
369
377
370
378
function transformClassMembers ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
371
379
// Declare private names.
372
- const privateNamedProperties = filter ( node . members , isPrivateNamedPropertyDeclaration ) ;
373
- privateNamedProperties . forEach ( property => addPrivateNameToEnvironment ( property . name ) ) ;
374
-
380
+ const privateNamedMembers = node . members
381
+ . filter ( element => isNamedDeclaration ( element ) && isPrivateName ( element . name ) ) ;
382
+ privateNamedMembers . forEach ( addPrivateName ) ;
383
+
384
+ pendingExpressions = pendingExpressions || [ ] ;
385
+ last ( privateNameEnvironmentStack ) . forEach ( entry => {
386
+ const { placement } = entry ;
387
+ switch ( placement ) {
388
+ case PrivateNamePlacement . InstanceField :
389
+ break ;
390
+ case PrivateNamePlacement . InstanceMethod :
391
+ entry = entry as PrivateNamedInstanceMethod ;
392
+ const func = privateNamedMethodToFunction ( entry . origFunc , entry . funcName , entry . accumulator ) ;
393
+ ( pendingExpressions = pendingExpressions || [ ] ) . push ( createAssignment (
394
+ entry . funcName ,
395
+ func
396
+ ) ) ;
397
+ break ;
398
+ default :
399
+ Debug . assertNever ( placement , "unexpected Private Name Placement" ) ;
400
+ }
401
+ } ) ;
375
402
const members : ClassElement [ ] = [ ] ;
376
- const constructor = transformConstructor ( node , isDerivedClass ) ;
403
+ const constructor = transformConstructor ( node , isDerivedClass , ! ! privateNamedMembers . length ) ;
377
404
if ( constructor ) {
378
405
members . push ( constructor ) ;
379
406
}
@@ -382,17 +409,24 @@ namespace ts {
382
409
return setTextRange ( createNodeArray ( members ) , /*location*/ node . members ) ;
383
410
}
384
411
385
- function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
412
+ function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean , declaresPrivateNames : boolean ) {
386
413
const constructor = visitNode ( getFirstConstructorWithBody ( node ) , visitor , isConstructorDeclaration ) ;
387
- const containsPropertyInitializerOrPrivateProperty = forEach (
388
- node . members ,
389
- member => isInitializedProperty ( member ) || isPrivateNamedPropertyDeclaration ( member )
390
- ) ;
391
- if ( ! containsPropertyInitializerOrPrivateProperty ) {
392
- return constructor ;
414
+ const containsPropertyInitializer = forEach ( node . members , isInitializedProperty ) ;
415
+ let body = constructor ? constructor . body : undefined ;
416
+ let parameters = constructor ? constructor . parameters : undefined ;
417
+ const shouldTransformConstructorBody = containsPropertyInitializer || declaresPrivateNames ;
418
+ if ( shouldTransformConstructorBody ) {
419
+ if ( containsPropertyInitializer ) {
420
+ parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
421
+ }
422
+ else {
423
+ // provide a scope for hoisted declarations for WeakSet or WeakMap for private name brand checks
424
+ // not needed if `visitParameterList` was called, because that starts/suspends the lexical environment
425
+ context . startLexicalEnvironment ( ) ;
426
+ context . suspendLexicalEnvironment ( ) ;
427
+ }
428
+ body = transformConstructorBody ( node , constructor , isDerivedClass , declaresPrivateNames ) ;
393
429
}
394
- const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
395
- const body = transformConstructorBody ( node , constructor , isDerivedClass ) ;
396
430
if ( ! body ) {
397
431
return undefined ;
398
432
}
@@ -402,7 +436,7 @@ namespace ts {
402
436
createConstructor (
403
437
/*decorators*/ undefined ,
404
438
/*modifiers*/ undefined ,
405
- parameters ,
439
+ parameters || [ ] ,
406
440
body
407
441
) ,
408
442
constructor || node
@@ -412,18 +446,25 @@ namespace ts {
412
446
) ;
413
447
}
414
448
415
- function transformConstructorBody ( node : ClassDeclaration | ClassExpression , constructor : ConstructorDeclaration | undefined , isDerivedClass : boolean ) {
449
+ function transformConstructorBody (
450
+ node : ClassDeclaration | ClassExpression ,
451
+ constructor : ConstructorDeclaration | undefined ,
452
+ isDerivedClass : boolean ,
453
+ classDeclaresPrivateNames : boolean ,
454
+ ) {
416
455
const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
417
456
418
- // Only generate synthetic constructor when there are property declarations to move.
419
- if ( ! constructor && ! some ( properties ) ) {
457
+ // Only generate synthetic constructor when there are property or private name declarations to move
458
+ if ( ! constructor && ! some ( properties ) && ! classDeclaresPrivateNames ) {
420
459
return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
421
460
}
422
461
462
+ let statements : Statement [ ] = [ ] ;
463
+
464
+ // was suspended to provide a scope for parameter properties and/or private names
423
465
resumeLexicalEnvironment ( ) ;
424
466
425
467
let indexOfFirstStatement = 0 ;
426
- let statements : Statement [ ] = [ ] ;
427
468
428
469
if ( ! constructor && isDerivedClass ) {
429
470
// Add a synthetic `super` call:
@@ -456,6 +497,26 @@ namespace ts {
456
497
// }
457
498
//
458
499
addInitializedPropertyStatements ( statements , properties , createThis ( ) ) ;
500
+ if ( classDeclaresPrivateNames ) {
501
+ last ( privateNameEnvironmentStack ) . forEach ( ( { placement, accumulator } ) => {
502
+ switch ( placement ) {
503
+ case PrivateNamePlacement . InstanceField :
504
+ // TODO: instance field add accumulator
505
+ break ;
506
+ case PrivateNamePlacement . InstanceMethod :
507
+ statements . push (
508
+ createExpressionStatement (
509
+ createCall (
510
+ createPropertyAccess ( accumulator , "add" ) ,
511
+ /* typeArguments */ undefined ,
512
+ [ createThis ( ) ]
513
+ )
514
+ )
515
+ ) ;
516
+ break ;
517
+ }
518
+ } ) ;
519
+ }
459
520
460
521
// Add existing statements, skipping the initial super call.
461
522
if ( constructor ) {
@@ -535,10 +596,10 @@ namespace ts {
535
596
if ( isPrivateName ( propertyName ) ) {
536
597
const privateNameInfo = accessPrivateName ( propertyName ) ;
537
598
if ( privateNameInfo ) {
538
- switch ( privateNameInfo . type ) {
539
- case PrivateNameType . InstanceField : {
599
+ switch ( privateNameInfo . placement ) {
600
+ case PrivateNamePlacement . InstanceField : {
540
601
return createCall (
541
- createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
602
+ createPropertyAccess ( privateNameInfo . accumulator , "set" ) ,
542
603
/*typeArguments*/ undefined ,
543
604
[ receiver , initializer || createVoidZero ( ) ]
544
605
) ;
@@ -564,18 +625,66 @@ namespace ts {
564
625
privateNameEnvironmentStack . pop ( ) ;
565
626
}
566
627
567
- function addPrivateNameToEnvironment ( name : PrivateName ) {
628
+ function privateNamedMethodToFunction ( declaration : MethodDeclaration , funcName : Identifier , accumulator : Identifier ) : FunctionExpression {
629
+ const params = declaration . parameters ;
630
+ let body = getMutableClone ( declaration . body || createBlock ( [ ] , true ) ) ;
631
+ body = visitEachChild ( body , visitor , context ) ;
632
+ const toPrepend = startOnNewLine (
633
+ createStatement (
634
+ createClassPrivateNamedCallCheckHelper ( context , accumulator )
635
+ )
636
+ ) ;
637
+ body . statements = setTextRange (
638
+ createNodeArray ( [
639
+ toPrepend ,
640
+ ...body . statements
641
+ ] ) ,
642
+ body . statements
643
+ ) ;
644
+ const func = createFunctionExpression (
645
+ /* modifiers */ undefined ,
646
+ /* asteriskToken */ undefined ,
647
+ funcName ,
648
+ /* typeParameters */ undefined ,
649
+ params ,
650
+ /* type */ undefined ,
651
+ body ) ;
652
+ return func ;
653
+ }
654
+
655
+
656
+ function addPrivateName ( element : ClassElement & { name : PrivateName } ) {
568
657
const env = last ( privateNameEnvironmentStack ) ;
569
- const text = getTextOfPropertyName ( name ) as string ;
570
- const weakMapName = createOptimisticUniqueName ( "_" + text . substring ( 1 ) ) ;
571
- weakMapName . autoGenerateFlags |= GeneratedIdentifierFlags . ReservedInNestedScopes ;
572
- hoistVariableDeclaration ( weakMapName ) ;
573
- env . set ( name . escapedText , { type : PrivateNameType . InstanceField , weakMapName } ) ;
574
- ( pendingExpressions || ( pendingExpressions = [ ] ) ) . push (
658
+ const text = getTextOfPropertyName ( element . name ) as string ;
659
+ const accumulator = createFileLevelUniqueName ( `_${ text . substring ( 1 ) } Private` ) ;
660
+ const { escapedText } = element . name ;
661
+ hoistVariableDeclaration ( accumulator ) ;
662
+
663
+ let identifierName : string ;
664
+ if ( hasModifier ( element , ModifierFlags . Static ) ) {
665
+ // statics not supported yet
666
+ return ;
667
+ }
668
+ if ( isPropertyDeclaration ( element ) ) {
669
+ identifierName = "WeakMap" ;
670
+ env . set ( escapedText , { placement : PrivateNamePlacement . InstanceField , accumulator } ) ;
671
+ }
672
+ else if ( isMethodDeclaration ( element ) ) {
673
+ identifierName = "WeakSet" ;
674
+ const escapedText = element . name . escapedText ;
675
+ const escapedTextNoHash = `_${ `${ escapedText } ` . slice ( 1 ) } ` ;
676
+ const funcName : Identifier = createFileLevelUniqueName ( escapedTextNoHash ) ;
677
+ env . set ( escapedText , { placement : PrivateNamePlacement . InstanceMethod , accumulator, funcName, origFunc : element } ) ;
678
+ hoistVariableDeclaration ( funcName ) ; // todo: hoist in lexical, not func scope
679
+ }
680
+ else {
681
+ return ;
682
+ }
683
+ ( pendingExpressions = pendingExpressions || [ ] ) . push (
575
684
createAssignment (
576
- weakMapName ,
685
+ accumulator ,
577
686
createNew (
578
- createIdentifier ( "WeakMap" ) ,
687
+ createIdentifier ( identifierName ) ,
579
688
/*typeArguments*/ undefined ,
580
689
[ ]
581
690
)
@@ -597,14 +706,14 @@ namespace ts {
597
706
if ( isPrivateName ( node . name ) ) {
598
707
const privateNameInfo = accessPrivateName ( node . name ) ;
599
708
if ( privateNameInfo ) {
600
- switch ( privateNameInfo . type ) {
601
- case PrivateNameType . InstanceField :
709
+ switch ( privateNameInfo . placement ) {
710
+ case PrivateNamePlacement . InstanceField :
602
711
return setOriginalNode (
603
712
setTextRange (
604
713
createClassPrivateFieldGetHelper (
605
714
context ,
606
- visitNode ( node . expression , visitor , isExpression ) ,
607
- privateNameInfo . weakMapName
715
+ visitNode ( node . expression , visitor , isExpression ) ,
716
+ privateNameInfo . accumulator
608
717
) ,
609
718
node
610
719
) ,
@@ -699,6 +808,23 @@ namespace ts {
699
808
) ;
700
809
receiver = generatedName ;
701
810
}
811
+ const privateNameEntry = last ( privateNameEnvironmentStack ) . get ( node . expression . name . escapedText ) ;
812
+ if ( privateNameEntry && privateNameEntry . placement === PrivateNamePlacement . InstanceMethod ) {
813
+ return setOriginalNode (
814
+ setTextRange (
815
+ createCall (
816
+ createPropertyAccess (
817
+ privateNameEntry . funcName ,
818
+ "call"
819
+ ) ,
820
+ /*typeArguments*/ undefined ,
821
+ [ createThis ( ) , ...node . arguments ]
822
+ ) ,
823
+ /* location */ node
824
+ ) ,
825
+ node
826
+ ) ;
827
+ }
702
828
return visitNode (
703
829
updateCall (
704
830
node ,
@@ -980,7 +1106,7 @@ namespace ts {
980
1106
}
981
1107
else if ( isAssignmentExpression ( node ) && isPropertyAccessExpression ( node . left ) && isPrivateName ( node . left . name ) ) {
982
1108
const privateNameInfo = accessPrivateName ( node . left . name ) ;
983
- if ( privateNameInfo && privateNameInfo . type === PrivateNameType . InstanceField ) {
1109
+ if ( privateNameInfo && privateNameInfo . placement === PrivateNamePlacement . InstanceField ) {
984
1110
if ( isCompoundAssignment ( node . operatorToken . kind ) ) {
985
1111
const isReceiverInlineable = isSimpleInlineableExpression ( node . left . expression ) ;
986
1112
const getReceiver = isReceiverInlineable ? node . left . expression : createTempVariable ( hoistVariableDeclaration ) ;
@@ -991,12 +1117,12 @@ namespace ts {
991
1117
createClassPrivateFieldSetHelper (
992
1118
context ,
993
1119
setReceiver ,
994
- privateNameInfo . weakMapName ,
1120
+ privateNameInfo . accumulator ,
995
1121
createBinary (
996
1122
createClassPrivateFieldGetHelper (
997
1123
context ,
998
1124
getReceiver ,
999
- privateNameInfo . weakMapName
1125
+ privateNameInfo . accumulator
1000
1126
) ,
1001
1127
getOperatorForCompoundAssignment ( node . operatorToken . kind ) ,
1002
1128
visitNode ( node . right , visitor )
@@ -1010,7 +1136,7 @@ namespace ts {
1010
1136
createClassPrivateFieldSetHelper (
1011
1137
context ,
1012
1138
node . left . expression ,
1013
- privateNameInfo . weakMapName ,
1139
+ privateNameInfo . accumulator ,
1014
1140
visitNode ( node . right , visitor )
1015
1141
) ,
1016
1142
node
@@ -1329,6 +1455,9 @@ namespace ts {
1329
1455
function visitMethodDeclaration ( node : MethodDeclaration ) {
1330
1456
const savedEnclosingFunctionFlags = enclosingFunctionFlags ;
1331
1457
enclosingFunctionFlags = getFunctionFlags ( node ) ;
1458
+ if ( isPrivateName ( node . name ) ) {
1459
+ return [ ] ;
1460
+ }
1332
1461
const updated = updateMethod (
1333
1462
node ,
1334
1463
/*decorators*/ undefined ,
@@ -1866,4 +1995,15 @@ namespace ts {
1866
1995
context . requestEmitHelper ( classPrivateFieldSetHelper ) ;
1867
1996
return createCall ( getHelperName ( "_classPrivateFieldSet" ) , /* typeArguments */ undefined , [ receiver , privateField , value ] ) ;
1868
1997
}
1998
+ const classPrivateNamedCallCheckHelper : EmitHelper = {
1999
+ name : "typescript:classPrivateNamedCallCheck" ,
2000
+ scoped : false ,
2001
+ text : `var _classPrivateNamedCallCheck = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};`
2002
+ } ;
2003
+
2004
+ function createClassPrivateNamedCallCheckHelper ( context : TransformationContext , weakSet : Identifier ) {
2005
+ context . requestEmitHelper ( classPrivateNamedCallCheckHelper ) ;
2006
+ return createCall ( getHelperName ( "_classPrivateNamedCallCheck" ) , /* typeArguments */ undefined , [ createThis ( ) , weakSet ] ) ;
2007
+ }
2008
+
1869
2009
}
0 commit comments