@@ -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,23 @@ 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 . 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 ) ;
386424 }
387- const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
388- const body = transformConstructorBody ( node , constructor , isDerivedClass ) ;
389425 if ( ! body ) {
390426 return undefined ;
391427 }
@@ -405,18 +441,25 @@ namespace ts {
405441 ) ;
406442 }
407443
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+ ) {
409450 const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
410451
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 ) {
413454 return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
414455 }
415456
457+ let statements : Statement [ ] = [ ] ;
458+
459+ // was suspended if/when parameters were visited
416460 resumeLexicalEnvironment ( ) ;
417461
418462 let indexOfFirstStatement = 0 ;
419- let statements : Statement [ ] = [ ] ;
420463
421464 if ( ! constructor && isDerivedClass ) {
422465 // Add a synthetic `super` call:
@@ -449,6 +492,26 @@ namespace ts {
449492 // }
450493 //
451494 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+ }
452515
453516 // Add existing statements, skipping the initial super call.
454517 if ( constructor ) {
@@ -528,10 +591,10 @@ namespace ts {
528591 if ( isPrivateName ( propertyName ) ) {
529592 const privateNameInfo = accessPrivateName ( propertyName ) ;
530593 if ( privateNameInfo ) {
531- switch ( privateNameInfo . type ) {
532- case PrivateNameType . InstanceField : {
594+ switch ( privateNameInfo . placement ) {
595+ case PrivateNamePlacement . InstanceField : {
533596 return createCall (
534- createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
597+ createPropertyAccess ( privateNameInfo . accumulator , "set" ) ,
535598 /*typeArguments*/ undefined ,
536599 [ receiver , initializer || createVoidZero ( ) ]
537600 ) ;
@@ -557,17 +620,66 @@ namespace ts {
557620 privateNameEnvironmentStack . pop ( ) ;
558621 }
559622
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 } ) {
561652 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 (
567679 createAssignment (
568- weakMapName ,
680+ accumulator ,
569681 createNew (
570- createIdentifier ( "WeakMap" ) ,
682+ createIdentifier ( identifierName ) ,
571683 /*typeArguments*/ undefined ,
572684 [ ]
573685 )
@@ -589,14 +701,14 @@ namespace ts {
589701 if ( isPrivateName ( node . name ) ) {
590702 const privateNameInfo = accessPrivateName ( node . name ) ;
591703 if ( privateNameInfo ) {
592- switch ( privateNameInfo . type ) {
593- case PrivateNameType . InstanceField :
704+ switch ( privateNameInfo . placement ) {
705+ case PrivateNamePlacement . InstanceField :
594706 return setOriginalNode (
595707 setTextRange (
596708 createClassPrivateFieldGetHelper (
597709 context ,
598710 visitNode ( node . expression , visitor , isExpression ) ,
599- privateNameInfo . weakMapName
711+ privateNameInfo . accumulator
600712 ) ,
601713 node
602714 ) ,
@@ -622,6 +734,23 @@ namespace ts {
622734 ) ;
623735 receiver = generatedName ;
624736 }
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+ }
625754 return visitNode (
626755 updateCall (
627756 node ,
@@ -903,7 +1032,7 @@ namespace ts {
9031032 }
9041033 else if ( isAssignmentExpression ( node ) && isPropertyAccessExpression ( node . left ) && isPrivateName ( node . left . name ) ) {
9051034 const privateNameInfo = accessPrivateName ( node . left . name ) ;
906- if ( privateNameInfo && privateNameInfo . type === PrivateNameType . InstanceField ) {
1035+ if ( privateNameInfo && privateNameInfo . placement === PrivateNamePlacement . InstanceField ) {
9071036 if ( isCompoundAssignment ( node . operatorToken . kind ) ) {
9081037 const isReceiverInlineable = isSimpleInlineableExpression ( node . left . expression ) ;
9091038 const getReceiver = isReceiverInlineable ? node . left . expression : createTempVariable ( hoistVariableDeclaration ) ;
@@ -914,12 +1043,12 @@ namespace ts {
9141043 createClassPrivateFieldSetHelper (
9151044 context ,
9161045 setReceiver ,
917- privateNameInfo . weakMapName ,
1046+ privateNameInfo . accumulator ,
9181047 createBinary (
9191048 createClassPrivateFieldGetHelper (
9201049 context ,
9211050 getReceiver ,
922- privateNameInfo . weakMapName
1051+ privateNameInfo . accumulator
9231052 ) ,
9241053 getOperatorForCompoundAssignment ( node . operatorToken . kind ) ,
9251054 visitNode ( node . right , visitor )
@@ -933,7 +1062,7 @@ namespace ts {
9331062 createClassPrivateFieldSetHelper (
9341063 context ,
9351064 node . left . expression ,
936- privateNameInfo . weakMapName ,
1065+ privateNameInfo . accumulator ,
9371066 visitNode ( node . right , visitor )
9381067 ) ,
9391068 node
@@ -1252,6 +1381,9 @@ namespace ts {
12521381 function visitMethodDeclaration ( node : MethodDeclaration ) {
12531382 const savedEnclosingFunctionFlags = enclosingFunctionFlags ;
12541383 enclosingFunctionFlags = getFunctionFlags ( node ) ;
1384+ if ( isPrivateName ( node . name ) ) {
1385+ return [ ] ;
1386+ }
12551387 const updated = updateMethod (
12561388 node ,
12571389 /*decorators*/ undefined ,
@@ -1789,4 +1921,15 @@ namespace ts {
17891921 context . requestEmitHelper ( classPrivateFieldSetHelper ) ;
17901922 return createCall ( getHelperName ( "_classPrivateFieldSet" ) , /* typeArguments */ undefined , [ receiver , privateField , value ] ) ;
17911923 }
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+
17921935}
0 commit comments