Skip to content

Commit 1c9e9e0

Browse files
committed
Elide var when emitting a module merged with an ES6 class
1 parent 96995e7 commit 1c9e9e0

5 files changed

Lines changed: 86 additions & 16 deletions

File tree

src/compiler/checker.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10387,16 +10387,30 @@ module ts {
1038710387
}
1038810388
}
1038910389

10390-
function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration {
10390+
function getFirstClassOrFunctionDeclaration(symbol: Symbol, nonAmbientOnly: boolean): Declaration {
1039110391
let declarations = symbol.declarations;
1039210392
for (let declaration of declarations) {
10393-
if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((<FunctionLikeDeclaration>declaration).body))) && !isInAmbientContext(declaration)) {
10393+
if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((<FunctionLikeDeclaration>declaration).body))) && !(nonAmbientOnly && isInAmbientContext(declaration))) {
1039410394
return declaration;
1039510395
}
1039610396
}
1039710397
return undefined;
1039810398
}
1039910399

10400+
function inSameLexicalScope(node1: Node, node2: Node) {
10401+
let container1 = getEnclosingBlockScopeContainer(node1);
10402+
let container2 = getEnclosingBlockScopeContainer(node2);
10403+
if (isGlobalSourceFile(container1)) {
10404+
return isGlobalSourceFile(container2);
10405+
}
10406+
else if (isGlobalSourceFile(container2)) {
10407+
return false;
10408+
}
10409+
else {
10410+
return container1 === container2;
10411+
}
10412+
}
10413+
1040010414
function checkModuleDeclaration(node: ModuleDeclaration) {
1040110415
if (produceDiagnostics) {
1040210416
// Grammar checking
@@ -10416,15 +10430,22 @@ module ts {
1041610430
&& symbol.declarations.length > 1
1041710431
&& !isInAmbientContext(node)
1041810432
&& isInstantiatedModule(node, compilerOptions.preserveConstEnums || compilerOptions.separateCompilation)) {
10419-
let classOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol);
10420-
if (classOrFunc) {
10421-
if (getSourceFileOfNode(node) !== getSourceFileOfNode(classOrFunc)) {
10433+
let nonAmbientClassOrFunc = getFirstClassOrFunctionDeclaration(symbol, /*nonAmbientOnly*/ true);
10434+
if (nonAmbientClassOrFunc) {
10435+
if (getSourceFileOfNode(node) !== getSourceFileOfNode(nonAmbientClassOrFunc)) {
1042210436
error(node.name, Diagnostics.A_module_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged);
1042310437
}
10424-
else if (node.pos < classOrFunc.pos) {
10438+
else if (node.pos < nonAmbientClassOrFunc.pos) {
1042510439
error(node.name, Diagnostics.A_module_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged);
1042610440
}
1042710441
}
10442+
10443+
// if the module merges with a class declaration in the same lexical scope, we need to track this
10444+
// to ensure the correct emit.
10445+
let anyClassOrFunc = getFirstClassOrFunctionDeclaration(symbol, /*nonAmbientOnly*/ false);
10446+
if (anyClassOrFunc && anyClassOrFunc.kind === SyntaxKind.ClassDeclaration && inSameLexicalScope(node, anyClassOrFunc)) {
10447+
getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass;
10448+
}
1042810449
}
1042910450

1043010451
// Checks for ambient external modules.

src/compiler/emitter.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4207,6 +4207,10 @@ var __param = this.__param || function(index, decorator) { return function (targ
42074207
return isInstantiatedModule(node, compilerOptions.preserveConstEnums || compilerOptions.separateCompilation);
42084208
}
42094209

4210+
function isModuleMergedWithES6Class(node: ModuleDeclaration) {
4211+
return languageVersion === ScriptTarget.ES6 && !!(resolver.getNodeCheckFlags(node) & NodeCheckFlags.LexicalModuleMergesWithClass);
4212+
}
4213+
42104214
function emitModuleDeclaration(node: ModuleDeclaration) {
42114215
// Emit only if this module is non-ambient.
42124216
let shouldEmit = shouldEmitModuleDeclaration(node);
@@ -4215,15 +4219,19 @@ var __param = this.__param || function(index, decorator) { return function (targ
42154219
return emitOnlyPinnedOrTripleSlashComments(node);
42164220
}
42174221

4218-
emitStart(node);
4219-
if (isES6ExportedDeclaration(node)) {
4220-
write("export ");
4222+
if (!isModuleMergedWithES6Class(node)) {
4223+
emitStart(node);
4224+
if (isES6ExportedDeclaration(node)) {
4225+
write("export ");
4226+
}
4227+
4228+
write("var ");
4229+
emit(node.name);
4230+
write(";");
4231+
emitEnd(node);
4232+
writeLine();
42214233
}
4222-
write("var ");
4223-
emit(node.name);
4224-
write(";");
4225-
emitEnd(node);
4226-
writeLine();
4234+
42274235
emitStart(node);
42284236
write("(function (");
42294237
emitStart(node.name);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,7 @@ module ts {
14051405
BlockScopedBindingInLoop = 0x00000100,
14061406
EmitDecorate = 0x00000200, // Emit __decorate
14071407
EmitParam = 0x00000400, // Emit __param helper for decorators
1408+
LexicalModuleMergesWithClass = 0x00000800, // Instantiated lexical module declaration is merged with a previous class declaration.
14081409
}
14091410

14101411
/* @internal */

src/compiler/utilities.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,9 @@ module ts {
211211
isCatchClauseVariableDeclaration(declaration);
212212
}
213213

214-
export function getEnclosingBlockScopeContainer(node: Node): Node {
215-
let current = node;
214+
// Gets the nearest enclosing block scope container that has the provided node as a descendant, that is not the provided node.
215+
export function getEnclosingBlockScopeContainer(node: Node): Node {
216+
let current = node.parent;
216217
while (current) {
217218
if (isFunctionLike(current)) {
218219
return current;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// @target: ES6
2+
// @filename: class.ts
3+
module X.Y {
4+
export class Point {
5+
constructor(x: number, y: number) {
6+
this.x = x;
7+
this.y = y;
8+
}
9+
x: number;
10+
y: number;
11+
}
12+
}
13+
14+
// @filename: module.ts
15+
module X.Y {
16+
export module Point {
17+
export var Origin = new Point(0, 0);
18+
}
19+
}
20+
21+
// @filename: test.ts
22+
//var cl: { x: number; y: number; }
23+
var cl = new X.Y.Point(1,1);
24+
var cl = X.Y.Point.Origin; // error not expected here same as bug 83996 ?
25+
26+
27+
// @filename: simple.ts
28+
class A {
29+
id: string;
30+
}
31+
32+
module A {
33+
export var Instance = new A();
34+
}
35+
36+
// ensure merging works as expected
37+
var a = A.Instance;
38+
var a = new A();
39+
var a: { id: string };

0 commit comments

Comments
 (0)