Merge pull request #8100 from Microsoft/exportTransforms

Correct export assignments for variables in transforms
This commit is contained in:
Daniel Rosenwasser 2016-04-20 12:20:29 -07:00
commit e645de3650
2 changed files with 99 additions and 37 deletions

View File

@ -562,35 +562,68 @@ namespace ts {
}
function visitVariableStatement(node: VariableStatement): VisitResult<Statement> {
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<Statement> {
const original = getOriginalNode(node);
if (original.kind === SyntaxKind.EnumDeclaration
&& hasModifier(original, ModifierFlags.Export)) {
return visitExpressionStatementForEnumDeclaration(node, <EnumDeclaration>original);
const origKind = original.kind;
if (origKind === SyntaxKind.EnumDeclaration || origKind === SyntaxKind.ModuleDeclaration) {
return visitExpressionStatementForEnumOrNamespaceDeclaration(node, <EnumDeclaration | ModuleDeclaration>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<Statement> {
if (isFirstDeclarationOfKind(original, SyntaxKind.EnumDeclaration)) {
const statements: Statement[] = [node];
addVarForExportedEnumDeclaration(statements, original);
return statements;
function visitExpressionStatementForEnumOrNamespaceDeclaration(node: ExpressionStatement, original: EnumDeclaration | ModuleDeclaration): VisitResult<Statement> {
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,

View File

@ -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) {