diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d27f5533d6..cbcc89a9d40 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5494,6 +5494,7 @@ namespace ts { const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; specifier = first(moduleSpecifiers.getModuleSpecifiers( symbol, + checker, specifierCompilerOptions, contextFile, moduleResolverHost, diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 259bbafb37f..6329387b281 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1422,8 +1422,7 @@ namespace ts { export function getPackageNameFromTypesPackageName(mangledName: string): string { const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); if (withoutAtTypePrefix !== mangledName) { - const withoutAtTypeNodePrefix = removePrefix(withoutAtTypePrefix, "node/"); - return unmangleScopedPackageName(withoutAtTypeNodePrefix); + return unmangleScopedPackageName(withoutAtTypePrefix); } return mangledName; } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 65b047162c7..d2fc45ee991 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -92,12 +92,13 @@ namespace ts.moduleSpecifiers { /** Returns an import for each symlink and for the realpath. */ export function getModuleSpecifiers( moduleSymbol: Symbol, + checker: TypeChecker, compilerOptions: CompilerOptions, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, userPreferences: UserPreferences, ): readonly string[] { - const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol); + const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); if (ambient) return [ambient]; const info = getInfo(importingSourceFile.path, host); @@ -369,13 +370,49 @@ namespace ts.moduleSpecifiers { return sortedPaths; } - function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined { + function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { const decl = find(moduleSymbol.declarations, d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; if (decl) { return decl.name.text; } + + // the module could be a namespace, which is export through "export=" from an ambient module. + /** + * declare module "m" { + * namespace ns { + * class c {} + * } + * export = ns; + * } + */ + // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" + const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, + d => { + if (!isModuleDeclaration(d)) return; + const topNamespace = getTopNamespace(d); + if (!(topNamespace?.parent?.parent + && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; + const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); + if (!exportAssignment) return; + const exportSymbol = checker.getSymbolAtLocation(exportAssignment); + if (!exportSymbol) return; + const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; + if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; + + function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { + while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { + namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; + } + return namespaceDeclaration; + } + } + ); + const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; + if (ambientModuleDeclare) { + return ambientModuleDeclare.name.text; + } } function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike): string | undefined { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 4ba390cca97..7497b8222a4 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -404,7 +404,7 @@ namespace ts.codefix { const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host); const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => - moduleSpecifiers.getModuleSpecifiers(moduleSymbol, compilerOptions, sourceFile, createModuleSpecifierResolutionHost(program, host) , preferences) + moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getTypeChecker(), compilerOptions, sourceFile, createModuleSpecifierResolutionHost(program, host), preferences) .map((moduleSpecifier): FixAddNewImport | FixUseImportType => // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. exportedSymbolIsTypeOnly && isJs diff --git a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.js b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.js index 7abc439c941..5140222d00e 100644 --- a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.js +++ b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.js @@ -11,30 +11,73 @@ declare module "events" { } export = EventEmitter; } +declare module "nestNamespaceModule" { + namespace a1.a2 { + class d { } + } + + namespace a1.a2.n3 { + class c { } + } + export = a1.a2; +} +declare module "renameModule" { + namespace a.b { + class c { } + } + import d = a.b; + export = d; +} //// [b.js] import { EventEmitter } from 'events'; -class Foo extends EventEmitter { - constructor() { - super(); - } +import { n3, d } from 'nestNamespaceModule'; +import { c } from 'renameModule'; + +export class Foo extends EventEmitter { } -export default Foo; + +export class Foo2 extends n3.c { +} + +export class Foo3 extends d { +} + +export class Foo4 extends c { + +} //// [b.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.Foo4 = exports.Foo3 = exports.Foo2 = exports.Foo = void 0; const events_1 = require("events"); +const nestNamespaceModule_1 = require("nestNamespaceModule"); +const renameModule_1 = require("renameModule"); class Foo extends events_1.EventEmitter { - constructor() { - super(); - } } -exports.default = Foo; +exports.Foo = Foo; +class Foo2 extends nestNamespaceModule_1.n3.c { +} +exports.Foo2 = Foo2; +class Foo3 extends nestNamespaceModule_1.d { +} +exports.Foo3 = Foo3; +class Foo4 extends renameModule_1.c { +} +exports.Foo4 = Foo4; //// [b.d.ts] -export default Foo; -declare class Foo extends EventEmitter { +export class Foo extends EventEmitter { +} +export class Foo2 extends n3.c { +} +export class Foo3 extends d { +} +export class Foo4 extends c { } import { EventEmitter } from "events"; +import { n3 } from "nestNamespaceModule"; +import { d } from "nestNamespaceModule"; +import { c } from "renameModule"; diff --git a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.symbols b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.symbols index 107341d01ec..954e38a8581 100644 --- a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.symbols +++ b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.symbols @@ -16,20 +16,79 @@ declare module "events" { export = EventEmitter; >EventEmitter : Symbol(EventEmitter, Decl(events.d.ts, 0, 25)) } +declare module "nestNamespaceModule" { +>"nestNamespaceModule" : Symbol("nestNamespaceModule", Decl(events.d.ts, 7, 1)) + + namespace a1.a2 { +>a1 : Symbol(a1, Decl(events.d.ts, 8, 38), Decl(events.d.ts, 11, 5)) +>a2 : Symbol(a2, Decl(events.d.ts, 9, 17), Decl(events.d.ts, 13, 17)) + + class d { } +>d : Symbol(d, Decl(events.d.ts, 9, 21)) + } + + namespace a1.a2.n3 { +>a1 : Symbol(a1, Decl(events.d.ts, 8, 38), Decl(events.d.ts, 11, 5)) +>a2 : Symbol(a2, Decl(events.d.ts, 9, 17), Decl(events.d.ts, 13, 17)) +>n3 : Symbol(n3, Decl(events.d.ts, 13, 20)) + + class c { } +>c : Symbol(c, Decl(events.d.ts, 13, 24)) + } + export = a1.a2; +>a1.a2 : Symbol(a1.a2, Decl(events.d.ts, 9, 17), Decl(events.d.ts, 13, 17)) +>a1 : Symbol(a1, Decl(events.d.ts, 8, 38), Decl(events.d.ts, 11, 5)) +>a2 : Symbol(a1.a2, Decl(events.d.ts, 9, 17), Decl(events.d.ts, 13, 17)) +} +declare module "renameModule" { +>"renameModule" : Symbol("renameModule", Decl(events.d.ts, 17, 1)) + + namespace a.b { +>a : Symbol(a, Decl(events.d.ts, 18, 31)) +>b : Symbol(b, Decl(events.d.ts, 19, 16)) + + class c { } +>c : Symbol(c, Decl(events.d.ts, 19, 19)) + } + import d = a.b; +>d : Symbol(d, Decl(events.d.ts, 21, 5)) +>a : Symbol(a, Decl(events.d.ts, 18, 31)) +>b : Symbol(d, Decl(events.d.ts, 19, 16)) + + export = d; +>d : Symbol(d, Decl(events.d.ts, 21, 5)) +} === /src/b.js === import { EventEmitter } from 'events'; >EventEmitter : Symbol(EventEmitter, Decl(b.js, 0, 8)) -class Foo extends EventEmitter { ->Foo : Symbol(Foo, Decl(b.js, 0, 38)) +import { n3, d } from 'nestNamespaceModule'; +>n3 : Symbol(n3, Decl(b.js, 1, 8)) +>d : Symbol(d, Decl(b.js, 1, 12)) + +import { c } from 'renameModule'; +>c : Symbol(c, Decl(b.js, 2, 8)) + +export class Foo extends EventEmitter { +>Foo : Symbol(Foo, Decl(b.js, 2, 33)) >EventEmitter : Symbol(EventEmitter, Decl(b.js, 0, 8)) - - constructor() { - super(); ->super : Symbol(EventEmitter, Decl(events.d.ts, 1, 28)) - } } -export default Foo; ->Foo : Symbol(Foo, Decl(b.js, 0, 38)) +export class Foo2 extends n3.c { +>Foo2 : Symbol(Foo2, Decl(b.js, 5, 1)) +>n3.c : Symbol(n3.c, Decl(events.d.ts, 13, 24)) +>n3 : Symbol(n3, Decl(b.js, 1, 8)) +>c : Symbol(n3.c, Decl(events.d.ts, 13, 24)) +} + +export class Foo3 extends d { +>Foo3 : Symbol(Foo3, Decl(b.js, 8, 1)) +>d : Symbol(d, Decl(b.js, 1, 12)) +} + +export class Foo4 extends c { +>Foo4 : Symbol(Foo4, Decl(b.js, 11, 1)) +>c : Symbol(c, Decl(b.js, 2, 8)) + +} diff --git a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.types b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.types index 4cdf90bb4e7..231dbdc6d3a 100644 --- a/tests/baselines/reference/importDeclFromTypeNodeInJsSource.types +++ b/tests/baselines/reference/importDeclFromTypeNodeInJsSource.types @@ -16,21 +16,79 @@ declare module "events" { export = EventEmitter; >EventEmitter : typeof EventEmitter } +declare module "nestNamespaceModule" { +>"nestNamespaceModule" : typeof import("nestNamespaceModule") + + namespace a1.a2 { +>a1 : typeof a1 +>a2 : typeof a2 + + class d { } +>d : d + } + + namespace a1.a2.n3 { +>a1 : typeof a1 +>a2 : typeof a2 +>n3 : typeof n3 + + class c { } +>c : c + } + export = a1.a2; +>a1.a2 : typeof a1.a2 +>a1 : typeof a1 +>a2 : typeof a1.a2 +} +declare module "renameModule" { +>"renameModule" : typeof import("renameModule") + + namespace a.b { +>a : typeof a +>b : typeof b + + class c { } +>c : c + } + import d = a.b; +>d : typeof d +>a : typeof a +>b : typeof d + + export = d; +>d : typeof d +} === /src/b.js === import { EventEmitter } from 'events'; >EventEmitter : typeof EventEmitter -class Foo extends EventEmitter { +import { n3, d } from 'nestNamespaceModule'; +>n3 : typeof n3 +>d : typeof d + +import { c } from 'renameModule'; +>c : typeof c + +export class Foo extends EventEmitter { >Foo : Foo >EventEmitter : EventEmitter - - constructor() { - super(); ->super() : void ->super : typeof EventEmitter - } } -export default Foo; ->Foo : Foo +export class Foo2 extends n3.c { +>Foo2 : Foo2 +>n3.c : n3.c +>n3 : typeof n3 +>c : typeof n3.c +} + +export class Foo3 extends d { +>Foo3 : Foo3 +>d : d +} + +export class Foo4 extends c { +>Foo4 : Foo4 +>c : c + +} diff --git a/tests/cases/compiler/importDeclFromTypeNodeInJsSource.ts b/tests/cases/compiler/importDeclFromTypeNodeInJsSource.ts index 040994a61d7..2843f627e66 100644 --- a/tests/cases/compiler/importDeclFromTypeNodeInJsSource.ts +++ b/tests/cases/compiler/importDeclFromTypeNodeInJsSource.ts @@ -15,12 +15,38 @@ declare module "events" { } export = EventEmitter; } +declare module "nestNamespaceModule" { + namespace a1.a2 { + class d { } + } + + namespace a1.a2.n3 { + class c { } + } + export = a1.a2; +} +declare module "renameModule" { + namespace a.b { + class c { } + } + import d = a.b; + export = d; +} // @filename: /src/b.js import { EventEmitter } from 'events'; -class Foo extends EventEmitter { - constructor() { - super(); - } +import { n3, d } from 'nestNamespaceModule'; +import { c } from 'renameModule'; + +export class Foo extends EventEmitter { } -export default Foo; \ No newline at end of file + +export class Foo2 extends n3.c { +} + +export class Foo3 extends d { +} + +export class Foo4 extends c { + +} \ No newline at end of file