Skip to content

Commit b326ac6

Browse files
Merge pull request #17856 from Microsoft/synthesizedNamespaces
[release-2.5] Fix emit for leading 'var' declarations for synthesized namespaces
2 parents a5c9f40 + f45ec92 commit b326ac6

9 files changed

+113
-25
lines changed

src/compiler/transformers/ts.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,17 @@ namespace ts {
151151
break;
152152
}
153153

154-
recordEmittedDeclarationInScope(node);
154+
// Record these declarations provided that they have a name.
155+
if ((node as ClassDeclaration | FunctionDeclaration).name) {
156+
recordEmittedDeclarationInScope(node as ClassDeclaration | FunctionDeclaration);
157+
}
158+
else {
159+
// These nodes should always have names unless they are default-exports;
160+
// however, class declaration parsing allows for undefined names, so syntactically invalid
161+
// programs may also have an undefined name.
162+
Debug.assert(node.kind === SyntaxKind.ClassDeclaration || hasModifier(node, ModifierFlags.Default));
163+
}
164+
155165
break;
156166
}
157167
}
@@ -2639,36 +2649,33 @@ namespace ts {
26392649
/**
26402650
* Records that a declaration was emitted in the current scope, if it was the first
26412651
* declaration for the provided symbol.
2642-
*
2643-
* NOTE: if there is ever a transformation above this one, we may not be able to rely
2644-
* on symbol names.
26452652
*/
2646-
function recordEmittedDeclarationInScope(node: Node) {
2647-
const name = node.symbol && node.symbol.escapedName;
2648-
if (name) {
2649-
if (!currentScopeFirstDeclarationsOfName) {
2650-
currentScopeFirstDeclarationsOfName = createUnderscoreEscapedMap<Node>();
2651-
}
2653+
function recordEmittedDeclarationInScope(node: FunctionDeclaration | ClassDeclaration | ModuleDeclaration | EnumDeclaration) {
2654+
if (!currentScopeFirstDeclarationsOfName) {
2655+
currentScopeFirstDeclarationsOfName = createUnderscoreEscapedMap<Node>();
2656+
}
26522657

2653-
if (!currentScopeFirstDeclarationsOfName.has(name)) {
2654-
currentScopeFirstDeclarationsOfName.set(name, node);
2655-
}
2658+
const name = declaredNameInScope(node);
2659+
if (!currentScopeFirstDeclarationsOfName.has(name)) {
2660+
currentScopeFirstDeclarationsOfName.set(name, node);
26562661
}
26572662
}
26582663

26592664
/**
2660-
* Determines whether a declaration is the first declaration with the same name emitted
2661-
* in the current scope.
2665+
* Determines whether a declaration is the first declaration with
2666+
* the same name emitted in the current scope.
26622667
*/
2663-
function isFirstEmittedDeclarationInScope(node: Node) {
2668+
function isFirstEmittedDeclarationInScope(node: ModuleDeclaration | EnumDeclaration) {
26642669
if (currentScopeFirstDeclarationsOfName) {
2665-
const name = node.symbol && node.symbol.escapedName;
2666-
if (name) {
2667-
return currentScopeFirstDeclarationsOfName.get(name) === node;
2668-
}
2670+
const name = declaredNameInScope(node);
2671+
return currentScopeFirstDeclarationsOfName.get(name) === node;
26692672
}
2673+
return true;
2674+
}
26702675

2671-
return false;
2676+
function declaredNameInScope(node: FunctionDeclaration | ClassDeclaration | ModuleDeclaration | EnumDeclaration): __String {
2677+
Debug.assertNode(node.name, isIdentifier);
2678+
return (node.name as Identifier).escapedText;
26722679
}
26732680

26742681
/**
@@ -2746,7 +2753,7 @@ namespace ts {
27462753
return createNotEmittedStatement(node);
27472754
}
27482755

2749-
Debug.assert(isIdentifier(node.name), "TypeScript module should have an Identifier name.");
2756+
Debug.assertNode(node.name, isIdentifier, "A TypeScript namespace should have an Identifier name.");
27502757
enableSubstitutionForNamespaceExports();
27512758

27522759
const statements: Statement[] = [];

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2923,7 +2923,7 @@ namespace ts {
29232923

29242924
export interface Symbol {
29252925
flags: SymbolFlags; // Symbol flags
2926-
escapedName: __String; // Name of symbol
2926+
escapedName: __String; // Name of symbol
29272927
declarations?: Declaration[]; // Declarations associated with this symbol
29282928
valueDeclaration?: Declaration; // First value declaration of the symbol
29292929
members?: SymbolTable; // Class, interface or literal instance members

src/harness/unittests/transform.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,70 @@ namespace ts {
7474
}
7575
}).outputText;
7676
});
77+
78+
testBaseline("rewrittenNamespace", () => {
79+
return ts.transpileModule(`namespace Reflect { const x = 1; }`, {
80+
transformers: {
81+
before: [forceNamespaceRewrite],
82+
},
83+
compilerOptions: {
84+
newLine: NewLineKind.CarriageReturnLineFeed,
85+
}
86+
}).outputText;
87+
});
88+
89+
testBaseline("rewrittenNamespaceFollowingClass", () => {
90+
return ts.transpileModule(`
91+
class C { foo = 10; static bar = 20 }
92+
namespace C { export let x = 10; }
93+
`, {
94+
transformers: {
95+
before: [forceNamespaceRewrite],
96+
},
97+
compilerOptions: {
98+
target: ts.ScriptTarget.ESNext,
99+
newLine: NewLineKind.CarriageReturnLineFeed,
100+
}
101+
}).outputText;
102+
});
103+
104+
testBaseline("synthesizedClassAndNamespaceCombination", () => {
105+
return ts.transpileModule("", {
106+
transformers: {
107+
before: [replaceWithClassAndNamespace],
108+
},
109+
compilerOptions: {
110+
target: ts.ScriptTarget.ESNext,
111+
newLine: NewLineKind.CarriageReturnLineFeed,
112+
}
113+
}).outputText;
114+
115+
function replaceWithClassAndNamespace() {
116+
return (sourceFile: ts.SourceFile) => {
117+
const result = getMutableClone(sourceFile);
118+
result.statements = ts.createNodeArray([
119+
ts.createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined),
120+
ts.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier("Foo"), createModuleBlock([createEmptyStatement()]))
121+
]);
122+
return result;
123+
};
124+
}
125+
});
126+
127+
function forceNamespaceRewrite(context: ts.TransformationContext) {
128+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
129+
return visitNode(sourceFile);
130+
131+
function visitNode<T extends ts.Node>(node: T): T {
132+
if (node.kind === ts.SyntaxKind.ModuleBlock) {
133+
const block = node as T & ts.ModuleBlock;
134+
const statements = ts.createNodeArray([...block.statements]);
135+
return ts.updateModuleBlock(block, statements) as typeof block;
136+
}
137+
return ts.visitEachChild(node, visitNode, context);
138+
}
139+
};
140+
}
77141
});
78142
}
79143

tests/baselines/reference/defaultExportsCannotMerge01.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ function Decl() {
3636
return 0;
3737
}
3838
exports.default = Decl;
39-
var Decl;
4039
(function (Decl) {
4140
Decl.x = 10;
4241
Decl.y = 20;

tests/baselines/reference/defaultExportsCannotMerge04.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
1818
function Foo() {
1919
}
2020
exports.default = Foo;
21-
var Foo;
2221
(function (Foo) {
2322
})(Foo || (Foo = {}));

tests/baselines/reference/parserEnumDeclaration4.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ enum void {
33
}
44

55
//// [parserEnumDeclaration4.js]
6+
var ;
67
(function () {
78
})( || ( = {}));
89
void {};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
var Reflect;
2+
(function (Reflect) {
3+
var x = 1;
4+
})(Reflect || (Reflect = {}));
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class C {
2+
constructor() {
3+
this.foo = 10;
4+
}
5+
}
6+
C.bar = 20;
7+
(function (C) {
8+
C.x = 10;
9+
})(C || (C = {}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Foo {
2+
}
3+
(function (Foo) {
4+
;
5+
})(Foo || (Foo = {}));

0 commit comments

Comments
 (0)