diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f54a3e0666f00..4ebea0c522de0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -87,6 +87,7 @@ import { getElementOrPropertyAccessName, getEmitScriptTarget, getEnclosingBlockScopeContainer, + getEnclosingContainer, getErrorSpanForNode, getEscapedTextOfIdentifierOrLiteral, getEscapedTextOfJsxNamespacedName, @@ -455,7 +456,8 @@ function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visite return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value } -const enum ContainerFlags { +/** @internal */ +export const enum ContainerFlags { // The current node is not a container, and no container manipulation should happen before // recursing into it. None = 0, @@ -2356,7 +2358,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const saveCurrentFlow = currentFlow; for (const typeAlias of delayedTypeAliases) { const host = typeAlias.parent.parent; - container = (findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) as IsContainer | undefined) || file; + container = (getEnclosingContainer(host) as IsContainer | undefined) || file; blockScopeContainer = (getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined) || file; currentFlow = initFlowNode({ flags: FlowFlags.Start }); parent = typeAlias; @@ -3768,7 +3770,8 @@ export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Ex return false; } -function getContainerFlags(node: Node): ContainerFlags { +/** @internal */ +export function getContainerFlags(node: Node): ContainerFlags { switch (node.kind) { case SyntaxKind.ClassExpression: case SyntaxKind.ClassDeclaration: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bdf2dff585a47..47a06c273be19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -274,6 +274,7 @@ import { getEmitModuleResolutionKind, getEmitScriptTarget, getEnclosingBlockScopeContainer, + getEnclosingContainer, getEntityNameFromTypeNode, getErrorSpanForNode, getEscapedTextOfIdentifierOrLiteral, @@ -39024,8 +39025,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { n.parent.kind !== SyntaxKind.ClassDeclaration && n.parent.kind !== SyntaxKind.ClassExpression && n.flags & NodeFlags.Ambient) { - if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { - // It is nested in an ambient context, which means it is automatically exported + const container = getEnclosingContainer(n); + if ((container && container.flags & NodeFlags.ExportContext) && !(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient export context, which means it is automatically exported flags |= ModifierFlags.Export; } flags |= ModifierFlags.Ambient; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 54b8f92786698..c681495eda0cb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -73,6 +73,7 @@ import { ConditionalExpression, ConstructorDeclaration, ConstructSignatureDeclaration, + ContainerFlags, contains, containsPath, createGetCanonicalFileName, @@ -162,6 +163,7 @@ import { GetCanonicalFileName, getCombinedModifierFlags, getCombinedNodeFlags, + getContainerFlags, getDirectoryPath, getJSDocAugmentsTag, getJSDocDeprecatedTagNoCache, @@ -2022,6 +2024,11 @@ export function isAnyImportOrReExport(node: Node): node is AnyImportOrReExport { return isAnyImportSyntax(node) || isExportDeclaration(node); } +/** @internal */ +export function getEnclosingContainer(node: Node): Node | undefined { + return findAncestor(node.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)); +} + // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. /** @internal */ diff --git a/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.errors.txt b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.errors.txt new file mode 100644 index 0000000000000..9874a41d1a3ce --- /dev/null +++ b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.errors.txt @@ -0,0 +1,19 @@ +replace-in-file/types/index.d.ts(4,19): error TS2395: Individual declarations in merged declaration 'replaceInFile' must be all exported or all local. +replace-in-file/types/index.d.ts(7,13): error TS2395: Individual declarations in merged declaration 'replaceInFile' must be all exported or all local. + + +==== replace-in-file/types/index.d.ts (2 errors) ==== + // repro from https://github.com/microsoft/TypeScript/issues/54342 + + declare module 'replace-in-file' { + export function replaceInFile(config: unknown): Promise; + ~~~~~~~~~~~~~ +!!! error TS2395: Individual declarations in merged declaration 'replaceInFile' must be all exported or all local. + export default replaceInFile; + + namespace replaceInFile { + ~~~~~~~~~~~~~ +!!! error TS2395: Individual declarations in merged declaration 'replaceInFile' must be all exported or all local. + export function sync(config: unknown): unknown[]; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.symbols b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.symbols new file mode 100644 index 0000000000000..69923fe4dcd63 --- /dev/null +++ b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.symbols @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/namespaceNotMergedWithFunctionDefaultExport.ts] //// + +=== replace-in-file/types/index.d.ts === +// repro from https://github.com/microsoft/TypeScript/issues/54342 + +declare module 'replace-in-file' { +>'replace-in-file' : Symbol("replace-in-file", Decl(index.d.ts, 0, 0)) + + export function replaceInFile(config: unknown): Promise; +>replaceInFile : Symbol(replaceInFile, Decl(index.d.ts, 2, 34)) +>config : Symbol(config, Decl(index.d.ts, 3, 32)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) + + export default replaceInFile; +>replaceInFile : Symbol(replaceInFile, Decl(index.d.ts, 2, 34), Decl(index.d.ts, 4, 31)) + + namespace replaceInFile { +>replaceInFile : Symbol(replaceInFile, Decl(index.d.ts, 2, 34), Decl(index.d.ts, 4, 31)) + + export function sync(config: unknown): unknown[]; +>sync : Symbol(sync, Decl(index.d.ts, 6, 27)) +>config : Symbol(config, Decl(index.d.ts, 7, 25)) + } +} diff --git a/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.types b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.types new file mode 100644 index 0000000000000..d7101b25dde5d --- /dev/null +++ b/tests/baselines/reference/namespaceNotMergedWithFunctionDefaultExport.types @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/namespaceNotMergedWithFunctionDefaultExport.ts] //// + +=== replace-in-file/types/index.d.ts === +// repro from https://github.com/microsoft/TypeScript/issues/54342 + +declare module 'replace-in-file' { +>'replace-in-file' : typeof import("replace-in-file") + + export function replaceInFile(config: unknown): Promise; +>replaceInFile : (config: unknown) => Promise +>config : unknown + + export default replaceInFile; +>replaceInFile : typeof replaceInFile + + namespace replaceInFile { +>replaceInFile : typeof replaceInFile + + export function sync(config: unknown): unknown[]; +>sync : (config: unknown) => unknown[] +>config : unknown + } +} diff --git a/tests/cases/compiler/namespaceNotMergedWithFunctionDefaultExport.ts b/tests/cases/compiler/namespaceNotMergedWithFunctionDefaultExport.ts new file mode 100644 index 0000000000000..ea86d2ca89ccd --- /dev/null +++ b/tests/cases/compiler/namespaceNotMergedWithFunctionDefaultExport.ts @@ -0,0 +1,14 @@ +// @moduleResolution: node10 +// @module: commonjs + +// repro from https://github.com/microsoft/TypeScript/issues/54342 + +// @Filename: replace-in-file/types/index.d.ts +declare module 'replace-in-file' { + export function replaceInFile(config: unknown): Promise; + export default replaceInFile; + + namespace replaceInFile { + export function sync(config: unknown): unknown[]; + } +} \ No newline at end of file