diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 35f8b981732..d8db4165faa 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -562,35 +562,68 @@ 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 necessary. + const originalKind = getOriginalNode(node).kind; + if (originalKind === SyntaxKind.ModuleDeclaration || + originalKind === SyntaxKind.EnumDeclaration || + originalKind === SyntaxKind.ClassDeclaration) { + + 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) + ), + node + ); + 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 { @@ -666,7 +699,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); } @@ -675,28 +711,54 @@ 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); + } + else if (origKind === SyntaxKind.ClassDeclaration) { + // 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 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) + } + + 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; + } } 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); } - return node; + addExportMemberAssignments(statements, original.name); + + 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 e9cb4118a12..d5b969926cc 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1312,7 +1312,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)); } } @@ -2253,7 +2253,7 @@ namespace ts { || (isES6ExportedDeclaration(node) && isFirstDeclarationOfKind(node, node.kind)); } - /** + /* * Adds a trailing VariableStatement for an enum or module declaration. */ function addVarForEnumExportedFromNamespace(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { @@ -2410,7 +2410,7 @@ namespace ts { function isES6ExportedDeclaration(node: Node) { return isExternalModuleExport(node) - && moduleKind >= ModuleKind.ES6; + && moduleKind === ModuleKind.ES6; } function shouldEmitVarForModuleDeclaration(node: ModuleDeclaration) {