diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 7d6d0f67d7aee..24d4c845acae5 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1394,7 +1394,13 @@ namespace ts { onlyRecordFailures = true; } } - return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` + // files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg` + // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + } + return undefined; } /*@internal*/ @@ -2178,7 +2184,7 @@ namespace ts { if (packageInfo && packageInfo.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; } - const pathAndExtension = + let pathAndExtension = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || loadNodeModuleFromDirectoryWorker( extensions, @@ -2188,6 +2194,16 @@ namespace ts { packageInfo && packageInfo.packageJsonContent, packageInfo && packageInfo.versionPaths ); + if ( + !pathAndExtension && packageInfo + && packageInfo.packageJsonContent.exports === undefined + && packageInfo.packageJsonContent.main === undefined + && state.features & NodeResolutionFeatures.EsmMode + ) { + // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume + // a default `index.js` entrypoint if no `main` or `exports` are present + pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state); + } return withPackageId(packageInfo, pathAndExtension); }; diff --git a/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.errors.txt b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.errors.txt new file mode 100644 index 0000000000000..cf759f6040d06 --- /dev/null +++ b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.errors.txt @@ -0,0 +1,31 @@ +tests/cases/compiler/index.ts(2,31): error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path. +tests/cases/compiler/index.ts(3,31): error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path. + + +==== tests/cases/compiler/node_modules/pkg/package.json (0 errors) ==== + { + "name": "pkg", + "version": "0.0.1" + } +==== tests/cases/compiler/node_modules/pkg/index.d.ts (0 errors) ==== + export const item = 4; +==== tests/cases/compiler/pkg/package.json (0 errors) ==== + { + "private": true + } +==== tests/cases/compiler/pkg/index.d.ts (0 errors) ==== + export const item = 4; +==== tests/cases/compiler/package.json (0 errors) ==== + { + "type": "module", + "private": true + } +==== tests/cases/compiler/index.ts (2 errors) ==== + import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import) + import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import) + ~~~~~~~ +!!! error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path. + import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_ + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path. + \ No newline at end of file diff --git a/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.js b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.js new file mode 100644 index 0000000000000..9ae72ef450a5e --- /dev/null +++ b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/nodeNextImportModeImplicitIndexResolution.ts] //// + +//// [package.json] +{ + "name": "pkg", + "version": "0.0.1" +} +//// [index.d.ts] +export const item = 4; +//// [package.json] +{ + "private": true +} +//// [index.d.ts] +export const item = 4; +//// [package.json] +{ + "type": "module", + "private": true +} +//// [index.ts] +import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import) +import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import) +import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_ + + +//// [index.js] +export {}; diff --git a/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.symbols b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.symbols new file mode 100644 index 0000000000000..c58b28d74ee89 --- /dev/null +++ b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.symbols @@ -0,0 +1,18 @@ +=== tests/cases/compiler/node_modules/pkg/index.d.ts === +export const item = 4; +>item : Symbol(item, Decl(index.d.ts, 0, 12)) + +=== tests/cases/compiler/pkg/index.d.ts === +export const item = 4; +>item : Symbol(item, Decl(index.d.ts, 0, 12)) + +=== tests/cases/compiler/index.ts === +import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import) +>item : Symbol(item, Decl(index.ts, 0, 8)) + +import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import) +>item2 : Symbol(item2, Decl(index.ts, 1, 8)) + +import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_ +>item3 : Symbol(item3, Decl(index.ts, 2, 8)) + diff --git a/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.types b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.types new file mode 100644 index 0000000000000..c90e8c11e83cc --- /dev/null +++ b/tests/baselines/reference/nodeNextImportModeImplicitIndexResolution.types @@ -0,0 +1,22 @@ +=== tests/cases/compiler/node_modules/pkg/index.d.ts === +export const item = 4; +>item : 4 +>4 : 4 + +=== tests/cases/compiler/pkg/index.d.ts === +export const item = 4; +>item : 4 +>4 : 4 + +=== tests/cases/compiler/index.ts === +import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import) +>item : 4 + +import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import) +>item : any +>item2 : any + +import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_ +>item : any +>item3 : any + diff --git a/tests/cases/compiler/nodeNextImportModeImplicitIndexResolution.ts b/tests/cases/compiler/nodeNextImportModeImplicitIndexResolution.ts new file mode 100644 index 0000000000000..bf20a669b4396 --- /dev/null +++ b/tests/cases/compiler/nodeNextImportModeImplicitIndexResolution.ts @@ -0,0 +1,23 @@ +// @module: nodenext +// @filename: node_modules/pkg/package.json +{ + "name": "pkg", + "version": "0.0.1" +} +// @filename: node_modules/pkg/index.d.ts +export const item = 4; +// @filename: pkg/package.json +{ + "private": true +} +// @filename: pkg/index.d.ts +export const item = 4; +// @filename: package.json +{ + "type": "module", + "private": true +} +// @filename: index.ts +import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import) +import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import) +import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_