@@ -13,18 +13,26 @@ namespace ts {
1313 /**
1414 * A mapping of private names to information needed for transformation.
1515 */
16- type PrivateNameEnvironment = UnderscoreEscapedMap < PrivateNamedInstanceField > ;
16+ type PrivateNameEnvironment = UnderscoreEscapedMap < PrivateNamedInstanceField | PrivateNamedInstanceMethod > ;
1717
1818 /**
1919 * Identifies the type of private name.
2020 */
21- const enum PrivateNameType {
22- InstanceField
21+ const enum PrivateNamePlacement {
22+ InstanceField ,
23+ InstanceMethod
2324 }
2425
2526 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 ;
2836 }
2937
3038 export function transformESNext ( context : TransformationContext ) {
@@ -365,11 +373,30 @@ namespace ts {
365373
366374 function transformClassMembers ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
367375 // 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+ } ) ;
371398 const members : ClassElement [ ] = [ ] ;
372- const constructor = transformConstructor ( node , isDerivedClass ) ;
399+ const constructor = transformConstructor ( node , isDerivedClass , ! ! privateNamedMembers . length ) ;
373400 if ( constructor ) {
374401 members . push ( constructor ) ;
375402 }
@@ -378,14 +405,24 @@ namespace ts {
378405 return setTextRange ( createNodeArray ( members ) , /*location*/ node . members ) ;
379406 }
380407
381- function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
408+ function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean , declaresPrivateNames : boolean ) {
382409 const constructor = visitNode ( getFirstConstructorWithBody ( node ) , visitor , isConstructorDeclaration ) ;
383410 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 ) ;
386425 }
387- const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
388- const body = transformConstructorBody ( node , constructor , isDerivedClass ) ;
389426 if ( ! body ) {
390427 return undefined ;
391428 }
@@ -395,7 +432,7 @@ namespace ts {
395432 createConstructor (
396433 /*decorators*/ undefined ,
397434 /*modifiers*/ undefined ,
398- parameters ,
435+ parameters || [ ] ,
399436 body
400437 ) ,
401438 constructor || node
@@ -405,18 +442,25 @@ namespace ts {
405442 ) ;
406443 }
407444
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+ ) {
409451 const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
410452
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 ) {
413455 return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
414456 }
415457
458+ let statements : Statement [ ] = [ ] ;
459+
460+ // was suspended to provide a scope for parameter properties and/or private names
416461 resumeLexicalEnvironment ( ) ;
417462
418463 let indexOfFirstStatement = 0 ;
419- let statements : Statement [ ] = [ ] ;
420464
421465 if ( ! constructor && isDerivedClass ) {
422466 // Add a synthetic `super` call:
@@ -449,6 +493,26 @@ namespace ts {
449493 // }
450494 //
451495 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+ }
452516
453517 // Add existing statements, skipping the initial super call.
454518 if ( constructor ) {
@@ -528,10 +592,10 @@ namespace ts {
528592 if ( isPrivateName ( propertyName ) ) {
529593 const privateNameInfo = accessPrivateName ( propertyName ) ;
530594 if ( privateNameInfo ) {
531- switch ( privateNameInfo . type ) {
532- case PrivateNameType . InstanceField : {
595+ switch ( privateNameInfo . placement ) {
596+ case PrivateNamePlacement . InstanceField : {
533597 return createCall (
534- createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
598+ createPropertyAccess ( privateNameInfo . accumulator , "set" ) ,
535599 /*typeArguments*/ undefined ,
536600 [ receiver , initializer || createVoidZero ( ) ]
537601 ) ;
@@ -557,17 +621,66 @@ namespace ts {
557621 privateNameEnvironmentStack . pop ( ) ;
558622 }
559623
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 } ) {
561653 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 (
567680 createAssignment (
568- weakMapName ,
681+ accumulator ,
569682 createNew (
570- createIdentifier ( "WeakMap" ) ,
683+ createIdentifier ( identifierName ) ,
571684 /*typeArguments*/ undefined ,
572685 [ ]
573686 )
@@ -589,14 +702,14 @@ namespace ts {
589702 if ( isPrivateName ( node . name ) ) {
590703 const privateNameInfo = accessPrivateName ( node . name ) ;
591704 if ( privateNameInfo ) {
592- switch ( privateNameInfo . type ) {
593- case PrivateNameType . InstanceField :
705+ switch ( privateNameInfo . placement ) {
706+ case PrivateNamePlacement . InstanceField :
594707 return setOriginalNode (
595708 setTextRange (
596709 createClassPrivateFieldGetHelper (
597710 context ,
598711 visitNode ( node . expression , visitor , isExpression ) ,
599- privateNameInfo . weakMapName
712+ privateNameInfo . accumulator
600713 ) ,
601714 node
602715 ) ,
@@ -622,6 +735,23 @@ namespace ts {
622735 ) ;
623736 receiver = generatedName ;
624737 }
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+ }
625755 return visitNode (
626756 updateCall (
627757 node ,
@@ -903,7 +1033,7 @@ namespace ts {
9031033 }
9041034 else if ( isAssignmentExpression ( node ) && isPropertyAccessExpression ( node . left ) && isPrivateName ( node . left . name ) ) {
9051035 const privateNameInfo = accessPrivateName ( node . left . name ) ;
906- if ( privateNameInfo && privateNameInfo . type === PrivateNameType . InstanceField ) {
1036+ if ( privateNameInfo && privateNameInfo . placement === PrivateNamePlacement . InstanceField ) {
9071037 if ( isCompoundAssignment ( node . operatorToken . kind ) ) {
9081038 const isReceiverInlineable = isSimpleInlineableExpression ( node . left . expression ) ;
9091039 const getReceiver = isReceiverInlineable ? node . left . expression : createTempVariable ( hoistVariableDeclaration ) ;
@@ -914,12 +1044,12 @@ namespace ts {
9141044 createClassPrivateFieldSetHelper (
9151045 context ,
9161046 setReceiver ,
917- privateNameInfo . weakMapName ,
1047+ privateNameInfo . accumulator ,
9181048 createBinary (
9191049 createClassPrivateFieldGetHelper (
9201050 context ,
9211051 getReceiver ,
922- privateNameInfo . weakMapName
1052+ privateNameInfo . accumulator
9231053 ) ,
9241054 getOperatorForCompoundAssignment ( node . operatorToken . kind ) ,
9251055 visitNode ( node . right , visitor )
@@ -933,7 +1063,7 @@ namespace ts {
9331063 createClassPrivateFieldSetHelper (
9341064 context ,
9351065 node . left . expression ,
936- privateNameInfo . weakMapName ,
1066+ privateNameInfo . accumulator ,
9371067 visitNode ( node . right , visitor )
9381068 ) ,
9391069 node
@@ -1252,6 +1382,9 @@ namespace ts {
12521382 function visitMethodDeclaration ( node : MethodDeclaration ) {
12531383 const savedEnclosingFunctionFlags = enclosingFunctionFlags ;
12541384 enclosingFunctionFlags = getFunctionFlags ( node ) ;
1385+ if ( isPrivateName ( node . name ) ) {
1386+ return [ ] ;
1387+ }
12551388 const updated = updateMethod (
12561389 node ,
12571390 /*decorators*/ undefined ,
@@ -1789,4 +1922,15 @@ namespace ts {
17891922 context . requestEmitHelper ( classPrivateFieldSetHelper ) ;
17901923 return createCall ( getHelperName ( "_classPrivateFieldSet" ) , /* typeArguments */ undefined , [ receiver , privateField , value ] ) ;
17911924 }
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+
17921936}
0 commit comments