From 5e2bd6b063a935d5cb5eda00d200a951cca662bf Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 20 Oct 2016 16:44:51 -0700 Subject: [PATCH] Move System module transform to end. --- src/compiler/binder.ts | 2 +- src/compiler/core.ts | 67 +- src/compiler/factory.ts | 40 +- src/compiler/transformer.ts | 10 +- src/compiler/transformers/destructuring.ts | 18 +- src/compiler/transformers/es2015.ts | 20 +- src/compiler/transformers/module/module.ts | 703 +++--- src/compiler/transformers/module/system.ts | 1915 ++++++++++------- src/compiler/transformers/ts.ts | 31 +- src/compiler/types.ts | 53 +- src/compiler/utilities.ts | 50 +- .../reference/capturedLetConstInLoop4.js | 4 +- .../reference/dottedNamesInSystem.js | 2 +- .../outFilerootDirModuleNamesSystem.js | 2 +- tests/baselines/reference/systemModule10.js | 4 +- .../baselines/reference/systemModule10_ES5.js | 4 +- tests/baselines/reference/systemModule11.js | 10 +- tests/baselines/reference/systemModule13.js | 6 +- tests/baselines/reference/systemModule14.js | 4 +- tests/baselines/reference/systemModule17.js | 8 +- tests/baselines/reference/systemModule3.js | 4 +- tests/baselines/reference/systemModule8.js | 2 +- tests/baselines/reference/systemModule9.js | 1 - ...stemModuleConstEnumsSeparateCompilation.js | 2 +- .../systemModuleDeclarationMerging.js | 2 +- .../reference/systemModuleExportDefault.js | 4 +- .../systemModuleNonTopLevelModuleMembers.js | 2 +- .../reference/systemModuleTargetES6.js | 6 +- 28 files changed, 1719 insertions(+), 1257 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2b3584be9e0..97644000b80 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2485,7 +2485,7 @@ namespace ts { && (leftKind === SyntaxKind.ObjectLiteralExpression || leftKind === SyntaxKind.ArrayLiteralExpression)) { // Destructuring assignments are ES6 syntax. - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.DestructuringAssignment; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; } else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken || operatorTokenKind === SyntaxKind.AsteriskAsteriskEqualsToken) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1dd8ed8ff7f..e143d4c4345 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -424,11 +424,16 @@ namespace ts { export function some(array: T[], predicate?: (value: T) => boolean): boolean { if (array) { - for (const v of array) { - if (!predicate || predicate(v)) { - return true; + if (predicate) { + for (const v of array) { + if (predicate(v)) { + return true; + } } } + else { + return array.length > 0; + } } return false; } @@ -485,6 +490,14 @@ namespace ts { return result; } + /** + * Appends a value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param value The value to append to the array. If `value` is `undefined`, nothing is + * appended. + */ export function append(to: T[] | undefined, value: T | undefined): T[] | undefined { if (value === undefined) return to; if (to === undefined) to = []; @@ -492,14 +505,20 @@ namespace ts { return to; } - export function addRange(to: T[], from: T[]): void { - if (to && from) { - for (const v of from) { - if (v !== undefined) { - to.push(v); - } - } + /** + * Appends a range of value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param from The values to append to the array. If `from` is `undefined`, nothing is + * appended. If an element of `from` is `undefined`, that element is not appended. + */ + export function addRange(to: T[] | undefined, from: T[] | undefined): T[] | undefined { + if (from === undefined) return to; + for (const v of from) { + to = append(to, v); } + return to; } export function rangeEquals(array1: T[], array2: T[], pos: number, end: number) { @@ -512,33 +531,43 @@ namespace ts { return true; } + /** + * Returns the first element of an array if non-empty, `undefined` otherwise. + */ export function firstOrUndefined(array: T[]): T { return array && array.length > 0 ? array[0] : undefined; } + /** + * Returns the last element of an array if non-empty, `undefined` otherwise. + */ + export function lastOrUndefined(array: T[]): T { + return array && array.length > 0 + ? array[array.length - 1] + : undefined; + } + + /** + * Returns the only element of an array if it contains only one element, `undefined` otherwise. + */ export function singleOrUndefined(array: T[]): T { return array && array.length === 1 ? array[0] : undefined; } + /** + * Returns the only element of an array if it contains only one element; otheriwse, returns the + * array. + */ export function singleOrMany(array: T[]): T | T[] { return array && array.length === 1 ? array[0] : array; } - /** - * Returns the last element of an array if non-empty, undefined otherwise. - */ - export function lastOrUndefined(array: T[]): T { - return array && array.length > 0 - ? array[array.length - 1] - : undefined; - } - /** * Performs a binary search, finding the index at which 'value' occurs in 'array'. * If no such index is found, returns the 2's-complement of first index at which diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 530d64e8aeb..2b36afa297c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1472,6 +1472,17 @@ namespace ts { return node; } + /** + * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in + * order to properly emit exports. + */ + export function createMergeDeclarationMarker(original: Node) { + const node = createNode(SyntaxKind.MergeDeclarationMarker); + node.emitNode = {}; + node.original = original; + return node; + } + /** * Creates a synthetic expression to act as a placeholder for a not-emitted expression in * order to preserve comments or sourcemap positions. @@ -2206,7 +2217,7 @@ namespace ts { * Gets whether an identifier should only be referred to by its local name. */ export function isLocalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportBindingName) === EmitFlags.LocalName; + return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; } /** @@ -2228,32 +2239,7 @@ namespace ts { * name points to an exported symbol. */ export function isExportName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportBindingName) === EmitFlags.ExportName; - } - - /** - * Gets the export binding name of a declaration for use in the left-hand side of assignment - * expressions. This is primarily used for declarations that can be referred to by name in the - * declaration's immediate scope (classes, enums, namespaces). If the declaration is exported - * and the name is the target of an assignment expression, its export binding name should be - * substituted with an expression that assigns *both* the local *and* export names of the - * declaration. If an export binding name appears in any other position it should be treated - * as a local name. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getExportBindingName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { - return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportBindingName); - } - - /** - * Gets whether an identifier should be treated as both an export name and a local name when - * it is the target of an assignment expression. - */ - export function isExportBindingName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportBindingName) === EmitFlags.ExportBindingName; + return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; } /** diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 08ff4a55a1a..6da6a54313b 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -112,10 +112,6 @@ namespace ts { transformers.push(transformTypeScript); - if (moduleKind === ModuleKind.System) { - transformers.push(moduleTransformerMap[moduleKind] || moduleTransformerMap[ModuleKind.None]); - } - if (jsx === JsxEmit.React) { transformers.push(transformJsx); } @@ -133,10 +129,10 @@ namespace ts { transformers.push(transformGenerators); } - if (moduleKind !== ModuleKind.System) { - transformers.push(moduleTransformerMap[moduleKind] || moduleTransformerMap[ModuleKind.None]); - } + transformers.push(moduleTransformerMap[moduleKind] || moduleTransformerMap[ModuleKind.None]); + // The ES5 transformer is last so that it can substitute expressions like `exports.default` + // for ES3. if (languageVersion < ScriptTarget.ES5) { transformers.push(transformES5); } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index c7219866df7..96e9ea23ab7 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -176,14 +176,15 @@ namespace ts { * * @param node The VariableDeclaration to flatten. * @param recordTempVariable A callback used to record new temporary variables. - * @param nameSubstitution An optional callback used to substitute binding names. + * @param createAssignmentCallback An optional callback used to create assignment expressions + * for non-temporary variables. * @param visitor An optional visitor to use to visit expressions. */ export function flattenVariableDestructuringToExpression( context: TransformationContext, node: VariableDeclaration, recordTempVariable: (name: Identifier) => void, - nameSubstitution?: (name: Identifier) => Expression, + createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression, visitor?: (node: Node) => VisitResult) { const pendingAssignments: Expression[] = []; @@ -195,18 +196,20 @@ namespace ts { return expression; function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { - const left = nameSubstitution && nameSubstitution(name) || name; - emitPendingAssignment(left, value, location, original); + const expression = createAssignmentCallback + ? createAssignmentCallback(name, value, location) + : createAssignment(name, value, location); + + emitPendingAssignment(expression, original); } function emitTempVariableAssignment(value: Expression, location: TextRange) { const name = createTempVariable(recordTempVariable); - emitPendingAssignment(name, value, location, /*original*/ undefined); + emitPendingAssignment(createAssignment(name, value, location), /*original*/ undefined); return name; } - function emitPendingAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { - const expression = createAssignment(name, value, location); + function emitPendingAssignment(expression: Expression, original: Node) { expression.original = original; // NOTE: this completely disables source maps, but aligns with the behavior of @@ -214,7 +217,6 @@ namespace ts { setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); pendingAssignments.push(expression); - return expression; } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 8d158cd665e..8ba416919fc 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -585,7 +585,7 @@ namespace ts { // }()); const variable = createVariableDeclaration( - getDeclarationName(node, /*allowComments*/ true), + getLocalName(node, /*allowComments*/ true), /*type*/ undefined, transformClassLikeDeclarationToExpression(node) ); @@ -601,14 +601,12 @@ namespace ts { // Add an `export default` statement for default exports (for `--target es5 --module es6`) if (hasModifier(node, ModifierFlags.Export)) { - if (hasModifier(node, ModifierFlags.Default)) { - const exportStatement = createExportDefault(getLocalName(node)); - setOriginalNode(exportStatement, statement); - statements.push(exportStatement); - } - else { - statements.push(createExternalModuleExport(getLocalName(node))); - } + const exportStatement = hasModifier(node, ModifierFlags.Default) + ? createExportDefault(getLocalName(node)) + : createExternalModuleExport(getLocalName(node)); + + setOriginalNode(exportStatement, statement); + statements.push(exportStatement); } const emitFlags = getEmitFlags(node); @@ -758,7 +756,7 @@ namespace ts { if (extendsClauseElement) { statements.push( createStatement( - createExtendsHelper(currentSourceFile.externalHelpersModuleName, getDeclarationName(node)), + createExtendsHelper(currentSourceFile.externalHelpersModuleName, getLocalName(node)), /*location*/ extendsClauseElement ) ); @@ -1694,7 +1692,7 @@ namespace ts { if (decl.initializer) { let assignment: Expression; if (isBindingPattern(decl.name)) { - assignment = flattenVariableDestructuringToExpression(context, decl, hoistVariableDeclaration, /*nameSubstitution*/ undefined, visitor); + assignment = flattenVariableDestructuringToExpression(context, decl, hoistVariableDeclaration, /*createAssignmentCallback*/ undefined, visitor); } else { assignment = createBinary(decl.name, SyntaxKind.EqualsToken, visitNode(decl.initializer, visitor, isExpression)); diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 2d8a513cc88..fc193a7850d 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -4,6 +4,12 @@ /*@internal*/ namespace ts { export function transformModule(context: TransformationContext) { + interface AsynchronousDependencies { + aliasedModuleNames: Expression[]; + unaliasedModuleNames: Expression[]; + importAliasNames: ParameterDeclaration[]; + } + const transformModuleDelegates = createMap<(node: SourceFile) => SourceFile>({ [ModuleKind.None]: transformCommonJSModule, [ModuleKind.CommonJS]: transformCommonJSModule, @@ -48,25 +54,22 @@ namespace ts { * @param node The SourceFile node. */ function transformSourceFile(node: SourceFile) { - if (isDeclarationFile(node)) { + if (isDeclarationFile(node) + || !(isExternalModule(node) + || compilerOptions.isolatedModules)) { return node; } - if (isExternalModule(node) || compilerOptions.isolatedModules) { - currentSourceFile = node; - currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); + currentSourceFile = node; + currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); - // Perform the transformation. - const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; - const updated = transformModule(node); - aggregateTransformFlags(updated); + // Perform the transformation. + const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; + const updated = transformModule(node); - currentSourceFile = undefined; - currentModuleInfo = undefined; - return updated; - } - - return node; + currentSourceFile = undefined; + currentModuleInfo = undefined; + return aggregateTransformFlags(updated); } /** @@ -78,8 +81,8 @@ namespace ts { startLexicalEnvironment(); const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, visitor); - addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); + addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); addRange(statements, endLexicalEnvironment()); addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); @@ -192,6 +195,56 @@ namespace ts { ); } + /** + * Collect the additional asynchronous dependencies for the module. + * + * @param node The source file. + * @param includeNonAmdDependencies A value indicating whether to include non-AMD dependencies. + */ + function collectAsynchronousDependencies(node: SourceFile, includeNonAmdDependencies: boolean): AsynchronousDependencies { + // names of modules with corresponding parameter in the factory function + const aliasedModuleNames: Expression[] = []; + + // names of modules with no corresponding parameters in factory function + const unaliasedModuleNames: Expression[] = []; + + // names of the parameters in the factory function; these + // parameters need to match the indexes of the corresponding + // module names in aliasedModuleNames. + const importAliasNames: ParameterDeclaration[] = []; + + // Fill in amd-dependency tags + for (const amdDependency of node.amdDependencies) { + if (amdDependency.name) { + aliasedModuleNames.push(createLiteral(amdDependency.path)); + importAliasNames.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, amdDependency.name)); + } + else { + unaliasedModuleNames.push(createLiteral(amdDependency.path)); + } + } + + for (const importNode of currentModuleInfo.externalImports) { + // Find the name of the external module + const externalModuleName = getExternalModuleNameLiteral(importNode, currentSourceFile, host, resolver, compilerOptions); + + // Find the name of the module alias, if there is one + const importAliasName = getLocalNameForExternalImport(importNode, currentSourceFile); + if (includeNonAmdDependencies && importAliasName) { + // Set emitFlags on the name of the classDeclaration + // This is so that when printer will not substitute the identifier + setEmitFlags(importAliasName, EmitFlags.NoSubstitution); + aliasedModuleNames.push(externalModuleName); + importAliasNames.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, importAliasName)); + } + else { + unaliasedModuleNames.push(externalModuleName); + } + } + + return { aliasedModuleNames, unaliasedModuleNames, importAliasNames }; + } + /** * Transforms a SourceFile into an AMD or UMD module body. * @@ -201,10 +254,10 @@ namespace ts { startLexicalEnvironment(); const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, visitor); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); // Visit each statement of the module body. - addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); // End the lexical environment for the module body // and merge any new lexical declarations. @@ -223,12 +276,53 @@ namespace ts { return body; } + /** + * Adds the down-level representation of `export=` to the statement list if one exists + * in the source file. + * + * @param statements The Statement list to modify. + * @param emitAsReturn A value indicating whether to emit the `export=` statement as a + * return statement. + */ + function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) { + if (currentModuleInfo.exportEquals) { + if (emitAsReturn) { + const statement = createReturn( + currentModuleInfo.exportEquals.expression, + /*location*/ currentModuleInfo.exportEquals + ); + + setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments); + statements.push(statement); + } + else { + const statement = createStatement( + createAssignment( + createPropertyAccess( + createIdentifier("module"), + "exports" + ), + currentModuleInfo.exportEquals.expression + ), + /*location*/ currentModuleInfo.exportEquals + ); + + setEmitFlags(statement, EmitFlags.NoComments); + statements.push(statement); + } + } + } + + // + // Top-Level Source Element Visitors + // + /** * Visits a node at the top level of the source file. * - * @param node The node. + * @param node The node to visit. */ - function visitor(node: Node): VisitResult { + function sourceElementVisitor(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.ImportDeclaration: return visitImportDeclaration(node); @@ -251,8 +345,8 @@ namespace ts { case SyntaxKind.ClassDeclaration: return visitClassDeclaration(node); - case SyntaxKind.NotEmittedStatement: - return visitNotEmittedStatement(node); + case SyntaxKind.MergeDeclarationMarker: + return visitMergeDeclarationMarker(node); case SyntaxKind.EndOfDeclarationMarker: return visitEndOfDeclarationMarker(node); @@ -264,26 +358,10 @@ namespace ts { } } - /** - * Visits a modifier. - * - * @param node The modifier. - */ - function modifierVisitor(node: Node): VisitResult { - // Elide module-specific modifiers. - switch (node.kind) { - case SyntaxKind.ExportKeyword: - case SyntaxKind.DefaultKeyword: - return undefined; - } - - return node; - } - /** * Visits an ImportDeclaration node. * - * @param node The ImportDeclaration node + * @param node The node to visit. */ function visitImportDeclaration(node: ImportDeclaration): VisitResult { let statements: Statement[]; @@ -363,14 +441,37 @@ namespace ts { ); } - statements = appendExportsOfImportDeclaration(statements, node); + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfImportDeclaration(statements, node); + } + return singleOrMany(statements); } + /** + * Creates a `require()` call to import an external module. + * + * @param importNode The declararation to import. + */ + function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { + const moduleName = getExternalModuleNameLiteral(importNode, currentSourceFile, host, resolver, compilerOptions); + const args: Expression[] = []; + if (moduleName) { + args.push(moduleName); + } + + return createCall(createIdentifier("require"), /*typeArguments*/ undefined, args); + } + /** * Visits an ImportEqualsDeclaration node. * - * @param node The ImportEqualsDeclaration node + * @param node The node to visit. */ function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); @@ -380,7 +481,7 @@ namespace ts { if (hasModifier(node, ModifierFlags.Export)) { statements = append(statements, createStatement( - createExportAssignment( + createExportExpression( node.name, createRequireCall(node) ), @@ -412,7 +513,7 @@ namespace ts { if (hasModifier(node, ModifierFlags.Export)) { statements = append(statements, createStatement( - createExportAssignment(getExportName(node), getLocalName(node)), + createExportExpression(getExportName(node), getLocalName(node)), /*location*/ node ) ); @@ -420,6 +521,7 @@ namespace ts { } if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node const id = getOriginalNodeId(node); deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node); } @@ -433,7 +535,7 @@ namespace ts { /** * Visits an ExportDeclaration node. * - * @param The ExportDeclaration node + * @param The node to visit. */ function visitExportDeclaration(node: ExportDeclaration): VisitResult { if (!node.moduleSpecifier) { @@ -468,7 +570,7 @@ namespace ts { ); statements.push( createStatement( - createExportAssignment(getExportName(specifier), exportedValue), + createExportExpression(getExportName(specifier), exportedValue), /*location*/ specifier ) ); @@ -496,7 +598,7 @@ namespace ts { /** * Visits an ExportAssignment node. * - * @param node The ExportAssignment node + * @param node The node to visit. */ function visitExportAssignment(node: ExportAssignment): VisitResult { if (node.isExportEquals) { @@ -518,9 +620,89 @@ namespace ts { } /** - * Visits a VariableStatement. + * Visits a FunctionDeclaration node. * - * @param node A VariableStatement node. + * @param node The node to visit. + */ + function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { + let statements: Statement[]; + if (hasModifier(node, ModifierFlags.Export)) { + statements = append(statements, + setOriginalNode( + createFunctionDeclaration( + /*decorators*/ undefined, + visitNodes(node.modifiers, modifierVisitor, isModifier), + node.asteriskToken, + getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), + /*typeParameters*/ undefined, + node.parameters, + /*type*/ undefined, + node.body, + /*location*/ node + ), + /*original*/ node + ) + ); + } + else { + statements = append(statements, node); + } + + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfHoistedDeclaration(statements, node); + } + + return singleOrMany(statements); + } + + /** + * Visits a ClassDeclaration node. + * + * @param node The node to visit. + */ + function visitClassDeclaration(node: ClassDeclaration): VisitResult { + let statements: Statement[]; + if (hasModifier(node, ModifierFlags.Export)) { + statements = append(statements, + setOriginalNode( + createClassDeclaration( + /*decorators*/ undefined, + visitNodes(node.modifiers, modifierVisitor, isModifier), + getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), + /*typeParameters*/ undefined, + node.heritageClauses, + node.members, + /*location*/ node + ), + /*original*/ node + ) + ); + } + else { + statements = append(statements, node); + } + + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfHoistedDeclaration(statements, node); + } + + return singleOrMany(statements); + } + + /** + * Visits a VariableStatement node. + * + * @param node The node to visit. */ function visitVariableStatement(node: VariableStatement): VisitResult { let statements: Statement[]; @@ -565,127 +747,50 @@ namespace ts { statements = appendExportsOfVariableStatement(statements, node); } - // statements = addExportsOfVariableStatement(statements, node); return singleOrMany(statements); } /** * Transforms an exported variable with an initializer into an expression. * - * @param node The variable to transform. + * @param node The node to transform. */ function transformInitializedVariable(node: VariableDeclaration): Expression { - const name = node.name; - if (isBindingPattern(name)) { + if (isBindingPattern(node.name)) { return flattenVariableDestructuringToExpression( context, node, hoistVariableDeclaration, - getModuleMemberName + createExportExpression ); } else { return createAssignment( - getModuleMemberName(name), + createPropertyAccess( + createIdentifier("exports"), + node.name, + /*location*/ node.name + ), node.initializer ); } } /** - * Visits a FunctionDeclaration. + * Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged + * and transformed declaration. * - * @param node A FunctionDeclaration node. + * @param node The node to visit. */ - function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { - let statements: Statement[]; - if (hasModifier(node, ModifierFlags.Export)) { - statements = append(statements, - setOriginalNode( - createFunctionDeclaration( - /*decorators*/ undefined, - visitNodes(node.modifiers, modifierVisitor, isModifier), - node.asteriskToken, - getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), - /*typeParameters*/ undefined, - node.parameters, - /*type*/ undefined, - node.body, - /*location*/ node - ), - /*original*/ node - ) - ); - } - else { - statements = append(statements, node); - } - - if (hasAssociatedEndOfDeclarationMarker(node)) { - // Defer exports until we encounter an EndOfDeclarationMarker node - const id = getOriginalNodeId(node); - deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); - } - else { - statements = appendExportsOfHoistedDeclaration(statements, node); - } - - return singleOrMany(statements); - } - - /** - * Visits a ClassDeclaration. - * - * @param node A ClassDeclaration node. - */ - function visitClassDeclaration(node: ClassDeclaration): VisitResult { - let statements: Statement[]; - if (hasModifier(node, ModifierFlags.Export)) { - statements = append(statements, - setOriginalNode( - createClassDeclaration( - /*decorators*/ undefined, - visitNodes(node.modifiers, modifierVisitor, isModifier), - getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), - /*typeParameters*/ undefined, - node.heritageClauses, - node.members, - /*location*/ node - ), - /*original*/ node - ) - ); - } - else { - statements = append(statements, node); - } - - if (hasAssociatedEndOfDeclarationMarker(node)) { - // Defer exports until we encounter an EndOfDeclarationMarker node - const id = getOriginalNodeId(node); - deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); - } - else { - statements = appendExportsOfHoistedDeclaration(statements, node); - } - - return singleOrMany(statements); - } - - /** - * Visits a NotEmittedStatement. - * - * @param node A NotEmittedStatement node. - */ - function visitNotEmittedStatement(node: NotEmittedStatement): VisitResult { + function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult { // For an EnumDeclaration or ModuleDeclaration that merges with a preceeding // declaration we do not emit a leading variable declaration. To preserve the // begin/end semantics of the declararation and to properly handle exports - // we wrap the leading variable declaration in a `NotEmittedStatement`. + // we wrapped the leading variable declaration in a `MergeDeclarationMarker`. // // To balance the declaration, add the exports of the elided variable // statement. - if (hasAssociatedEndOfDeclarationMarker(node.original) && node.original.kind === SyntaxKind.VariableStatement) { + if (hasAssociatedEndOfDeclarationMarker(node) && node.original.kind === SyntaxKind.VariableStatement) { const id = getOriginalNodeId(node); deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node.original); } @@ -694,9 +799,19 @@ namespace ts { } /** - * Visits a DeclarationMarker used as a placeholder for the end of a transformed declaration. + * Determines whether a node has an associated EndOfDeclarationMarker. * - * @param node A DeclarationMarker node. + * @param node The node to test. + */ + function hasAssociatedEndOfDeclarationMarker(node: Node) { + return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0; + } + + /** + * Visits a DeclarationMarker used as a placeholder for the end of a transformed + * declaration. + * + * @param node The node to visit. */ function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult { // For some transformations we emit an `EndOfDeclarationMarker` to mark the actual @@ -716,10 +831,9 @@ namespace ts { * Appends the exports of an ImportDeclaration to a statement list, returning the * statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be - * appended. + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param decl The declaration whose exports are to be recorded. */ function appendExportsOfImportDeclaration(statements: Statement[] | undefined, decl: ImportDeclaration): Statement[] | undefined { @@ -728,25 +842,27 @@ namespace ts { } const importClause = decl.importClause; - if (importClause) { - if (importClause.name) { - statements = appendExportsOfDeclaration(statements, importClause); - } + if (!importClause) { + return statements; + } - const namedBindings = importClause.namedBindings; - if (namedBindings) { - switch (namedBindings.kind) { - case SyntaxKind.NamespaceImport: - statements = appendExportsOfDeclaration(statements, namedBindings); - break; + if (importClause.name) { + statements = appendExportsOfDeclaration(statements, importClause); + } - case SyntaxKind.NamedImports: - for (const importBinding of namedBindings.elements) { - statements = appendExportsOfDeclaration(statements, importBinding); - } + const namedBindings = importClause.namedBindings; + if (namedBindings) { + switch (namedBindings.kind) { + case SyntaxKind.NamespaceImport: + statements = appendExportsOfDeclaration(statements, namedBindings); + break; - break; - } + case SyntaxKind.NamedImports: + for (const importBinding of namedBindings.elements) { + statements = appendExportsOfDeclaration(statements, importBinding); + } + + break; } } @@ -757,10 +873,9 @@ namespace ts { * Appends the exports of an ImportEqualsDeclaration to a statement list, returning the * statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be - * appended. + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param decl The declaration whose exports are to be recorded. */ function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, decl: ImportEqualsDeclaration): Statement[] | undefined { @@ -775,10 +890,9 @@ namespace ts { * Appends the exports of a VariableStatement to a statement list, returning the statement * list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be - * appended. + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param node The VariableStatement whose exports are to be recorded. */ function appendExportsOfVariableStatement(statements: Statement[] | undefined, node: VariableStatement): Statement[] | undefined { @@ -797,10 +911,9 @@ namespace ts { * Appends the exports of a VariableDeclaration or BindingElement to a statement list, * returning the statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be - * appended. + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param decl The declaration whose exports are to be recorded. */ function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement): Statement[] | undefined { @@ -826,9 +939,8 @@ namespace ts { * Appends the exports of a ClassDeclaration or FunctionDeclaration to a statement list, * returning the statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are * appended. * @param decl The declaration whose exports are to be recorded. */ @@ -852,10 +964,9 @@ namespace ts { /** * Appends the exports of a declaration to a statement list, returning the statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * * @param statements A statement list to which the down-level export statements are to be - * appended. + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param decl The declaration to export. */ function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration): Statement[] | undefined { @@ -873,9 +984,9 @@ namespace ts { * Appends the down-level representation of an export to a statement list, returning the * statement list. * - * If `statements` is `undefined`, a new array is allocated if statements are appended. - * - * @param statements The statement list to modify. + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. * @param exportName The name of the export. * @param expression The expression to export. * @param location The location to use for source maps and comments for the export. @@ -888,7 +999,7 @@ namespace ts { if (languageVersion === ScriptTarget.ES3) { statements = append(statements, createStatement( - createExportAssignment( + createExportExpression( createIdentifier("__esModule"), createLiteral(true) ) @@ -920,56 +1031,71 @@ namespace ts { } /** - * Adds the down-level representation of `export=` to the statement list if one exists - * in the source file. + * Creates a call to the current file's export function to export a value. * - * @param statements The Statement list to modify. - * @param emitAsReturn A value indicating whether to emit the `export=` statement as a - * return statement. + * @param name The bound name of the export. + * @param value The exported value. + * @param location The location to use for source maps and comments for the export. + * @param allowComments An optional value indicating whether to emit comments for the statement. */ - function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) { - if (currentModuleInfo.exportEquals) { - if (emitAsReturn) { - const statement = createReturn( - currentModuleInfo.exportEquals.expression, - /*location*/ currentModuleInfo.exportEquals - ); - - setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments); - statements.push(statement); - } - else { - const statement = createStatement( - createAssignment( - createPropertyAccess( - createIdentifier("module"), - "exports" - ), - currentModuleInfo.exportEquals.expression - ), - /*location*/ currentModuleInfo.exportEquals - ); - - setEmitFlags(statement, EmitFlags.NoComments); - statements.push(statement); - } + function createExportStatement(name: Identifier, value: Expression, location?: TextRange, allowComments?: boolean) { + const statement = createStatement(createExportExpression(name, value), location); + startOnNewLine(statement); + if (!allowComments) { + setEmitFlags(statement, EmitFlags.NoComments); } + + return statement; } /** - * Determines whether a node has an associated EndDeclarationMarker. + * Creates a call to the current file's export function to export a value. * - * @param node The node to test. + * @param name The bound name of the export. + * @param value The exported value. + * @param location The location to use for source maps and comments for the export. */ - function hasAssociatedEndOfDeclarationMarker(node: Node) { - return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0; + function createExportExpression(name: Identifier, value: Expression, location?: TextRange) { + return createAssignment( + createPropertyAccess( + createIdentifier("exports"), + getSynthesizedClone(name) + ), + value, + location + ); } + // + // Modifier Visitors + // + /** - * Hook for node emit. + * Visit nodes to elide module-specific modifiers. * + * @param node The node to visit. + */ + function modifierVisitor(node: Node): VisitResult { + // Elide module-specific modifiers. + switch (node.kind) { + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: + return undefined; + } + + return node; + } + + // + // Emit Notification + // + + /** + * Hook for node emit notifications. + * + * @param emitContext A context hint for the emitter. * @param node The node to emit. - * @param emitCallback A callback used to emit the node in the printer. + * @param emit A callback used to emit the node in the printer. */ function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { if (node.kind === SyntaxKind.SourceFile) { @@ -988,12 +1114,15 @@ namespace ts { } } + // + // Substitutions + // + /** * Hooks node substitutions. * + * @param emitContext A context hint for the emitter. * @param node The node to substitute. - * @param isExpression A value indicating whether the node is to be used in an expression - * position. */ function onSubstituteNode(emitContext: EmitContext, node: Node) { node = previousOnSubstituteNode(emitContext, node); @@ -1015,7 +1144,7 @@ namespace ts { * Substitution for a ShorthandPropertyAssignment whose declaration name is an imported * or exported symbol. * - * @param node A ShorthandPropertyAssignment + * @param node The node to substitute. */ function substituteShorthandPropertyAssignment(node: ShorthandPropertyAssignment): ObjectLiteralElementLike { const name = node.name; @@ -1035,7 +1164,7 @@ namespace ts { /** * Substitution for an Expression that may contain an imported or exported symbol. * - * @param node An Expression + * @param node The node to substitute. */ function substituteExpression(node: Expression) { switch (node.kind) { @@ -1052,14 +1181,14 @@ namespace ts { } /** - * Substitution for an Identifier expression that may contain an imported or exported symbol. + * Substitution for an Identifier expression that may contain an imported or exported + * symbol. * - * @param node An Identifier expression + * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { - const emitFlags = getEmitFlags(node); - if ((emitFlags & EmitFlags.LocalName) === 0) { - const exportContainer = resolver.getReferencedExportContainer(node, (emitFlags & EmitFlags.ExportName) !== 0); + if (!isGeneratedIdentifier(node) && !isLocalName(node)) { + const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node)); if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { return createPropertyAccess( createIdentifier("exports"), @@ -1093,7 +1222,7 @@ namespace ts { /** * Substitution for a BinaryExpression that may contain an imported or exported symbol. * - * @param node A BinaryExpression + * @param node The node to substitute. */ function substituteBinaryExpression(node: BinaryExpression): Expression { // When we see an assignment expression whose left-hand side is an exported symbol, @@ -1109,19 +1238,14 @@ namespace ts { && !isGeneratedIdentifier(node.left) && !isLocalName(node.left) && !isDeclarationNameOfEnumOrNamespace(node.left)) { - const exportedNames = getExportsOfName(node.left); + const exportedNames = getExports(node.left); if (exportedNames) { - // Since we will be reusing this node as part of the substitution, we - // mark it to prevent triggering this rule again. - noSubstitution[getNodeId(node)] = true; - + // For each additional export of the declaration, apply an export assignment. let expression: Expression = node; for (const exportName of exportedNames) { - expression = createExportAssignment(exportName, expression); - - // Mark the transformed node to prevent possibly triggering this rule - // again. + // Mark the node to prevent triggering this rule again. noSubstitution[getNodeId(expression)] = true; + expression = createExportExpression(exportName, expression, /*location*/ node); } return expression; @@ -1134,7 +1258,7 @@ namespace ts { /** * Substitution for a UnaryExpression that may contain an imported or exported symbol. * - * @param node A UnaryExpression. + * @param node The node to substitute. */ function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression { // When we see a prefix or postfix increment expression whose operand is an exported @@ -1146,29 +1270,24 @@ namespace ts { // - We do not substitute identifiers that were originally the name of an enum or // namespace due to how they are transformed in TypeScript. // - We only substitute identifiers that are exported at the top level. - if (isIdentifier(node.operand) + if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) + && isIdentifier(node.operand) && !isGeneratedIdentifier(node.operand) && !isLocalName(node.operand) && !isDeclarationNameOfEnumOrNamespace(node.operand)) { - const exportedNames = getExportsOfName(node.operand); + const exportedNames = getExports(node.operand); if (exportedNames) { - let expression = node.kind === SyntaxKind.PostfixUnaryExpression + let expression: Expression = node.kind === SyntaxKind.PostfixUnaryExpression ? createBinary( node.operand, createToken(node.operator === SyntaxKind.PlusPlusToken ? SyntaxKind.PlusEqualsToken : SyntaxKind.MinusEqualsToken), createLiteral(1), /*location*/ node) : node; - - // Since we will be reusing this node as part of the substitution, we - // mark it to prevent triggering this rule again. - noSubstitution[getNodeId(expression)] = true; - for (const exportName of exportedNames) { - expression = createExportAssignment(exportName, expression); - // Mark the transformed node to prevent triggering the assignment - // expression substitution in `substituteBinaryExpression`. + // Mark the node to prevent triggering this rule again. noSubstitution[getNodeId(expression)] = true; + expression = createExportExpression(exportName, expression); } return expression; @@ -1179,11 +1298,11 @@ namespace ts { } /** - * Gets the exports of a name. + * Gets the additional exports of a name. * * @param name The name. */ - function getExportsOfName(name: Identifier): Identifier[] | undefined { + function getExports(name: Identifier): Identifier[] | undefined { if (!isGeneratedIdentifier(name)) { const valueDeclaration = resolver.getReferencedImportDeclaration(name) || resolver.getReferencedValueDeclaration(name); @@ -1193,93 +1312,5 @@ namespace ts { } } } - - function getModuleMemberName(name: Identifier) { - return createPropertyAccess( - createIdentifier("exports"), - name, - /*location*/ name - ); - } - - function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { - const moduleName = getExternalModuleNameLiteral(importNode, currentSourceFile, host, resolver, compilerOptions); - const args: Expression[] = []; - if (moduleName) { - args.push(moduleName); - } - - return createCall(createIdentifier("require"), /*typeArguments*/ undefined, args); - } - - function createExportStatement(name: Identifier, value: Expression, location?: TextRange, allowComments?: boolean) { - const statement = createStatement(createExportAssignment(name, value), location); - startOnNewLine(statement); - if (!allowComments) { - setEmitFlags(statement, EmitFlags.NoComments); - } - - return statement; - } - - function createExportAssignment(name: Identifier, value: Expression) { - return createAssignment( - createPropertyAccess( - createIdentifier("exports"), - getSynthesizedClone(name) - ), - value - ); - } - - interface AsynchronousDependencies { - aliasedModuleNames: Expression[]; - unaliasedModuleNames: Expression[]; - importAliasNames: ParameterDeclaration[]; - } - - function collectAsynchronousDependencies(node: SourceFile, includeNonAmdDependencies: boolean): AsynchronousDependencies { - // names of modules with corresponding parameter in the factory function - const aliasedModuleNames: Expression[] = []; - - // names of modules with no corresponding parameters in factory function - const unaliasedModuleNames: Expression[] = []; - - // names of the parameters in the factory function; these - // parameters need to match the indexes of the corresponding - // module names in aliasedModuleNames. - const importAliasNames: ParameterDeclaration[] = []; - - // Fill in amd-dependency tags - for (const amdDependency of node.amdDependencies) { - if (amdDependency.name) { - aliasedModuleNames.push(createLiteral(amdDependency.path)); - importAliasNames.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, amdDependency.name)); - } - else { - unaliasedModuleNames.push(createLiteral(amdDependency.path)); - } - } - - for (const importNode of currentModuleInfo.externalImports) { - // Find the name of the external module - const externalModuleName = getExternalModuleNameLiteral(importNode, currentSourceFile, host, resolver, compilerOptions); - - // Find the name of the module alias, if there is one - const importAliasName = getLocalNameForExternalImport(importNode, currentSourceFile); - if (includeNonAmdDependencies && importAliasName) { - // Set emitFlags on the name of the classDeclaration - // This is so that when printer will not substitute the identifier - setEmitFlags(importAliasName, EmitFlags.NoSubstitution); - aliasedModuleNames.push(externalModuleName); - importAliasNames.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, importAliasName)); - } - else { - unaliasedModuleNames.push(externalModuleName); - } - } - - return { aliasedModuleNames, unaliasedModuleNames, importAliasNames }; - } } } diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 8bea1e3d28a..f1267831719 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -12,8 +12,7 @@ namespace ts { const { startLexicalEnvironment, endLexicalEnvironment, - hoistVariableDeclaration, - hoistFunctionDeclaration, + hoistVariableDeclaration } = context; const compilerOptions = context.getCompilerOptions(); @@ -23,58 +22,43 @@ namespace ts { const previousOnEmitNode = context.onEmitNode; context.onSubstituteNode = onSubstituteNode; context.onEmitNode = onEmitNode; - context.enableSubstitution(SyntaxKind.Identifier); - context.enableSubstitution(SyntaxKind.BinaryExpression); - context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); - context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); - context.enableEmitNotification(SyntaxKind.SourceFile); + context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols. + context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols. + context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols. + context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); // Substitutes updates to exported symbols. + context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file. - const exportFunctionForFileMap: Identifier[] = []; - let currentSourceFile: SourceFile; - let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; - let exportSpecifiers: Map; - let exportEquals: ExportAssignment; - let hasExportStarsToExportValues: boolean; - let exportFunctionForFile: Identifier; - let contextObjectForFile: Identifier; - let exportedLocalNames: Identifier[]; - let exportedFunctionDeclarations: ExpressionStatement[]; + const moduleInfoMap = createMap(); // The ExternalModuleInfo for each file. + const deferredExports = createMap(); // Exports to defer until an EndOfDeclarationMarker is found. + const exportFunctionsMap = createMap(); // The export function associated with a source file. + const noSubstitutionMap = createMap>(); // Set of nodes for which substitution rules should be ignored for each file. + let currentSourceFile: SourceFile; // The current file. + let moduleInfo: ExternalModuleInfo; // ExternalModuleInfo for the current file. + let exportFunction: Identifier; // The export function for the current file. + let contextObject: Identifier; // The context object for the current file. + let hoistedStatements: Statement[]; let enclosingBlockScopedContainer: Node; - let currentParent: Node; - let currentNode: Node; + let noSubstitution: Map; // Set of nodes for which substitution rules should be ignored. return transformSourceFile; + /** + * Transforms the module aspects of a SourceFile. + * + * @param node The SourceFile node. + */ function transformSourceFile(node: SourceFile) { - if (isDeclarationFile(node)) { + if (isDeclarationFile(node) + || !(isExternalModule(node) + || compilerOptions.isolatedModules)) { return node; } - if (isExternalModule(node) || compilerOptions.isolatedModules) { - currentSourceFile = node; - currentNode = node; + const id = getOriginalNodeId(node); + currentSourceFile = node; + enclosingBlockScopedContainer = node; - // Perform the transformation. - const updated = transformSystemModuleWorker(node); - aggregateTransformFlags(updated); - - currentSourceFile = undefined; - externalImports = undefined; - exportSpecifiers = undefined; - exportEquals = undefined; - hasExportStarsToExportValues = false; - exportFunctionForFile = undefined; - contextObjectForFile = undefined; - exportedLocalNames = undefined; - exportedFunctionDeclarations = undefined; - return updated; - } - - return node; - } - - function transformSystemModuleWorker(node: SourceFile) { // System modules have the following shape: // // System.register(['dep-1', ... 'dep-n'], function(exports) {/* module body function */}) @@ -87,67 +71,103 @@ namespace ts { // // The only exception in this rule is postfix unary operators, // see comment to 'substitutePostfixUnaryExpression' for more details - Debug.assert(!exportFunctionForFile); // Collect information about the external module and dependency groups. - ({ externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues } = collectExternalModuleInfo(node, resolver)); + moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(node, resolver); // Make sure that the name of the 'exports' function does not conflict with // existing identifiers. - exportFunctionForFile = createUniqueName("exports"); - contextObjectForFile = createUniqueName("context"); - - exportFunctionForFileMap[getOriginalNodeId(node)] = exportFunctionForFile; - - const dependencyGroups = collectDependencyGroups(externalImports); - - const statements: Statement[] = []; + exportFunction = exportFunctionsMap[id] = createUniqueName("exports"); + contextObject = createUniqueName("context"); // Add the body of the module. - addSystemModuleBody(statements, node, dependencyGroups); - - const moduleName = tryGetModuleNameFromFile(node, host, compilerOptions); - const dependencies = createArrayLiteral(map(dependencyGroups, getNameOfDependencyGroup)); - const body = createFunctionExpression( + const dependencyGroups = collectDependencyGroups(moduleInfo.externalImports); + const moduleBodyFunction = createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, /*name*/ undefined, /*typeParameters*/ undefined, [ - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, exportFunctionForFile), - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, contextObjectForFile) + createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, exportFunction), + createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, contextObject) ], /*type*/ undefined, - setEmitFlags( - createBlock(statements, /*location*/ undefined, /*multiLine*/ true), - EmitFlags.EmitEmitHelpers - ) + createSystemModuleBody(node, dependencyGroups) ); // Write the call to `System.register` // Clear the emit-helpers flag for later passes since we'll have already used it in the module body // So the helper will be emit at the correct position instead of at the top of the source-file - return updateSourceFile(node, [ - createStatement( - createCall( - createPropertyAccess(createIdentifier("System"), "register"), - /*typeArguments*/ undefined, - moduleName - ? [moduleName, dependencies, body] - : [dependencies, body] + const moduleName = tryGetModuleNameFromFile(node, host, compilerOptions); + const dependencies = createArrayLiteral(map(dependencyGroups, dependencyGroup => dependencyGroup.name)); + const updated = updateSourceFileNode( + node, + createNodeArray([ + createStatement( + createCall( + createPropertyAccess(createIdentifier("System"), "register"), + /*typeArguments*/ undefined, + moduleName + ? [moduleName, dependencies, moduleBodyFunction] + : [dependencies, moduleBodyFunction] + ) ) - ) - ], /*nodeEmitFlags*/ ~EmitFlags.EmitEmitHelpers & getEmitFlags(node)); + ], node.statements) + ); + + setEmitFlags(updated, getEmitFlags(node) & ~EmitFlags.EmitEmitHelpers); + + if (noSubstitution) { + noSubstitutionMap[id] = noSubstitution; + noSubstitution = undefined; + } + + currentSourceFile = undefined; + moduleInfo = undefined; + exportFunction = undefined; + contextObject = undefined; + hoistedStatements = undefined; + enclosingBlockScopedContainer = undefined; + + return aggregateTransformFlags(updated); + } + + /** + * Collects the dependency groups for this files imports. + * + * @param externalImports The imports for the file. + */ + function collectDependencyGroups(externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]) { + const groupIndices = createMap(); + const dependencyGroups: DependencyGroup[] = []; + for (let i = 0; i < externalImports.length; i++) { + const externalImport = externalImports[i]; + const externalModuleName = getExternalModuleNameLiteral(externalImport, currentSourceFile, host, resolver, compilerOptions); + const text = externalModuleName.text; + if (hasProperty(groupIndices, text)) { + // deduplicate/group entries in dependency list by the dependency name + const groupIndex = groupIndices[text]; + dependencyGroups[groupIndex].externalImports.push(externalImport); + } + else { + groupIndices[text] = dependencyGroups.length; + dependencyGroups.push({ + name: externalModuleName, + externalImports: [externalImport] + }); + } + } + + return dependencyGroups; } /** * Adds the statements for the module body function for the source file. * - * @param statements The output statements for the module body. * @param node The source file for the module. - * @param statementOffset The offset at which to begin visiting the statements of the SourceFile. + * @param dependencyGroups The grouped dependencies of the module. */ - function addSystemModuleBody(statements: Statement[], node: SourceFile, dependencyGroups: DependencyGroup[]) { + function createSystemModuleBody(node: SourceFile, dependencyGroups: DependencyGroup[]) { // Shape of the body in system modules: // // function (exports) { @@ -175,10 +195,9 @@ namespace ts { // Will be transformed to: // // function(exports) { - // var file_1; // local alias - // var y; // function foo() { return y + file_1.x(); } // exports("foo", foo); + // var file_1, y; // return { // setters: [ // function(v) { file_1 = v } @@ -190,13 +209,15 @@ namespace ts { // }; // } + const statements: Statement[] = []; + // We start a new lexical environment in this function body, but *not* in the // body of the execute function. This allows us to emit temporary declarations // only in the outer module body and not in the inner one. startLexicalEnvironment(); // Add any prologue directives. - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, visitSourceElement); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); // var __moduleName = context_1 && context_1.id; statements.push( @@ -207,8 +228,8 @@ namespace ts { "__moduleName", /*type*/ undefined, createLogicalAnd( - contextObjectForFile, - createPropertyAccess(contextObjectForFile, "id") + contextObject, + createPropertyAccess(contextObject, "id") ) ) ]) @@ -220,27 +241,23 @@ namespace ts { // as we both emit transformations as well as aggregate some data used when creating // setters. This allows us to reduce the number of times we need to loop through the // statements of the source file. - const executeStatements = visitNodes(node.statements, visitSourceElement, isStatement, statementOffset); - - // We emit the lexical environment (hoisted variables and function declarations) - // early to align roughly with our previous emit output. - // Two key differences in this approach are: - // - Temporary variables will appear at the top rather than at the bottom of the file - // - Calls to the exporter for exported function declarations are grouped after - // the declarations. - addRange(statements, endLexicalEnvironment()); + const executeStatements = visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset); // Emit early exports for function declarations. - addRange(statements, exportedFunctionDeclarations); + addRange(statements, hoistedStatements); + + // We emit hoisted variables early to align roughly with our previous emit output. + // Two key differences in this approach are: + // - Temporary variables will appear at the top rather than at the bottom of the file + addRange(statements, endLexicalEnvironment()); const exportStarFunction = addExportStarIfNeeded(statements); - statements.push( createReturn( setMultiLine( createObjectLiteral([ createPropertyAssignment("setters", - generateSetters(exportStarFunction, dependencyGroups) + createSettersArray(exportStarFunction, dependencyGroups) ), createPropertyAssignment("execute", createFunctionExpression( @@ -262,24 +279,34 @@ namespace ts { ) ) ); + + const body = createBlock(statements, /*location*/ undefined, /*multiLine*/ true); + setEmitFlags(body, EmitFlags.EmitEmitHelpers); + return body; } + /** + * Adds an exportStar function to a statement list if it is needed for the file. + * + * @param statements A statement list. + */ function addExportStarIfNeeded(statements: Statement[]) { - if (!hasExportStarsToExportValues) { + if (!moduleInfo.hasExportStarsToExportValues) { return; } + // when resolving exports local exported entries/indirect exported entries in the module // should always win over entries with similar names that were added via star exports // to support this we store names of local/indirect exported entries in a set. // this set is used to filter names brought by star expors. // local names set should only be added if we have anything exported - if (!exportedLocalNames && isEmpty(exportSpecifiers)) { + if (!moduleInfo.exportedNames && isEmpty(moduleInfo.exportSpecifiers)) { // no exported declarations (export var ...) or export specifiers (export {x}) // check if we have any non star export declarations. let hasExportDeclarationWithExportClause = false; - for (const externalImport of externalImports) { - if (externalImport.kind === SyntaxKind.ExportDeclaration && (externalImport).exportClause) { + for (const externalImport of moduleInfo.externalImports) { + if (externalImport.kind === SyntaxKind.ExportDeclaration && externalImport.exportClause) { hasExportDeclarationWithExportClause = true; break; } @@ -287,24 +314,30 @@ namespace ts { if (!hasExportDeclarationWithExportClause) { // we still need to emit exportStar helper - return addExportStarFunction(statements, /*localNames*/ undefined); + const exportStarFunction = createExportStarFunction(/*localNames*/ undefined); + statements.push(exportStarFunction); + return exportStarFunction.name; } } const exportedNames: ObjectLiteralElementLike[] = []; - if (exportedLocalNames) { - for (const exportedLocalName of exportedLocalNames) { + if (moduleInfo.exportedNames) { + for (const exportedLocalName of moduleInfo.exportedNames) { + if (exportedLocalName.text === "default") { + continue; + } + // write name of exported declaration, i.e 'export var x...' exportedNames.push( createPropertyAssignment( - createLiteral(exportedLocalName.text), + createLiteral(exportedLocalName), createLiteral(true) ) ); } } - for (const externalImport of externalImports) { + for (const externalImport of moduleInfo.externalImports) { if (externalImport.kind !== SyntaxKind.ExportDeclaration) { continue; } @@ -340,14 +373,90 @@ namespace ts { ) ); - return addExportStarFunction(statements, exportedNamesStorageRef); + const exportStarFunction = createExportStarFunction(exportedNamesStorageRef); + statements.push(exportStarFunction); + return exportStarFunction.name; } /** - * Emits a setter callback for each dependency group. - * @param write The callback used to write each callback. + * Creates an exportStar function for the file, with an optional set of excluded local + * names. + * + * @param localNames An optional reference to an object containing a set of excluded local + * names. */ - function generateSetters(exportStarFunction: Identifier, dependencyGroups: DependencyGroup[]) { + function createExportStarFunction(localNames: Identifier | undefined) { + const exportStarFunction = createUniqueName("exportStar"); + const m = createIdentifier("m"); + const n = createIdentifier("n"); + const exports = createIdentifier("exports"); + let condition: Expression = createStrictInequality(n, createLiteral("default")); + if (localNames) { + condition = createLogicalAnd( + condition, + createLogicalNot(createHasOwnProperty(localNames, n)) + ); + } + + return createFunctionDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + exportStarFunction, + /*typeParameters*/ undefined, + [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, m)], + /*type*/ undefined, + createBlock([ + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + exports, + /*type*/ undefined, + createObjectLiteral([]) + ) + ]) + ), + createForIn( + createVariableDeclarationList([ + createVariableDeclaration(n, /*type*/ undefined) + ]), + m, + createBlock([ + setEmitFlags( + createIf( + condition, + createStatement( + createAssignment( + createElementAccess(exports, n), + createElementAccess(m, n) + ) + ) + ), + EmitFlags.SingleLine + ) + ]) + ), + createStatement( + createCall( + exportFunction, + /*typeArguments*/ undefined, + [exports] + ) + ) + ], + /*location*/ undefined, + /*multiline*/ true) + ); + } + + /** + * Creates an array setter callbacks for each dependency group. + * + * @param exportStarFunction A reference to an exportStarFunction for the file. + * @param dependencyGroups An array of grouped dependencies. + */ + function createSettersArray(exportStarFunction: Identifier, dependencyGroups: DependencyGroup[]) { const setters: Expression[] = []; for (const group of dependencyGroups) { // derive a unique name for parameter from the first named entry in the group @@ -402,7 +511,7 @@ namespace ts { statements.push( createStatement( createCall( - exportFunctionForFile, + exportFunction, /*typeArguments*/ undefined, [createObjectLiteral(properties, /*location*/ undefined, /*multiline*/ true)] ) @@ -445,7 +554,16 @@ namespace ts { return createArrayLiteral(setters, /*location*/ undefined, /*multiLine*/ true); } - function visitSourceElement(node: Node): VisitResult { + // + // Top-level Source Element Visitors + // + + /** + * Visit source elements at the top-level of a module. + * + * @param node The node to visit. + */ + function sourceElementVisitor(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.ImportDeclaration: return visitImportDeclaration(node); @@ -460,33 +578,564 @@ namespace ts { return visitExportAssignment(node); default: - return visitNestedNode(node); + return nestedElementVisitor(node); } } - function visitNestedNode(node: Node): VisitResult { - const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; - const savedCurrentParent = currentParent; - const savedCurrentNode = currentNode; - - const currentGrandparent = currentParent; - currentParent = currentNode; - currentNode = node; - - if (currentParent && isBlockScope(currentParent, currentGrandparent)) { - enclosingBlockScopedContainer = currentParent; + /** + * Visits an ImportDeclaration node. + * + * @param node The node to visit. + */ + function visitImportDeclaration(node: ImportDeclaration): VisitResult { + let statements: Statement[]; + if (node.importClause) { + hoistVariableDeclaration(getLocalNameForExternalImport(node, currentSourceFile)); } - const result = visitNestedNodeWorker(node); + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfImportDeclaration(statements, node); + } - enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; - currentParent = savedCurrentParent; - currentNode = savedCurrentNode; - - return result; + return singleOrMany(statements); } - function visitNestedNodeWorker(node: Node): VisitResult { + /** + * Visits an ImportEqualsDeclaration node. + * + * @param node The node to visit. + */ + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { + Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); + + let statements: Statement[]; + hoistVariableDeclaration(getLocalNameForExternalImport(node, currentSourceFile)); + + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfImportEqualsDeclaration(statements, node); + } + + return singleOrMany(statements); + } + + /** + * Visits an ExportDeclaration node. ExportDeclarations are elided as they are handled via + * `appendExportsOfDeclaration`. + * + * @param The node to visit. + */ + function visitExportDeclaration(node: ExportDeclaration): VisitResult { + return undefined; + } + + /** + * Visits an ExportAssignment node. + * + * @param node The node to visit. + */ + function visitExportAssignment(node: ExportAssignment): VisitResult { + if (node.isExportEquals) { + // Elide `export=` as it is illegal in a SystemJS module. + return undefined; + } + + const expression = visitNode(node.expression, destructuringVisitor, isExpression); + const original = node.original; + if (original && hasAssociatedEndOfDeclarationMarker(original)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportStatement(deferredExports[id], createIdentifier("default"), expression, /*allowComments*/ true); + } + else { + return createExportStatement(createIdentifier("default"), expression, /*allowComments*/ true); + } + } + + /** + * Visits a FunctionDeclaration, hoisting it to the outer module body function. + * + * @param node The node to visit. + */ + function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { + if (hasModifier(node, ModifierFlags.Export)) { + hoistedStatements = append(hoistedStatements, + updateFunctionDeclaration( + node, + node.decorators, + visitNodes(node.modifiers, modifierVisitor, isModifier), + getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), + /*typeParameters*/ undefined, + visitNodes(node.parameters, destructuringVisitor, isParameterDeclaration), + /*type*/ undefined, + visitNode(node.body, destructuringVisitor, isBlock))); + } + else { + hoistedStatements = append(hoistedStatements, node); + } + + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); + } + else { + hoistedStatements = appendExportsOfHoistedDeclaration(hoistedStatements, node); + } + + return undefined; + } + + /** + * Visits a ClassDeclaration, hoisting its name to the outer module body function. + * + * @param node The node to visit. + */ + function visitClassDeclaration(node: ClassDeclaration): VisitResult { + let statements: Statement[]; + + // Hoist the name of the class declaration to the outer module body function. + const name = getLocalName(node); + hoistVariableDeclaration(name); + + // Rewrite the class declaration into an assignment of a class expression. + statements = append(statements, + createStatement( + createAssignment( + name, + createClassExpression( + /*modifiers*/ undefined, + node.name, + /*typeParameters*/ undefined, + visitNodes(node.heritageClauses, destructuringVisitor, isHeritageClause), + visitNodes(node.members, destructuringVisitor, isClassElement), + /*location*/ node + ) + ), + /*location*/ node + ) + ); + + if (hasAssociatedEndOfDeclarationMarker(node)) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); + } + else { + statements = appendExportsOfHoistedDeclaration(statements, node); + } + + return singleOrMany(statements); + } + + /** + * Visits a variable statement, hoisting declared names to the top-level module body. + * Each declaration is rewritten into an assignment expression. + * + * @param node The node to visit. + */ + function visitVariableStatement(node: VariableStatement): VisitResult { + if (!shouldHoistVariableDeclarationList(node.declarationList)) { + return visitNode(node, destructuringVisitor, isStatement); + } + + let expressions: Expression[]; + const isExportedDeclaration = hasModifier(node, ModifierFlags.Export); + const isMarkedDeclaration = hasAssociatedEndOfDeclarationMarker(node); + for (const variable of node.declarationList.declarations) { + if (variable.initializer) { + expressions = append(expressions, transformInitializedVariable(variable, isExportedDeclaration && !isMarkedDeclaration)); + } + else { + hoistBindingElement(variable); + } + } + + let statements: Statement[]; + if (expressions) { + statements = append(statements, createStatement(inlineExpressions(expressions), /*location*/ node)); + } + + if (isMarkedDeclaration) { + // Defer exports until we encounter an EndOfDeclarationMarker node + const id = getOriginalNodeId(node); + deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node, isExportedDeclaration); + } + else { + statements = appendExportsOfVariableStatement(statements, node, /*exportSelf*/ false); + } + + return singleOrMany(statements); + } + + /** + * Hoists the declared names of a VariableDeclaration or BindingElement. + * + * @param node The declaration to hoist. + */ + function hoistBindingElement(node: VariableDeclaration | BindingElement): void { + if (isBindingPattern(node.name)) { + for (const element of node.name.elements) { + if (!isOmittedExpression(element)) { + hoistBindingElement(element); + } + } + } + else { + hoistVariableDeclaration(getSynthesizedClone(node.name)); + } + } + + /** + * Determines whether a VariableDeclarationList should be hoisted. + * + * @param node The node to test. + */ + function shouldHoistVariableDeclarationList(node: VariableDeclarationList) { + // hoist only non-block scoped declarations or block scoped declarations parented by source file + return (getEmitFlags(node) & EmitFlags.NoHoisting) === 0 + && (enclosingBlockScopedContainer.kind === SyntaxKind.SourceFile + || (getOriginalNode(node).flags & NodeFlags.BlockScoped) === 0); + } + + /** + * Transform an initialized variable declaration into an expression. + * + * @param node The node to transform. + * @param isExportedDeclaration A value indicating whether the variable is exported. + */ + function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression { + const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment; + return isBindingPattern(node.name) + ? flattenVariableDestructuringToExpression(context, node, hoistVariableDeclaration, createAssignment, destructuringVisitor) + : createAssignment(node.name, visitNode(node.initializer, destructuringVisitor, isExpression)); + } + + /** + * Creates an assignment expression for an exported variable declaration. + * + * @param name The name of the variable. + * @param value The value of the variable's initializer. + * @param location The source map location for the assignment. + */ + function createExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) { + return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ true); + } + + /** + * Creates an assignment expression for a non-exported variable declaration. + * + * @param name The name of the variable. + * @param value The value of the variable's initializer. + * @param location The source map location for the assignment. + */ + function createNonExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) { + return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ false); + } + + /** + * Creates an assignment expression for a variable declaration. + * + * @param name The name of the variable. + * @param value The value of the variable's initializer. + * @param location The source map location for the assignment. + * @param isExportedDeclaration A value indicating whether the variable is exported. + */ + function createVariableAssignment(name: Identifier, value: Expression, location: TextRange, isExportedDeclaration: boolean) { + hoistVariableDeclaration(getSynthesizedClone(name)); + return isExportedDeclaration + ? createExportExpression(name, preventSubstitution(createAssignment(name, value, location))) + : preventSubstitution(createAssignment(name, value, location)); + } + + /** + * Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged + * and transformed declaration. + * + * @param node The node to visit. + */ + function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult { + // For an EnumDeclaration or ModuleDeclaration that merges with a preceeding + // declaration we do not emit a leading variable declaration. To preserve the + // begin/end semantics of the declararation and to properly handle exports + // we wrapped the leading variable declaration in a `MergeDeclarationMarker`. + // + // To balance the declaration, we defer the exports of the elided variable + // statement until we visit this declaration's `EndOfDeclarationMarker`. + if (hasAssociatedEndOfDeclarationMarker(node) && node.original.kind === SyntaxKind.VariableStatement) { + const id = getOriginalNodeId(node); + const isExportedDeclaration = hasModifier(node.original, ModifierFlags.Export); + deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node.original, isExportedDeclaration); + } + + return node; + } + + /** + * Determines whether a node has an associated EndOfDeclarationMarker. + * + * @param node The node to test. + */ + function hasAssociatedEndOfDeclarationMarker(node: Node) { + return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0; + } + + /** + * Visits a DeclarationMarker used as a placeholder for the end of a transformed + * declaration. + * + * @param node The node to visit. + */ + function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult { + // For some transformations we emit an `EndOfDeclarationMarker` to mark the actual + // end of the transformed declaration. We use this marker to emit any deferred exports + // of the declaration. + const id = getOriginalNodeId(node); + const statements = deferredExports[id]; + if (statements) { + delete deferredExports[id]; + return append(statements, node); + } + + return node; + } + + /** + * Appends the exports of an ImportDeclaration to a statement list, returning the + * statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param decl The declaration whose exports are to be recorded. + */ + function appendExportsOfImportDeclaration(statements: Statement[], decl: ImportDeclaration) { + if (moduleInfo.exportEquals) { + return statements; + } + + const importClause = decl.importClause; + if (!importClause) { + return statements; + } + + if (importClause.name) { + statements = appendExportsOfDeclaration(statements, importClause); + } + + const namedBindings = importClause.namedBindings; + if (namedBindings) { + switch (namedBindings.kind) { + case SyntaxKind.NamespaceImport: + statements = appendExportsOfDeclaration(statements, namedBindings); + break; + + case SyntaxKind.NamedImports: + for (const importBinding of namedBindings.elements) { + statements = appendExportsOfDeclaration(statements, importBinding); + } + + break; + } + } + + return statements; + } + + /** + * Appends the export of an ImportEqualsDeclaration to a statement list, returning the + * statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param decl The declaration whose exports are to be recorded. + */ + function appendExportsOfImportEqualsDeclaration(statements: Statement[], decl: ImportEqualsDeclaration): Statement[] | undefined { + if (moduleInfo.exportEquals) { + return statements; + } + + return appendExportsOfDeclaration(statements, decl); + } + + /** + * Appends the exports of a VariableStatement to a statement list, returning the statement + * list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param node The VariableStatement whose exports are to be recorded. + * @param exportSelf A value indicating whether to also export each VariableDeclaration of + * `nodes` declaration list. + */ + function appendExportsOfVariableStatement(statements: Statement[] | undefined, node: VariableStatement, exportSelf: boolean): Statement[] | undefined { + if (moduleInfo.exportEquals) { + return statements; + } + + for (const decl of node.declarationList.declarations) { + if (decl.initializer || exportSelf) { + statements = appendExportsOfBindingElement(statements, decl, exportSelf); + } + } + + return statements; + } + + /** + * Appends the exports of a VariableDeclaration or BindingElement to a statement list, + * returning the statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param decl The declaration whose exports are to be recorded. + * @param exportSelf A value indicating whether to also export the declaration itself. + */ + function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement, exportSelf: boolean): Statement[] | undefined { + if (moduleInfo.exportEquals) { + return statements; + } + + if (isBindingPattern(decl.name)) { + for (const element of decl.name.elements) { + if (!isOmittedExpression(element)) { + statements = appendExportsOfBindingElement(statements, element, exportSelf); + } + } + } + else if (!isGeneratedIdentifier(decl.name)) { + let excludeName: string; + if (exportSelf) { + statements = appendExportStatement(statements, decl.name, getLocalName(decl)); + excludeName = decl.name.text; + } + + statements = appendExportsOfDeclaration(statements, decl, excludeName); + } + + return statements; + } + + /** + * Appends the exports of a ClassDeclaration or FunctionDeclaration to a statement list, + * returning the statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param decl The declaration whose exports are to be recorded. + */ + function appendExportsOfHoistedDeclaration(statements: Statement[] | undefined, decl: ClassDeclaration | FunctionDeclaration): Statement[] | undefined { + if (moduleInfo.exportEquals) { + return statements; + } + + let excludeName: string; + if (hasModifier(decl, ModifierFlags.Export)) { + const exportName = hasModifier(decl, ModifierFlags.Default) ? createLiteral("default") : decl.name; + statements = appendExportStatement(statements, exportName, getLocalName(decl)); + excludeName = exportName.text; + } + + if (decl.name) { + statements = appendExportsOfDeclaration(statements, decl, excludeName); + } + + return statements; + } + + /** + * Appends the exports of a declaration to a statement list, returning the statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param decl The declaration to export. + * @param excludeName An optional name to exclude from exports. + */ + function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration, excludeName?: string): Statement[] | undefined { + if (moduleInfo.exportEquals) { + return statements; + } + + const name = getDeclarationName(decl); + const exportSpecifiers = moduleInfo.exportSpecifiers[name.text]; + if (exportSpecifiers) { + for (const exportSpecifier of exportSpecifiers) { + if (exportSpecifier.name.text !== excludeName) { + statements = appendExportStatement(statements, exportSpecifier.name, name); + } + } + } + return statements; + } + + /** + * Appends the down-level representation of an export to a statement list, returning the + * statement list. + * + * @param statements A statement list to which the down-level export statements are to be + * appended. If `statements` is `undefined`, a new array is allocated if statements are + * appended. + * @param exportName The name of the export. + * @param expression The expression to export. + * @param allowComments Whether to allow comments on the export. + */ + function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier | StringLiteral, expression: Expression, allowComments?: boolean): Statement[] | undefined { + statements = append(statements, createExportStatement(exportName, expression, allowComments)); + return statements; + } + + /** + * Creates a call to the current file's export function to export a value. + * + * @param name The bound name of the export. + * @param value The exported value. + * @param allowComments An optional value indicating whether to emit comments for the statement. + */ + function createExportStatement(name: Identifier | StringLiteral, value: Expression, allowComments?: boolean) { + const statement = createStatement(createExportExpression(name, value)); + startOnNewLine(statement); + if (!allowComments) { + setEmitFlags(statement, EmitFlags.NoComments); + } + + return statement; + } + + /** + * Creates a call to the current file's export function to export a value. + * + * @param name The bound name of the export. + * @param value The exported value. + */ + function createExportExpression(name: Identifier | StringLiteral, value: Expression) { + const exportName = isIdentifier(name) ? createLiteral(name) : name; + return createCall(exportFunction, /*typeArguments*/ undefined, [exportName, value]); + } + + // + // Top-Level or Nested Source Element Visitors + // + + /** + * Visit nested elements at the top-level of a module. + * + * @param node The node to visit. + */ + function nestedElementVisitor(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.VariableStatement: return visitVariableStatement(node); @@ -539,361 +1188,170 @@ namespace ts { case SyntaxKind.Block: return visitBlock(node); - case SyntaxKind.ExpressionStatement: - return visitExpressionStatement(node); + case SyntaxKind.MergeDeclarationMarker: + return visitMergeDeclarationMarker(node); + + case SyntaxKind.EndOfDeclarationMarker: + return visitEndOfDeclarationMarker(node); default: - return node; + return destructuringVisitor(node); } } - function visitImportDeclaration(node: ImportDeclaration): Node { - if (node.importClause && contains(externalImports, node)) { - hoistVariableDeclaration(getLocalNameForExternalImport(node, currentSourceFile)); - } - - return undefined; - } - - function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): Node { - if (contains(externalImports, node)) { - hoistVariableDeclaration(getLocalNameForExternalImport(node, currentSourceFile)); - } - - // NOTE(rbuckton): Do we support export import = require('') in System? - return undefined; - } - - function visitExportDeclaration(node: ExportDeclaration): VisitResult { - if (!node.moduleSpecifier) { - const statements: Statement[] = []; - addRange(statements, map(node.exportClause.elements, visitExportSpecifier)); - return statements; - } - - return undefined; - } - - function visitExportSpecifier(specifier: ExportSpecifier): Statement { - recordExportName(specifier.name); - return createExportStatement( - specifier.name, - specifier.propertyName || specifier.name - ); - } - - function visitExportAssignment(node: ExportAssignment): Statement { - if (node.isExportEquals) { - // Elide `export=` as it is illegal in a SystemJS module. - return undefined; - } - - return createExportStatement( - createLiteral("default"), - node.expression - ); - } - - /** - * Visits a variable statement, hoisting declared names to the top-level module body. - * Each declaration is rewritten into an assignment expression. - * - * @param node The variable statement to visit. - */ - function visitVariableStatement(node: VariableStatement): VisitResult { - // hoist only non-block scoped declarations or block scoped declarations parented by source file - const shouldHoist = - ((getCombinedNodeFlags(getOriginalNode(node.declarationList)) & NodeFlags.BlockScoped) == 0) || - enclosingBlockScopedContainer.kind === SyntaxKind.SourceFile; - if (!shouldHoist) { - return node; - } - const isExported = hasModifier(node, ModifierFlags.Export); - const expressions: Expression[] = []; - for (const variable of node.declarationList.declarations) { - const visited = transformVariable(variable, isExported); - if (visited) { - expressions.push(visited); - } - } - - if (expressions.length) { - return createStatement(inlineExpressions(expressions), node); - } - - return undefined; - } - - /** - * Transforms a VariableDeclaration into one or more assignment expressions. - * - * @param node The VariableDeclaration to transform. - * @param isExported A value used to indicate whether the containing statement was exported. - */ - function transformVariable(node: VariableDeclaration, isExported: boolean): VariableDeclaration | Expression { - // Hoist any bound names within the declaration. - hoistBindingElement(node, isExported); - - if (!node.initializer) { - // If the variable has no initializer, ignore it. - return; - } - - const name = node.name; - if (isIdentifier(name)) { - // If the variable has an IdentifierName, write out an assignment expression in its place. - return createAssignment(name, node.initializer); - } - else { - // If the variable has a BindingPattern, flatten the variable into multiple assignment expressions. - return flattenVariableDestructuringToExpression(context, node, hoistVariableDeclaration); - } - } - - /** - * Visits a FunctionDeclaration, hoisting it to the outer module body function. - * - * @param node The function declaration to visit. - */ - function visitFunctionDeclaration(node: FunctionDeclaration): Node { - if (hasModifier(node, ModifierFlags.Export)) { - // If the function is exported, ensure it has a name and rewrite the function without any export flags. - const name = node.name || getGeneratedNameForNode(node); - // Keep async modifier for ES2017 transformer - const isAsync = hasModifier(node, ModifierFlags.Async); - const newNode = createFunctionDeclaration( - /*decorators*/ undefined, - isAsync ? [createNode(SyntaxKind.AsyncKeyword)] : undefined, - node.asteriskToken, - name, - /*typeParameters*/ undefined, - node.parameters, - /*type*/ undefined, - node.body, - /*location*/ node); - - // Record a declaration export in the outer module body function. - recordExportedFunctionDeclaration(node); - - if (!hasModifier(node, ModifierFlags.Default)) { - recordExportName(name); - } - - setOriginalNode(newNode, node); - node = newNode; - } - - // Hoist the function declaration to the outer module body function. - hoistFunctionDeclaration(node); - return undefined; - } - - function visitExpressionStatement(node: ExpressionStatement): VisitResult { - const originalNode = getOriginalNode(node); - if ((originalNode.kind === SyntaxKind.ModuleDeclaration || originalNode.kind === SyntaxKind.EnumDeclaration) && hasModifier(originalNode, ModifierFlags.Export)) { - const name = getDeclarationName(originalNode); - return [ - node, - createExportStatement(name, name) - ]; - } - return node; - } - - /** - * Visits a ClassDeclaration, hoisting its name to the outer module body function. - * - * @param node The class declaration to visit. - */ - function visitClassDeclaration(node: ClassDeclaration): VisitResult { - // Hoist the name of the class declaration to the outer module body function. - const name = getDeclarationName(node); - hoistVariableDeclaration(name); - - const statements: Statement[] = []; - - // Rewrite the class declaration into an assignment of a class expression. - statements.push( - createStatement( - createAssignment( - name, - createClassExpression( - /*modifiers*/ undefined, - node.name, - /*typeParameters*/ undefined, - node.heritageClauses, - node.members, - /*location*/ node - ) - ), - /*location*/ node - ) - ); - - // If the class was exported, write a declaration export to the inner module body function. - if (hasModifier(node, ModifierFlags.Export)) { - if (!hasModifier(node, ModifierFlags.Default)) { - recordExportName(name); - } - - statements.push(createDeclarationExport(node)); - } - - return statements; - } - - function shouldHoistLoopInitializer(node: VariableDeclarationList | Expression) { - return isVariableDeclarationList(node) && (getCombinedNodeFlags(node) & NodeFlags.BlockScoped) === 0; - } - /** * Visits the body of a ForStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitForStatement(node: ForStatement): ForStatement { - const initializer = node.initializer; - if (shouldHoistLoopInitializer(initializer)) { - const expressions: Expression[] = []; - for (const variable of (initializer).declarations) { - const visited = transformVariable(variable, /*isExported*/ false); - if (visited) { - expressions.push(visited); - } - }; + function visitForStatement(node: ForStatement): VisitResult { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; - return createFor( - expressions.length - ? inlineExpressions(expressions) - : createOmittedExpression(), - node.condition, - node.incrementor, - visitNode(node.statement, visitNestedNode, isStatement), - /*location*/ node - ); - } - else { - return visitEachChild(node, visitNestedNode, context); - } - } + node = updateFor( + node, + visitForInitializer(node.initializer), + visitNode(node.condition, destructuringVisitor, isExpression, /*optional*/ true), + visitNode(node.incrementor, destructuringVisitor, isExpression, /*optional*/ true), + visitNode(node.statement, nestedElementVisitor, isStatement) + ); - /** - * Transforms and hoists the declaration list of a ForInStatement or ForOfStatement into an expression. - * - * @param node The decalaration list to transform. - */ - function transformForBinding(node: VariableDeclarationList): Expression { - const firstDeclaration = firstOrUndefined(node.declarations); - hoistBindingElement(firstDeclaration, /*isExported*/ false); - - const name = firstDeclaration.name; - return isIdentifier(name) - ? name - : flattenVariableDestructuringToExpression(context, firstDeclaration, hoistVariableDeclaration); + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; } /** * Visits the body of a ForInStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitForInStatement(node: ForInStatement): ForInStatement { - const initializer = node.initializer; - if (shouldHoistLoopInitializer(initializer)) { - return updateForIn( - node, - transformForBinding(initializer), - node.expression, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock) - ); - } - else { - return visitEachChild(node, visitNestedNode, context); - } + function visitForInStatement(node: ForInStatement): VisitResult { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; + + node = updateForIn( + node, + visitForInitializer(node.initializer), + visitNode(node.expression, destructuringVisitor, isExpression), + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + ); + + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; } /** * Visits the body of a ForOfStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitForOfStatement(node: ForOfStatement): ForOfStatement { - const initializer = node.initializer; - if (shouldHoistLoopInitializer(initializer)) { - return updateForOf( - node, - transformForBinding(initializer), - node.expression, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock) - ); + function visitForOfStatement(node: ForOfStatement): VisitResult { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; + + node = updateForOf( + node, + visitForInitializer(node.initializer), + visitNode(node.expression, destructuringVisitor, isExpression), + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) + ); + + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; + } + + /** + * Determines whether to hoist the initializer of a ForStatement, ForInStatement, or + * ForOfStatement. + * + * @param node The node to test. + */ + function shouldHoistForInitializer(node: ForInitializer): node is VariableDeclarationList { + return isVariableDeclarationList(node) + && shouldHoistVariableDeclarationList(node); + } + + /** + * Visits the initializer of a ForStatement, ForInStatement, or ForOfStatement + * + * @param node The node to visit. + */ + function visitForInitializer(node: ForInitializer): ForInitializer { + if (shouldHoistForInitializer(node)) { + let expressions: Expression[]; + for (const variable of node.declarations) { + expressions = append(expressions, transformInitializedVariable(variable, /*isExportedDeclaration*/ false)); + } + + return expressions ? inlineExpressions(expressions) : createOmittedExpression(); } else { - return visitEachChild(node, visitNestedNode, context); + return visitEachChild(node, nestedElementVisitor, context); } } /** * Visits the body of a DoStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitDoStatement(node: DoStatement) { + function visitDoStatement(node: DoStatement): VisitResult { return updateDo( node, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock), - node.expression + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock), + visitNode(node.expression, destructuringVisitor, isExpression) ); } /** * Visits the body of a WhileStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitWhileStatement(node: WhileStatement) { + function visitWhileStatement(node: WhileStatement): VisitResult { return updateWhile( node, - node.expression, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.expression, destructuringVisitor, isExpression), + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) ); } /** * Visits the body of a LabeledStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitLabeledStatement(node: LabeledStatement) { + function visitLabeledStatement(node: LabeledStatement): VisitResult { return updateLabel( node, node.label, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) ); } /** * Visits the body of a WithStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitWithStatement(node: WithStatement) { + function visitWithStatement(node: WithStatement): VisitResult { return updateWith( node, - node.expression, - visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock) + visitNode(node.expression, destructuringVisitor, isExpression), + visitNode(node.statement, nestedElementVisitor, isStatement, /*optional*/ false, liftToBlock) ); } /** * Visits the body of a SwitchStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitSwitchStatement(node: SwitchStatement) { + function visitSwitchStatement(node: SwitchStatement): VisitResult { return updateSwitch( node, - node.expression, - visitNode(node.caseBlock, visitNestedNode, isCaseBlock) + visitNode(node.expression, destructuringVisitor, isExpression), + visitNode(node.caseBlock, nestedElementVisitor, isCaseBlock) ); } @@ -902,90 +1360,221 @@ namespace ts { * * @param node The node to visit. */ - function visitCaseBlock(node: CaseBlock) { - return updateCaseBlock( + function visitCaseBlock(node: CaseBlock): CaseBlock { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; + + node = updateCaseBlock( node, - visitNodes(node.clauses, visitNestedNode, isCaseOrDefaultClause) + visitNodes(node.clauses, nestedElementVisitor, isCaseOrDefaultClause) ); + + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; } /** * Visits the body of a CaseClause to hoist declarations. * - * @param node The clause to visit. + * @param node The node to visit. */ - function visitCaseClause(node: CaseClause) { + function visitCaseClause(node: CaseClause): VisitResult { return updateCaseClause( node, - node.expression, - visitNodes(node.statements, visitNestedNode, isStatement) + visitNode(node.expression, destructuringVisitor, isExpression), + visitNodes(node.statements, nestedElementVisitor, isStatement) ); } /** * Visits the body of a DefaultClause to hoist declarations. * - * @param node The clause to visit. + * @param node The node to visit. */ - function visitDefaultClause(node: DefaultClause) { - return visitEachChild(node, visitNestedNode, context); + function visitDefaultClause(node: DefaultClause): VisitResult { + return visitEachChild(node, nestedElementVisitor, context); } /** * Visits the body of a TryStatement to hoist declarations. * - * @param node The statement to visit. + * @param node The node to visit. */ - function visitTryStatement(node: TryStatement) { - return visitEachChild(node, visitNestedNode, context); + function visitTryStatement(node: TryStatement): VisitResult { + return visitEachChild(node, nestedElementVisitor, context); } /** * Visits the body of a CatchClause to hoist declarations. * - * @param node The clause to visit. + * @param node The node to visit. */ - function visitCatchClause(node: CatchClause) { - return updateCatchClause( + function visitCatchClause(node: CatchClause): CatchClause { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; + + node = updateCatchClause( node, node.variableDeclaration, - visitNode(node.block, visitNestedNode, isBlock) + visitNode(node.block, nestedElementVisitor, isBlock) ); + + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; } /** * Visits the body of a Block to hoist declarations. * - * @param node The block to visit. + * @param node The node to visit. */ - function visitBlock(node: Block) { - return visitEachChild(node, visitNestedNode, context); + function visitBlock(node: Block): Block { + const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; + enclosingBlockScopedContainer = node; + + node = visitEachChild(node, nestedElementVisitor, context); + + enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; + return node; } // - // Substitutions + // Destructuring Assignment Visitors // + /** + * Visit nodes to flatten destructuring assignments to exported symbols. + * + * @param node The node to visit. + */ + function destructuringVisitor(node: Node): VisitResult { + if (node.transformFlags & TransformFlags.DestructuringAssignment + && node.kind === SyntaxKind.BinaryExpression) { + return visitDestructuringAssignment(node); + } + else if (node.transformFlags & TransformFlags.ContainsDestructuringAssignment) { + return visitEachChild(node, destructuringVisitor, context); + } + else { + return node; + } + } + + /** + * Visits a DestructuringAssignment to flatten destructuring to exported symbols. + * + * @param node The node to visit. + */ + function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult { + if (hasExportedReferenceInDestructuringTarget(node.left)) { + return flattenDestructuringAssignment(context, node, /*needsValue*/ true, hoistVariableDeclaration, destructuringVisitor); + } + + return visitEachChild(node, destructuringVisitor, context); + } + + /** + * Determines whether the target of a destructuring assigment refers to an exported symbol. + * + * @param node The destructuring target. + */ + function hasExportedReferenceInDestructuringTarget(node: Expression | ObjectLiteralElementLike): boolean { + if (isAssignmentExpression(node)) { + return hasExportedReferenceInDestructuringTarget(node.left); + } + else if (isSpreadElementExpression(node)) { + return hasExportedReferenceInDestructuringTarget(node.expression); + } + else if (isObjectLiteralExpression(node)) { + return some(node.properties, hasExportedReferenceInDestructuringTarget); + } + else if (isArrayLiteralExpression(node)) { + return some(node.elements, hasExportedReferenceInDestructuringTarget); + } + else if (isShorthandPropertyAssignment(node)) { + return hasExportedReferenceInDestructuringTarget(node.name); + } + else if (isPropertyAssignment(node)) { + return hasExportedReferenceInDestructuringTarget(node.initializer); + } + else if (isIdentifier(node)) { + const container = resolver.getReferencedExportContainer(node); + return container !== undefined && container.kind === SyntaxKind.SourceFile; + } + else { + return false; + } + } + + // + // Modifier Visitors + // + + /** + * Visit nodes to elide module-specific modifiers. + * + * @param node The node to visit. + */ + function modifierVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: + return undefined; + } + return node; + } + + // + // Emit Notification + // + + /** + * Hook for node emit notifications. + * + * @param emitContext A context hint for the emitter. + * @param node The node to emit. + * @param emit A callback used to emit the node in the printer. + */ function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { if (node.kind === SyntaxKind.SourceFile) { - exportFunctionForFile = exportFunctionForFileMap[getOriginalNodeId(node)]; + const id = getOriginalNodeId(node); + currentSourceFile = node; + moduleInfo = moduleInfoMap[id]; + exportFunction = exportFunctionsMap[id]; + noSubstitution = noSubstitutionMap[id]; + + if (noSubstitution) { + delete noSubstitutionMap[id]; + } + previousOnEmitNode(emitContext, node, emitCallback); - exportFunctionForFile = undefined; + + currentSourceFile = undefined; + moduleInfo = undefined; + exportFunction = undefined; + noSubstitution = undefined; } else { previousOnEmitNode(emitContext, node, emitCallback); } } + // + // Substitutions + // + /** * Hooks node substitutions. * + * @param emitContext A context hint for the emitter. * @param node The node to substitute. - * @param isExpression A value indicating whether the node is to be used in an expression - * position. */ function onSubstituteNode(emitContext: EmitContext, node: Node) { node = previousOnSubstituteNode(emitContext, node); + if (isSubstitutionPrevented(node)) { + return node; + } + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } @@ -1008,373 +1597,167 @@ namespace ts { case SyntaxKind.PostfixUnaryExpression: return substituteUnaryExpression(node); } + return node; } /** - * Substitution for identifiers exported at the top level of a module. + * Substitution for an Identifier expression that may contain an imported or exported symbol. + * + * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { - const importDeclaration = resolver.getReferencedImportDeclaration(node); - if (importDeclaration) { - const importBinding = createImportBinding(importDeclaration); - if (importBinding) { - return importBinding; + // When we see an identifier in an expression position that + // points to an imported symbol, we should substitute a qualified + // reference to the imported symbol if one is needed. + // + // - We do not substitute generated identifiers for any reason. + // - We do not substitute identifiers tagged with the LocalName flag. + if (!isGeneratedIdentifier(node) && !isLocalName(node)) { + const importDeclaration = resolver.getReferencedImportDeclaration(node); + if (importDeclaration) { + if (isImportClause(importDeclaration)) { + return createPropertyAccess( + getGeneratedNameForNode(importDeclaration.parent), + createIdentifier("default"), + /*location*/ node + ); + } + else if (isImportSpecifier(importDeclaration)) { + return createPropertyAccess( + getGeneratedNameForNode(importDeclaration.parent.parent.parent), + getSynthesizedClone(importDeclaration.propertyName || importDeclaration.name), + /*location*/ node + ); + } } } return node; } + /** + * Substitution for a BinaryExpression that may contain an imported or exported symbol. + * + * @param node The node to substitute. + */ function substituteBinaryExpression(node: BinaryExpression): Expression { - if (isAssignmentOperator(node.operatorToken.kind)) { - return substituteAssignmentExpression(node); + // When we see an assignment expression whose left-hand side is an exported symbol, + // we should ensure all exports of that symbol are updated with the correct value. + // + // - We do not substitute generated identifiers for any reason. + // - We do not substitute identifiers tagged with the LocalName flag. + // - We do not substitute identifiers that were originally the name of an enum or + // namespace due to how they are transformed in TypeScript. + // - We only substitute identifiers that are exported at the top level. + if (isAssignmentOperator(node.operatorToken.kind) + && isIdentifier(node.left) + && !isGeneratedIdentifier(node.left) + && !isLocalName(node.left) + && !isDeclarationNameOfEnumOrNamespace(node.left)) { + const exportedNames = getExports(node.left); + if (exportedNames) { + // For each additional export of the declaration, apply an export assignment. + let expression: Expression = node; + for (const exportName of exportedNames) { + expression = createExportExpression(exportName, preventSubstitution(expression)); + } + + return expression; + } } return node; } - function substituteAssignmentExpression(node: BinaryExpression): Expression { - setEmitFlags(node, EmitFlags.NoSubstitution); - - const left = node.left; - switch (left.kind) { - case SyntaxKind.Identifier: - const exportDeclaration = resolver.getReferencedExportContainer(left); - if (exportDeclaration) { - return createExportExpression(left, node); - } - break; - - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ArrayLiteralExpression: - if (hasExportedReferenceInDestructuringPattern(left)) { - return substituteDestructuring(node); - } - break; - } - - return node; - } - - function isExportedBinding(name: Identifier) { - const container = resolver.getReferencedExportContainer(name); - return container && container.kind === SyntaxKind.SourceFile; - } - - function hasExportedReferenceInDestructuringPattern(node: ObjectLiteralExpression | ArrayLiteralExpression | Identifier): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: - return isExportedBinding(node); - - case SyntaxKind.ObjectLiteralExpression: - for (const property of (node).properties) { - if (hasExportedReferenceInObjectDestructuringElement(property)) { - return true; - } - } - - break; - - case SyntaxKind.ArrayLiteralExpression: - for (const element of (node).elements) { - if (hasExportedReferenceInArrayDestructuringElement(element)) { - return true; - } - } - - break; - } - - return false; - } - - function hasExportedReferenceInObjectDestructuringElement(node: ObjectLiteralElementLike): boolean { - if (isShorthandPropertyAssignment(node)) { - return isExportedBinding(node.name); - } - else if (isPropertyAssignment(node)) { - return hasExportedReferenceInDestructuringElement(node.initializer); - } - else { - return false; - } - } - - function hasExportedReferenceInArrayDestructuringElement(node: Expression): boolean { - if (isSpreadElementExpression(node)) { - const expression = node.expression; - return isIdentifier(expression) && isExportedBinding(expression); - } - else { - return hasExportedReferenceInDestructuringElement(node); - } - } - - function hasExportedReferenceInDestructuringElement(node: Expression): boolean { - if (isBinaryExpression(node)) { - const left = node.left; - return node.operatorToken.kind === SyntaxKind.EqualsToken - && isDestructuringPattern(left) - && hasExportedReferenceInDestructuringPattern(left); - } - else if (isIdentifier(node)) { - return isExportedBinding(node); - } - else if (isSpreadElementExpression(node)) { - const expression = node.expression; - return isIdentifier(expression) && isExportedBinding(expression); - } - else if (isDestructuringPattern(node)) { - return hasExportedReferenceInDestructuringPattern(node); - } - else { - return false; - } - } - - function isDestructuringPattern(node: Expression): node is ObjectLiteralExpression | ArrayLiteralExpression | Identifier { - const kind = node.kind; - return kind === SyntaxKind.Identifier - || kind === SyntaxKind.ObjectLiteralExpression - || kind === SyntaxKind.ArrayLiteralExpression; - } - - function substituteDestructuring(node: BinaryExpression) { - return flattenDestructuringAssignment(context, node, /*needsValue*/ true, hoistVariableDeclaration); - } - + /** + * Substitution for a UnaryExpression that may contain an imported or exported symbol. + * + * @param node The node to substitute. + */ function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression { - const operand = node.operand; - const operator = node.operator; - const substitute = - isIdentifier(operand) && - ( - node.kind === SyntaxKind.PostfixUnaryExpression || - (node.kind === SyntaxKind.PrefixUnaryExpression && (operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken)) - ); + // When we see a prefix or postfix increment expression whose operand is an exported + // symbol, we should ensure all exports of that symbol are updated with the correct + // value. + // + // - We do not substitute generated identifiers for any reason. + // - We do not substitute identifiers tagged with the LocalName flag. + // - We do not substitute identifiers that were originally the name of an enum or + // namespace due to how they are transformed in TypeScript. + // - We only substitute identifiers that are exported at the top level. + if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) + && isIdentifier(node.operand) + && !isGeneratedIdentifier(node.operand) + && !isLocalName(node.operand) + && !isDeclarationNameOfEnumOrNamespace(node.operand)) { + const exportedNames = getExports(node.operand); + if (exportedNames) { + let expression: Expression = node.kind === SyntaxKind.PostfixUnaryExpression + ? createPrefix( + node.operator, + node.operand, + /*location*/ node) + : node; - if (substitute) { - const exportDeclaration = resolver.getReferencedExportContainer(operand); - if (exportDeclaration) { - const expr = createPrefix(node.operator, operand, node); - setEmitFlags(expr, EmitFlags.NoSubstitution); - const call = createExportExpression(operand, expr); - if (node.kind === SyntaxKind.PrefixUnaryExpression) { - return call; + for (const exportName of exportedNames) { + expression = createExportExpression(exportName, preventSubstitution(expression)); } - else { - // export function returns the value that was passes as the second argument - // however for postfix unary expressions result value should be the value before modification. - // emit 'x++' as '(export('x', ++x) - 1)' and 'x--' as '(export('x', --x) + 1)' - return operator === SyntaxKind.PlusPlusToken - ? createSubtract(call, createLiteral(1)) - : createAdd(call, createLiteral(1)); + + if (node.kind === SyntaxKind.PostfixUnaryExpression) { + expression = node.operator === SyntaxKind.PlusPlusToken + ? createSubtract(preventSubstitution(expression), createLiteral(1)) + : createAdd(preventSubstitution(expression), createLiteral(1)); } + + return expression; } } + return node; } /** - * Gets a name to use for a DeclarationStatement. - * @param node The declaration statement. + * Gets the exports of a name. + * + * @param name The name. */ - function getDeclarationName(node: DeclarationStatement) { - return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node); - } + function getExports(name: Identifier) { + let exportedNames: Identifier[]; + if (!isGeneratedIdentifier(name)) { + const valueDeclaration = resolver.getReferencedImportDeclaration(name) + || resolver.getReferencedValueDeclaration(name); - function addExportStarFunction(statements: Statement[], localNames: Identifier) { - const exportStarFunction = createUniqueName("exportStar"); - const m = createIdentifier("m"); - const n = createIdentifier("n"); - const exports = createIdentifier("exports"); - let condition: Expression = createStrictInequality(n, createLiteral("default")); - if (localNames) { - condition = createLogicalAnd( - condition, - createLogicalNot(createHasOwnProperty(localNames, n)) - ); - } + if (valueDeclaration) { + const exportContainer = resolver.getReferencedExportContainer(name, /*prefixLocals*/ false); + if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { + exportedNames = append(exportedNames, getDeclarationName(valueDeclaration)); + } - statements.push( - createFunctionDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - exportStarFunction, - /*typeParameters*/ undefined, - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, m)], - /*type*/ undefined, - createBlock([ - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - exports, - /*type*/ undefined, - createObjectLiteral([]) - ) - ]) - ), - createForIn( - createVariableDeclarationList([ - createVariableDeclaration(n, /*type*/ undefined) - ]), - m, - createBlock([ - setEmitFlags( - createIf( - condition, - createStatement( - createAssignment( - createElementAccess(exports, n), - createElementAccess(m, n) - ) - ) - ), - EmitFlags.SingleLine - ) - ]) - ), - createStatement( - createCall( - exportFunctionForFile, - /*typeArguments*/ undefined, - [exports] - ) - ) - ], - /*location*/ undefined, - /*multiline*/ true) - ) - ); - - return exportStarFunction; - } - - /** - * Creates a call to the current file's export function to export a value. - * @param name The bound name of the export. - * @param value The exported value. - */ - function createExportExpression(name: Identifier | StringLiteral, value: Expression) { - const exportName = isIdentifier(name) ? createLiteral(name.text) : name; - return createCall(exportFunctionForFile, /*typeArguments*/ undefined, [exportName, value]); - } - - /** - * Creates a call to the current file's export function to export a value. - * @param name The bound name of the export. - * @param value The exported value. - */ - function createExportStatement(name: Identifier | StringLiteral, value: Expression) { - return createStatement(createExportExpression(name, value)); - } - - /** - * Creates a call to the current file's export function to export a declaration. - * @param node The declaration to export. - */ - function createDeclarationExport(node: DeclarationStatement) { - const declarationName = getDeclarationName(node); - const exportName = hasModifier(node, ModifierFlags.Default) ? createLiteral("default") : declarationName; - return createExportStatement(exportName, declarationName); - } - - function createImportBinding(importDeclaration: Declaration): LeftHandSideExpression { - let importAlias: Identifier; - let name: Identifier; - if (isImportClause(importDeclaration)) { - importAlias = getGeneratedNameForNode(importDeclaration.parent); - name = createIdentifier("default"); - } - else if (isImportSpecifier(importDeclaration)) { - importAlias = getGeneratedNameForNode(importDeclaration.parent.parent.parent); - name = importDeclaration.propertyName || importDeclaration.name; - } - else { - return undefined; - } - - return createPropertyAccess(importAlias, getSynthesizedClone(name)); - } - - function collectDependencyGroups(externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]) { - const groupIndices = createMap(); - const dependencyGroups: DependencyGroup[] = []; - for (let i = 0; i < externalImports.length; i++) { - const externalImport = externalImports[i]; - const externalModuleName = getExternalModuleNameLiteral(externalImport, currentSourceFile, host, resolver, compilerOptions); - const text = externalModuleName.text; - if (hasProperty(groupIndices, text)) { - // deduplicate/group entries in dependency list by the dependency name - const groupIndex = groupIndices[text]; - dependencyGroups[groupIndex].externalImports.push(externalImport); - continue; - } - else { - groupIndices[text] = dependencyGroups.length; - dependencyGroups.push({ - name: externalModuleName, - externalImports: [externalImport] - }); + exportedNames = addRange(exportedNames, moduleInfo && moduleInfo.exportedBindings[getOriginalNodeId(valueDeclaration)]); } } - return dependencyGroups; + return exportedNames; } - function getNameOfDependencyGroup(dependencyGroup: DependencyGroup) { - return dependencyGroup.name; + /** + * Prevent substitution of a node for this transformer. + * + * @param node The node which should not be substituted. + */ + function preventSubstitution(node: T): T { + if (noSubstitution === undefined) noSubstitution = createMap(); + noSubstitution[getNodeId(node)] = true; + return node; } - function recordExportName(name: Identifier) { - if (!exportedLocalNames) { - exportedLocalNames = []; - } - - exportedLocalNames.push(name); - } - - function recordExportedFunctionDeclaration(node: FunctionDeclaration) { - if (!exportedFunctionDeclarations) { - exportedFunctionDeclarations = []; - } - - exportedFunctionDeclarations.push(createDeclarationExport(node)); - } - - function hoistBindingElement(node: VariableDeclaration | ArrayBindingElement, isExported: boolean): void { - if (isOmittedExpression(node)) { - return; - } - - const name = node.name; - if (isIdentifier(name)) { - hoistVariableDeclaration(getSynthesizedClone(name)); - if (isExported) { - recordExportName(name); - } - } - else if (isBindingPattern(name)) { - forEach(name.elements, isExported ? hoistExportedBindingElement : hoistNonExportedBindingElement); - } - } - - function hoistExportedBindingElement(node: VariableDeclaration | ArrayBindingElement) { - hoistBindingElement(node, /*isExported*/ true); - } - - function hoistNonExportedBindingElement(node: VariableDeclaration | ArrayBindingElement) { - hoistBindingElement(node, /*isExported*/ false); - } - - function updateSourceFile(node: SourceFile, statements: Statement[], nodeEmitFlags: EmitFlags) { - const updated = getMutableClone(node); - updated.statements = createNodeArray(statements, node.statements); - setEmitFlags(updated, nodeEmitFlags); - return updated; + /** + * Determines whether a node should not be substituted. + * + * @param node The node to test. + */ + function isSubstitutionPrevented(node: Node) { + return noSubstitution && node.id && noSubstitution[node.id]; } } } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 0e1821159be..6d2d70e7fbd 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2353,7 +2353,7 @@ namespace ts { context, node, hoistVariableDeclaration, - getNamespaceMemberNameWithSourceMapsAndWithoutComments, + createNamespaceExportExpression, visitor ); } @@ -2709,9 +2709,13 @@ namespace ts { return true; } else { - const notEmittedStatement = createNotEmittedStatement(statement); - setEmitFlags(notEmittedStatement, EmitFlags.NoComments); - statements.push(notEmittedStatement); + // For an EnumDeclaration or ModuleDeclaration that merges with a preceeding + // declaration we do not emit a leading variable declaration. To preserve the + // begin/end semantics of the declararation and to properly handle exports + // we wrap the leading variable declaration in a `MergeDeclarationMarker`. + const mergeMarker = createMergeDeclarationMarker(statement); + setEmitFlags(mergeMarker, EmitFlags.NoComments | EmitFlags.HasEndOfDeclarationMarker); + statements.push(mergeMarker); return false; } } @@ -3061,10 +3065,13 @@ namespace ts { createVariableStatement( visitNodes(node.modifiers, modifierVisitor, isModifier), createVariableDeclarationList([ - createVariableDeclaration( - node.name, - /*type*/ undefined, - moduleReference + setOriginalNode( + createVariableDeclaration( + node.name, + /*type*/ undefined, + moduleReference + ), + node ) ]), node @@ -3152,6 +3159,10 @@ namespace ts { ); } + function createNamespaceExportExpression(exportName: Identifier, exportValue: Expression, location?: TextRange) { + return createAssignment(getNamespaceMemberNameWithSourceMapsAndWithoutComments(exportName), exportValue, location); + } + function getNamespaceMemberNameWithSourceMapsAndWithoutComments(name: Identifier) { return getNamespaceMemberName(currentNamespaceContainerName, name, /*allowComments*/ false, /*allowSourceMaps*/ true); } @@ -3343,11 +3354,11 @@ namespace ts { function trySubstituteNamespaceExportedName(node: Identifier): Expression { // If this is explicitly a local name, do not substitute. - if (enabledSubstitutions & applicableSubstitutions && (getEmitFlags(node) & EmitFlags.LocalName) === 0) { + if (enabledSubstitutions & applicableSubstitutions && !isLocalName(node)) { // If we are nested within a namespace declaration, we may need to qualifiy // an identifier that is exported from a merged namespace. const container = resolver.getReferencedExportContainer(node, /*prefixLocals*/ false); - if (container) { + if (container && container.kind !== SyntaxKind.SourceFile) { const substitute = (applicableSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && container.kind === SyntaxKind.ModuleDeclaration) || (applicableSubstitutions & TypeScriptSubstitutionFlags.NonQualifiedEnumMembers && container.kind === SyntaxKind.EnumDeclaration); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 93fd9afb79c..2c3765210e7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -362,6 +362,7 @@ namespace ts { // Transformation nodes NotEmittedStatement, PartiallyEmittedExpression, + MergeDeclarationMarker, EndOfDeclarationMarker, // Enum value count @@ -1156,6 +1157,21 @@ namespace ts { right: Expression; } + export interface AssignmentExpression extends BinaryExpression { + left: LeftHandSideExpression; + operatorToken: Token; + } + + export interface ObjectDestructuringAssignment extends AssignmentExpression { + left: ObjectLiteralExpression; + } + + export interface ArrayDestructuringAssignment extends AssignmentExpression { + left: ArrayLiteralExpression; + } + + export type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; + export interface ConditionalExpression extends Expression { kind: SyntaxKind.ConditionalExpression; condition: Expression; @@ -1436,6 +1452,14 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } + /** + * Marks the beginning of a merged transformed declaration. + */ + /* @internal */ + export interface MergeDeclarationMarker extends Statement { + kind: SyntaxKind.MergeDeclarationMarker; + } + export interface EmptyStatement extends Statement { kind: SyntaxKind.EmptyStatement; } @@ -3380,22 +3404,23 @@ namespace ts { Generator = 1 << 10, ContainsGenerator = 1 << 11, DestructuringAssignment = 1 << 12, + ContainsDestructuringAssignment = 1 << 13, // Markers // - Flags used to indicate that a subtree contains a specific transformation. - ContainsDecorators = 1 << 13, - ContainsPropertyInitializer = 1 << 14, - ContainsLexicalThis = 1 << 15, - ContainsCapturedLexicalThis = 1 << 16, - ContainsLexicalThisInComputedPropertyName = 1 << 17, - ContainsDefaultValueAssignments = 1 << 18, - ContainsParameterPropertyAssignments = 1 << 19, - ContainsSpreadElementExpression = 1 << 20, - ContainsComputedPropertyName = 1 << 21, - ContainsBlockScopedBinding = 1 << 22, - ContainsBindingPattern = 1 << 23, - ContainsYield = 1 << 24, - ContainsHoistedDeclarationOrCompletion = 1 << 25, + ContainsDecorators = 1 << 14, + ContainsPropertyInitializer = 1 << 15, + ContainsLexicalThis = 1 << 16, + ContainsCapturedLexicalThis = 1 << 17, + ContainsLexicalThisInComputedPropertyName = 1 << 18, + ContainsDefaultValueAssignments = 1 << 19, + ContainsParameterPropertyAssignments = 1 << 20, + ContainsSpreadElementExpression = 1 << 21, + ContainsComputedPropertyName = 1 << 22, + ContainsBlockScopedBinding = 1 << 23, + ContainsBindingPattern = 1 << 24, + ContainsYield = 1 << 25, + ContainsHoistedDeclarationOrCompletion = 1 << 26, HasComputedFlags = 1 << 29, // Transform flags have been computed. @@ -3407,6 +3432,7 @@ namespace ts { AssertES2016 = ES2016 | ContainsES2016, AssertES2015 = ES2015 | ContainsES2015, AssertGenerator = Generator | ContainsGenerator, + AssertDestructuringAssignment = DestructuringAssignment | ContainsDestructuringAssignment, // Scope Exclusions // - Bitmasks that exclude flags from propagating out of a specific context @@ -3464,7 +3490,6 @@ namespace ts { NoNestedComments = 1 << 16, ExportName = 1 << 17, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). LocalName = 1 << 18, // Ensure an export prefix is not added for an identifier that points to an exported declaration. - ExportBindingName = LocalName | ExportName, Indented = 1 << 19, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). NoIndentation = 1 << 20, // Do not indent the node. AsyncFunctionBody = 1 << 21, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ac0dd1af720..91e95873a3f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3084,7 +3084,13 @@ namespace ts { } } - export function isDestructuringAssignment(node: Node): node is BinaryExpression { + export function isAssignmentExpression(node: Node): node is AssignmentExpression { + return isBinaryExpression(node) + && isAssignmentOperator(node.operatorToken.kind) + && isLeftHandSideExpression(node.left); + } + + export function isDestructuringAssignment(node: Node): node is DestructuringAssignment { if (isBinaryExpression(node)) { if (node.operatorToken.kind === SyntaxKind.EqualsToken) { const kind = node.left.kind; @@ -3522,6 +3528,7 @@ namespace ts { const exportSpecifiers = createMap(); const exportedBindings = createMap(); const uniqueExports = createMap(); + let hasExportDefault = false; let exportEquals: ExportAssignment = undefined; let hasExportStarsToExportValues = false; for (const node of sourceFile.statements) { @@ -3540,15 +3547,6 @@ namespace ts { externalImports.push(node); } - if (hasModifier(node, ModifierFlags.Export)) { - // export import x = ... - const name = (node).name; - if (!uniqueExports[name.text]) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports[name.text] = name; - } - } - break; case SyntaxKind.ExportDeclaration: @@ -3590,11 +3588,10 @@ namespace ts { } break; - case SyntaxKind.VariableDeclaration: - // export var x + case SyntaxKind.VariableStatement: if (hasModifier(node, ModifierFlags.Export)) { for (const decl of (node).declarationList.declarations) { - collectExportedVariableInfo(decl, exportedBindings, uniqueExports); + collectExportedVariableInfo(decl, uniqueExports); } } break; @@ -3603,9 +3600,9 @@ namespace ts { if (hasModifier(node, ModifierFlags.Export)) { if (hasModifier(node, ModifierFlags.Default)) { // export default function() { } - if (!uniqueExports["default"]) { + if (!hasExportDefault) { multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - uniqueExports["default"] = createIdentifier("default"); + hasExportDefault = true; } } else { @@ -3623,8 +3620,9 @@ namespace ts { if (hasModifier(node, ModifierFlags.Export)) { if (hasModifier(node, ModifierFlags.Default)) { // export default class { } - if (!uniqueExports["default"]) { + if (!hasExportDefault) { multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; } } else { @@ -3640,25 +3638,24 @@ namespace ts { } } - const exportedNames: Identifier[] = []; + let exportedNames: Identifier[]; for (const key in uniqueExports) { - exportedNames.push(uniqueExports[key]); + exportedNames = ts.append(exportedNames, uniqueExports[key]); } return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames }; } - function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, exportedNames: Map, uniqueExports: Map) { + function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map) { if (isBindingPattern(decl.name)) { for (const element of decl.name.elements) { if (!isOmittedExpression(element)) { - collectExportedVariableInfo(element, exportedNames, uniqueExports); + collectExportedVariableInfo(element, uniqueExports); } } } else if (!isGeneratedIdentifier(decl.name)) { if (!uniqueExports[decl.name.text]) { - multiMapAdd(exportedNames, getOriginalNodeId(decl), decl.name); uniqueExports[decl.name.text] = decl.name; } } @@ -3901,6 +3898,14 @@ namespace ts { // Expression + export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { + return node.kind === SyntaxKind.ArrayLiteralExpression; + } + + export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { + return node.kind === SyntaxKind.ObjectLiteralExpression; + } + export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { return node.kind === SyntaxKind.PropertyAccessExpression; } @@ -4161,7 +4166,8 @@ namespace ts { || kind === SyntaxKind.WhileStatement || kind === SyntaxKind.WithStatement || kind === SyntaxKind.NotEmittedStatement - || kind === SyntaxKind.EndOfDeclarationMarker; + || kind === SyntaxKind.EndOfDeclarationMarker + || kind === SyntaxKind.MergeDeclarationMarker; } export function isDeclaration(node: Node): node is Declaration { diff --git a/tests/baselines/reference/capturedLetConstInLoop4.js b/tests/baselines/reference/capturedLetConstInLoop4.js index e3587ad64c6..2cade08f0a6 100644 --- a/tests/baselines/reference/capturedLetConstInLoop4.js +++ b/tests/baselines/reference/capturedLetConstInLoop4.js @@ -151,13 +151,13 @@ System.register([], function (exports_1, context_1) { function exportedFoo() { return v0 + v00 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8; } + exports_1("exportedFoo", exportedFoo); //======const function exportedFoo2() { return v0_c + v00_c + v1_c + v2_c + v3_c + v4_c + v5_c + v6_c + v7_c + v8_c; } - var v0, v00, v1, v2, v3, v4, v5, v6, v7, v8, v0_c, v00_c, v1_c, v2_c, v3_c, v4_c, v5_c, v6_c, v7_c, v8_c; - exports_1("exportedFoo", exportedFoo); exports_1("exportedFoo2", exportedFoo2); + var v0, v00, v1, v2, v3, v4, v5, v6, v7, v8, v0_c, v00_c, v1_c, v2_c, v3_c, v4_c, v5_c, v6_c, v7_c, v8_c; return { setters: [], execute: function () { diff --git a/tests/baselines/reference/dottedNamesInSystem.js b/tests/baselines/reference/dottedNamesInSystem.js index e337e8cb123..257d7e616af 100644 --- a/tests/baselines/reference/dottedNamesInSystem.js +++ b/tests/baselines/reference/dottedNamesInSystem.js @@ -14,8 +14,8 @@ System.register([], function (exports_1, context_1) { function bar() { return A.B.C.foo(); } - var A; exports_1("bar", bar); + var A; return { setters: [], execute: function () { diff --git a/tests/baselines/reference/outFilerootDirModuleNamesSystem.js b/tests/baselines/reference/outFilerootDirModuleNamesSystem.js index 6754f3ef89a..efc49f90209 100644 --- a/tests/baselines/reference/outFilerootDirModuleNamesSystem.js +++ b/tests/baselines/reference/outFilerootDirModuleNamesSystem.js @@ -15,8 +15,8 @@ System.register("b", ["a"], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; function foo() { new a_1.default(); } - var a_1; exports_1("default", foo); + var a_1; return { setters: [ function (a_1_1) { diff --git a/tests/baselines/reference/systemModule10.js b/tests/baselines/reference/systemModule10.js index 10afa784ebb..a54afee15d7 100644 --- a/tests/baselines/reference/systemModule10.js +++ b/tests/baselines/reference/systemModule10.js @@ -24,10 +24,10 @@ System.register(["file1", "file2"], function (exports_1, context_1) { } ], execute: function () { - exports_1("x", file1_1.x); - exports_1("y", file1_1.x); exports_1("n", file1_1["default"]); exports_1("n1", file1_1["default"]); + exports_1("x", file1_1.x); + exports_1("y", file1_1.x); exports_1("n2", n2); exports_1("n3", n2); } diff --git a/tests/baselines/reference/systemModule10_ES5.js b/tests/baselines/reference/systemModule10_ES5.js index 830c611bd38..bac2a6003a4 100644 --- a/tests/baselines/reference/systemModule10_ES5.js +++ b/tests/baselines/reference/systemModule10_ES5.js @@ -24,10 +24,10 @@ System.register(["file1", "file2"], function (exports_1, context_1) { } ], execute: function () { - exports_1("x", file1_1.x); - exports_1("y", file1_1.x); exports_1("n", file1_1.default); exports_1("n1", file1_1.default); + exports_1("x", file1_1.x); + exports_1("y", file1_1.x); exports_1("n2", n2); exports_1("n3", n2); } diff --git a/tests/baselines/reference/systemModule11.js b/tests/baselines/reference/systemModule11.js index 7f120cec40d..41574af1d9a 100644 --- a/tests/baselines/reference/systemModule11.js +++ b/tests/baselines/reference/systemModule11.js @@ -46,8 +46,8 @@ System.register(["bar"], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; function foo() { } - var x; exports_1("foo", foo); + var x; var exportedNames_1 = { "x": true, "foo": true @@ -95,8 +95,6 @@ System.register(["bar"], function (exports_1, context_1) { } ], execute: function () { - exports_1("x", x); - exports_1("y1", y); } }; }); @@ -139,10 +137,10 @@ System.register(["a"], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; function foo() { } - function default_1() { } - var x, z, z1; exports_1("foo", foo); + function default_1() { } exports_1("default", default_1); + var x, z, z1; return { setters: [ function (a_1_1) { @@ -153,8 +151,6 @@ System.register(["a"], function (exports_1, context_1) { } ], execute: function () { - exports_1("z", z); - exports_1("z2", z1); } }; }); diff --git a/tests/baselines/reference/systemModule13.js b/tests/baselines/reference/systemModule13.js index c527fd3e811..d3fee049e04 100644 --- a/tests/baselines/reference/systemModule13.js +++ b/tests/baselines/reference/systemModule13.js @@ -8,12 +8,12 @@ for ([x] of [[1]]) {} System.register([], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; - var x, y, z, _a, z0, z1, _b; + var x, y, z, z0, z1, _a, _b; return { setters: [], execute: function () { - _a = [1, 2, 3], exports_1("x", x = _a[0]), exports_1("y", y = _a[1]), exports_1("z", z = _a[2]); - _b = { a: true, b: { c: "123" } }, exports_1("z0", z0 = _b.a), exports_1("z1", z1 = _b.b.c); + exports_1("x", x = (_a = [1, 2, 3], _a[0])), exports_1("y", y = _a[1]), exports_1("z", z = _a[2]); + exports_1("z0", z0 = (_b = { a: true, b: { c: "123" } }, _b.a)), exports_1("z1", z1 = _b.b.c); for (var _i = 0, _a = [[1]]; _i < _a.length; _i++) { exports_1("x", x = _a[_i][0]); } diff --git a/tests/baselines/reference/systemModule14.js b/tests/baselines/reference/systemModule14.js index bebe9244e07..ba4adc7aabc 100644 --- a/tests/baselines/reference/systemModule14.js +++ b/tests/baselines/reference/systemModule14.js @@ -17,6 +17,8 @@ System.register(["foo"], function (exports_1, context_1) { function foo() { return foo_1.a; } + exports_1("foo", foo); + exports_1("b", foo); var foo_1, x; return { setters: [ @@ -25,9 +27,7 @@ System.register(["foo"], function (exports_1, context_1) { } ], execute: function () { - exports_1("foo", foo); x = 1; - exports_1("b", foo); } }; }); diff --git a/tests/baselines/reference/systemModule17.js b/tests/baselines/reference/systemModule17.js index df3d722f3d2..bcdf6612356 100644 --- a/tests/baselines/reference/systemModule17.js +++ b/tests/baselines/reference/systemModule17.js @@ -71,18 +71,18 @@ System.register(["f1"], function (exports_1, context_1) { ], execute: function () { x = 1; + exports_1("x", x); + exports_1("x1", x); (function (N) { N.x = 1; })(N || (N = {})); IX = N.x; - exports_1("x", x); - exports_1("x1", x); + exports_1("IX", IX); + exports_1("IX1", IX); exports_1("A", f1_1.A); exports_1("A1", f1_1.A); exports_1("EA", f1_1.A); exports_1("EA1", f1_1.A); - exports_1("IX", IX); - exports_1("IX1", IX); } }; }); diff --git a/tests/baselines/reference/systemModule3.js b/tests/baselines/reference/systemModule3.js index a8f93941c89..109cbb7187c 100644 --- a/tests/baselines/reference/systemModule3.js +++ b/tests/baselines/reference/systemModule3.js @@ -67,9 +67,9 @@ System.register([], function (exports_1, context_1) { setters: [], execute: function () { default_1 = (function () { - function class_1() { + function default_1() { } - return class_1; + return default_1; }()); exports_1("default", default_1); } diff --git a/tests/baselines/reference/systemModule8.js b/tests/baselines/reference/systemModule8.js index 467c52298fb..3065d4f35b3 100644 --- a/tests/baselines/reference/systemModule8.js +++ b/tests/baselines/reference/systemModule8.js @@ -62,7 +62,7 @@ System.register([], function (exports_1, context_1) { for (exports_1("x", x = 18);; exports_1("x", --x)) { } for (var x_1 = 50;;) { } exports_1("y", y = [1][0]); - _a = { a: true, b: { c: "123" } }, exports_1("z0", z0 = _a.a), exports_1("z1", z1 = _a.b.c); + exports_1("z0", z0 = (_a = { a: true, b: { c: "123" } }, _a.a)), exports_1("z1", z1 = _a.b.c); for (var _i = 0, _a = [[1]]; _i < _a.length; _i++) { exports_1("x", x = _a[_i][0]); } diff --git a/tests/baselines/reference/systemModule9.js b/tests/baselines/reference/systemModule9.js index e46b97a71a2..9e497140083 100644 --- a/tests/baselines/reference/systemModule9.js +++ b/tests/baselines/reference/systemModule9.js @@ -70,7 +70,6 @@ System.register(["file1", "file2", "file3", "file4", "file5", "file6", "file7"], ns2.f(); ns3.f(); y = true; - exports_1("x", x); exports_1("z", y); } }; diff --git a/tests/baselines/reference/systemModuleConstEnumsSeparateCompilation.js b/tests/baselines/reference/systemModuleConstEnumsSeparateCompilation.js index ca68d952ab3..132d27a4ea0 100644 --- a/tests/baselines/reference/systemModuleConstEnumsSeparateCompilation.js +++ b/tests/baselines/reference/systemModuleConstEnumsSeparateCompilation.js @@ -20,8 +20,8 @@ System.register([], function (exports_1, context_1) { use(TopLevelConstEnum.X); use(M.NonTopLevelConstEnum.X); } - var TopLevelConstEnum, M; exports_1("foo", foo); + var TopLevelConstEnum, M; return { setters: [], execute: function () { diff --git a/tests/baselines/reference/systemModuleDeclarationMerging.js b/tests/baselines/reference/systemModuleDeclarationMerging.js index 27cdd9f1572..d60a315b0a6 100644 --- a/tests/baselines/reference/systemModuleDeclarationMerging.js +++ b/tests/baselines/reference/systemModuleDeclarationMerging.js @@ -14,8 +14,8 @@ System.register([], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; function F() { } - var C, E; exports_1("F", F); + var C, E; return { setters: [], execute: function () { diff --git a/tests/baselines/reference/systemModuleExportDefault.js b/tests/baselines/reference/systemModuleExportDefault.js index 53f63ecb027..67341a04a0b 100644 --- a/tests/baselines/reference/systemModuleExportDefault.js +++ b/tests/baselines/reference/systemModuleExportDefault.js @@ -48,9 +48,9 @@ System.register([], function (exports_1, context_1) { setters: [], execute: function () { default_1 = (function () { - function class_1() { + function default_1() { } - return class_1; + return default_1; }()); exports_1("default", default_1); } diff --git a/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js b/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js index c6d3c8295e0..df68f32b3bd 100644 --- a/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js +++ b/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js @@ -17,8 +17,8 @@ System.register([], function (exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; function TopLevelFunction() { } - var TopLevelClass, TopLevelModule, TopLevelEnum, TopLevelModule2; exports_1("TopLevelFunction", TopLevelFunction); + var TopLevelClass, TopLevelModule, TopLevelEnum, TopLevelModule2; return { setters: [], execute: function () { diff --git a/tests/baselines/reference/systemModuleTargetES6.js b/tests/baselines/reference/systemModuleTargetES6.js index a049ea78395..2fe48f4c57b 100644 --- a/tests/baselines/reference/systemModuleTargetES6.js +++ b/tests/baselines/reference/systemModuleTargetES6.js @@ -20,12 +20,12 @@ System.register([], function (exports_1, context_1) { function myFunction() { return new MyClass(); } + exports_1("myFunction", myFunction); function myFunction2() { return new MyClass2(); } - var MyClass, MyClass2; - exports_1("myFunction", myFunction); exports_1("myFunction2", myFunction2); + var MyClass, MyClass2; return { setters: [], execute: function () { @@ -35,8 +35,8 @@ System.register([], function (exports_1, context_1) { MyClass2 = class MyClass2 { static getInstance() { return MyClass2.value; } }; - exports_1("MyClass2", MyClass2); MyClass2.value = 42; + exports_1("MyClass2", MyClass2); } }; });