Skip to content

Commit 6756e3e

Browse files
authored
Merge pull request #15379 from Microsoft/fix14945
Fix class name emit in ES5
2 parents 15df12d + 1d1ebd0 commit 6756e3e

File tree

10 files changed

+313
-21
lines changed

10 files changed

+313
-21
lines changed

src/compiler/factory.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,6 +2949,28 @@ namespace ts {
29492949
);
29502950
}
29512951

2952+
/**
2953+
* Gets the internal name of a declaration. This is primarily used for declarations that can be
2954+
* referred to by name in the body of an ES5 class function body. An internal name will *never*
2955+
* be prefixed with an module or namespace export modifier like "exports." when emitted as an
2956+
* expression. An internal name will also *never* be renamed due to a collision with a block
2957+
* scoped variable.
2958+
*
2959+
* @param node The declaration.
2960+
* @param allowComments A value indicating whether comments may be emitted for the name.
2961+
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
2962+
*/
2963+
export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) {
2964+
return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName);
2965+
}
2966+
2967+
/**
2968+
* Gets whether an identifier should only be referred to by its internal name.
2969+
*/
2970+
export function isInternalName(node: Identifier) {
2971+
return (getEmitFlags(node) & EmitFlags.InternalName) !== 0;
2972+
}
2973+
29522974
/**
29532975
* Gets the local name of a declaration. This is primarily used for declarations that can be
29542976
* referred to by name in the declaration's immediate scope (classes, enums, namespaces). A

src/compiler/transformers/es2015.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ namespace ts {
813813

814814
// Create a synthetic text range for the return statement.
815815
const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken);
816-
const localName = getLocalName(node);
816+
const localName = getInternalName(node);
817817

818818
// The following partially-emitted expression exists purely to align our sourcemap
819819
// emit with the original emitter.
@@ -870,7 +870,7 @@ namespace ts {
870870
/*decorators*/ undefined,
871871
/*modifiers*/ undefined,
872872
/*asteriskToken*/ undefined,
873-
getDeclarationName(node),
873+
getInternalName(node),
874874
/*typeParameters*/ undefined,
875875
transformConstructorParameters(constructor, hasSynthesizedSuper),
876876
/*type*/ undefined,
@@ -3725,7 +3725,7 @@ namespace ts {
37253725
function substituteIdentifier(node: Identifier) {
37263726
// Only substitute the identifier if we have enabled substitutions for block-scoped
37273727
// bindings.
3728-
if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) {
3728+
if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) {
37293729
const original = getParseTreeNode(node, isIdentifier);
37303730
if (original && isNameOfDeclarationWithCollidingName(original)) {
37313731
return setTextRange(getGeneratedNameForNode(original), node);
@@ -3778,16 +3778,42 @@ namespace ts {
37783778
* @param node An Identifier node.
37793779
*/
37803780
function substituteExpressionIdentifier(node: Identifier): Identifier {
3781-
if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) {
3781+
if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) {
37823782
const declaration = resolver.getReferencedDeclarationWithCollidingName(node);
3783-
if (declaration) {
3783+
if (declaration && !(isClassLike(declaration) && isPartOfClassBody(declaration, node))) {
37843784
return setTextRange(getGeneratedNameForNode(declaration.name), node);
37853785
}
37863786
}
37873787

37883788
return node;
37893789
}
37903790

3791+
function isPartOfClassBody(declaration: ClassLikeDeclaration, node: Identifier) {
3792+
let currentNode = getParseTreeNode(node);
3793+
if (!currentNode || currentNode === declaration || currentNode.end <= declaration.pos || currentNode.pos >= declaration.end) {
3794+
// if the node has no correlation to a parse tree node, its definitely not
3795+
// part of the body.
3796+
// if the node is outside of the document range of the declaration, its
3797+
// definitely not part of the body.
3798+
return false;
3799+
}
3800+
const blockScope = getEnclosingBlockScopeContainer(declaration);
3801+
while (currentNode) {
3802+
if (currentNode === blockScope || currentNode === declaration) {
3803+
// if we are in the enclosing block scope of the declaration, we are definitely
3804+
// not inside the class body.
3805+
return false;
3806+
}
3807+
if (isClassElement(currentNode) && currentNode.parent === declaration) {
3808+
// we are in the class body, but we treat static fields as outside of the class body
3809+
return currentNode.kind !== SyntaxKind.PropertyDeclaration
3810+
|| (getModifierFlags(currentNode) & ModifierFlags.Static) === 0;
3811+
}
3812+
currentNode = currentNode.parent;
3813+
}
3814+
return false;
3815+
}
3816+
37913817
/**
37923818
* Substitutes `this` when contained within an arrow function.
37933819
*
@@ -3802,8 +3828,9 @@ namespace ts {
38023828
}
38033829

38043830
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
3805-
const expression = getLocalName(node);
3806-
return hasModifier(member, ModifierFlags.Static) ? expression : createPropertyAccess(expression, "prototype");
3831+
return hasModifier(member, ModifierFlags.Static)
3832+
? getInternalName(node)
3833+
: createPropertyAccess(getInternalName(node), "prototype");
38073834
}
38083835

38093836
function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration, hasExtendsClause: boolean) {

src/compiler/types.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3940,14 +3940,15 @@ namespace ts {
39403940
HelperName = 1 << 12,
39413941
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).
39423942
LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration.
3943-
Indented = 1 << 15, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
3944-
NoIndentation = 1 << 16, // Do not indent the node.
3945-
AsyncFunctionBody = 1 << 17,
3946-
ReuseTempVariableScope = 1 << 18, // Reuse the existing temp variable scope during emit.
3947-
CustomPrologue = 1 << 19, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
3948-
NoHoisting = 1 << 20, // Do not hoist this declaration in --module system
3949-
HasEndOfDeclarationMarker = 1 << 21, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
3950-
Iterator = 1 << 22, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
3943+
InternalName = 1 << 15, // The name is internal to an ES5 class body function.
3944+
Indented = 1 << 16, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
3945+
NoIndentation = 1 << 17, // Do not indent the node.
3946+
AsyncFunctionBody = 1 << 18,
3947+
ReuseTempVariableScope = 1 << 19, // Reuse the existing temp variable scope during emit.
3948+
CustomPrologue = 1 << 20, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
3949+
NoHoisting = 1 << 21, // Do not hoist this declaration in --module system
3950+
HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
3951+
Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
39513952
}
39523953

39533954
export interface EmitHelper {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [classBlockScoping.ts]
2+
function f(b: boolean) {
3+
let Foo: any;
4+
if (b) {
5+
Foo = class Foo {
6+
static y = new Foo();
7+
8+
static x() {
9+
new Foo();
10+
}
11+
12+
m() {
13+
new Foo();
14+
}
15+
};
16+
17+
new Foo();
18+
}
19+
else {
20+
class Foo {
21+
static y = new Foo();
22+
23+
static x() {
24+
new Foo();
25+
}
26+
27+
m() {
28+
new Foo();
29+
}
30+
}
31+
32+
new Foo();
33+
}
34+
}
35+
36+
//// [classBlockScoping.js]
37+
function f(b) {
38+
var Foo;
39+
if (b) {
40+
Foo = (_a = (function () {
41+
function Foo() {
42+
}
43+
Foo.x = function () {
44+
new Foo();
45+
};
46+
Foo.prototype.m = function () {
47+
new Foo();
48+
};
49+
return Foo;
50+
}()),
51+
_a.y = new _a(),
52+
_a);
53+
new Foo();
54+
}
55+
else {
56+
var Foo_1 = (function () {
57+
function Foo() {
58+
}
59+
Foo.x = function () {
60+
new Foo();
61+
};
62+
Foo.prototype.m = function () {
63+
new Foo();
64+
};
65+
return Foo;
66+
}());
67+
Foo_1.y = new Foo_1();
68+
new Foo_1();
69+
}
70+
var _a;
71+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/compiler/classBlockScoping.ts ===
2+
function f(b: boolean) {
3+
>f : Symbol(f, Decl(classBlockScoping.ts, 0, 0))
4+
>b : Symbol(b, Decl(classBlockScoping.ts, 0, 11))
5+
6+
let Foo: any;
7+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5))
8+
9+
if (b) {
10+
>b : Symbol(b, Decl(classBlockScoping.ts, 0, 11))
11+
12+
Foo = class Foo {
13+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5))
14+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9))
15+
16+
static y = new Foo();
17+
>y : Symbol(Foo.y, Decl(classBlockScoping.ts, 3, 21))
18+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9))
19+
20+
static x() {
21+
>x : Symbol(Foo.x, Decl(classBlockScoping.ts, 4, 27))
22+
23+
new Foo();
24+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9))
25+
}
26+
27+
m() {
28+
>m : Symbol(Foo.m, Decl(classBlockScoping.ts, 8, 7))
29+
30+
new Foo();
31+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9))
32+
}
33+
};
34+
35+
new Foo();
36+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5))
37+
}
38+
else {
39+
class Foo {
40+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8))
41+
42+
static y = new Foo();
43+
>y : Symbol(Foo.y, Decl(classBlockScoping.ts, 18, 15))
44+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8))
45+
46+
static x() {
47+
>x : Symbol(Foo.x, Decl(classBlockScoping.ts, 19, 27))
48+
49+
new Foo();
50+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8))
51+
}
52+
53+
m() {
54+
>m : Symbol(Foo.m, Decl(classBlockScoping.ts, 23, 7))
55+
56+
new Foo();
57+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8))
58+
}
59+
}
60+
61+
new Foo();
62+
>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8))
63+
}
64+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
=== tests/cases/compiler/classBlockScoping.ts ===
2+
function f(b: boolean) {
3+
>f : (b: boolean) => void
4+
>b : boolean
5+
6+
let Foo: any;
7+
>Foo : any
8+
9+
if (b) {
10+
>b : boolean
11+
12+
Foo = class Foo {
13+
>Foo = class Foo { static y = new Foo(); static x() { new Foo(); } m() { new Foo(); } } : typeof Foo
14+
>Foo : any
15+
>class Foo { static y = new Foo(); static x() { new Foo(); } m() { new Foo(); } } : typeof Foo
16+
>Foo : typeof Foo
17+
18+
static y = new Foo();
19+
>y : Foo
20+
>new Foo() : Foo
21+
>Foo : typeof Foo
22+
23+
static x() {
24+
>x : () => void
25+
26+
new Foo();
27+
>new Foo() : Foo
28+
>Foo : typeof Foo
29+
}
30+
31+
m() {
32+
>m : () => void
33+
34+
new Foo();
35+
>new Foo() : Foo
36+
>Foo : typeof Foo
37+
}
38+
};
39+
40+
new Foo();
41+
>new Foo() : any
42+
>Foo : any
43+
}
44+
else {
45+
class Foo {
46+
>Foo : Foo
47+
48+
static y = new Foo();
49+
>y : Foo
50+
>new Foo() : Foo
51+
>Foo : typeof Foo
52+
53+
static x() {
54+
>x : () => void
55+
56+
new Foo();
57+
>new Foo() : Foo
58+
>Foo : typeof Foo
59+
}
60+
61+
m() {
62+
>m : () => void
63+
64+
new Foo();
65+
>new Foo() : Foo
66+
>Foo : typeof Foo
67+
}
68+
}
69+
70+
new Foo();
71+
>new Foo() : Foo
72+
>Foo : typeof Foo
73+
}
74+
}

tests/baselines/reference/classDeclarationBlockScoping1.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ var C = (function () {
1515
}());
1616
{
1717
var C_1 = (function () {
18-
function C_1() {
18+
function C() {
1919
}
20-
return C_1;
20+
return C;
2121
}());
2222
}

tests/baselines/reference/classDeclarationBlockScoping2.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ function f() {
1919
var c1 = C;
2020
{
2121
var C_1 = (function () {
22-
function C_1() {
22+
function C() {
2323
}
24-
return C_1;
24+
return C;
2525
}());
2626
var c2 = C_1;
2727
}

tests/baselines/reference/localTypes1.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,9 @@ function f3(b) {
206206
}
207207
else {
208208
var A_1 = (function () {
209-
function A_1() {
209+
function A() {
210210
}
211-
return A_1;
211+
return A;
212212
}());
213213
var c = [new A_1()];
214214
c[0].x = E.B;

0 commit comments

Comments
 (0)