@@ -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 ) {
@@ -369,11 +377,30 @@ namespace ts {
369377
370378 function transformClassMembers ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
371379 // 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+ } ) ;
375402 const members : ClassElement [ ] = [ ] ;
376- const constructor = transformConstructor ( node , isDerivedClass ) ;
403+ const constructor = transformConstructor ( node , isDerivedClass , ! ! privateNamedMembers . length ) ;
377404 if ( constructor ) {
378405 members . push ( constructor ) ;
379406 }
@@ -382,17 +409,24 @@ namespace ts {
382409 return setTextRange ( createNodeArray ( members ) , /*location*/ node . members ) ;
383410 }
384411
385- function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
412+ function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean , declaresPrivateNames : boolean ) {
386413 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 ) ;
393429 }
394- const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
395- const body = transformConstructorBody ( node , constructor , isDerivedClass ) ;
396430 if ( ! body ) {
397431 return undefined ;
398432 }
@@ -402,7 +436,7 @@ namespace ts {
402436 createConstructor (
403437 /*decorators*/ undefined ,
404438 /*modifiers*/ undefined ,
405- parameters ,
439+ parameters || [ ] ,
406440 body
407441 ) ,
408442 constructor || node
@@ -412,18 +446,25 @@ namespace ts {
412446 ) ;
413447 }
414448
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+ ) {
416455 const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
417456
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 ) {
420459 return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
421460 }
422461
462+ let statements : Statement [ ] = [ ] ;
463+
464+ // was suspended to provide a scope for parameter properties and/or private names
423465 resumeLexicalEnvironment ( ) ;
424466
425467 let indexOfFirstStatement = 0 ;
426- let statements : Statement [ ] = [ ] ;
427468
428469 if ( ! constructor && isDerivedClass ) {
429470 // Add a synthetic `super` call:
@@ -456,6 +497,26 @@ namespace ts {
456497 // }
457498 //
458499 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+ }
459520
460521 // Add existing statements, skipping the initial super call.
461522 if ( constructor ) {
@@ -535,10 +596,10 @@ namespace ts {
535596 if ( isPrivateName ( propertyName ) ) {
536597 const privateNameInfo = accessPrivateName ( propertyName ) ;
537598 if ( privateNameInfo ) {
538- switch ( privateNameInfo . type ) {
539- case PrivateNameType . InstanceField : {
599+ switch ( privateNameInfo . placement ) {
600+ case PrivateNamePlacement . InstanceField : {
540601 return createCall (
541- createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
602+ createPropertyAccess ( privateNameInfo . accumulator , "set" ) ,
542603 /*typeArguments*/ undefined ,
543604 [ receiver , initializer || createVoidZero ( ) ]
544605 ) ;
@@ -564,18 +625,66 @@ namespace ts {
564625 privateNameEnvironmentStack . pop ( ) ;
565626 }
566627
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 } ) {
568657 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 (
575684 createAssignment (
576- weakMapName ,
685+ accumulator ,
577686 createNew (
578- createIdentifier ( "WeakMap" ) ,
687+ createIdentifier ( identifierName ) ,
579688 /*typeArguments*/ undefined ,
580689 [ ]
581690 )
@@ -597,14 +706,14 @@ namespace ts {
597706 if ( isPrivateName ( node . name ) ) {
598707 const privateNameInfo = accessPrivateName ( node . name ) ;
599708 if ( privateNameInfo ) {
600- switch ( privateNameInfo . type ) {
601- case PrivateNameType . InstanceField :
709+ switch ( privateNameInfo . placement ) {
710+ case PrivateNamePlacement . InstanceField :
602711 return setOriginalNode (
603712 setTextRange (
604713 createClassPrivateFieldGetHelper (
605714 context ,
606- visitNode ( node . expression , visitor , isExpression ) ,
607- privateNameInfo . weakMapName
715+ visitNode ( node . expression , visitor , isExpression ) ,
716+ privateNameInfo . accumulator
608717 ) ,
609718 node
610719 ) ,
@@ -699,6 +808,23 @@ namespace ts {
699808 ) ;
700809 receiver = generatedName ;
701810 }
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+ }
702828 return visitNode (
703829 updateCall (
704830 node ,
@@ -980,7 +1106,7 @@ namespace ts {
9801106 }
9811107 else if ( isAssignmentExpression ( node ) && isPropertyAccessExpression ( node . left ) && isPrivateName ( node . left . name ) ) {
9821108 const privateNameInfo = accessPrivateName ( node . left . name ) ;
983- if ( privateNameInfo && privateNameInfo . type === PrivateNameType . InstanceField ) {
1109+ if ( privateNameInfo && privateNameInfo . placement === PrivateNamePlacement . InstanceField ) {
9841110 if ( isCompoundAssignment ( node . operatorToken . kind ) ) {
9851111 const isReceiverInlineable = isSimpleInlineableExpression ( node . left . expression ) ;
9861112 const getReceiver = isReceiverInlineable ? node . left . expression : createTempVariable ( hoistVariableDeclaration ) ;
@@ -991,12 +1117,12 @@ namespace ts {
9911117 createClassPrivateFieldSetHelper (
9921118 context ,
9931119 setReceiver ,
994- privateNameInfo . weakMapName ,
1120+ privateNameInfo . accumulator ,
9951121 createBinary (
9961122 createClassPrivateFieldGetHelper (
9971123 context ,
9981124 getReceiver ,
999- privateNameInfo . weakMapName
1125+ privateNameInfo . accumulator
10001126 ) ,
10011127 getOperatorForCompoundAssignment ( node . operatorToken . kind ) ,
10021128 visitNode ( node . right , visitor )
@@ -1010,7 +1136,7 @@ namespace ts {
10101136 createClassPrivateFieldSetHelper (
10111137 context ,
10121138 node . left . expression ,
1013- privateNameInfo . weakMapName ,
1139+ privateNameInfo . accumulator ,
10141140 visitNode ( node . right , visitor )
10151141 ) ,
10161142 node
@@ -1329,6 +1455,9 @@ namespace ts {
13291455 function visitMethodDeclaration ( node : MethodDeclaration ) {
13301456 const savedEnclosingFunctionFlags = enclosingFunctionFlags ;
13311457 enclosingFunctionFlags = getFunctionFlags ( node ) ;
1458+ if ( isPrivateName ( node . name ) ) {
1459+ return [ ] ;
1460+ }
13321461 const updated = updateMethod (
13331462 node ,
13341463 /*decorators*/ undefined ,
@@ -1866,4 +1995,15 @@ namespace ts {
18661995 context . requestEmitHelper ( classPrivateFieldSetHelper ) ;
18671996 return createCall ( getHelperName ( "_classPrivateFieldSet" ) , /* typeArguments */ undefined , [ receiver , privateField , value ] ) ;
18681997 }
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+
18692009}
0 commit comments