diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 24d4c845aca..e87983470f7 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1819,7 +1819,17 @@ namespace ts { // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. - return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + + // Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions) + // (technically it only emits a deprecation warning in esm packages right now, but that's probably + // enough to mean we don't need to support it) + const features = state.features; + if (jsonContent?.type !== "module") { + state.features &= ~NodeResolutionFeatures.EsmMode; + } + const result = nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + state.features = features; + return result; }; const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; diff --git a/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.js b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.js new file mode 100644 index 00000000000..9eb7e3dd4bf --- /dev/null +++ b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.js @@ -0,0 +1,42 @@ +//// [tests/cases/compiler/nodeNextEsmImportsOfPackagesWithExtensionlessMains.ts] //// + +//// [package.json] +{ + "name": "@types/ip", + "version": "1.1.0", + "main": "", + "types": "index" +} +//// [index.d.ts] +export function address(): string; +//// [package.json] +{ + "name": "nullthrows", + "version": "1.1.1", + "main": "nullthrows.js", + "types": "nullthrows.d.ts" +} +//// [nullthrows.d.ts] +declare function nullthrows(x: any): any; +declare namespace nullthrows { + export {nullthrows as default}; +} +export = nullthrows; +//// [package.json] +{ + "type": "module" +} +//// [index.ts] +import * as ip from 'ip'; +import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself + +export function getAddress(): string { + return nullthrows(ip.address()); +} + +//// [index.js] +import * as ip from 'ip'; +import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself +export function getAddress() { + return nullthrows(ip.address()); +} diff --git a/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.symbols b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.symbols new file mode 100644 index 00000000000..01fae1acf1a --- /dev/null +++ b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.symbols @@ -0,0 +1,35 @@ +=== tests/cases/compiler/index.ts === +import * as ip from 'ip'; +>ip : Symbol(ip, Decl(index.ts, 0, 6)) + +import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself +>nullthrows : Symbol(nullthrows, Decl(index.ts, 1, 6)) + +export function getAddress(): string { +>getAddress : Symbol(getAddress, Decl(index.ts, 1, 36)) + + return nullthrows(ip.address()); +>nullthrows : Symbol(nullthrows, Decl(index.ts, 1, 6)) +>ip.address : Symbol(ip.address, Decl(index.d.ts, 0, 0)) +>ip : Symbol(ip, Decl(index.ts, 0, 6)) +>address : Symbol(ip.address, Decl(index.d.ts, 0, 0)) +} +=== tests/cases/compiler/node_modules/@types/ip/index.d.ts === +export function address(): string; +>address : Symbol(address, Decl(index.d.ts, 0, 0)) + +=== tests/cases/compiler/node_modules/nullthrows/nullthrows.d.ts === +declare function nullthrows(x: any): any; +>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41)) +>x : Symbol(x, Decl(nullthrows.d.ts, 0, 28)) + +declare namespace nullthrows { +>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41)) + + export {nullthrows as default}; +>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41)) +>default : Symbol(default, Decl(nullthrows.d.ts, 2, 12)) +} +export = nullthrows; +>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41)) + diff --git a/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.types b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.types new file mode 100644 index 00000000000..5fbeeb4fa24 --- /dev/null +++ b/tests/baselines/reference/nodeNextEsmImportsOfPackagesWithExtensionlessMains.types @@ -0,0 +1,37 @@ +=== tests/cases/compiler/index.ts === +import * as ip from 'ip'; +>ip : typeof ip + +import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself +>nullthrows : typeof nullthrows + +export function getAddress(): string { +>getAddress : () => string + + return nullthrows(ip.address()); +>nullthrows(ip.address()) : any +>nullthrows : typeof nullthrows +>ip.address() : string +>ip.address : () => string +>ip : typeof ip +>address : () => string +} +=== tests/cases/compiler/node_modules/@types/ip/index.d.ts === +export function address(): string; +>address : () => string + +=== tests/cases/compiler/node_modules/nullthrows/nullthrows.d.ts === +declare function nullthrows(x: any): any; +>nullthrows : typeof nullthrows +>x : any + +declare namespace nullthrows { +>nullthrows : typeof nullthrows + + export {nullthrows as default}; +>nullthrows : typeof nullthrows +>default : typeof nullthrows +} +export = nullthrows; +>nullthrows : typeof nullthrows + diff --git a/tests/cases/compiler/nodeNextEsmImportsOfPackagesWithExtensionlessMains.ts b/tests/cases/compiler/nodeNextEsmImportsOfPackagesWithExtensionlessMains.ts new file mode 100644 index 00000000000..50d00350014 --- /dev/null +++ b/tests/cases/compiler/nodeNextEsmImportsOfPackagesWithExtensionlessMains.ts @@ -0,0 +1,36 @@ +// @noImplicitReferences: true +// @module: nodenext +// @outDir: esm +// @filename: node_modules/@types/ip/package.json +{ + "name": "@types/ip", + "version": "1.1.0", + "main": "", + "types": "index" +} +// @filename: node_modules/@types/ip/index.d.ts +export function address(): string; +// @filename: node_modules/nullthrows/package.json +{ + "name": "nullthrows", + "version": "1.1.1", + "main": "nullthrows.js", + "types": "nullthrows.d.ts" +} +// @filename: node_modules/nullthrows/nullthrows.d.ts +declare function nullthrows(x: any): any; +declare namespace nullthrows { + export {nullthrows as default}; +} +export = nullthrows; +// @filename: package.json +{ + "type": "module" +} +// @filename: index.ts +import * as ip from 'ip'; +import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself + +export function getAddress(): string { + return nullthrows(ip.address()); +} \ No newline at end of file