From e13a07e3bdeb12aa6a8640950e71e4175bb964e2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 13 Apr 2016 17:31:53 -0700 Subject: [PATCH 1/5] Emit 'exports.foo' assignments for bindings that are exported in specifiers. --- src/compiler/transformers/module/module.ts | 104 ++++++++++++++------- src/compiler/transformers/ts.ts | 21 +++-- 2 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 9d7dc0b2a82..1fa1d0473db 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -559,34 +559,64 @@ namespace ts { } function visitVariableStatement(node: VariableStatement): VisitResult { - if (hasModifier(node, ModifierFlags.Export)) { - // If the variable is for a declaration that has a local name, - // do not elide the declaration. - const original = getOriginalNode(node); - if (original.kind === SyntaxKind.EnumDeclaration - || original.kind === SyntaxKind.ModuleDeclaration) { - return setOriginalNode( - createVariableStatement( - /*modifiers*/ undefined, - node.declarationList - ), - node - ); + // If the variable is for a generated declaration, + // we should maintain it and just strip off the 'export' modifier if necesary. + const original = getOriginalNode(node); + if (original.kind === SyntaxKind.EnumDeclaration || original.kind === SyntaxKind.ModuleDeclaration) { + if (!hasModifier(node, ModifierFlags.Export)) { + return node; } - const variables = getInitializedVariables(node.declarationList); - if (variables.length === 0) { - // elide statement if there are no initialized variables - return undefined; - } - - return createStatement( - inlineExpressions( - map(variables, transformInitializedVariable) - ) + return setOriginalNode( + createVariableStatement( + /*modifiers*/ undefined, + node.declarationList + ), + node ); } - return node; + + const resultStatements: Statement[] = []; + + // If we're exporting these variables, then these just become assignments to 'exports.blah'. + // We only want to emit assignments for variables with initializers. + if (hasModifier(node, ModifierFlags.Export)) { + const variables = getInitializedVariables(node.declarationList); + if (variables.length > 0) { + let inlineAssignments = createStatement( + inlineExpressions( + map(variables, transformInitializedVariable) + ) + ); + resultStatements.push(inlineAssignments); + } + } + else { + resultStatements.push(node); + } + + // While we might not have been exported here, each variable might have been exported + // later on in an export specifier (e.g. `export {foo as blah, bar}`). + for (const decl of node.declarationList.declarations) { + addExportMemberAssignmentsForBindingName(resultStatements, decl.name); + } + + return resultStatements; + } + + /** + * Creates appropriate assignments for each binding identifier that is exported in an export specifier, + * and inserts it into 'resultStatements'. + */ + function addExportMemberAssignmentsForBindingName(resultStatements: Statement[], name: BindingName): void { + if (isBindingPattern(name)) { + for (const element of name.elements) { + addExportMemberAssignmentsForBindingName(resultStatements, element.name) + } + } + else { + addExportMemberAssignments(resultStatements, name); + } } function transformInitializedVariable(node: VariableDeclaration): Expression { @@ -665,28 +695,34 @@ namespace ts { function visitExpressionStatement(node: ExpressionStatement): VisitResult { const original = getOriginalNode(node); - if (original.kind === SyntaxKind.EnumDeclaration - && hasModifier(original, ModifierFlags.Export)) { - return visitExpressionStatementForEnumDeclaration(node, original); + const origKind = original.kind; + if (origKind === SyntaxKind.EnumDeclaration || origKind === SyntaxKind.ModuleDeclaration) { + return visitExpressionStatementForEnumOrNamespaceDeclaration(node, original); } return node; } - function visitExpressionStatementForEnumDeclaration(node: ExpressionStatement, original: EnumDeclaration): VisitResult { - if (isFirstDeclarationOfKind(original, SyntaxKind.EnumDeclaration)) { - const statements: Statement[] = [node]; - addVarForExportedEnumDeclaration(statements, original); - return statements; + function visitExpressionStatementForEnumOrNamespaceDeclaration(node: ExpressionStatement, original: EnumDeclaration | ModuleDeclaration): VisitResult { + const statements: Statement[] = [node]; + + // Preserve old behavior for enums in which a variable statement is emitted after the body itself. + if (hasModifier(original, ModifierFlags.Export) && + original.kind === SyntaxKind.EnumDeclaration && + isFirstDeclarationOfKind(original, SyntaxKind.EnumDeclaration)) { + addVarForExportedEnumOrNamespaceDeclaration(statements, original); + } + else { + addExportMemberAssignments(statements, original.name); } - return node; + return statements; } /** * Adds a trailing VariableStatement for an enum or module declaration. */ - function addVarForExportedEnumDeclaration(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { + function addVarForExportedEnumOrNamespaceDeclaration(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { statements.push( createVariableStatement( /*modifiers*/ undefined, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index b436c254ff0..115f1d681c9 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2229,14 +2229,17 @@ namespace ts { function addVarForEnumDeclaration(statements: Statement[], node: EnumDeclaration) { // Emit a variable statement for the enum. statements.push( - createVariableStatement( - isES6ExportedDeclaration(node) - ? visitNodes(node.modifiers, visitor, isModifier) - : undefined, - [createVariableDeclaration( - getDeclarationName(node) - )], - /*location*/ node + setOriginalNode( + createVariableStatement( + isES6ExportedDeclaration(node) + ? visitNodes(node.modifiers, visitor, isModifier) + : undefined, + [createVariableDeclaration( + getDeclarationName(node) + )], + /*location*/ node + ), + /*original*/ node ) ); } @@ -2398,7 +2401,7 @@ namespace ts { function isES6ExportedDeclaration(node: Node) { return isExternalModuleExport(node) - && moduleKind >= ModuleKind.ES6; + && moduleKind === ModuleKind.ES6; } function shouldEmitVarForModuleDeclaration(node: ModuleDeclaration) { From 482dfb61bec9a3367b8e207e5bd15301344659d6 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 14 Apr 2016 12:08:53 -0700 Subject: [PATCH 2/5] Fixed emit for decorated classes that eventually get exported. --- src/compiler/transformers/module/module.ts | 22 +++++++++++++++++++--- src/compiler/transformers/ts.ts | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 1fa1d0473db..7599281e142 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -561,8 +561,11 @@ namespace ts { function visitVariableStatement(node: VariableStatement): VisitResult { // If the variable is for a generated declaration, // we should maintain it and just strip off the 'export' modifier if necesary. - const original = getOriginalNode(node); - if (original.kind === SyntaxKind.EnumDeclaration || original.kind === SyntaxKind.ModuleDeclaration) { + const originalKind = getOriginalNode(node).kind; + if (originalKind === SyntaxKind.ModuleDeclaration || + originalKind === SyntaxKind.EnumDeclaration || + originalKind === SyntaxKind.ClassDeclaration) { + if (!hasModifier(node, ModifierFlags.Export)) { return node; } @@ -686,7 +689,10 @@ namespace ts { statements.push(node); } - if (node.name) { + // Decorators end up creating a series of assignment expressions which overwrite + // the local binding that we export, so we need to defer from exporting decorated classes + // until the decoration assignments take place. We do this when visiting expression-statements. + if (node.name && !(node.decorators && node.decorators.length)) { addExportMemberAssignments(statements, node.name); } @@ -696,9 +702,19 @@ namespace ts { function visitExpressionStatement(node: ExpressionStatement): VisitResult { const original = getOriginalNode(node); const origKind = original.kind; + if (origKind === SyntaxKind.EnumDeclaration || origKind === SyntaxKind.ModuleDeclaration) { return visitExpressionStatementForEnumOrNamespaceDeclaration(node, original); } + else if (origKind === SyntaxKind.ClassDeclaration) { + // The decorated assignment for a class name will need to be transformed. + const classDecl = original as ClassDeclaration; + if (classDecl.name) { + const statements = [node]; + addExportMemberAssignments(statements, classDecl.name); + return statements; + } + } return node; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 115f1d681c9..001a08dca67 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1304,7 +1304,7 @@ namespace ts { function addConstructorDecorationStatement(statements: Statement[], node: ClassDeclaration, decoratedClassAlias: Identifier) { const expression = generateConstructorDecorationExpression(node, decoratedClassAlias); if (expression) { - statements.push(createStatement(expression)); + statements.push(setOriginalNode(createStatement(expression), node)); } } From bce1a06c0826e2ceb7c03f986822501812076849 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 14 Apr 2016 12:31:12 -0700 Subject: [PATCH 3/5] Added an assertion to ensure export assignments for expression statements are only emitted for decorated clases. --- src/compiler/transformers/module/module.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 7599281e142..5d789ed6280 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -707,11 +707,14 @@ namespace ts { return visitExpressionStatementForEnumOrNamespaceDeclaration(node, original); } else if (origKind === SyntaxKind.ClassDeclaration) { - // The decorated assignment for a class name will need to be transformed. + // The decorated assignment for a class name will potentially need to be transformed. const classDecl = original as ClassDeclaration; if (classDecl.name) { const statements = [node]; addExportMemberAssignments(statements, classDecl.name); + if (statements.length > 1) { + Debug.assert(!!classDecl.decorators, "Expression statements should only have an export member assignment when decorated.") + } return statements; } } From 3dd340179ccbfde0715dfd3d81a331797ca50de5 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 15 Apr 2016 16:42:54 -0700 Subject: [PATCH 4/5] Correct export assignments for when the variable declaration has an export modifier. --- src/compiler/transformers/module/module.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 14134a0e206..d6de01342be 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -717,7 +717,13 @@ namespace ts { const classDecl = original as ClassDeclaration; if (classDecl.name) { const statements = [node]; + // Avoid emitting a default because that will typically be taken care of for us. + if (hasModifier(classDecl, ModifierFlags.Export) && !hasModifier(classDecl, ModifierFlags.Default)) { + addExportMemberAssignment(statements, classDecl) + } + addExportMemberAssignments(statements, classDecl.name); + if (statements.length > 1) { Debug.assert(!!classDecl.decorators, "Expression statements should only have an export member assignment when decorated.") } From 74557b6ffc321ee391700b1dcad57e1daffe326f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 19 Apr 2016 15:51:02 -0700 Subject: [PATCH 5/5] Addressed CR feedback (which fixed 'tests/cases/conformance/es6/modules/exportsAndImports3-amd.ts'). --- src/compiler/transformers/module/module.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index d6de01342be..da70b4d44b5 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -563,7 +563,7 @@ namespace ts { function visitVariableStatement(node: VariableStatement): VisitResult { // If the variable is for a generated declaration, - // we should maintain it and just strip off the 'export' modifier if necesary. + // we should maintain it and just strip off the 'export' modifier if necessary. const originalKind = getOriginalNode(node).kind; if (originalKind === SyntaxKind.ModuleDeclaration || originalKind === SyntaxKind.EnumDeclaration || @@ -713,11 +713,13 @@ namespace ts { return visitExpressionStatementForEnumOrNamespaceDeclaration(node, original); } else if (origKind === SyntaxKind.ClassDeclaration) { - // The decorated assignment for a class name will potentially need to be transformed. + // The decorated assignment for a class name may need to be transformed. const classDecl = original as ClassDeclaration; if (classDecl.name) { const statements = [node]; - // Avoid emitting a default because that will typically be taken care of for us. + // Avoid emitting a default because a decorated default-exported class will have been rewritten in the TS transformer to + // a decorator assignment (`foo = __decorate(...)`) followed by a separate default export declaration (`export default foo`). + // We will eventually take care of that default export assignment when we transform the generated default export declaration. if (hasModifier(classDecl, ModifierFlags.Export) && !hasModifier(classDecl, ModifierFlags.Default)) { addExportMemberAssignment(statements, classDecl) } @@ -743,9 +745,8 @@ namespace ts { isFirstDeclarationOfKind(original, SyntaxKind.EnumDeclaration)) { addVarForExportedEnumOrNamespaceDeclaration(statements, original); } - else { - addExportMemberAssignments(statements, original.name); - } + + addExportMemberAssignments(statements, original.name); return statements; }