From 136e2181bf8c2e115158173192eebacb33e93804 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 28 Jun 2018 14:14:40 -0400 Subject: [PATCH 01/30] Start ES2015 transformation Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index b121eb315db84..e68fa123b9881 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -101,11 +101,35 @@ namespace ts { return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue); case SyntaxKind.CatchClause: return visitCatchClause(node as CatchClause); + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.PropertyAccessExpression: + return visitPropertyAccessExpression(node as PropertyAccessExpression); default: return visitEachChild(node, visitor, context); } } + function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { + if (node.name.isPrivateName) { + return setOriginalNode( + setTextRange( + createClassPrivateFieldGetHelper(context, node.expression, node.name), + /* location */ node + ), + node + ); + } + return visitEachChild(node, visitor, context); + } + + function visitPropertyDeclaration(node: PropertyDeclaration): VisitResult { + if (isIdentifier(node.name) && node.name.isPrivateName) { + createClassPrivateFieldHelper(context, node.name); + } + return visitEachChild(node, visitor, context); + } + function visitAwaitExpression(node: AwaitExpression): Expression { if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) { return setOriginalNode( @@ -917,6 +941,36 @@ namespace ts { ); } + export function createClassPrivateFieldHelper(context: TransformationContext, privateField: Identifier) { + context.requestEmitHelper({ + name: 'typescript:classPrivateField', + scoped: true, + text: helperString`var ${"_" + privateField.escapedText} = new WeakMap();` + }); + } + + const classPrivateFieldGetHelper: EmitHelper = { + name: "typescript:classPrivateFieldGet", + scoped: false, + text: `var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };` + } + + export function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { + context.requestEmitHelper(classPrivateFieldGetHelper); + return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]); + } + + const classPrivateFieldSetHelper: EmitHelper = { + name: "typescript:classPrivateFieldSet", + scoped: false, + text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };` + } + + export function createClassPrivateFieldSetHelper(context: TransformationContext, expression: Expression) { + context.requestEmitHelper(classPrivateFieldSetHelper); + return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ expression ]); + } + const awaitHelper: EmitHelper = { name: "typescript:await", scoped: false, From d0a89b8f349583e7ad1f36383820677060080f04 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 28 Jun 2018 14:33:50 -0400 Subject: [PATCH 02/30] Fix lint errors Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index e68fa123b9881..ce28293aa7ba1 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -943,7 +943,7 @@ namespace ts { export function createClassPrivateFieldHelper(context: TransformationContext, privateField: Identifier) { context.requestEmitHelper({ - name: 'typescript:classPrivateField', + name: "typescript:classPrivateField", scoped: true, text: helperString`var ${"_" + privateField.escapedText} = new WeakMap();` }); @@ -953,7 +953,7 @@ namespace ts { name: "typescript:classPrivateFieldGet", scoped: false, text: `var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };` - } + }; export function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { context.requestEmitHelper(classPrivateFieldGetHelper); @@ -964,7 +964,7 @@ namespace ts { name: "typescript:classPrivateFieldSet", scoped: false, text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };` - } + }; export function createClassPrivateFieldSetHelper(context: TransformationContext, expression: Expression) { context.requestEmitHelper(classPrivateFieldSetHelper); From 1e7326d88ab4af82aa7eebe1915a8f11cd3fd0b6 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Fri, 29 Jun 2018 11:43:39 -0400 Subject: [PATCH 03/30] Transform private name references. Generate private field initializers. Signed-off-by: Joseph Watts --- src/compiler/binder.ts | 10 ++ src/compiler/transformers/esnext.ts | 154 +++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ebc43e006bdfc..9b5fff3eb26d7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3311,6 +3311,11 @@ namespace ts { transformFlags |= TransformFlags.ContainsPropertyInitializer; } + // Private names are an ESNext feature. + if (isIdentifier(node.name) && node.name.isPrivateName) { + transformFlags |= TransformFlags.AssertESNext; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.NodeExcludes; } @@ -3448,6 +3453,11 @@ namespace ts { transformFlags |= TransformFlags.ContainsSuper; } + // Private names are an ESNext feature. + if (isIdentifier(node.name) && node.name.isPrivateName) { + transformFlags |= TransformFlags.AssertESNext; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.PropertyAccessExcludes; } diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index ce28293aa7ba1..fdf5dedce1d4a 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -26,6 +26,16 @@ namespace ts { let enclosingFunctionFlags: FunctionFlags; let enclosingSuperContainerFlags: NodeCheckFlags = 0; + + /** + * Maps private names to the generated name of the WeakMap. + */ + interface PrivateNameEnvironment { + [name: string]: { weakMapName: Identifier, initializer: Expression | undefined } + } + let privateNameEnvironmentStack: PrivateNameEnvironment[] = []; + let privateNameEnvironmentIndex = -1; + return chainBundle(transformSourceFile); function transformSourceFile(node: SourceFile) { @@ -105,16 +115,41 @@ namespace ts { return visitPropertyDeclaration(node as PropertyDeclaration); case SyntaxKind.PropertyAccessExpression: return visitPropertyAccessExpression(node as PropertyAccessExpression); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return visitClassLikeDeclaration(node as ClassLikeDeclaration); default: return visitEachChild(node, visitor, context); } } + function currentPrivateNameEnvironment() { + return privateNameEnvironmentStack[privateNameEnvironmentIndex]; + } + + 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; + } + const weakMapName = createFileLevelUniqueName('_' + nameString.substring(1)); + environment[nameString] = { + weakMapName, initializer + }; + return weakMapName; + } + function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { if (node.name.isPrivateName) { + const weakMapName = addPrivateNameToEnvironment(node.name); return setOriginalNode( setTextRange( - createClassPrivateFieldGetHelper(context, node.expression, node.name), + createClassPrivateFieldGetHelper(context, node.expression, weakMapName), /* location */ node ), node @@ -125,11 +160,99 @@ namespace ts { function visitPropertyDeclaration(node: PropertyDeclaration): VisitResult { if (isIdentifier(node.name) && node.name.isPrivateName) { - createClassPrivateFieldHelper(context, node.name); + addPrivateNameToEnvironment(node.name); } return visitEachChild(node, visitor, context); } + function visitClassLikeDeclaration(node: ClassLikeDeclaration): VisitResult { + // Create private name environment. + privateNameEnvironmentStack[++privateNameEnvironmentIndex] = {}; + // Visit children. + node = visitEachChild(node, visitor, context); + // Create WeakMaps for private properties. + // TODO: insert these WeakMap statements somewhere in the code... + const privateNameEnvironment = currentPrivateNameEnvironment(); + for (let propertyName in privateNameEnvironment) { + const { weakMapName } = privateNameEnvironment[propertyName]; + /*const weakMapStatement = */createVariableStatement( + /* modifiers */ undefined, + [ + createVariableDeclaration(weakMapName, + /* typeNode */ undefined, + createNew( + createIdentifier('WeakMap'), + /* typeArguments */ undefined, + /* argumentsArray */ undefined + )) + ] + ); + } + const initializerStatements = Object.keys(privateNameEnvironment).map(name => { + return createStatement( + createCall( + createPropertyAccess(privateNameEnvironment[name].weakMapName, 'set'), + /* typeArguments */ undefined, + [privateNameEnvironment[name].initializer || 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; + return updateConstructor( + member, + member.decorators, + member.modifiers, + member.parameters, + createBlock(statements, member.body ? member.body.multiLine : undefined) + ); + } + 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 node; + } + function visitAwaitExpression(node: AwaitExpression): Expression { if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) { return setOriginalNode( @@ -290,6 +413,18 @@ namespace ts { visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression) ); } + else if (isAssignmentOperator(node.operatorToken.kind) && + isPropertyAccessExpression(node.left) && + isIdentifier(node.left.name) && + node.left.name.isPrivateName) + { + // If assigning to a private property, rewrite it as a call to the helper function. + const weakMapName = addPrivateNameToEnvironment(node.left.name); + return setOriginalNode( + createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right), + node + ); + } return visitEachChild(node, visitor, context); } @@ -942,11 +1077,20 @@ namespace ts { } export function createClassPrivateFieldHelper(context: TransformationContext, privateField: Identifier) { + let mapName = null; + const text = (uniqueName: EmitHelperUniqueNameCallback) => { + const str = helperString`var ${"_" + privateField.escapedText} = new WeakMap();`; + return str(name => { + mapName = name; + return uniqueName(name); + }); + }; context.requestEmitHelper({ name: "typescript:classPrivateField", scoped: true, - text: helperString`var ${"_" + privateField.escapedText} = new WeakMap();` + text }); + return mapName; } const classPrivateFieldGetHelper: EmitHelper = { @@ -966,9 +1110,9 @@ namespace ts { text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };` }; - export function createClassPrivateFieldSetHelper(context: TransformationContext, expression: Expression) { + export function createClassPrivateFieldSetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier, value: Expression) { context.requestEmitHelper(classPrivateFieldSetHelper); - return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ expression ]); + return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ receiver, privateField, value ]); } const awaitHelper: EmitHelper = { From 4abc3542e7c5c67afade4cefda5bce5dcf865e3d Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Fri, 29 Jun 2018 12:17:23 -0400 Subject: [PATCH 04/30] Generate WeakMap instances for private fields. Fix field initialization. Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index fdf5dedce1d4a..848a92985d499 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -165,35 +165,32 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitClassLikeDeclaration(node: ClassLikeDeclaration): VisitResult { + function visitClassLikeDeclaration(node: ClassLikeDeclaration): Node[] { // Create private name environment. privateNameEnvironmentStack[++privateNameEnvironmentIndex] = {}; // Visit children. node = visitEachChild(node, visitor, context); // Create WeakMaps for private properties. - // TODO: insert these WeakMap statements somewhere in the code... const privateNameEnvironment = currentPrivateNameEnvironment(); - for (let propertyName in privateNameEnvironment) { - const { weakMapName } = privateNameEnvironment[propertyName]; - /*const weakMapStatement = */createVariableStatement( + const weakMapDeclarations = Object.keys(privateNameEnvironment).map(name => { + const { weakMapName } = privateNameEnvironment[name]; + return createVariableStatement( /* modifiers */ undefined, - [ - createVariableDeclaration(weakMapName, - /* typeNode */ undefined, - createNew( - createIdentifier('WeakMap'), - /* typeArguments */ undefined, - /* argumentsArray */ undefined - )) - ] + [createVariableDeclaration(weakMapName, + /* typeNode */ undefined, + createNew( + createIdentifier('WeakMap'), + /* typeArguments */ undefined, + /* argumentsArray */ undefined + ))] ); - } + }); const initializerStatements = Object.keys(privateNameEnvironment).map(name => { return createStatement( createCall( createPropertyAccess(privateNameEnvironment[name].weakMapName, 'set'), /* typeArguments */ undefined, - [privateNameEnvironment[name].initializer || createVoidZero()] + [createThis(), privateNameEnvironment[name].initializer || createVoidZero()] ) ); }); @@ -250,7 +247,7 @@ namespace ts { } // Destroy private name environment. delete privateNameEnvironmentStack[privateNameEnvironmentIndex--]; - return node; + return [ ...weakMapDeclarations, node ]; } function visitAwaitExpression(node: AwaitExpression): Expression { From f7fc9afbffb8fa479cdab5d8843e944e7478b96e Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Fri, 29 Jun 2018 12:19:11 -0400 Subject: [PATCH 05/30] Remove unused function. Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 848a92985d499..bdc28eb824c9e 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -1073,23 +1073,6 @@ namespace ts { ); } - export function createClassPrivateFieldHelper(context: TransformationContext, privateField: Identifier) { - let mapName = null; - const text = (uniqueName: EmitHelperUniqueNameCallback) => { - const str = helperString`var ${"_" + privateField.escapedText} = new WeakMap();`; - return str(name => { - mapName = name; - return uniqueName(name); - }); - }; - context.requestEmitHelper({ - name: "typescript:classPrivateField", - scoped: true, - text - }); - return mapName; - } - const classPrivateFieldGetHelper: EmitHelper = { name: "typescript:classPrivateFieldGet", scoped: false, From f65b7bbbfe91546df36b8e4f3c6cb9d60d5f5b49 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Mon, 2 Jul 2018 15:35:04 -0400 Subject: [PATCH 06/30] Clean up private name initializer code Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index bdc28eb824c9e..51e6f3a13ebbb 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; @@ -128,19 +128,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; } @@ -173,7 +167,7 @@ namespace ts { // Create WeakMaps for private properties. const privateNameEnvironment = currentPrivateNameEnvironment(); const weakMapDeclarations = Object.keys(privateNameEnvironment).map(name => { - const { weakMapName } = privateNameEnvironment[name]; + const weakMapName = privateNameEnvironment[name]; return createVariableStatement( /* modifiers */ undefined, [createVariableDeclaration(weakMapName, @@ -188,9 +182,9 @@ namespace ts { 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()] ) ); }); From 23a8d6058231ce05eb8dcadaee6b3ae49f68e13e Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 3 Jul 2018 13:36:00 -0400 Subject: [PATCH 07/30] Clean up private field class transformation. Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 119 +++++++++++++++------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 51e6f3a13ebbb..fdeb76af70c72 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -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); } @@ -159,12 +160,42 @@ 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]; @@ -179,6 +210,15 @@ 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( @@ -188,60 +228,33 @@ namespace ts { ) ); }); - 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 => { + const ctor = find(members, (member) => isConstructorDeclaration(member)) as ConstructorDeclaration | undefined; + if (ctor) { + const body = ctor.body ? + updateBlock(ctor.body, [...initializerStatements, ...ctor.body.statements]) : + createBlock(initializerStatements, /* multiLine */ undefined); + return members.map(member => { if (isConstructorDeclaration(member)) { - let statements = member.body ? - [...initializerStatements, ...member.body.statements] : - initializerStatements; 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 { From 7c63b54fec92598cb3533b2731c0e24ce617dc87 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 3 Jul 2018 15:00:16 -0400 Subject: [PATCH 08/30] Fix private name transformation clash with constructor overload list Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index fdeb76af70c72..0154b37a96b2b 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -228,13 +228,14 @@ namespace ts { ) ); }); - const ctor = find(members, (member) => isConstructorDeclaration(member)) as ConstructorDeclaration | undefined; + const ctor = find( + members, + (member) => isConstructorDeclaration(member) && !!member.body + ) as ConstructorDeclaration | undefined; if (ctor) { - const body = ctor.body ? - updateBlock(ctor.body, [...initializerStatements, ...ctor.body.statements]) : - createBlock(initializerStatements, /* multiLine */ undefined); + const body = updateBlock(ctor.body!, [...initializerStatements, ...ctor.body!.statements]); return members.map(member => { - if (isConstructorDeclaration(member)) { + if (member === ctor) { return updateConstructor( ctor, ctor.decorators, From 832f457b90cac3f073a82e42f4cce637ee1b46fe Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 5 Jul 2018 17:33:26 -0400 Subject: [PATCH 09/30] Remove unnecessary exporting of private name helper functions. Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 0154b37a96b2b..0c23c3a1a671a 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -1087,7 +1087,7 @@ namespace ts { text: `var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };` }; - export function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { + function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { context.requestEmitHelper(classPrivateFieldGetHelper); return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]); } @@ -1098,7 +1098,7 @@ namespace ts { text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };` }; - export function createClassPrivateFieldSetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier, value: Expression) { + function createClassPrivateFieldSetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier, value: Expression) { context.requestEmitHelper(classPrivateFieldSetHelper); return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ receiver, privateField, value ]); } From fe4f98a1e7fe1b680330da67c02e039093b056bf Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 19 Jul 2018 16:53:31 -0400 Subject: [PATCH 10/30] Transform compound assignment expressions Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 35 +++++++++++++++++++++---- src/compiler/transformers/generators.ts | 22 ---------------- src/compiler/utilities.ts | 22 ++++++++++++++++ 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 0c23c3a1a671a..5c738e34c2ed4 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -423,12 +423,37 @@ namespace ts { isIdentifier(node.left.name) && node.left.name.isPrivateName) { - // If assigning to a private property, rewrite it as a call to the helper function. const weakMapName = addPrivateNameToEnvironment(node.left.name); - return setOriginalNode( - createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right), - node - ); + if (isCompoundAssignment(node.operatorToken.kind)) { + let setReceiver: Expression; + let getReceiver: Identifier; + if (!isIdentifier(node.left.expression) && !isKeyword(node.left.expression.kind)) { + getReceiver = createTempVariable(/* recordTempVariable */ undefined); + hoistVariableDeclaration(getReceiver); + setReceiver = createBinary(getReceiver, SyntaxKind.EqualsToken, node.left.expression); + } else { + getReceiver = node.left.expression as Identifier; + setReceiver = node.left.expression as Identifier; + } + return setOriginalNode( + createClassPrivateFieldSetHelper( + context, + setReceiver, + weakMapName, + createBinary( + createClassPrivateFieldGetHelper(context, getReceiver, weakMapName), + getOperatorForCompoundAssignment(node.operatorToken.kind), + node.right + ) + ), + node + ); + } else { + return setOriginalNode( + createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right), + node + ); + } } return visitEachChild(node, visitor, context); } diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 5778b47a4f8b4..66089c15388d3 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -667,28 +667,6 @@ namespace ts { } } - function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { - return kind >= SyntaxKind.FirstCompoundAssignment - && kind <= SyntaxKind.LastCompoundAssignment; - } - - function getOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher { - switch (kind) { - case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; - case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; - case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; - case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; - case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; - case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; - case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; - case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; - case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; - case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; - case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; - } - } - /** * Visits a right-associative binary expression containing `yield`. * diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b8e12539e07da..a7fa1b63b438e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3738,6 +3738,28 @@ namespace ts { && isLeftHandSideExpression(node.left); } + export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { + return kind >= SyntaxKind.FirstCompoundAssignment + && kind <= SyntaxKind.LastCompoundAssignment; + } + + export function getOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher { + switch (kind) { + case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; + case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; + case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; + case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; + case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; + case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; + case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; + case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; + case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; + case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; + } + } + export function isDestructuringAssignment(node: Node): node is DestructuringAssignment { if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) { const kind = node.left.kind; From 82932482acbdc24add7fa03fc52e64b14905f24b Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 19 Jul 2018 16:58:59 -0400 Subject: [PATCH 11/30] Fix linter errors Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 5c738e34c2ed4..97cda3a7814dc 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -31,9 +31,9 @@ namespace ts { * Maps private names to the generated name of the WeakMap. */ interface PrivateNameEnvironment { - [name: string]: Identifier + [name: string]: Identifier; } - let privateNameEnvironmentStack: PrivateNameEnvironment[] = []; + const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; return chainBundle(transformSourceFile); @@ -134,7 +134,7 @@ namespace ts { if (nameString in environment) { return environment[nameString]; } - const weakMapName = createFileLevelUniqueName('_' + nameString.substring(1)); + const weakMapName = createFileLevelUniqueName("_" + nameString.substring(1)); environment[nameString] = weakMapName; return weakMapName; } @@ -204,7 +204,7 @@ namespace ts { [createVariableDeclaration(weakMapName, /* typeNode */ undefined, createNew( - createIdentifier('WeakMap'), + createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined ))] @@ -222,7 +222,7 @@ namespace ts { const initializerStatements = Object.keys(privateNameEnvironment).map(name => { return createStatement( createCall( - createPropertyAccess(privateNameEnvironment[name], 'set'), + createPropertyAccess(privateNameEnvironment[name], "set"), /* typeArguments */ undefined, [createThis(), createVoidZero()] ) @@ -421,8 +421,8 @@ namespace ts { else if (isAssignmentOperator(node.operatorToken.kind) && isPropertyAccessExpression(node.left) && isIdentifier(node.left.name) && - node.left.name.isPrivateName) - { + node.left.name.isPrivateName) { + const weakMapName = addPrivateNameToEnvironment(node.left.name); if (isCompoundAssignment(node.operatorToken.kind)) { let setReceiver: Expression; @@ -431,7 +431,8 @@ namespace ts { getReceiver = createTempVariable(/* recordTempVariable */ undefined); hoistVariableDeclaration(getReceiver); setReceiver = createBinary(getReceiver, SyntaxKind.EqualsToken, node.left.expression); - } else { + } + else { getReceiver = node.left.expression as Identifier; setReceiver = node.left.expression as Identifier; } @@ -448,7 +449,8 @@ namespace ts { ), node ); - } else { + } + else { return setOriginalNode( createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right), node From d6cc08c5b5e241ad42c811500e5cc2a3620f2168 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Mon, 20 Aug 2018 16:39:35 -0400 Subject: [PATCH 12/30] Use IIFE, transform initializers for classes with private names. Signed-off-by: Joseph Watts --- src/compiler/binder.ts | 4 +- src/compiler/transformers/es2015.ts | 12 ++++- src/compiler/transformers/esnext.ts | 69 ++++++++++++++++++----------- src/compiler/transformers/ts.ts | 34 +++++++++----- 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9b5fff3eb26d7..713708952ffae 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3312,7 +3312,7 @@ namespace ts { } // Private names are an ESNext feature. - if (isIdentifier(node.name) && node.name.isPrivateName) { + if (isPrivateName(node.name)) { transformFlags |= TransformFlags.AssertESNext; } @@ -3454,7 +3454,7 @@ namespace ts { } // Private names are an ESNext feature. - if (isIdentifier(node.name) && node.name.isPrivateName) { + if (isPrivateName(node.name)) { transformFlags |= TransformFlags.AssertESNext; } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index da0de696608fd..1822b89dbd9ad 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3306,11 +3306,14 @@ namespace ts { // The class statements are the statements generated by visiting the first statement with initializer of the // body (1), while all other statements are added to remainingStatements (2) - const isVariableStatementWithInitializer = (stmt: Statement) => isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer; + const isVariableStatementWithInitializer = (stmt: Statement) => !isEndOfDeclarationMarker(stmt) && + isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer; + const isEndOfDeclarationMarker = (stmt: Statement) => stmt.kind === SyntaxKind.EndOfDeclarationMarker; const bodyStatements = visitNodes(body.statements, visitor, isStatement); const classStatements = filter(bodyStatements, isVariableStatementWithInitializer); const remainingStatements = filter(bodyStatements, stmt => !isVariableStatementWithInitializer(stmt)); const varStatement = cast(first(classStatements), isVariableStatement); + const endOfDeclarationMarkers = filter(bodyStatements, isEndOfDeclarationMarker); // We know there is only one variable declaration here as we verified this in an // earlier call to isTypeScriptClassWrapper @@ -3382,12 +3385,17 @@ namespace ts { addRange(statements, funcStatements, classBodyEnd + 1); } + // Add other class statements (such as the WeakMap declarations output by the 'esnext' + // transformer for private names). + addRange(statements, classStatements, /*start*/ 1); + // Add the remaining statements of the outer wrapper. addRange(statements, remainingStatements); // The 'es2015' class transform may add an end-of-declaration marker. If so we will add it // after the remaining statements from the 'ts' transformer. - addRange(statements, classStatements, /*start*/ 1); + addRange(statements, endOfDeclarationMarkers); + // Recreate any outer parentheses or partially-emitted expressions to preserve source map // and comment locations. diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 97cda3a7814dc..de640f2845eaa 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -31,7 +31,10 @@ namespace ts { * Maps private names to the generated name of the WeakMap. */ interface PrivateNameEnvironment { - [name: string]: Identifier; + [name: string]: { + weakMap: Identifier; + initializer?: Expression; + }; } const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; @@ -111,8 +114,6 @@ namespace ts { return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue); case SyntaxKind.CatchClause: return visitCatchClause(node as CatchClause); - case SyntaxKind.PropertyDeclaration: - return visitPropertyDeclaration(node as PropertyDeclaration); case SyntaxKind.PropertyAccessExpression: return visitPropertyAccessExpression(node as PropertyAccessExpression); case SyntaxKind.ClassDeclaration: @@ -128,20 +129,32 @@ namespace ts { return privateNameEnvironmentStack[privateNameEnvironmentIndex]; } - function addPrivateNameToEnvironment(name: Identifier, - environment: PrivateNameEnvironment = currentPrivateNameEnvironment()) { - const nameString = getTextOfIdentifierOrLiteral(name); + function addPrivateName(name: PrivateName, initializer?: Expression) { + const environment = currentPrivateNameEnvironment(); + const nameString = getTextOfNode(name); if (nameString in environment) { - return environment[nameString]; + throw new Error("Redeclaring private name " + nameString + "."); } - const weakMapName = createFileLevelUniqueName("_" + nameString.substring(1)); - environment[nameString] = weakMapName; - return weakMapName; + const weakMap = createFileLevelUniqueName("_" + nameString.substring(1)); + environment[nameString] = { + weakMap, + initializer + }; + return weakMap; + } + + function accessPrivateName(name: PrivateName) { + const environment = currentPrivateNameEnvironment(); + const nameString = getTextOfNode(name); + if (nameString in environment) { + return environment[nameString].weakMap; + } + throw new Error("Accessing undeclared private name."); } function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { - if (node.name.isPrivateName) { - const weakMapName = addPrivateNameToEnvironment(node.name); + if (isPrivateName(node.name)) { + const weakMapName = accessPrivateName(node.name); return setOriginalNode( setTextRange( createClassPrivateFieldGetHelper(context, node.expression, weakMapName), @@ -153,15 +166,21 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitPropertyDeclaration(node: PropertyDeclaration): VisitResult { - if (isIdentifier(node.name) && node.name.isPrivateName) { - addPrivateNameToEnvironment(node.name); + function visitorCollectPrivateNames(node: Node): VisitResult { + if (isPropertyDeclaration(node) && isPrivateName(node.name)) { + addPrivateName(node.name, node.initializer); + return undefined; } - return visitEachChild(node, visitor, context); + // Don't collect private names from nested classes. + if (isClassLike(node)) { + return node; + } + return visitEachChild(node, visitorCollectPrivateNames, context); } function visitClassDeclaration(node: ClassDeclaration) { startPrivateNameEnvironment(); + node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); node = updateClassDeclaration( node, @@ -172,7 +191,7 @@ namespace ts { node.heritageClauses, transformClassMembers(node.members) ); - return [...endPrivateNameEnvironment(), node]; + return [node, ...endPrivateNameEnvironment()]; } function visitClassExpression(node: ClassExpression) { @@ -186,7 +205,7 @@ namespace ts { node.heritageClauses, transformClassMembers(node.members) ); - return [...endPrivateNameEnvironment(), node]; + return [node, ...endPrivateNameEnvironment()]; } function startPrivateNameEnvironment() { @@ -198,10 +217,10 @@ namespace ts { function endPrivateNameEnvironment(): Statement[] { const privateNameEnvironment = currentPrivateNameEnvironment(); const weakMapDeclarations = Object.keys(privateNameEnvironment).map(name => { - const weakMapName = privateNameEnvironment[name]; + const privateName = privateNameEnvironment[name]; return createVariableStatement( /* modifiers */ undefined, - [createVariableDeclaration(weakMapName, + [createVariableDeclaration(privateName.weakMap, /* typeNode */ undefined, createNew( createIdentifier("WeakMap"), @@ -220,11 +239,12 @@ namespace ts { const privateNameEnvironment = currentPrivateNameEnvironment(); // Initialize private properties. const initializerStatements = Object.keys(privateNameEnvironment).map(name => { + const privateName = privateNameEnvironment[name]; return createStatement( createCall( - createPropertyAccess(privateNameEnvironment[name], "set"), + createPropertyAccess(privateName.weakMap, "set"), /* typeArguments */ undefined, - [createThis(), createVoidZero()] + [createThis(), privateName.initializer || createVoidZero()] ) ); }); @@ -420,10 +440,9 @@ namespace ts { } else if (isAssignmentOperator(node.operatorToken.kind) && isPropertyAccessExpression(node.left) && - isIdentifier(node.left.name) && - node.left.name.isPrivateName) { + isPrivateName(node.left.name)) { - const weakMapName = addPrivateNameToEnvironment(node.left.name); + const weakMapName = accessPrivateName(node.left.name); if (isCompoundAssignment(node.operatorToken.kind)) { let setReceiver: Expression; let getReceiver: Identifier; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 6eb8b6e0ef634..441ef05b92494 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -593,9 +593,10 @@ namespace ts { return parameter.decorators !== undefined && parameter.decorators.length > 0; } - function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray) { + function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray, privateProperties: ReadonlyArray) { let facts = ClassFacts.None; if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties; + if (languageVersion < ScriptTarget.ESNext && some(privateProperties)) facts |= ClassFacts.UseImmediatelyInvokedFunctionExpression; const extendsClauseElement = getEffectiveBaseTypeNode(node); if (extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword) facts |= ClassFacts.IsDerivedClass; if (shouldEmitDecorateCallForClass(node)) facts |= ClassFacts.HasConstructorDecorators; @@ -623,7 +624,7 @@ namespace ts { pendingExpressions = undefined; const staticProperties = getInitializedProperties(node, /*isStatic*/ true); - const facts = getClassFacts(node, staticProperties); + const facts = getClassFacts(node, staticProperties, getPrivateProperties(node)); if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression) { context.startLexicalEnvironment(); @@ -973,13 +974,13 @@ namespace ts { // Check if we have property assignment inside class declaration. // If there is a property assignment, we need to emit constructor whether users define it or not // If there is no property assignment, we can omit constructor if users do not define it - const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); + const hasNonPrivateInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member) && isPrivateProperty(member)); const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments; const constructor = getFirstConstructorWithBody(node); // If the class does not contain nodes that require a synthesized constructor, // accept the current constructor if it exists. - if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { + if (!hasNonPrivateInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { return visitEachChild(constructor, visitor, context); } @@ -1194,6 +1195,14 @@ namespace ts { ); } + function isPrivateProperty(member: ClassElement): member is PropertyDeclaration { + return member.kind === SyntaxKind.PropertyDeclaration && + !!member.name && isPrivateName(member.name); + } + + function getPrivateProperties(node: ClassExpression | ClassDeclaration): ReadonlyArray { + return filter(node.members, isPrivateProperty); + } /** * Gets all property declarations with initializers on either the static or instance side of a class. * @@ -1242,11 +1251,12 @@ namespace ts { */ function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { for (const property of properties) { - const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); - statements.push(statement); + if (!isPrivateProperty(property)) { + const statement = createStatement(transformInitializedProperty(property, receiver)); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + statements.push(statement); + } } } @@ -2237,7 +2247,11 @@ namespace ts { return !nodeIsMissing(node.body); } - function visitPropertyDeclaration(node: PropertyDeclaration): undefined { + function visitPropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration | undefined { + if (isPrivateName(node.name)) { + // Keep the private name declaration. + return node; + } const expr = getPropertyNameExpressionIfNeeded(node.name, some(node.decorators) || !!node.initializer, /*omitSimple*/ true); if (expr && !isSimpleInlineableExpression(expr)) { (pendingExpressions || (pendingExpressions = [])).push(expr); From 0c22191fdf76d96a3e9b3f7c60e32bb788fe6921 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 21 Aug 2018 14:34:50 -0400 Subject: [PATCH 13/30] Fix class expression transformation, punt on IIFE class wrappers Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 45 ++++++++++++++++++++++------- src/compiler/transformers/ts.ts | 26 ++++++++--------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index de640f2845eaa..eac838b5a98e8 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -191,7 +191,13 @@ namespace ts { node.heritageClauses, transformClassMembers(node.members) ); - return [node, ...endPrivateNameEnvironment()]; + const statements = [ + node, + ...createPrivateNameWeakMapDeclarations( + endPrivateNameEnvironment() + ) + ]; + return statements; } function visitClassExpression(node: ClassExpression) { @@ -205,7 +211,9 @@ namespace ts { node.heritageClauses, transformClassMembers(node.members) ); - return [node, ...endPrivateNameEnvironment()]; + const expressions = createPrivateNameWeakMapAssignments(endPrivateNameEnvironment()); + expressions.push(node); + return createCommaList(expressions); } function startPrivateNameEnvironment() { @@ -214,24 +222,39 @@ namespace ts { return currentPrivateNameEnvironment(); } - function endPrivateNameEnvironment(): Statement[] { + function endPrivateNameEnvironment(): PrivateNameEnvironment { const privateNameEnvironment = currentPrivateNameEnvironment(); - const weakMapDeclarations = Object.keys(privateNameEnvironment).map(name => { - const privateName = privateNameEnvironment[name]; + // Destroy private name environment. + delete privateNameEnvironmentStack[privateNameEnvironmentIndex--]; + return privateNameEnvironment; + } + + function createPrivateNameWeakMapDeclarations(environment: PrivateNameEnvironment): Statement[] { + return Object.keys(environment).map(name => { + const privateName = environment[name]; return createVariableStatement( /* modifiers */ undefined, [createVariableDeclaration(privateName.weakMap, /* typeNode */ undefined, - createNew( - createIdentifier("WeakMap"), + createNew( + createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined - ))] + ))] + ); + }); + } + + function createPrivateNameWeakMapAssignments(environment: PrivateNameEnvironment): Expression[] { + return Object.keys(environment).map(name => { + const privateName = environment[name]; + hoistVariableDeclaration(privateName.weakMap); + return createBinary( + privateName.weakMap, + SyntaxKind.EqualsToken, + createNew(createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined) ); }); - // Destroy private name environment. - delete privateNameEnvironmentStack[privateNameEnvironmentIndex--]; - return weakMapDeclarations; } function transformClassMembers(members: ReadonlyArray): ClassElement[] { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 441ef05b92494..7a4c0f4e06e50 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -593,10 +593,9 @@ namespace ts { return parameter.decorators !== undefined && parameter.decorators.length > 0; } - function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray, privateProperties: ReadonlyArray) { + function getClassFacts(node: ClassDeclaration, staticProperties: ReadonlyArray) { let facts = ClassFacts.None; if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties; - if (languageVersion < ScriptTarget.ESNext && some(privateProperties)) facts |= ClassFacts.UseImmediatelyInvokedFunctionExpression; const extendsClauseElement = getEffectiveBaseTypeNode(node); if (extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword) facts |= ClassFacts.IsDerivedClass; if (shouldEmitDecorateCallForClass(node)) facts |= ClassFacts.HasConstructorDecorators; @@ -624,7 +623,7 @@ namespace ts { pendingExpressions = undefined; const staticProperties = getInitializedProperties(node, /*isStatic*/ true); - const facts = getClassFacts(node, staticProperties, getPrivateProperties(node)); + const facts = getClassFacts(node, staticProperties); if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression) { context.startLexicalEnvironment(); @@ -974,13 +973,13 @@ namespace ts { // Check if we have property assignment inside class declaration. // If there is a property assignment, we need to emit constructor whether users define it or not // If there is no property assignment, we can omit constructor if users do not define it - const hasNonPrivateInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member) && isPrivateProperty(member)); + const hasInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member) && !isPrivateProperty(member)); const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments; const constructor = getFirstConstructorWithBody(node); // If the class does not contain nodes that require a synthesized constructor, // accept the current constructor if it exists. - if (!hasNonPrivateInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { + if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { return visitEachChild(constructor, visitor, context); } @@ -1200,9 +1199,6 @@ namespace ts { !!member.name && isPrivateName(member.name); } - function getPrivateProperties(node: ClassExpression | ClassDeclaration): ReadonlyArray { - return filter(node.members, isPrivateProperty); - } /** * Gets all property declarations with initializers on either the static or instance side of a class. * @@ -1251,12 +1247,13 @@ namespace ts { */ function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { for (const property of properties) { - if (!isPrivateProperty(property)) { - const statement = createStatement(transformInitializedProperty(property, receiver)); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - statements.push(statement); + if (isPrivateProperty(property)) { + continue; } + const statement = createStatement(transformInitializedProperty(property, receiver)); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + statements.push(statement); } } @@ -1269,6 +1266,9 @@ namespace ts { function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { const expressions: Expression[] = []; for (const property of properties) { + if (isPrivateProperty(property)) { + continue; + } const expression = transformInitializedProperty(property, receiver); startOnNewLine(expression); setSourceMapRange(expression, moveRangePastModifiers(property)); From 34be11c59e029b289af24d7fff808a187430c573 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 21 Aug 2018 15:22:06 -0400 Subject: [PATCH 14/30] Only transform classes when there are private names. Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index eac838b5a98e8..b3d362a4bd70d 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -182,6 +182,9 @@ namespace ts { startPrivateNameEnvironment(); node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); + if (!currentPrivateNameEnvironment().length) { + return node; + } node = updateClassDeclaration( node, node.decorators, @@ -203,6 +206,9 @@ namespace ts { function visitClassExpression(node: ClassExpression) { startPrivateNameEnvironment(); node = visitEachChild(node, visitor, context); + if (!currentPrivateNameEnvironment().length) { + return node; + } node = updateClassExpression( node, node.modifiers, From 8cbf34f1a3d672132aeec13166297208fcb14d80 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 21 Aug 2018 16:00:16 -0400 Subject: [PATCH 15/30] Fix weak map generation and class transformation Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 59 +++++++++++++++-------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index b3d362a4bd70d..33ccaa6c06659 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -182,44 +182,45 @@ namespace ts { startPrivateNameEnvironment(); node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); - if (!currentPrivateNameEnvironment().length) { - return node; - } - node = updateClassDeclaration( - node, - node.decorators, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - transformClassMembers(node.members) + const statements = createPrivateNameWeakMapDeclarations( + currentPrivateNameEnvironment() ); - const statements = [ - node, - ...createPrivateNameWeakMapDeclarations( - endPrivateNameEnvironment() - ) - ]; + if (statements.length) { + node = updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node.members) + ); + } + prependStatements(statements, [node]); + endPrivateNameEnvironment(); return statements; } function visitClassExpression(node: ClassExpression) { startPrivateNameEnvironment(); + node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); - if (!currentPrivateNameEnvironment().length) { - return node; - } - node = updateClassExpression( - node, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - transformClassMembers(node.members) + const expressions = createPrivateNameWeakMapAssignments( + currentPrivateNameEnvironment() ); - const expressions = createPrivateNameWeakMapAssignments(endPrivateNameEnvironment()); + if (expressions.length) { + node = updateClassExpression( + node, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node.members) + ); + } expressions.push(node); - return createCommaList(expressions); + endPrivateNameEnvironment(); + return expressions.length > 1 ? createCommaList(expressions) : expressions[0]; } function startPrivateNameEnvironment() { From 4f1a883d4dc5c97a5f0dfe63970f7f570d129f0b Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 21 Aug 2018 17:22:29 -0400 Subject: [PATCH 16/30] Fix build error and cast __String to string Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 33ccaa6c06659..f8f84a7cea73c 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -131,7 +131,7 @@ namespace ts { function addPrivateName(name: PrivateName, initializer?: Expression) { const environment = currentPrivateNameEnvironment(); - const nameString = getTextOfNode(name); + const nameString = name.escapedText as string; if (nameString in environment) { throw new Error("Redeclaring private name " + nameString + "."); } @@ -145,7 +145,7 @@ namespace ts { function accessPrivateName(name: PrivateName) { const environment = currentPrivateNameEnvironment(); - const nameString = getTextOfNode(name); + const nameString = name.escapedText as string; if (nameString in environment) { return environment[nameString].weakMap; } @@ -196,7 +196,7 @@ namespace ts { transformClassMembers(node.members) ); } - prependStatements(statements, [node]); + statements.unshift(node); endPrivateNameEnvironment(); return statements; } From d66e135ddef29de9b420c445e37a213b9912f981 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 10:51:45 -0400 Subject: [PATCH 17/30] Fix out-of-order private named property initialization Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 4 ++-- src/compiler/transformers/ts.ts | 14 ++------------ src/compiler/types.ts | 4 ++++ src/compiler/utilities.ts | 4 ++++ 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index f8f84a7cea73c..4d8dc69d26604 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -167,8 +167,8 @@ namespace ts { } function visitorCollectPrivateNames(node: Node): VisitResult { - if (isPropertyDeclaration(node) && isPrivateName(node.name)) { - addPrivateName(node.name, node.initializer); + if (isPrivateNamedPropertyDeclaration(node)) { + addPrivateName(node.name); return undefined; } // Don't collect private names from nested classes. diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 7a4c0f4e06e50..5a5c119c0d71d 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -973,7 +973,7 @@ namespace ts { // Check if we have property assignment inside class declaration. // If there is a property assignment, we need to emit constructor whether users define it or not // If there is no property assignment, we can omit constructor if users do not define it - const hasInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member) && !isPrivateProperty(member)); + const hasInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member)); const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments; const constructor = getFirstConstructorWithBody(node); @@ -1194,11 +1194,6 @@ namespace ts { ); } - function isPrivateProperty(member: ClassElement): member is PropertyDeclaration { - return member.kind === SyntaxKind.PropertyDeclaration && - !!member.name && isPrivateName(member.name); - } - /** * Gets all property declarations with initializers on either the static or instance side of a class. * @@ -1247,12 +1242,10 @@ namespace ts { */ function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { for (const property of properties) { - if (isPrivateProperty(property)) { - continue; - } const statement = createStatement(transformInitializedProperty(property, receiver)); setSourceMapRange(statement, moveRangePastModifiers(property)); setCommentRange(statement, property); + setOriginalNode(statement, property); statements.push(statement); } } @@ -1266,9 +1259,6 @@ namespace ts { function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { const expressions: Expression[] = []; for (const property of properties) { - if (isPrivateProperty(property)) { - continue; - } const expression = transformInitializedProperty(property, receiver); startOnNewLine(expression); setSourceMapRange(expression, moveRangePastModifiers(property)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4b2357bc63e77..a86badd2c56a4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -864,6 +864,10 @@ namespace ts { initializer?: Expression; // Optional initializer } + export interface PrivateNamedPropertyDeclaration extends PropertyDeclaration { + name: PrivateName; + } + export interface ObjectLiteralElement extends NamedDeclaration { _objectLiteralBrandBrand: any; name?: PropertyName; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a7fa1b63b438e..74c1e6086514f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6091,6 +6091,10 @@ namespace ts { } } + export function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration { + return node && isPropertyDeclaration(node) && isPrivateName(node.name); + } + // Type members export function isTypeElement(node: Node): node is TypeElement { From fab8ff966e49053d9e14b4d1a9ba8bb6aafa074d Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 12:16:34 -0400 Subject: [PATCH 18/30] Clean up private name assignment expression temp variable emit Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 4d8dc69d26604..f70e8f53e87fe 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -475,15 +475,17 @@ namespace ts { const weakMapName = accessPrivateName(node.left.name); if (isCompoundAssignment(node.operatorToken.kind)) { let setReceiver: Expression; - let getReceiver: Identifier; - if (!isIdentifier(node.left.expression) && !isKeyword(node.left.expression.kind)) { - getReceiver = createTempVariable(/* recordTempVariable */ undefined); - hoistVariableDeclaration(getReceiver); - setReceiver = createBinary(getReceiver, SyntaxKind.EqualsToken, node.left.expression); + let getReceiver: Expression; + const receiverExpr = node.left.expression; + if (!isIdentifier(receiverExpr) && !isThisProperty(node.left) && !isSuperProperty(node.left)) { + const tempVariable = createTempVariable(/* recordTempVariable */ undefined); + hoistVariableDeclaration(tempVariable); + setReceiver = createBinary(tempVariable, SyntaxKind.EqualsToken, node.left.expression); + getReceiver = tempVariable; } else { - getReceiver = node.left.expression as Identifier; - setReceiver = node.left.expression as Identifier; + getReceiver = node.left.expression; + setReceiver = node.left.expression; } return setOriginalNode( createClassPrivateFieldSetHelper( From 4fb90826b518df91f505bf43b7c60831223b6aac Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 12:31:35 -0400 Subject: [PATCH 19/30] Remove private named property declaration initializer in TS transform Signed-off-by: Joseph Watts --- src/compiler/transformers/ts.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5a5c119c0d71d..5f0a10df777ff 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2239,8 +2239,16 @@ namespace ts { function visitPropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration | undefined { if (isPrivateName(node.name)) { - // Keep the private name declaration. - return node; + // Keep the private name declaration (without the initializer - which will be moved to the constructor). + return updateProperty( + node, + node.decorators, + node.modifiers, + node.name, + node.questionToken, + node.type, + /*initializer*/ undefined + ); } const expr = getPropertyNameExpressionIfNeeded(node.name, some(node.decorators) || !!node.initializer, /*omitSimple*/ true); if (expr && !isSimpleInlineableExpression(expr)) { From fec6ad8603b011b9038f39a83aab5df8fc6397eb Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 14:09:59 -0400 Subject: [PATCH 20/30] Emit unchanged output for undeclared private names Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index f70e8f53e87fe..c1860a5d6358d 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -149,12 +149,16 @@ namespace ts { if (nameString in environment) { return environment[nameString].weakMap; } - throw new Error("Accessing undeclared private name."); + // Undeclared private name. + return undefined; } function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { if (isPrivateName(node.name)) { const weakMapName = accessPrivateName(node.name); + if (!weakMapName) { + return node; + } return setOriginalNode( setTextRange( createClassPrivateFieldGetHelper(context, node.expression, weakMapName), @@ -473,6 +477,10 @@ namespace ts { isPrivateName(node.left.name)) { const weakMapName = accessPrivateName(node.left.name); + if (!weakMapName) { + // Don't change output for undeclared private names (error). + return node; + } if (isCompoundAssignment(node.operatorToken.kind)) { let setReceiver: Expression; let getReceiver: Expression; From 9916d1c9a433b3f52e2354a97658ad672521526a Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 14:49:21 -0400 Subject: [PATCH 21/30] Update privateNameField baseline Signed-off-by: Joseph Watts --- tests/baselines/reference/privateNameField.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/privateNameField.js b/tests/baselines/reference/privateNameField.js index f432be2b3d336..c65f812af0dd9 100644 --- a/tests/baselines/reference/privateNameField.js +++ b/tests/baselines/reference/privateNameField.js @@ -7,9 +7,12 @@ class A { } //// [privateNameField.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; var A = /** @class */ (function () { function A(name) { - this.#name = name; + _name.set(this, void 0); + _classPrivateFieldSet(this, _name, name); } return A; }()); +var _name = new WeakMap; From 3b28ff718f7077235da666fec1d71b22fa0f20b3 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 14:54:51 -0400 Subject: [PATCH 22/30] Accept public API changes Signed-off-by: Joseph Watts --- tests/baselines/reference/api/tsserverlibrary.d.ts | 4 ++++ tests/baselines/reference/api/typescript.d.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1ab1a986d1ad1..0918ac03f824c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -619,6 +619,9 @@ declare namespace ts { type?: TypeNode; initializer?: Expression; } + interface PrivateNamedPropertyDeclaration extends PropertyDeclaration { + name: PrivateName; + } interface ObjectLiteralElement extends NamedDeclaration { _objectLiteralBrandBrand: any; name?: PropertyName; @@ -3458,6 +3461,7 @@ declare namespace ts { function isClassElement(node: Node): node is ClassElement; function isClassLike(node: Node): node is ClassLikeDeclaration; function isAccessor(node: Node): node is AccessorDeclaration; + function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration; function isTypeElement(node: Node): node is TypeElement; function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement; function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ea78225538eac..acafe6f5d59a1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -619,6 +619,9 @@ declare namespace ts { type?: TypeNode; initializer?: Expression; } + interface PrivateNamedPropertyDeclaration extends PropertyDeclaration { + name: PrivateName; + } interface ObjectLiteralElement extends NamedDeclaration { _objectLiteralBrandBrand: any; name?: PropertyName; @@ -3458,6 +3461,7 @@ declare namespace ts { function isClassElement(node: Node): node is ClassElement; function isClassLike(node: Node): node is ClassLikeDeclaration; function isAccessor(node: Node): node is AccessorDeclaration; + function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration; function isTypeElement(node: Node): node is TypeElement; function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement; function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; From 1c4554a1bc04f2ec791f240c35706565fb24c153 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 14:58:06 -0400 Subject: [PATCH 23/30] Create initialized private named field conformance test Signed-off-by: Joseph Watts --- .../reference/privateNameFieldInitializer.js | 16 ++++++++++++++++ .../privateNameFieldInitializer.symbols | 8 ++++++++ .../reference/privateNameFieldInitializer.types | 9 +++++++++ .../privateNames/privateNameFieldInitializer.ts | 3 +++ 4 files changed, 36 insertions(+) create mode 100644 tests/baselines/reference/privateNameFieldInitializer.js create mode 100644 tests/baselines/reference/privateNameFieldInitializer.symbols create mode 100644 tests/baselines/reference/privateNameFieldInitializer.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts diff --git a/tests/baselines/reference/privateNameFieldInitializer.js b/tests/baselines/reference/privateNameFieldInitializer.js new file mode 100644 index 0000000000000..5263b7a4e5b34 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldInitializer.js @@ -0,0 +1,16 @@ +//// [privateNameFieldInitializer.ts] +class Test { + #property: number = 100; +} + + +//// [privateNameFieldInitializer.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var Test = /** @class */ (function () { + function Test() { + _property.set(this, void 0); + _classPrivateFieldSet(this, _property, 100); + } + return Test; +}()); +var _property = new WeakMap; diff --git a/tests/baselines/reference/privateNameFieldInitializer.symbols b/tests/baselines/reference/privateNameFieldInitializer.symbols new file mode 100644 index 0000000000000..ed146bda6509e --- /dev/null +++ b/tests/baselines/reference/privateNameFieldInitializer.symbols @@ -0,0 +1,8 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameFieldInitializer.ts, 0, 0)) + + #property: number = 100; +>#property : Symbol(Test[#property], Decl(privateNameFieldInitializer.ts, 0, 12)) +} + diff --git a/tests/baselines/reference/privateNameFieldInitializer.types b/tests/baselines/reference/privateNameFieldInitializer.types new file mode 100644 index 0000000000000..2aba97086ace9 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldInitializer.types @@ -0,0 +1,9 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts === +class Test { +>Test : Test + + #property: number = 100; +>#property : number +>100 : 100 +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts b/tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts new file mode 100644 index 0000000000000..267a29c982a83 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts @@ -0,0 +1,3 @@ +class Test { + #property: number = 100; +} From 8ba9238c33ca6ce996243aff0f801b5e45fde522 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:00:09 -0400 Subject: [PATCH 24/30] Create private named field accessor conformance test Signed-off-by: Joseph Watts --- .../reference/privateNameFieldAccess.js | 21 +++++++++++++++++++ .../reference/privateNameFieldAccess.symbols | 19 +++++++++++++++++ .../reference/privateNameFieldAccess.types | 20 ++++++++++++++++++ .../privateNames/privateNameFieldAccess.ts | 6 ++++++ 4 files changed, 66 insertions(+) create mode 100644 tests/baselines/reference/privateNameFieldAccess.js create mode 100644 tests/baselines/reference/privateNameFieldAccess.symbols create mode 100644 tests/baselines/reference/privateNameFieldAccess.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts diff --git a/tests/baselines/reference/privateNameFieldAccess.js b/tests/baselines/reference/privateNameFieldAccess.js new file mode 100644 index 0000000000000..60cae8f40371b --- /dev/null +++ b/tests/baselines/reference/privateNameFieldAccess.js @@ -0,0 +1,21 @@ +//// [privateNameFieldAccess.ts] +class Test { + #field: number; + method() { + console.log(this.#field); + } +} + + +//// [privateNameFieldAccess.js] +var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); }; +var Test = /** @class */ (function () { + function Test() { + _field.set(this, void 0); + } + Test.prototype.method = function () { + console.log(_classPrivateFieldGet(this, _field)); + }; + return Test; +}()); +var _field = new WeakMap; diff --git a/tests/baselines/reference/privateNameFieldAccess.symbols b/tests/baselines/reference/privateNameFieldAccess.symbols new file mode 100644 index 0000000000000..8410c84c92b50 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldAccess.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameFieldAccess.ts, 0, 0)) + + #field: number; +>#field : Symbol(Test[#field], Decl(privateNameFieldAccess.ts, 0, 12)) + + method() { +>method : Symbol(Test.method, Decl(privateNameFieldAccess.ts, 1, 19)) + + console.log(this.#field); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>this.#field : Symbol(Test[#field], Decl(privateNameFieldAccess.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldAccess.ts, 0, 0)) + } +} + diff --git a/tests/baselines/reference/privateNameFieldAccess.types b/tests/baselines/reference/privateNameFieldAccess.types new file mode 100644 index 0000000000000..c32358360be40 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldAccess.types @@ -0,0 +1,20 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts === +class Test { +>Test : Test + + #field: number; +>#field : number + + method() { +>method : () => void + + console.log(this.#field); +>console.log(this.#field) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>this.#field : number +>this : this + } +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts b/tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts new file mode 100644 index 0000000000000..c1fbc86317788 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameFieldAccess.ts @@ -0,0 +1,6 @@ +class Test { + #field: number; + method() { + console.log(this.#field); + } +} From c1d12f28cdc38a5ccd0fd96454b569c7bf23dd2c Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:02:05 -0400 Subject: [PATCH 25/30] Create private named field mutation conformance test Signed-off-by: Joseph Watts --- .../reference/privateNameFieldMutate.js | 19 +++++++++++++++++++ .../reference/privateNameFieldMutate.symbols | 14 ++++++++++++++ .../reference/privateNameFieldMutate.types | 16 ++++++++++++++++ .../privateNames/privateNameFieldMutate.ts | 6 ++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/baselines/reference/privateNameFieldMutate.js create mode 100644 tests/baselines/reference/privateNameFieldMutate.symbols create mode 100644 tests/baselines/reference/privateNameFieldMutate.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts diff --git a/tests/baselines/reference/privateNameFieldMutate.js b/tests/baselines/reference/privateNameFieldMutate.js new file mode 100644 index 0000000000000..d8b0fb2898113 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutate.js @@ -0,0 +1,19 @@ +//// [privateNameFieldMutate.ts] +class Test { + #field: number; + constructor() { + this.#field = 100; + } +} + + +//// [privateNameFieldMutate.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var Test = /** @class */ (function () { + function Test() { + _field.set(this, void 0); + _classPrivateFieldSet(this, _field, 100); + } + return Test; +}()); +var _field = new WeakMap; diff --git a/tests/baselines/reference/privateNameFieldMutate.symbols b/tests/baselines/reference/privateNameFieldMutate.symbols new file mode 100644 index 0000000000000..a65469aeda9fb --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutate.symbols @@ -0,0 +1,14 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameFieldMutate.ts, 0, 0)) + + #field: number; +>#field : Symbol(Test[#field], Decl(privateNameFieldMutate.ts, 0, 12)) + + constructor() { + this.#field = 100; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutate.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutate.ts, 0, 0)) + } +} + diff --git a/tests/baselines/reference/privateNameFieldMutate.types b/tests/baselines/reference/privateNameFieldMutate.types new file mode 100644 index 0000000000000..68e50b8670bf1 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutate.types @@ -0,0 +1,16 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts === +class Test { +>Test : Test + + #field: number; +>#field : number + + constructor() { + this.#field = 100; +>this.#field = 100 : 100 +>this.#field : number +>this : this +>100 : 100 + } +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts new file mode 100644 index 0000000000000..f5ffdd6490c79 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutate.ts @@ -0,0 +1,6 @@ +class Test { + #field: number; + constructor() { + this.#field = 100; + } +} From c8ee3708eadf04c365f7f0082471d91c22fc769f Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:06:56 -0400 Subject: [PATCH 26/30] Create private named field binary mutator conformance test Signed-off-by: Joseph Watts --- .../privateNameFieldMutateBinaryOp.js | 42 ++++++++++ .../privateNameFieldMutateBinaryOp.symbols | 58 +++++++++++++ .../privateNameFieldMutateBinaryOp.types | 82 +++++++++++++++++++ .../privateNameFieldMutateBinaryOp.ts | 17 ++++ 4 files changed, 199 insertions(+) create mode 100644 tests/baselines/reference/privateNameFieldMutateBinaryOp.js create mode 100644 tests/baselines/reference/privateNameFieldMutateBinaryOp.symbols create mode 100644 tests/baselines/reference/privateNameFieldMutateBinaryOp.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts diff --git a/tests/baselines/reference/privateNameFieldMutateBinaryOp.js b/tests/baselines/reference/privateNameFieldMutateBinaryOp.js new file mode 100644 index 0000000000000..8e2a94b89e39e --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateBinaryOp.js @@ -0,0 +1,42 @@ +//// [privateNameFieldMutateBinaryOp.ts] +class Test { + #field: number; + constructor() { + this.#field += 1; + this.#field -= 2; + this.#field /= 3; + this.#field *= 4; + this.#field |= 5; + this.#field **= 6; + this.#field %= 7; + this.#field <<= 8; + this.#field >>= 9; + this.#field >>>= 10; + this.#field &= 11; + this.#field ^= 12; + } +} + + +//// [privateNameFieldMutateBinaryOp.js] +var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); }; +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var Test = /** @class */ (function () { + function Test() { + _field.set(this, void 0); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) + 1); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) - 2); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) / 3); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) * 4); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) | 5); + _classPrivateFieldSet(this, _field, Math.pow(_classPrivateFieldGet(this, _field), 6)); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) % 7); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) << 8); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) >> 9); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) >>> 10); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) & 11); + _classPrivateFieldSet(this, _field, _classPrivateFieldGet(this, _field) ^ 12); + } + return Test; +}()); +var _field = new WeakMap; diff --git a/tests/baselines/reference/privateNameFieldMutateBinaryOp.symbols b/tests/baselines/reference/privateNameFieldMutateBinaryOp.symbols new file mode 100644 index 0000000000000..77dd44462f666 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateBinaryOp.symbols @@ -0,0 +1,58 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + #field: number; +>#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) + + constructor() { + this.#field += 1; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field -= 2; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field /= 3; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field *= 4; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field |= 5; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field **= 6; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field %= 7; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field <<= 8; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field >>= 9; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field >>>= 10; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field &= 11; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + + this.#field ^= 12; +>this.#field : Symbol(Test[#field], Decl(privateNameFieldMutateBinaryOp.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameFieldMutateBinaryOp.ts, 0, 0)) + } +} + diff --git a/tests/baselines/reference/privateNameFieldMutateBinaryOp.types b/tests/baselines/reference/privateNameFieldMutateBinaryOp.types new file mode 100644 index 0000000000000..efd935fd68c2b --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateBinaryOp.types @@ -0,0 +1,82 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts === +class Test { +>Test : Test + + #field: number; +>#field : number + + constructor() { + this.#field += 1; +>this.#field += 1 : number +>this.#field : number +>this : this +>1 : 1 + + this.#field -= 2; +>this.#field -= 2 : number +>this.#field : number +>this : this +>2 : 2 + + this.#field /= 3; +>this.#field /= 3 : number +>this.#field : number +>this : this +>3 : 3 + + this.#field *= 4; +>this.#field *= 4 : number +>this.#field : number +>this : this +>4 : 4 + + this.#field |= 5; +>this.#field |= 5 : number +>this.#field : number +>this : this +>5 : 5 + + this.#field **= 6; +>this.#field **= 6 : number +>this.#field : number +>this : this +>6 : 6 + + this.#field %= 7; +>this.#field %= 7 : number +>this.#field : number +>this : this +>7 : 7 + + this.#field <<= 8; +>this.#field <<= 8 : number +>this.#field : number +>this : this +>8 : 8 + + this.#field >>= 9; +>this.#field >>= 9 : number +>this.#field : number +>this : this +>9 : 9 + + this.#field >>>= 10; +>this.#field >>>= 10 : number +>this.#field : number +>this : this +>10 : 10 + + this.#field &= 11; +>this.#field &= 11 : number +>this.#field : number +>this : this +>11 : 11 + + this.#field ^= 12; +>this.#field ^= 12 : number +>this.#field : number +>this : this +>12 : 12 + } +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts new file mode 100644 index 0000000000000..056886c54e7af --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateBinaryOp.ts @@ -0,0 +1,17 @@ +class Test { + #field: number; + constructor() { + this.#field += 1; + this.#field -= 2; + this.#field /= 3; + this.#field *= 4; + this.#field |= 5; + this.#field **= 6; + this.#field %= 7; + this.#field <<= 8; + this.#field >>= 9; + this.#field >>>= 10; + this.#field &= 11; + this.#field ^= 12; + } +} From ddc58f1f715bd438c655b8c3785e7db0788b48fb Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:09:38 -0400 Subject: [PATCH 27/30] Create private name non-trivial receiver mutation conformance test Signed-off-by: Joseph Watts --- ...rivateNameFieldMutateNontrivialReceiver.js | 47 ++++++++ ...eNameFieldMutateNontrivialReceiver.symbols | 66 +++++++++++ ...ateNameFieldMutateNontrivialReceiver.types | 104 ++++++++++++++++++ ...rivateNameFieldMutateNontrivialReceiver.ts | 18 +++ 4 files changed, 235 insertions(+) create mode 100644 tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.js create mode 100644 tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.symbols create mode 100644 tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts diff --git a/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.js b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.js new file mode 100644 index 0000000000000..a8ce123290523 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.js @@ -0,0 +1,47 @@ +//// [privateNameFieldMutateNontrivialReceiver.ts] +class Test { + #field: number; + static mutate(getReceiver: () => Test) { + getReceiver().#field = 100; + getReceiver().#field += 1; + getReceiver().#field -= 2; + getReceiver().#field /= 3; + getReceiver().#field *= 4; + getReceiver().#field |= 5; + getReceiver().#field **= 6; + getReceiver().#field %= 7; + getReceiver().#field <<= 8; + getReceiver().#field >>= 9; + getReceiver().#field >>>= 10; + getReceiver().#field &= 11; + getReceiver().#field ^= 12; + } +} + + +//// [privateNameFieldMutateNontrivialReceiver.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); }; +var Test = /** @class */ (function () { + function Test() { + _field.set(this, void 0); + } + Test.mutate = function (getReceiver) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; + _classPrivateFieldSet(getReceiver(), _field, 100); + _classPrivateFieldSet(_a = getReceiver(), _field, _classPrivateFieldGet(_a, _field) + 1); + _classPrivateFieldSet(_b = getReceiver(), _field, _classPrivateFieldGet(_b, _field) - 2); + _classPrivateFieldSet(_c = getReceiver(), _field, _classPrivateFieldGet(_c, _field) / 3); + _classPrivateFieldSet(_d = getReceiver(), _field, _classPrivateFieldGet(_d, _field) * 4); + _classPrivateFieldSet(_e = getReceiver(), _field, _classPrivateFieldGet(_e, _field) | 5); + _classPrivateFieldSet(_f = getReceiver(), _field, Math.pow(_classPrivateFieldGet(_f, _field), 6)); + _classPrivateFieldSet(_g = getReceiver(), _field, _classPrivateFieldGet(_g, _field) % 7); + _classPrivateFieldSet(_h = getReceiver(), _field, _classPrivateFieldGet(_h, _field) << 8); + _classPrivateFieldSet(_j = getReceiver(), _field, _classPrivateFieldGet(_j, _field) >> 9); + _classPrivateFieldSet(_k = getReceiver(), _field, _classPrivateFieldGet(_k, _field) >>> 10); + _classPrivateFieldSet(_l = getReceiver(), _field, _classPrivateFieldGet(_l, _field) & 11); + _classPrivateFieldSet(_m = getReceiver(), _field, _classPrivateFieldGet(_m, _field) ^ 12); + }; + return Test; +}()); +var _field = new WeakMap; diff --git a/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.symbols b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.symbols new file mode 100644 index 0000000000000..dc6a6487fd86f --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.symbols @@ -0,0 +1,66 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 0)) + + #field: number; +>#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) + + static mutate(getReceiver: () => Test) { +>mutate : Symbol(Test.mutate, Decl(privateNameFieldMutateNontrivialReceiver.ts, 1, 19)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) +>Test : Symbol(Test, Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 0)) + + getReceiver().#field = 100; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field += 1; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field -= 2; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field /= 3; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field *= 4; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field |= 5; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field **= 6; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field %= 7; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field <<= 8; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field >>= 9; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field >>>= 10; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field &= 11; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + + getReceiver().#field ^= 12; +>getReceiver().#field : Symbol(Test[#field], Decl(privateNameFieldMutateNontrivialReceiver.ts, 0, 12)) +>getReceiver : Symbol(getReceiver, Decl(privateNameFieldMutateNontrivialReceiver.ts, 2, 18)) + } +} + diff --git a/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.types b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.types new file mode 100644 index 0000000000000..8083ba4e510f9 --- /dev/null +++ b/tests/baselines/reference/privateNameFieldMutateNontrivialReceiver.types @@ -0,0 +1,104 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts === +class Test { +>Test : Test + + #field: number; +>#field : number + + static mutate(getReceiver: () => Test) { +>mutate : (getReceiver: () => Test) => void +>getReceiver : () => Test + + getReceiver().#field = 100; +>getReceiver().#field = 100 : 100 +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>100 : 100 + + getReceiver().#field += 1; +>getReceiver().#field += 1 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>1 : 1 + + getReceiver().#field -= 2; +>getReceiver().#field -= 2 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>2 : 2 + + getReceiver().#field /= 3; +>getReceiver().#field /= 3 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>3 : 3 + + getReceiver().#field *= 4; +>getReceiver().#field *= 4 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>4 : 4 + + getReceiver().#field |= 5; +>getReceiver().#field |= 5 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>5 : 5 + + getReceiver().#field **= 6; +>getReceiver().#field **= 6 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>6 : 6 + + getReceiver().#field %= 7; +>getReceiver().#field %= 7 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>7 : 7 + + getReceiver().#field <<= 8; +>getReceiver().#field <<= 8 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>8 : 8 + + getReceiver().#field >>= 9; +>getReceiver().#field >>= 9 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>9 : 9 + + getReceiver().#field >>>= 10; +>getReceiver().#field >>>= 10 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>10 : 10 + + getReceiver().#field &= 11; +>getReceiver().#field &= 11 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>11 : 11 + + getReceiver().#field ^= 12; +>getReceiver().#field ^= 12 : number +>getReceiver().#field : number +>getReceiver() : Test +>getReceiver : () => Test +>12 : 12 + } +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts new file mode 100644 index 0000000000000..1ba92e98918e1 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameFieldMutateNontrivialReceiver.ts @@ -0,0 +1,18 @@ +class Test { + #field: number; + static mutate(getReceiver: () => Test) { + getReceiver().#field = 100; + getReceiver().#field += 1; + getReceiver().#field -= 2; + getReceiver().#field /= 3; + getReceiver().#field *= 4; + getReceiver().#field |= 5; + getReceiver().#field **= 6; + getReceiver().#field %= 7; + getReceiver().#field <<= 8; + getReceiver().#field >>= 9; + getReceiver().#field >>>= 10; + getReceiver().#field &= 11; + getReceiver().#field ^= 12; + } +} From 282447417f08d5a67e13f1016264fb133fe25ccb Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:26:39 -0400 Subject: [PATCH 28/30] Fix private name assignment referencing private names in the RHS expr Signed-off-by: Joseph Watts --- src/compiler/transformers/esnext.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index c1860a5d6358d..3c3234b174817 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -503,7 +503,7 @@ namespace ts { createBinary( createClassPrivateFieldGetHelper(context, getReceiver, weakMapName), getOperatorForCompoundAssignment(node.operatorToken.kind), - node.right + visitNode(node.right, visitor) ) ), node @@ -511,7 +511,12 @@ namespace ts { } else { return setOriginalNode( - createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right), + createClassPrivateFieldSetHelper( + context, + node.left.expression, + weakMapName, + visitNode(node.right, visitor) + ), node ); } From e2ce860f7e6cfe96a263e4797227b96c1a0e5f8a Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:27:08 -0400 Subject: [PATCH 29/30] Create private named field initialization order conformance test Signed-off-by: Joseph Watts --- .../privateNameInitializationOrder.js | 25 ++++++++++++++++++ .../privateNameInitializationOrder.symbols | 21 +++++++++++++++ .../privateNameInitializationOrder.types | 26 +++++++++++++++++++ .../privateNameInitializationOrder.ts | 6 +++++ 4 files changed, 78 insertions(+) create mode 100644 tests/baselines/reference/privateNameInitializationOrder.js create mode 100644 tests/baselines/reference/privateNameInitializationOrder.symbols create mode 100644 tests/baselines/reference/privateNameInitializationOrder.types create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts diff --git a/tests/baselines/reference/privateNameInitializationOrder.js b/tests/baselines/reference/privateNameInitializationOrder.js new file mode 100644 index 0000000000000..daefcf894dcbe --- /dev/null +++ b/tests/baselines/reference/privateNameInitializationOrder.js @@ -0,0 +1,25 @@ +//// [privateNameInitializationOrder.ts] +let a = 0; +class Test { + #one = ++a; + normalProp = ++a; + #two = this.#one + 1; +} + + +//// [privateNameInitializationOrder.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); }; +var a = 0; +var Test = /** @class */ (function () { + function Test() { + _one.set(this, void 0); + _two.set(this, void 0); + _classPrivateFieldSet(this, _one, ++a); + this.normalProp = ++a; + _classPrivateFieldSet(this, _two, _classPrivateFieldGet(this, _one) + 1); + } + return Test; +}()); +var _one = new WeakMap; +var _two = new WeakMap; diff --git a/tests/baselines/reference/privateNameInitializationOrder.symbols b/tests/baselines/reference/privateNameInitializationOrder.symbols new file mode 100644 index 0000000000000..4e1907226bb69 --- /dev/null +++ b/tests/baselines/reference/privateNameInitializationOrder.symbols @@ -0,0 +1,21 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts === +let a = 0; +>a : Symbol(a, Decl(privateNameInitializationOrder.ts, 0, 3)) + +class Test { +>Test : Symbol(Test, Decl(privateNameInitializationOrder.ts, 0, 10)) + + #one = ++a; +>#one : Symbol(Test[#one], Decl(privateNameInitializationOrder.ts, 1, 12)) +>a : Symbol(a, Decl(privateNameInitializationOrder.ts, 0, 3)) + + normalProp = ++a; +>normalProp : Symbol(Test.normalProp, Decl(privateNameInitializationOrder.ts, 2, 15)) +>a : Symbol(a, Decl(privateNameInitializationOrder.ts, 0, 3)) + + #two = this.#one + 1; +>#two : Symbol(Test[#two], Decl(privateNameInitializationOrder.ts, 3, 21)) +>this.#one : Symbol(Test[#one], Decl(privateNameInitializationOrder.ts, 1, 12)) +>this : Symbol(Test, Decl(privateNameInitializationOrder.ts, 0, 10)) +} + diff --git a/tests/baselines/reference/privateNameInitializationOrder.types b/tests/baselines/reference/privateNameInitializationOrder.types new file mode 100644 index 0000000000000..5f12b7a054689 --- /dev/null +++ b/tests/baselines/reference/privateNameInitializationOrder.types @@ -0,0 +1,26 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts === +let a = 0; +>a : number +>0 : 0 + +class Test { +>Test : Test + + #one = ++a; +>#one : number +>++a : number +>a : number + + normalProp = ++a; +>normalProp : number +>++a : number +>a : number + + #two = this.#one + 1; +>#two : number +>this.#one + 1 : number +>this.#one : number +>this : this +>1 : 1 +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts b/tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts new file mode 100644 index 0000000000000..90906299640fb --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameInitializationOrder.ts @@ -0,0 +1,6 @@ +let a = 0; +class Test { + #one = ++a; + normalProp = ++a; + #two = this.#one + 1; +} From a63b52d8786d5404a707e96d0ac644dde6648b0a Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Tue, 11 Sep 2018 15:54:02 -0400 Subject: [PATCH 30/30] Revert unnecessary changes Signed-off-by: Joseph Watts --- src/compiler/transformers/ts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5f0a10df777ff..3ce50892297a0 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -973,7 +973,7 @@ namespace ts { // Check if we have property assignment inside class declaration. // If there is a property assignment, we need to emit constructor whether users define it or not // If there is no property assignment, we can omit constructor if users do not define it - const hasInstancePropertyWithInitializer = forEach(node.members, member => isInstanceInitializedProperty(member)); + const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments; const constructor = getFirstConstructorWithBody(node); @@ -1242,7 +1242,7 @@ namespace ts { */ function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { for (const property of properties) { - const statement = createStatement(transformInitializedProperty(property, receiver)); + const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); setSourceMapRange(statement, moveRangePastModifiers(property)); setCommentRange(statement, property); setOriginalNode(statement, property);