@@ -10,6 +10,23 @@ namespace ts {
10
10
ClassAliases = 1 << 1 ,
11
11
}
12
12
13
+ /**
14
+ * A mapping of private names to information needed for transformation.
15
+ */
16
+ type PrivateNameEnvironment = UnderscoreEscapedMap < PrivateNamedInstanceField > ;
17
+
18
+ /**
19
+ * Identifies the type of private name.
20
+ */
21
+ const enum PrivateNameType {
22
+ InstanceField
23
+ }
24
+
25
+ interface PrivateNamedInstanceField {
26
+ type : PrivateNameType . InstanceField ;
27
+ weakMapName : Identifier ;
28
+ }
29
+
13
30
export function transformESNext ( context : TransformationContext ) {
14
31
const {
15
32
resumeLexicalEnvironment,
@@ -52,6 +69,8 @@ namespace ts {
52
69
*/
53
70
let pendingStatements : Statement [ ] | undefined ;
54
71
72
+ const privateNameEnvironmentStack : PrivateNameEnvironment [ ] = [ ] ;
73
+
55
74
return chainBundle ( transformSourceFile ) ;
56
75
57
76
function transformSourceFile ( node : SourceFile ) {
@@ -230,6 +249,7 @@ namespace ts {
230
249
function visitClassDeclaration ( node : ClassDeclaration ) {
231
250
const savedPendingExpressions = pendingExpressions ;
232
251
pendingExpressions = undefined ;
252
+ startPrivateNameEnvironment ( ) ;
233
253
234
254
const extendsClauseElement = getEffectiveBaseTypeNode ( node ) ;
235
255
const isDerivedClass = ! ! ( extendsClauseElement && skipOuterExpressions ( extendsClauseElement . expression ) . kind !== SyntaxKind . NullKeyword ) ;
@@ -250,6 +270,7 @@ namespace ts {
250
270
if ( some ( pendingExpressions ) ) {
251
271
statements . push ( createExpressionStatement ( inlineExpressions ( pendingExpressions ! ) ) ) ;
252
272
}
273
+ endPrivateNameEnvironment ( ) ;
253
274
pendingExpressions = savedPendingExpressions ;
254
275
255
276
// Emit static property assignment. Because classDeclaration is lexically evaluated,
@@ -268,6 +289,7 @@ namespace ts {
268
289
function visitClassExpression ( node : ClassExpression ) : Expression {
269
290
const savedPendingExpressions = pendingExpressions ;
270
291
pendingExpressions = undefined ;
292
+ startPrivateNameEnvironment ( ) ;
271
293
272
294
// If this class expression is a transformation of a decorated class declaration,
273
295
// then we want to output the pendingExpressions as statements, not as inlined
@@ -295,7 +317,7 @@ namespace ts {
295
317
if ( isDecoratedClassDeclaration ) {
296
318
Debug . assertDefined ( pendingStatements , "Decorated classes transformed by TypeScript are expected to be within a variable declaration." ) ;
297
319
298
- // Write any pending expressions from elided or moved computed property names
320
+ // Write any pending expressions from elided or moved computed property names or private names
299
321
if ( some ( pendingExpressions ) ) {
300
322
pendingStatements ! . push ( createExpressionStatement ( inlineExpressions ( pendingExpressions ! ) ) ) ;
301
323
}
@@ -304,6 +326,7 @@ namespace ts {
304
326
if ( some ( staticProperties ) ) {
305
327
addInitializedPropertyStatements ( pendingStatements ! , staticProperties , getInternalName ( node ) ) ;
306
328
}
329
+ endPrivateNameEnvironment ( ) ;
307
330
return classExpression ;
308
331
}
309
332
else {
@@ -328,28 +351,38 @@ namespace ts {
328
351
expressions . push ( startOnNewLine ( temp ) ) ;
329
352
330
353
pendingExpressions = savedPendingExpressions ;
354
+ endPrivateNameEnvironment ( ) ;
331
355
return inlineExpressions ( expressions ) ;
332
356
}
333
357
}
334
358
335
359
pendingExpressions = savedPendingExpressions ;
360
+ endPrivateNameEnvironment ( ) ;
336
361
return classExpression ;
337
362
}
338
363
339
364
function transformClassMembers ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
365
+ // Declare private names.
366
+ const privateNamedProperties = filter ( node . members , isPrivateNamedPropertyDeclaration ) ;
367
+ privateNamedProperties . forEach ( property => addPrivateNameToEnvironment ( property . name ) ) ;
368
+
340
369
const members : ClassElement [ ] = [ ] ;
341
370
const constructor = transformConstructor ( node , isDerivedClass ) ;
342
371
if ( constructor ) {
343
372
members . push ( constructor ) ;
344
373
}
374
+
345
375
addRange ( members , visitNodes ( node . members , classElementVisitor , isClassElement ) ) ;
346
376
return setTextRange ( createNodeArray ( members ) , /*location*/ node . members ) ;
347
377
}
348
378
349
379
function transformConstructor ( node : ClassDeclaration | ClassExpression , isDerivedClass : boolean ) {
350
380
const constructor = visitNode ( getFirstConstructorWithBody ( node ) , visitor , isConstructorDeclaration ) ;
351
- const containsPropertyInitializer = forEach ( node . members , isInitializedProperty ) ;
352
- if ( ! containsPropertyInitializer ) {
381
+ const containsPropertyInitializerOrPrivateProperty = forEach (
382
+ node . members ,
383
+ member => isInitializedProperty ( member ) || isPrivateNamedPropertyDeclaration ( member )
384
+ ) ;
385
+ if ( ! containsPropertyInitializerOrPrivateProperty ) {
353
386
return constructor ;
354
387
}
355
388
const parameters = visitParameterList ( constructor ? constructor . parameters : undefined , visitor , context ) ;
@@ -374,9 +407,9 @@ namespace ts {
374
407
}
375
408
376
409
function transformConstructorBody ( node : ClassDeclaration | ClassExpression , constructor : ConstructorDeclaration | undefined , isDerivedClass : boolean ) {
377
- const properties = getInitializedProperties ( node , /*isStatic*/ false ) ;
410
+ const properties = filter ( node . members , ( node ) : node is PropertyDeclaration => isPropertyDeclaration ( node ) && ! hasStaticModifier ( node ) ) ;
378
411
379
- // Only generate synthetic constructor when there are property initializers to move.
412
+ // Only generate synthetic constructor when there are property declarations to move.
380
413
if ( ! constructor && ! some ( properties ) ) {
381
414
return visitFunctionBody ( /*node*/ undefined , visitor , context ) ;
382
415
}
@@ -445,7 +478,11 @@ namespace ts {
445
478
*/
446
479
function addInitializedPropertyStatements ( statements : Statement [ ] , properties : ReadonlyArray < PropertyDeclaration > , receiver : LeftHandSideExpression ) {
447
480
for ( const property of properties ) {
448
- const statement = createExpressionStatement ( transformInitializedProperty ( property , receiver ) ) ;
481
+ const expression = transformProperty ( property , receiver ) ;
482
+ if ( ! expression ) {
483
+ continue ;
484
+ }
485
+ const statement = createExpressionStatement ( expression ) ;
449
486
setSourceMapRange ( statement , moveRangePastModifiers ( property ) ) ;
450
487
setCommentRange ( statement , property ) ;
451
488
setOriginalNode ( statement , property ) ;
@@ -462,7 +499,10 @@ namespace ts {
462
499
function generateInitializedPropertyExpressions ( properties : ReadonlyArray < PropertyDeclaration > , receiver : LeftHandSideExpression ) {
463
500
const expressions : Expression [ ] = [ ] ;
464
501
for ( const property of properties ) {
465
- const expression = transformInitializedProperty ( property , receiver ) ;
502
+ const expression = transformProperty ( property , receiver ) ;
503
+ if ( ! expression ) {
504
+ continue ;
505
+ }
466
506
startOnNewLine ( expression ) ;
467
507
setSourceMapRange ( expression , moveRangePastModifiers ( property ) ) ;
468
508
setCommentRange ( expression , property ) ;
@@ -479,17 +519,74 @@ namespace ts {
479
519
* @param property The property declaration.
480
520
* @param receiver The object receiving the property assignment.
481
521
*/
482
- function transformInitializedProperty ( property : PropertyDeclaration , receiver : LeftHandSideExpression ) {
522
+ function transformProperty ( property : PropertyDeclaration , receiver : LeftHandSideExpression ) {
483
523
// We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
484
524
const propertyName = isComputedPropertyName ( property . name ) && ! isSimpleInlineableExpression ( property . name . expression )
485
525
? updateComputedPropertyName ( property . name , getGeneratedNameForNode ( property . name ) )
486
526
: property . name ;
487
- const initializer = visitNode ( property . initializer ! , visitor , isExpression ) ;
527
+ const initializer = visitNode ( property . initializer , visitor , isExpression ) ;
528
+
529
+ if ( isPrivateName ( propertyName ) ) {
530
+ const privateNameInfo = accessPrivateName ( propertyName ) ;
531
+ if ( privateNameInfo ) {
532
+ switch ( privateNameInfo . type ) {
533
+ case PrivateNameType . InstanceField : {
534
+ return createCall (
535
+ createPropertyAccess ( privateNameInfo . weakMapName , "set" ) ,
536
+ /*typeArguments*/ undefined ,
537
+ [ receiver , initializer || createVoidZero ( ) ]
538
+ ) ;
539
+ }
540
+ }
541
+ }
542
+ }
543
+ if ( ! initializer ) {
544
+ return undefined ;
545
+ }
488
546
const memberAccess = createMemberAccessForPropertyName ( receiver , propertyName , /*location*/ propertyName ) ;
489
547
490
548
return createAssignment ( memberAccess , initializer ) ;
491
549
}
492
550
551
+ function startPrivateNameEnvironment ( ) {
552
+ const env : PrivateNameEnvironment = createUnderscoreEscapedMap ( ) ;
553
+ privateNameEnvironmentStack . push ( env ) ;
554
+ return env ;
555
+ }
556
+
557
+ function endPrivateNameEnvironment ( ) {
558
+ privateNameEnvironmentStack . pop ( ) ;
559
+ }
560
+
561
+ function addPrivateNameToEnvironment ( name : PrivateName ) {
562
+ const env = last ( privateNameEnvironmentStack ) ;
563
+ const text = getTextOfPropertyName ( name ) as string ;
564
+ const weakMapName = createOptimisticUniqueName ( "_" + text . substring ( 1 ) ) ;
565
+ weakMapName . autoGenerateFlags |= GeneratedIdentifierFlags . ReservedInNestedScopes ;
566
+ hoistVariableDeclaration ( weakMapName ) ;
567
+ env . set ( name . escapedText , { type : PrivateNameType . InstanceField , weakMapName } ) ;
568
+ ( pendingExpressions || ( pendingExpressions = [ ] ) ) . push (
569
+ createAssignment (
570
+ weakMapName ,
571
+ createNew (
572
+ createIdentifier ( "WeakMap" ) ,
573
+ /*typeArguments*/ undefined ,
574
+ [ ]
575
+ )
576
+ )
577
+ ) ;
578
+ }
579
+
580
+ function accessPrivateName ( name : PrivateName ) {
581
+ for ( let i = privateNameEnvironmentStack . length - 1 ; i >= 0 ; -- i ) {
582
+ const env = privateNameEnvironmentStack [ i ] ;
583
+ if ( env . has ( name . escapedText ) ) {
584
+ return env . get ( name . escapedText ) ;
585
+ }
586
+ }
587
+ return undefined ;
588
+ }
589
+
493
590
function enableSubstitutionForClassAliases ( ) {
494
591
if ( ( enabledSubstitutions & ESNextSubstitutionFlags . ClassAliases ) === 0 ) {
495
592
enabledSubstitutions |= ESNextSubstitutionFlags . ClassAliases ;
0 commit comments