Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,17 @@ namespace ts {
break;
}

recordEmittedDeclarationInScope(node);
// Record these declarations provided that they have a name.
if ((node as ClassDeclaration | FunctionDeclaration).name) {
recordEmittedDeclarationInScope(node as ClassDeclaration | FunctionDeclaration);
}
else {
// These nodes should always have names unless they are default-exports;
// however, class declaration parsing allows for undefined names, so syntactically invalid
// programs may also have an undefined name.
Debug.assert(node.kind === SyntaxKind.ClassDeclaration || hasModifier(node, ModifierFlags.Default));
}

break;
}
}
Expand Down Expand Up @@ -2639,36 +2649,33 @@ namespace ts {
/**
* Records that a declaration was emitted in the current scope, if it was the first
* declaration for the provided symbol.
*
* NOTE: if there is ever a transformation above this one, we may not be able to rely
* on symbol names.
*/
function recordEmittedDeclarationInScope(node: Node) {
const name = node.symbol && node.symbol.escapedName;
if (name) {
if (!currentScopeFirstDeclarationsOfName) {
currentScopeFirstDeclarationsOfName = createUnderscoreEscapedMap<Node>();
}
function recordEmittedDeclarationInScope(node: FunctionDeclaration | ClassDeclaration | ModuleDeclaration | EnumDeclaration) {
if (!currentScopeFirstDeclarationsOfName) {
currentScopeFirstDeclarationsOfName = createUnderscoreEscapedMap<Node>();
}

if (!currentScopeFirstDeclarationsOfName.has(name)) {
currentScopeFirstDeclarationsOfName.set(name, node);
}
const name = declaredNameInScope(node);
if (!currentScopeFirstDeclarationsOfName.has(name)) {
currentScopeFirstDeclarationsOfName.set(name, node);
}
}

/**
* Determines whether a declaration is the first declaration with the same name emitted
* in the current scope.
* Determines whether a declaration is the first declaration with
* the same name emitted in the current scope.
*/
function isFirstEmittedDeclarationInScope(node: Node) {
function isFirstEmittedDeclarationInScope(node: ModuleDeclaration | EnumDeclaration) {
if (currentScopeFirstDeclarationsOfName) {
const name = node.symbol && node.symbol.escapedName;
if (name) {
return currentScopeFirstDeclarationsOfName.get(name) === node;
}
const name = declaredNameInScope(node);
return currentScopeFirstDeclarationsOfName.get(name) === node;
}
return true;
}

return false;
function declaredNameInScope(node: FunctionDeclaration | ClassDeclaration | ModuleDeclaration | EnumDeclaration): __String {
Debug.assertNode(node.name, isIdentifier);
return (node.name as Identifier).escapedText;
}

/**
Expand Down Expand Up @@ -2746,7 +2753,7 @@ namespace ts {
return createNotEmittedStatement(node);
}

Debug.assert(isIdentifier(node.name), "TypeScript module should have an Identifier name.");
Debug.assertNode(node.name, isIdentifier, "A TypeScript namespace should have an Identifier name.");
enableSubstitutionForNamespaceExports();

const statements: Statement[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2896,7 +2896,7 @@ namespace ts {

export interface Symbol {
flags: SymbolFlags; // Symbol flags
escapedName: __String; // Name of symbol
escapedName: __String; // Name of symbol
declarations?: Declaration[]; // Declarations associated with this symbol
valueDeclaration?: Declaration; // First value declaration of the symbol
members?: SymbolTable; // Class, interface or literal instance members
Expand Down
64 changes: 64 additions & 0 deletions src/harness/unittests/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,70 @@ namespace ts {
}
}).outputText;
});

testBaseline("rewrittenNamespace", () => {
return ts.transpileModule(`namespace Reflect { const x = 1; }`, {
transformers: {
before: [forceNamespaceRewrite],
},
compilerOptions: {
newLine: NewLineKind.CarriageReturnLineFeed,
}
}).outputText;
});

testBaseline("rewrittenNamespaceFollowingClass", () => {
return ts.transpileModule(`
class C { foo = 10; static bar = 20 }
namespace C { export let x = 10; }
`, {
transformers: {
before: [forceNamespaceRewrite],
},
compilerOptions: {
target: ts.ScriptTarget.ESNext,
newLine: NewLineKind.CarriageReturnLineFeed,
}
}).outputText;
});

testBaseline("synthesizedClassAndNamespaceCombination", () => {
return ts.transpileModule("", {
transformers: {
before: [replaceWithClassAndNamespace],
},
compilerOptions: {
target: ts.ScriptTarget.ESNext,
newLine: NewLineKind.CarriageReturnLineFeed,
}
}).outputText;

function replaceWithClassAndNamespace() {
return (sourceFile: ts.SourceFile) => {
const result = getMutableClone(sourceFile);
result.statements = ts.createNodeArray([
ts.createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined),
ts.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier("Foo"), createModuleBlock([createEmptyStatement()]))
]);
return result;
};
}
});

function forceNamespaceRewrite(context: ts.TransformationContext) {
return (sourceFile: ts.SourceFile): ts.SourceFile => {
return visitNode(sourceFile);

function visitNode<T extends ts.Node>(node: T): T {
if (node.kind === ts.SyntaxKind.ModuleBlock) {
const block = node as T & ts.ModuleBlock;
const statements = ts.createNodeArray([...block.statements]);
return ts.updateModuleBlock(block, statements) as typeof block;
}
return ts.visitEachChild(node, visitNode, context);
}
};
}
});
}

1 change: 0 additions & 1 deletion tests/baselines/reference/defaultExportsCannotMerge01.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ function Decl() {
return 0;
}
exports.default = Decl;
var Decl;
(function (Decl) {
Decl.x = 10;
Decl.y = 20;
Expand Down
1 change: 0 additions & 1 deletion tests/baselines/reference/defaultExportsCannotMerge04.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
function Foo() {
}
exports.default = Foo;
var Foo;
(function (Foo) {
})(Foo || (Foo = {}));
1 change: 1 addition & 0 deletions tests/baselines/reference/parserEnumDeclaration4.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ enum void {
}

//// [parserEnumDeclaration4.js]
var ;
(function () {
})( || ( = {}));
void {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var Reflect;
(function (Reflect) {
var x = 1;
})(Reflect || (Reflect = {}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class C {
constructor() {
this.foo = 10;
}
}
C.bar = 20;
(function (C) {
C.x = 10;
})(C || (C = {}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Foo {
}
(function (Foo) {
;
})(Foo || (Foo = {}));