diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 9792089881024..e9a0b18070db6 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -31,7 +31,7 @@ namespace ts { * Maps private names to the generated name of the WeakMap. */ interface PrivateNameEnvironment { - [name: string]: { weakMapName: Identifier, initializer: Expression | undefined } + [name: string]: Identifier } let privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; @@ -116,8 +116,9 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: return visitPropertyAccessExpression(node as PropertyAccessExpression); case SyntaxKind.ClassDeclaration: + return visitClassDeclaration(node as ClassDeclaration); case SyntaxKind.ClassExpression: - return visitClassLikeDeclaration(node as ClassLikeDeclaration); + return visitClassExpression(node as ClassExpression); default: return visitEachChild(node, visitor, context); } @@ -128,19 +129,13 @@ namespace ts { } function addPrivateNameToEnvironment(name: Identifier, - initializer?: Expression, environment: PrivateNameEnvironment = currentPrivateNameEnvironment()) { const nameString = getTextOfIdentifierOrLiteral(name); if (nameString in environment) { - if (initializer) { - environment[nameString].initializer = initializer; - } - return environment[nameString].weakMapName; + return environment[nameString]; } const weakMapName = createFileLevelUniqueName('_' + nameString.substring(1)); - environment[nameString] = { - weakMapName, initializer - }; + environment[nameString] = weakMapName; return weakMapName; } @@ -165,15 +160,45 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitClassLikeDeclaration(node: ClassLikeDeclaration): Node[] { + function visitClassDeclaration(node: ClassDeclaration) { + startPrivateNameEnvironment(); + node = visitEachChild(node, visitor, context); + node = updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node.members) + ); + return [...endPrivateNameEnvironment(), node]; + } + + function visitClassExpression(node: ClassExpression) { + startPrivateNameEnvironment(); + node = visitEachChild(node, visitor, context); + node = updateClassExpression( + node, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node.members) + ); + return [...endPrivateNameEnvironment(), node]; + } + + function startPrivateNameEnvironment() { // Create private name environment. privateNameEnvironmentStack[++privateNameEnvironmentIndex] = {}; - // Visit children. - node = visitEachChild(node, visitor, context); - // Create WeakMaps for private properties. + return currentPrivateNameEnvironment(); + } + + function endPrivateNameEnvironment(): Statement[] { const privateNameEnvironment = currentPrivateNameEnvironment(); const weakMapDeclarations = Object.keys(privateNameEnvironment).map(name => { - const { weakMapName } = privateNameEnvironment[name]; + const weakMapName = privateNameEnvironment[name]; return createVariableStatement( /* modifiers */ undefined, [createVariableDeclaration(weakMapName, @@ -185,69 +210,52 @@ namespace ts { ))] ); }); + // Destroy private name environment. + delete privateNameEnvironmentStack[privateNameEnvironmentIndex--]; + return weakMapDeclarations; + } + + function transformClassMembers(members: ReadonlyArray): ClassElement[] { + // Rewrite constructor with private name initializers. + const privateNameEnvironment = currentPrivateNameEnvironment(); + // Initialize private properties. const initializerStatements = Object.keys(privateNameEnvironment).map(name => { return createStatement( createCall( - createPropertyAccess(privateNameEnvironment[name].weakMapName, 'set'), + createPropertyAccess(privateNameEnvironment[name], 'set'), /* typeArguments */ undefined, - [createThis(), privateNameEnvironment[name].initializer || createVoidZero()] + [createThis(), createVoidZero()] ) ); }); - let members = [...node.members]; - let ctor = find(members, (member) => isConstructorDeclaration(member)); - if (!ctor) { - // Create constructor with private field initializers. - ctor = createConstructor( - /* decorators */ undefined, - /* modifiers */ undefined, - /* parameters */ [], - createBlock(initializerStatements) - ); - members.unshift(ctor); - } else { - // Update existing constructor to add private field initializers. - members = members.map(member => { - if (isConstructorDeclaration(member)) { - let statements = member.body ? - [...initializerStatements, ...member.body.statements] : - initializerStatements; + const ctor = find( + members, + (member) => isConstructorDeclaration(member) && !!member.body + ) as ConstructorDeclaration | undefined; + if (ctor) { + const body = updateBlock(ctor.body!, [...initializerStatements, ...ctor.body!.statements]); + return members.map(member => { + if (member === ctor) { return updateConstructor( - member, - member.decorators, - member.modifiers, - member.parameters, - createBlock(statements, member.body ? member.body.multiLine : undefined) + ctor, + ctor.decorators, + ctor.modifiers, + ctor.parameters, + body ); } return member; - }) - } - - // Update class members. - if (isClassDeclaration(node)) { - node = updateClassDeclaration( - node, - node.decorators, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - members - ); - } else if (isClassExpression(node)) { - node = updateClassExpression( - node, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - members - ); + }); } - // Destroy private name environment. - delete privateNameEnvironmentStack[privateNameEnvironmentIndex--]; - return [ ...weakMapDeclarations, node ]; + return [ + createConstructor( + /* decorators */ undefined, + /* modifiers */ undefined, + /* parameters */ [], + createBlock(initializerStatements) + ), + ...members + ]; } function visitAwaitExpression(node: AwaitExpression): Expression {