diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0b07fd8e26e..aeaad03267b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16076,7 +16076,7 @@ namespace ts { // When resolved as an expression identifier, if the given node references an exported entity, return the declaration // node of the exported entity's container. Otherwise, return undefined. - function getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration { + function getReferencedExportContainer(node: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration { let symbol = getReferencedValueSymbol(node); if (symbol) { if (symbol.flags & SymbolFlags.ExportValue) { @@ -16084,7 +16084,7 @@ namespace ts { // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the // kinds that we do NOT prefix. const exportSymbol = getMergedSymbol(symbol.exportSymbol); - if (exportSymbol.flags & SymbolFlags.ExportHasLocal) { + if (exportSymbol.flags & SymbolFlags.ExportHasLocal && !prefixLocals) { return undefined; } symbol = exportSymbol; diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 107d6446982..1568f17ecf7 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -258,6 +258,9 @@ namespace ts { case SyntaxKind.ClassDeclaration: return visitClassDeclaration(node); + case SyntaxKind.ExpressionStatement: + return visitExpressionStatement(node); + default: // This visitor does not descend into the tree, as export/import statements // are only transformed at the top level of a file. @@ -544,24 +547,33 @@ namespace ts { } } - function addExportMemberAssignment(statements: Statement[], node: FunctionDeclaration | ClassDeclaration) { + function addExportMemberAssignment(statements: Statement[], node: DeclarationStatement) { if (hasModifier(node, ModifierFlags.Default)) { - addExportDefault(statements, node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node), /*location*/ node); + addExportDefault(statements, getDeclarationName(node), /*location*/ node); } else { statements.push( - startOnNewLine( - createStatement( - createExportAssignment(node.name, node.name), - /*location*/ node - ) - ) + createExportStatement(node.name, node.name, /*location*/ node) ); } } function visitVariableStatement(node: VariableStatement): VisitResult { if (hasModifier(node, ModifierFlags.Export)) { + // If the variable is for a declaration that has a local name, + // do not elide the declaration. + const original = getOriginalNode(node); + if (original.kind === SyntaxKind.EnumDeclaration + || original.kind === SyntaxKind.ModuleDeclaration) { + return setOriginalNode( + createVariableStatement( + /*modifiers*/ undefined, + node.declarationList + ), + node + ); + } + const variables = getInitializedVariables(node.declarationList); if (variables.length === 0) { // elide statement if there are no initialized variables @@ -651,6 +663,87 @@ namespace ts { return singleOrMany(statements); } + function visitExpressionStatement(node: ExpressionStatement): VisitResult { + const original = getOriginalNode(node); + if (hasModifier(original, ModifierFlags.Export)) { + switch (original.kind) { + case SyntaxKind.EnumDeclaration: + return visitExpressionStatementForEnumDeclaration(node, original); + + case SyntaxKind.ModuleDeclaration: + return visitExpressionStatementForModuleDeclaration(node, original); + } + } + + return node; + } + + function visitExpressionStatementForEnumDeclaration(node: ExpressionStatement, original: EnumDeclaration): VisitResult { + if (isFirstDeclarationOfKind(original, SyntaxKind.EnumDeclaration)) { + const statements: Statement[] = [node]; + addVarForExportedEnumOrModule(statements, original); + return statements; + } + + return node; + } + + function isModuleMergedWithES6Class(node: ModuleDeclaration) { + return languageVersion === ScriptTarget.ES6 + && isMergedWithClass(node); + } + + function visitExpressionStatementForModuleDeclaration(node: ExpressionStatement, original: ModuleDeclaration): VisitResult { + if (isFirstDeclarationOfKind(original, SyntaxKind.ModuleDeclaration)) { + const statements: Statement[] = [node]; + if (isModuleMergedWithES6Class(original)) { + addAssignmentForExportedModule(statements, original); + } + else { + addVarForExportedEnumOrModule(statements, original); + } + + return statements; + } + + return node; + } + + /** + * Adds a trailing VariableStatement for an enum or module declaration. + */ + function addVarForExportedEnumOrModule(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { + statements.push( + createVariableStatement( + /*modifiers*/ undefined, + [createVariableDeclaration( + getDeclarationName(node), + createPropertyAccess(createIdentifier("exports"), getDeclarationName(node)) + )], + /*location*/ node + ) + ); + } + + /** + * Adds a trailing assignment for a module declaration. + */ + function addAssignmentForExportedModule(statements: Statement[], node: ModuleDeclaration) { + statements.push( + createStatement( + createAssignment( + getDeclarationName(node), + createPropertyAccess(createIdentifier("exports"), getDeclarationName(node)) + ), + /*location*/ node + ) + ); + } + + function getDeclarationName(node: DeclarationStatement) { + return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node); + } + function substituteExpression(node: Expression) { node = previousExpressionSubstitution(node); if (isIdentifier(node)) { @@ -663,7 +756,7 @@ namespace ts { function substituteExpressionIdentifier(node: Identifier): Expression { const original = getOriginalNode(node); if (isIdentifier(original)) { - const container = resolver.getReferencedExportContainer(original); + const container = resolver.getReferencedExportContainer(original, (getNodeEmitFlags(node) & NodeEmitFlags.PrefixExportedLocal) !== 0); if (container) { if (container.kind === SyntaxKind.SourceFile) { return createPropertyAccess( @@ -760,6 +853,10 @@ namespace ts { return createCall(createIdentifier("require"), args); } + function createExportStatement(name: Identifier, value: Expression, location?: TextRange) { + return startOnNewLine(createStatement(createExportAssignment(name, value), location)); + } + function createExportAssignment(name: Identifier, value: Expression) { return createAssignment( name.originalKeywordKind && languageVersion === ScriptTarget.ES3 diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 8e08ec05796..a74ed97c3da 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -94,7 +94,7 @@ namespace ts { * * @param node The node to visit. */ - function visitWithStack(node: Node, visitor: (node: Node) => VisitResult): VisitResult { + function saveStateAndInvoke(node: Node, f: (node: Node) => T): T { // Save state const savedCurrentScope = currentScope; const savedCurrentParent = currentParent; @@ -103,7 +103,7 @@ namespace ts { // Handle state changes before visiting a node. onBeforeVisitNode(node); - const visited = visitor(node); + const visited = f(node); // Restore state currentScope = savedCurrentScope; @@ -119,7 +119,7 @@ namespace ts { * @param node The node to visit. */ function visitor(node: Node): VisitResult { - return visitWithStack(node, visitorWorker); + return saveStateAndInvoke(node, visitorWorker); } /** @@ -146,7 +146,7 @@ namespace ts { * @param node The node to visit. */ function namespaceElementVisitor(node: Node): VisitResult { - return visitWithStack(node, namespaceElementVisitorWorker); + return saveStateAndInvoke(node, namespaceElementVisitorWorker); } /** @@ -155,7 +155,7 @@ namespace ts { * @param node The node to visit. */ function namespaceElementVisitorWorker(node: Node): VisitResult { - if (node.transformFlags & TransformFlags.TypeScript || isExported(node)) { + if (node.transformFlags & TransformFlags.TypeScript || hasModifier(node, ModifierFlags.Export)) { // This node is explicitly marked as TypeScript, or is exported at the namespace // level, so we should transform the node. return visitTypeScript(node); @@ -174,7 +174,7 @@ namespace ts { * @param node The node to visit. */ function classElementVisitor(node: Node): VisitResult { - return visitWithStack(node, classElementVisitorWorker); + return saveStateAndInvoke(node, classElementVisitorWorker); } /** @@ -462,7 +462,7 @@ namespace ts { // Otherwise, if the class was exported at the top level and was decorated, emit an export // declaration or export default for the class. if (isNamespaceExport(node)) { - addNamespaceExport(statements, name, name); + addExportMemberAssignment(statements, node); } else if (node.decorators) { if (isDefaultExternalModuleExport(node)) { @@ -619,16 +619,19 @@ namespace ts { // let ${name} = ${classExpression}; addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - name, - classExpression - ) - ], - /*location*/ undefined, - NodeFlags.Let) + setOriginalNode( + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + name, + classExpression + ) + ], + /*location*/ undefined, + NodeFlags.Let) + ), + /*original*/ node ) ); @@ -1785,6 +1788,17 @@ namespace ts { ); } + /** + * Determines whether to emit a function-like declaration. We should not emit the + * declaration if it is an overload, is abstract, or is an ambient declaration. + * + * @param node The declaration node. + */ + function shouldEmitFunctionLikeDeclaration(node: FunctionLikeDeclaration) { + return !nodeIsMissing(node.body) + && !hasModifier(node, ModifierFlags.Abstract | ModifierFlags.Ambient); + } + /** * Visits a method declaration of a class. * @@ -1797,7 +1811,7 @@ namespace ts { * @param node The method node. */ function visitMethodDeclaration(node: MethodDeclaration) { - if (shouldElideFunctionLikeDeclaration(node)) { + if (!shouldEmitFunctionLikeDeclaration(node)) { return undefined; } @@ -1813,6 +1827,16 @@ namespace ts { ); } + /** + * Determines whether to emit an accessor declaration. We should not emit the + * declaration if it is abstract or is an ambient declaration. + * + * @param node The declaration node. + */ + function shouldEmitAccessorDeclaration(node: AccessorDeclaration) { + return !hasModifier(node, ModifierFlags.Abstract | ModifierFlags.Ambient); + } + /** * Visits a get accessor declaration of a class. * @@ -1823,7 +1847,7 @@ namespace ts { * @param node The get accessor node. */ function visitGetAccessor(node: GetAccessorDeclaration) { - if (shouldElideAccessorDeclaration(node)) { + if (!shouldEmitAccessorDeclaration(node)) { return undefined; } @@ -1835,16 +1859,6 @@ namespace ts { ); } - /** - * Determines whether a function-like declaration should be elided. A declaration should - * be elided if it is an overload, is abstract, or is an ambient declaration. - * - * @param node The declaration node. - */ - function shouldElideAccessorDeclaration(node: AccessorDeclaration) { - return hasModifier(node, ModifierFlags.Abstract | ModifierFlags.Ambient); - } - /** * Visits a set accessor declaration of a class. * @@ -1855,7 +1869,7 @@ namespace ts { * @param node The set accessor node. */ function visitSetAccessor(node: SetAccessorDeclaration) { - if (shouldElideAccessorDeclaration(node)) { + if (!shouldEmitAccessorDeclaration(node)) { return undefined; } @@ -1879,7 +1893,7 @@ namespace ts { * @param node The function node. */ function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { - if (shouldElideFunctionLikeDeclaration(node)) { + if (!shouldEmitFunctionLikeDeclaration(node)) { return undefined; } @@ -1893,10 +1907,9 @@ namespace ts { ); if (isNamespaceExport(node)) { - return [ - func, - createNamespaceExport(getSynthesizedClone(node.name), getSynthesizedClone(node.name)) - ]; + const statements: Statement[] = [func]; + addExportMemberAssignment(statements, node); + return statements; } return func; @@ -1924,17 +1937,6 @@ namespace ts { ); } - /** - * Determines whether a function-like declaration should be elided. A declaration should - * be elided if it is an overload, is abstract, or is an ambient declaration. - * - * @param node The declaration node. - */ - function shouldElideFunctionLikeDeclaration(node: FunctionLikeDeclaration) { - return nodeIsMissing(node.body) - || hasModifier(node, ModifierFlags.Abstract | ModifierFlags.Ambient); - } - /** * @remarks * This function will be called when one of the following conditions are met: @@ -2109,155 +2111,6 @@ namespace ts { } } - /** - * Visits an enum declaration. - * - * This function will be called any time a TypeScript enum is encountered. - * - * @param node The enum declaration node. - */ - function visitEnumDeclaration(node: EnumDeclaration): VisitResult { - if (shouldElideEnumDeclaration(node)) { - return undefined; - } - - const savedCurrentNamespaceLocalName = currentNamespaceLocalName; - const statements: Statement[] = []; - - let location: TextRange = node; - if (!isExported(node) || (isExternalModuleExport(node) && isFirstDeclarationOfKind(node, SyntaxKind.EnumDeclaration))) { - // Emit a VariableStatement if the enum is not exported, or is the first enum - // with the same name exported from an external module. - statements.push( - createVariableStatement( - visitNodes(node.modifiers, visitor, isModifier), - createVariableDeclarationList([ - createVariableDeclaration(node.name) - ]), - location - ) - ); - location = undefined; - } - - const name = isNamespaceExport(node) - ? getNamespaceMemberName(node.name) - : getSynthesizedClone(node.name); - - currentNamespaceLocalName = getGeneratedNameForNode(node); - - // (function (x) { - // x[x["y"] = 0] = "y"; - // ... - // })(x || (x = {})); - statements.push( - setOriginalNode( - createStatement( - createCall( - createParen( - createFunctionExpression( - /*asteriskToken*/ undefined, - /*name*/ undefined, - [createParameter(currentNamespaceLocalName)], - transformEnumBody(node) - ) - ), - [createLogicalOr( - name, - createAssignment( - name, - createObjectLiteral() - ) - )] - ), - location - ), node - ) - ); - - if (isNamespaceExport(node)) { - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration(node.name, name) - ]), - location - ) - ); - } - - currentNamespaceLocalName = savedCurrentNamespaceLocalName; - return statements; - } - - /** - * Transforms the body of an enum declaration. - * - * @param node The enum declaration node. - */ - function transformEnumBody(node: EnumDeclaration): Block { - const statements: Statement[] = []; - startLexicalEnvironment(); - addNodes(statements, map(node.members, transformEnumMember)); - addNodes(statements, endLexicalEnvironment()); - return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); - } - - /** - * Transforms an enum member into a statement. - * - * @param member The enum member node. - */ - function transformEnumMember(member: EnumMember): Statement { - const name = getExpressionForPropertyName(member); - return createStatement( - createAssignment( - createElementAccess( - currentNamespaceLocalName, - createAssignment( - createElementAccess( - currentNamespaceLocalName, - name - ), - transformEnumMemberDeclarationValue(member) - ) - ), - name - ), - member - ); - } - - /** - * Transforms the value of an enum member. - * - * @param member The enum member node. - */ - function transformEnumMemberDeclarationValue(member: EnumMember): Expression { - const value = resolver.getConstantValue(member); - if (value !== undefined) { - return createLiteral(value); - } - else if (member.initializer) { - return visitNode(member.initializer, visitor, isExpression); - } - else { - return createVoidZero(); - } - } - - /** - * Determines whether to elide an enum declaration. - * - * @param node The enum declaration node. - */ - function shouldElideEnumDeclaration(node: EnumDeclaration) { - return isConst(node) - && !compilerOptions.preserveConstEnums - && !compilerOptions.isolatedModules; - } - /** * Visits an await expression. * @@ -2328,6 +2181,182 @@ namespace ts { return child; } + /** + * Adds a leading VariableStatement for an enum or module declaration. + */ + function addVarForEnumOrModuleDeclaration(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { + // Emit a variable statement for the enum. + statements.push( + createVariableStatement( + visitNodes(node.modifiers, visitor, isModifier), + [createVariableDeclaration( + getDeclarationName(node) + )], + /*location*/ node + ) + ); + } + + /** + * Adds a trailing VariableStatement for an enum or module declaration. + */ + function addVarForEnumOrModuleExportedFromNamespace(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { + statements.push( + createVariableStatement( + /*modifiers*/ undefined, + [createVariableDeclaration( + getDeclarationName(node), + getDeclarationNameExpression(node) + )], + /*location*/ node + ) + ) + } + + function addAssignmentForModuleExportedFromNamespace(statements: Statement[], node: ModuleDeclaration) { + statements.push( + createStatement( + createAssignment( + getDeclarationName(node), + setNodeEmitFlags(getDeclarationName(node), NodeEmitFlags.PrefixExportedLocal) + ), + /*location*/ node + ) + ); + } + + /** + * Determines whether to emit an enum declaration. + * + * @param node The enum declaration node. + */ + function shouldEmitEnumDeclaration(node: EnumDeclaration) { + return !isConst(node) + || compilerOptions.preserveConstEnums + || compilerOptions.isolatedModules; + } + + function shouldEmitVarForEnumOrModuleDeclaration(node: EnumDeclaration | ModuleDeclaration) { + return !hasModifier(node, ModifierFlags.Export) + || (isExternalModuleExport(node) && isFirstDeclarationOfKind(node, node.kind)); + } + + /** + * Visits an enum declaration. + * + * This function will be called any time a TypeScript enum is encountered. + * + * @param node The enum declaration node. + */ + function visitEnumDeclaration(node: EnumDeclaration): VisitResult { + if (!shouldEmitEnumDeclaration(node)) { + return undefined; + } + + const statements: Statement[] = []; + if (shouldEmitVarForEnumOrModuleDeclaration(node)) { + addVarForEnumOrModuleDeclaration(statements, node); + } + + const localName = getGeneratedNameForNode(node); + const name = getDeclarationNameExpression(node); + + // (function (x) { + // x[x["y"] = 0] = "y"; + // ... + // })(x || (x = {})); + statements.push( + setOriginalNode( + createStatement( + createCall( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [createParameter(localName)], + transformEnumBody(node, localName) + ), + [createLogicalOr( + name, + createAssignment( + name, + createObjectLiteral() + ) + )] + ), + /*location*/ node + ), + /*original*/ node + ) + ); + + if (isNamespaceExport(node)) { + addVarForEnumOrModuleExportedFromNamespace(statements, node); + } + + return statements; + } + + /** + * Transforms the body of an enum declaration. + * + * @param node The enum declaration node. + */ + function transformEnumBody(node: EnumDeclaration, localName: Identifier): Block { + const savedCurrentNamespaceLocalName = currentNamespaceLocalName; + currentNamespaceLocalName = localName; + + const statements: Statement[] = []; + startLexicalEnvironment(); + addNodes(statements, map(node.members, transformEnumMember)); + addNodes(statements, endLexicalEnvironment()); + + currentNamespaceLocalName = savedCurrentNamespaceLocalName; + return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); + } + + /** + * Transforms an enum member into a statement. + * + * @param member The enum member node. + */ + function transformEnumMember(member: EnumMember): Statement { + const name = getExpressionForPropertyName(member); + return createStatement( + createAssignment( + createElementAccess( + currentNamespaceLocalName, + createAssignment( + createElementAccess( + currentNamespaceLocalName, + name + ), + transformEnumMemberDeclarationValue(member) + ) + ), + name + ), + member + ); + } + + /** + * Transforms the value of an enum member. + * + * @param member The enum member node. + */ + function transformEnumMemberDeclarationValue(member: EnumMember): Expression { + const value = resolver.getConstantValue(member); + if (value !== undefined) { + return createLiteral(value); + } + else if (member.initializer) { + return visitNode(member.initializer, visitor, isExpression); + } + else { + return createVoidZero(); + } + } + /** * Determines whether to elide a module declaration. * @@ -2337,20 +2366,9 @@ namespace ts { return isInstantiatedModule(node, compilerOptions.preserveConstEnums || compilerOptions.isolatedModules); } - /** - * Determines whether a module declaration has a name that merges with a class declaration. - * - * @param node The module declaration node. - */ - function isModuleMergedWithClass(node: ModuleDeclaration) { + function isModuleMergedWithES6Class(node: ModuleDeclaration) { return languageVersion === ScriptTarget.ES6 - && !!(resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.LexicalModuleMergesWithClass); - } - - function shouldEmitVarForModuleDeclaration(node: ModuleDeclaration) { - return !isModuleMergedWithClass(node) - && (!isExternalModuleExport(node) - || isFirstDeclarationOfKind(node, SyntaxKind.ModuleDeclaration)); + && isMergedWithClass(node); } /** @@ -2365,41 +2383,29 @@ namespace ts { return undefined; } - Debug.assert(isIdentifier(node.name)); - + Debug.assert(isIdentifier(node.name), "TypeScript module should have an Identifier name."); enableExpressionSubstitutionForNamespaceExports(); - const savedCurrentNamespaceLocalName = currentNamespaceLocalName; - const savedCurrentNamespace = currentNamespace; const statements: Statement[] = []; - - if (shouldEmitVarForModuleDeclaration(node)) { - // var x; - statements.push( - createVariableStatement( - visitNodes(node.modifiers, visitor, isModifier), - createVariableDeclarationList([ - createVariableDeclaration(node.name) - ]), - /*location*/ node - ) - ); + if (!isModuleMergedWithES6Class(node) && (isNamespaceExport(node) || shouldEmitVarForEnumOrModuleDeclaration(node))) { + addVarForEnumOrModuleDeclaration(statements, node); } - const name = isNamespaceExport(node) - ? getNamespaceMemberName(node.name) - : getSynthesizedClone(node.name); + const localName = getGeneratedNameForNode(node); + const name = getDeclarationNameExpression(node); - currentNamespaceLocalName = getGeneratedNameForNode(node); - currentNamespace = node; - - const moduleParam = createLogicalOr( - name, - createAssignment( + let moduleArg = + createLogicalOr( name, - createObjectLiteral() - ) - ); + createAssignment( + name, + createObjectLiteral() + ) + ); + + if (isNamespaceExport(node)) { + moduleArg = createAssignment(getDeclarationName(node), moduleArg); + } // (function (x_1) { // x_1.y = ...; @@ -2409,30 +2415,22 @@ namespace ts { setOriginalNode( createStatement( createCall( - createParen( - createFunctionExpression( - /*asteriskToken*/ undefined, - /*name*/ undefined, - [createParameter(currentNamespaceLocalName)], - transformModuleBody(node) - ) + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [createParameter(localName)], + transformModuleBody(node, localName) ), - [ - isNamespaceExport(node) - ? createAssignment(getSynthesizedClone(node.name), moduleParam) - : moduleParam - ] + [moduleArg] ), /*location*/ node ), - node + /*original*/ node ), NodeEmitFlags.AdviseOnEmitNode ) ); - currentNamespaceLocalName = savedCurrentNamespaceLocalName; - currentNamespace = savedCurrentNamespace; return statements; } @@ -2441,7 +2439,12 @@ namespace ts { * * @param node The module declaration node. */ - function transformModuleBody(node: ModuleDeclaration): Block { + function transformModuleBody(node: ModuleDeclaration, namespaceLocalName: Identifier): Block { + const savedCurrentNamespaceLocalName = currentNamespaceLocalName; + const savedCurrentNamespace = currentNamespace; + currentNamespaceLocalName = namespaceLocalName; + currentNamespace = node; + const statements: Statement[] = []; startLexicalEnvironment(); const body = node.body; @@ -2453,9 +2456,26 @@ namespace ts { } addNodes(statements, endLexicalEnvironment()); + + currentNamespaceLocalName = savedCurrentNamespaceLocalName; + currentNamespace = savedCurrentNamespace; return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); } + /** + * Determines whether to emit an import equals declaration. + * + * @param node The import equals declaration node. + */ + function shouldEmitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + // preserve old compiler's behavior: emit 'var' for import declaration (even if we do not consider them referenced) when + // - current file is not external module + // - import declaration is top level and target is value imported by entity name + return resolver.isReferencedAliasDeclaration(node) + || (!isExternalModule(currentSourceFile) + && resolver.isTopLevelValueImportEqualsWithEntityName(node)); + } + /** * Visits an import equals declaration. * @@ -2466,7 +2486,7 @@ namespace ts { return visitEachChild(node, visitor, context); } - if (shouldElideImportEqualsDeclaration(node)) { + if (!shouldEmitImportEqualsDeclaration(node)) { return undefined; } @@ -2498,35 +2518,13 @@ namespace ts { } } - /** - * Determines whether to elide an import equals declaration. - * - * @param node The import equals declaration node. - */ - function shouldElideImportEqualsDeclaration(node: ImportEqualsDeclaration) { - // preserve old compiler's behavior: emit 'var' for import declaration (even if we do not consider them referenced) when - // - current file is not external module - // - import declaration is top level and target is value imported by entity name - return !resolver.isReferencedAliasDeclaration(node) - && (isExternalModule(currentSourceFile) || !resolver.isTopLevelValueImportEqualsWithEntityName(node)); - } - - /** - * Gets a value indicating whether the node is exported. - * - * @param node The node to test. - */ - function isExported(node: Node) { - return hasModifier(node, ModifierFlags.Export); - } - /** * Gets a value indicating whether the node is exported from a namespace. * * @param node The node to test. */ function isNamespaceExport(node: Node) { - return currentNamespace !== undefined && isExported(node); + return currentNamespace !== undefined && hasModifier(node, ModifierFlags.Export); } /** @@ -2535,7 +2533,7 @@ namespace ts { * @param node The node to test. */ function isExternalModuleExport(node: Node) { - return currentNamespace === undefined && isExported(node); + return currentNamespace === undefined && hasModifier(node, ModifierFlags.Export); } /** @@ -2557,16 +2555,6 @@ namespace ts { return isExternalModuleExport(node) && hasModifier(node, ModifierFlags.Default); } - /** - * Gets a value indicating whether a node is the first declaration of its kind. - * - * @param node A Declaration node. - * @param kind The SyntaxKind to find among related declarations. - */ - function isFirstDeclarationOfKind(node: Declaration, kind: SyntaxKind) { - const original = getOriginalNode(node); - return original.symbol && getDeclarationOfKind(original.symbol, kind) === original; - } /** * Creates a statement for the provided expression. This is used in calls to `map`. @@ -2575,8 +2563,8 @@ namespace ts { return createStatement(expression, /*location*/ undefined); } - function addNamespaceExport(statements: Statement[], exportName: Identifier, exportValue: Expression, location?: TextRange) { - statements.push(createNamespaceExport(exportName, exportValue, location)); + function addExportMemberAssignment(statements: Statement[], node: DeclarationStatement) { + statements.push(createNamespaceExport(getDeclarationName(node), getDeclarationName(node))); } function createNamespaceExport(exportName: Identifier, exportValue: Expression, location?: TextRange) { @@ -2601,10 +2589,23 @@ namespace ts { return createPropertyAccess(currentNamespaceLocalName, getSynthesizedClone(name)); } - function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration | EnumDeclaration) { + function getDeclarationName(node: DeclarationStatement | ClassExpression) { return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node); } + function getDeclarationNameExpression(node: DeclarationStatement) { + const name = getDeclarationName(node); + if (isNamespaceExport(node)) { + return getNamespaceMemberName(name); + } + else { + // We set the "PrefixExportedLocal" flag to indicate to any module transformer + // downstream that any `exports.` prefix should be added. + setNodeEmitFlags(name, getNodeEmitFlags(name) | NodeEmitFlags.PrefixExportedLocal); + return name; + } + } + function getClassPrototype(node: ClassExpression | ClassDeclaration) { return createPropertyAccess(getDeclarationName(node), "prototype"); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e54eb596ff0..23fb90c4437 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1924,7 +1924,7 @@ namespace ts { /* @internal */ export interface EmitResolver { hasGlobalName(name: string): boolean; - getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration; + getReferencedExportContainer(node: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration; getReferencedImportDeclaration(node: Identifier): Declaration; getReferencedDeclarationWithCollidingName(node: Identifier): Declaration; isDeclarationWithCollidingName(node: Declaration): boolean; @@ -2856,6 +2856,7 @@ namespace ts { CapturesThis = 1 << 11, // The function captures a lexical `this` NoSourceMap = 1 << 12, // Do not emit a source map location for this node. NoNestedSourceMaps = 1 << 13, // Do not emit source map locations for children of this node. + PrefixExportedLocal = 1 << 14, } /** Additional context provided to `visitEachChild` */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ace7b6158f1..21efafcc3a8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3034,6 +3034,31 @@ namespace ts { return node.initializer !== undefined; } + /** + * Gets a value indicating whether a node is merged with a class declaration in the same scope. + */ + export function isMergedWithClass(node: Node) { + if (node.symbol) { + for (const declaration of node.symbol.declarations) { + if (declaration.kind === SyntaxKind.ClassDeclaration && declaration !== node) { + return true; + } + } + } + + return false; + } + + /** + * Gets a value indicating whether a node is the first declaration of its kind. + * + * @param node A Declaration node. + * @param kind The SyntaxKind to find among related declarations. + */ + export function isFirstDeclarationOfKind(node: Node, kind: SyntaxKind) { + return node.symbol && getDeclarationOfKind(node.symbol, kind) === node; + } + // Node tests // // All node tests in the following list should *not* reference parent pointers so that diff --git a/tests/baselines/reference/systemModule7.js b/tests/baselines/reference/systemModule7.js index bf34e3f786c..d25ca627e44 100644 --- a/tests/baselines/reference/systemModule7.js +++ b/tests/baselines/reference/systemModule7.js @@ -21,7 +21,7 @@ System.register([], function (exports_1, context_1) { // filename: instantiatedModule.ts (function (M) { var x = 1; - })(M = M || (M = {})); + })(M || (M = {})); exports_1("M", M); } }; diff --git a/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js b/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js index b0498b23f79..fef8524146c 100644 --- a/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js +++ b/tests/baselines/reference/systemModuleNonTopLevelModuleMembers.js @@ -30,7 +30,7 @@ System.register([], function (exports_1, context_1) { exports_1("TopLevelClass", TopLevelClass); (function (TopLevelModule) { var v; - })(TopLevelModule = TopLevelModule || (TopLevelModule = {})); + })(TopLevelModule || (TopLevelModule = {})); exports_1("TopLevelModule", TopLevelModule); (function (TopLevelEnum) { TopLevelEnum[TopLevelEnum["E"] = 0] = "E"; @@ -53,7 +53,7 @@ System.register([], function (exports_1, context_1) { NonTopLevelEnum[NonTopLevelEnum["E"] = 0] = "E"; })(TopLevelModule2.NonTopLevelEnum || (TopLevelModule2.NonTopLevelEnum = {})); var NonTopLevelEnum = TopLevelModule2.NonTopLevelEnum; - })(TopLevelModule2 = TopLevelModule2 || (TopLevelModule2 = {})); + })(TopLevelModule2 || (TopLevelModule2 = {})); exports_1("TopLevelModule2", TopLevelModule2); } };