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