From c1103424b8a40bad1e154d728110876a6ebae98f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 25 Apr 2017 12:44:02 -0700 Subject: [PATCH] Fix class name emit in ES5 --- src/compiler/factory.ts | 22 +++++++++++++++++++ src/compiler/transformers/es2015.ts | 13 ++++++----- src/compiler/types.ts | 17 +++++++------- .../classDeclarationBlockScoping1.js | 4 ++-- .../classDeclarationBlockScoping2.js | 4 ++-- tests/baselines/reference/localTypes1.js | 4 ++-- 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 5c7486debe3..05c26916167 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2937,6 +2937,28 @@ namespace ts { ); } + /** + * Gets the internal name of a declaration. This is primarily used for declarations that can be + * referred to by name in the body of an ES5 class function body. An internal name will *never* + * be prefixed with an module or namespace export modifier like "exports." when emitted as an + * expression. An internal name will also *never* be renamed due to a collision with a block + * scoped variable. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); + } + + /** + * Gets whether an identifier should only be referred to by its internal name. + */ + export function isInternalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; + } + /** * Gets the local name of a declaration. This is primarily used for declarations that can be * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 0dc6ed4a964..a430476958e 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -813,7 +813,7 @@ namespace ts { // Create a synthetic text range for the return statement. const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken); - const localName = getLocalName(node); + const localName = getInternalName(node); // The following partially-emitted expression exists purely to align our sourcemap // emit with the original emitter. @@ -870,7 +870,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*asteriskToken*/ undefined, - getDeclarationName(node), + getInternalName(node), /*typeParameters*/ undefined, transformConstructorParameters(constructor, hasSynthesizedSuper), /*type*/ undefined, @@ -3725,7 +3725,7 @@ namespace ts { function substituteIdentifier(node: Identifier) { // Only substitute the identifier if we have enabled substitutions for block-scoped // bindings. - if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { + if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) { const original = getParseTreeNode(node, isIdentifier); if (original && isNameOfDeclarationWithCollidingName(original)) { return setTextRange(getGeneratedNameForNode(original), node); @@ -3778,7 +3778,7 @@ namespace ts { * @param node An Identifier node. */ function substituteExpressionIdentifier(node: Identifier): Identifier { - if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { + if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) { const declaration = resolver.getReferencedDeclarationWithCollidingName(node); if (declaration) { return setTextRange(getGeneratedNameForNode(declaration.name), node); @@ -3802,8 +3802,9 @@ namespace ts { } function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) { - const expression = getLocalName(node); - return hasModifier(member, ModifierFlags.Static) ? expression : createPropertyAccess(expression, "prototype"); + return hasModifier(member, ModifierFlags.Static) + ? getLocalName(node) + : createPropertyAccess(getInternalName(node), "prototype"); } function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration, hasExtendsClause: boolean) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2389ba80753..57cf17f7100 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3940,14 +3940,15 @@ namespace ts { HelperName = 1 << 12, ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration. - Indented = 1 << 15, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). - NoIndentation = 1 << 16, // Do not indent the node. - AsyncFunctionBody = 1 << 17, - ReuseTempVariableScope = 1 << 18, // Reuse the existing temp variable scope during emit. - CustomPrologue = 1 << 19, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). - NoHoisting = 1 << 20, // Do not hoist this declaration in --module system - HasEndOfDeclarationMarker = 1 << 21, // Declaration has an associated NotEmittedStatement to mark the end of the declaration - Iterator = 1 << 22, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. + InternalName = 1 << 15, // The name is internal to an ES5 class body function. + Indented = 1 << 16, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). + NoIndentation = 1 << 17, // Do not indent the node. + AsyncFunctionBody = 1 << 18, + ReuseTempVariableScope = 1 << 19, // Reuse the existing temp variable scope during emit. + CustomPrologue = 1 << 20, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). + NoHoisting = 1 << 21, // Do not hoist this declaration in --module system + HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration + Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. } export interface EmitHelper { diff --git a/tests/baselines/reference/classDeclarationBlockScoping1.js b/tests/baselines/reference/classDeclarationBlockScoping1.js index ac3195af696..03c6254d9ea 100644 --- a/tests/baselines/reference/classDeclarationBlockScoping1.js +++ b/tests/baselines/reference/classDeclarationBlockScoping1.js @@ -15,8 +15,8 @@ var C = (function () { }()); { var C_1 = (function () { - function C_1() { + function C() { } - return C_1; + return C; }()); } diff --git a/tests/baselines/reference/classDeclarationBlockScoping2.js b/tests/baselines/reference/classDeclarationBlockScoping2.js index c3e3a89677a..7cc8a064a0c 100644 --- a/tests/baselines/reference/classDeclarationBlockScoping2.js +++ b/tests/baselines/reference/classDeclarationBlockScoping2.js @@ -19,9 +19,9 @@ function f() { var c1 = C; { var C_1 = (function () { - function C_1() { + function C() { } - return C_1; + return C; }()); var c2 = C_1; } diff --git a/tests/baselines/reference/localTypes1.js b/tests/baselines/reference/localTypes1.js index ab12be6d9fc..0932790dcfd 100644 --- a/tests/baselines/reference/localTypes1.js +++ b/tests/baselines/reference/localTypes1.js @@ -206,9 +206,9 @@ function f3(b) { } else { var A_1 = (function () { - function A_1() { + function A() { } - return A_1; + return A; }()); var c = [new A_1()]; c[0].x = E.B;