diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 30cbc37fd14e9..48f9eb95a48b2 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -2313,7 +2313,18 @@ function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: str return undefined; } const trailingParts = parts.slice(nameParts.length); - return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); + const subpath = !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`; + // Maybe TODO: splitting extensions into two priorities should be unnecessary, except + // https://github.com/microsoft/TypeScript/issues/50762 makes the behavior different. + // As long as that bug exists, we need to do two passes here in self-name loading + // in order to be consistent with (non-self) library-name loading in + // `loadModuleFromNearestNodeModulesDirectoryWorker`, which uses two passes in order + // to prioritize `@types` packages higher up the directory tree over untyped + // implementation packages. + const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration); + const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration); + return loadModuleFromExports(scope, priorityExtensions, subpath, state, cache, redirectedReference) + || loadModuleFromExports(scope, secondaryExtensions, subpath, state, cache, redirectedReference); } function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { diff --git a/tests/baselines/reference/selfNameModuleAugmentation.symbols b/tests/baselines/reference/selfNameModuleAugmentation.symbols new file mode 100644 index 0000000000000..6eb34ea7ea7b3 --- /dev/null +++ b/tests/baselines/reference/selfNameModuleAugmentation.symbols @@ -0,0 +1,21 @@ +=== /node_modules/acorn-walk/dist/walk.d.ts === +export {}; +declare module 'acorn-walk' { +>'acorn-walk' : Symbol("/node_modules/acorn-walk/dist/walk", Decl(walk.d.ts, 0, 0), Decl(walk.d.ts, 0, 10)) + + export function simple(node: any, visitors: any, base?: any, state?: any): any; +>simple : Symbol(simple, Decl(walk.d.ts, 1, 29)) +>node : Symbol(node, Decl(walk.d.ts, 2, 25)) +>visitors : Symbol(visitors, Decl(walk.d.ts, 2, 35)) +>base : Symbol(base, Decl(walk.d.ts, 2, 50)) +>state : Symbol(state, Decl(walk.d.ts, 2, 62)) +} + +=== /node_modules/acorn-walk/dist/walk.mjs === + +export {}; + +=== /index.ts === +import { simple } from 'acorn-walk'; +>simple : Symbol(simple, Decl(index.ts, 0, 8)) + diff --git a/tests/baselines/reference/selfNameModuleAugmentation.trace.json b/tests/baselines/reference/selfNameModuleAugmentation.trace.json new file mode 100644 index 0000000000000..8dbb624c695f7 --- /dev/null +++ b/tests/baselines/reference/selfNameModuleAugmentation.trace.json @@ -0,0 +1,39 @@ +[ + "======== Resolving module 'acorn-walk' from '/node_modules/acorn-walk/dist/walk.d.ts'. ========", + "Explicitly specified module resolution kind: 'Bundler'.", + "File '/node_modules/acorn-walk/dist/package.json' does not exist.", + "Found 'package.json' at '/node_modules/acorn-walk/package.json'.", + "Matched 'exports' condition 'import'.", + "Using 'exports' subpath '.' with target './dist/walk.mjs'.", + "File name '/node_modules/acorn-walk/dist/walk.mjs' has a '.mjs' extension - stripping it.", + "File '/node_modules/acorn-walk/dist/walk.mts' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.d.mts' does not exist.", + "Saw non-matching condition 'require'.", + "Matched 'exports' condition 'default'.", + "Using 'exports' subpath '.' with target './dist/walk.js'.", + "File name '/node_modules/acorn-walk/dist/walk.js' has a '.js' extension - stripping it.", + "File '/node_modules/acorn-walk/dist/walk.ts' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.tsx' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/acorn-walk/dist/walk.d.ts', result '/node_modules/acorn-walk/dist/walk.d.ts'.", + "======== Module name 'acorn-walk' was successfully resolved to '/node_modules/acorn-walk/dist/walk.d.ts' with Package ID 'acorn-walk/dist/walk.d.ts@8.2.0'. ========", + "======== Resolving module 'acorn-walk' from '/index.ts'. ========", + "Explicitly specified module resolution kind: 'Bundler'.", + "File '/package.json' does not exist.", + "Loading module 'acorn-walk' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.", + "File '/node_modules/acorn-walk/package.json' exists according to earlier cached lookups.", + "Matched 'exports' condition 'import'.", + "Using 'exports' subpath '.' with target './dist/walk.mjs'.", + "File name '/node_modules/acorn-walk/dist/walk.mjs' has a '.mjs' extension - stripping it.", + "File '/node_modules/acorn-walk/dist/walk.mts' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.d.mts' does not exist.", + "Saw non-matching condition 'require'.", + "Matched 'exports' condition 'default'.", + "Using 'exports' subpath '.' with target './dist/walk.js'.", + "File name '/node_modules/acorn-walk/dist/walk.js' has a '.js' extension - stripping it.", + "File '/node_modules/acorn-walk/dist/walk.ts' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.tsx' does not exist.", + "File '/node_modules/acorn-walk/dist/walk.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/acorn-walk/dist/walk.d.ts', result '/node_modules/acorn-walk/dist/walk.d.ts'.", + "======== Module name 'acorn-walk' was successfully resolved to '/node_modules/acorn-walk/dist/walk.d.ts' with Package ID 'acorn-walk/dist/walk.d.ts@8.2.0'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/selfNameModuleAugmentation.types b/tests/baselines/reference/selfNameModuleAugmentation.types new file mode 100644 index 0000000000000..9b58bffe926ed --- /dev/null +++ b/tests/baselines/reference/selfNameModuleAugmentation.types @@ -0,0 +1,21 @@ +=== /node_modules/acorn-walk/dist/walk.d.ts === +export {}; +declare module 'acorn-walk' { +>'acorn-walk' : typeof import("/node_modules/acorn-walk/dist/walk") + + export function simple(node: any, visitors: any, base?: any, state?: any): any; +>simple : (node: any, visitors: any, base?: any, state?: any) => any +>node : any +>visitors : any +>base : any +>state : any +} + +=== /node_modules/acorn-walk/dist/walk.mjs === + +export {}; + +=== /index.ts === +import { simple } from 'acorn-walk'; +>simple : (node: any, visitors: any, base?: any, state?: any) => any + diff --git a/tests/cases/conformance/moduleResolution/selfNameModuleAugmentation.ts b/tests/cases/conformance/moduleResolution/selfNameModuleAugmentation.ts new file mode 100644 index 0000000000000..191d71cb0a5d0 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/selfNameModuleAugmentation.ts @@ -0,0 +1,43 @@ +// @moduleResolution: bundler +// @allowJs: true +// @noEmit: true +// @traceResolution: true + +// @Filename: /node_modules/acorn-walk/package.json +{ + "name": "acorn-walk", + "version": "8.2.0", + "main": "dist/walk.js", + "types": "dist/walk.d.ts", + "exports": { + ".": [ + { + "import": "./dist/walk.mjs", + "require": "./dist/walk.js", + "default": "./dist/walk.js" + }, + "./dist/walk.js" + ], + "./package.json": "./package.json" + } +} + +// The correct behavior for this test would be for both the module augmentation +// and the import to resolve to `walk.mjs` (triggering at least one implicit any +// error, I think?). However, https://github.com/microsoft/TypeScript/issues/50762 +// causes the import to resolve through the `default` condition, replacing `.js` +// with `.d.ts` to find `walk.d.ts`. While this is incorrect, it's important that +// the module augmentation, which resolves through self-name resolution, resolves +// to the same module as the external import. + +// @Filename: /node_modules/acorn-walk/dist/walk.d.ts +export {}; +declare module 'acorn-walk' { + export function simple(node: any, visitors: any, base?: any, state?: any): any; +} + +// @Filename: /node_modules/acorn-walk/dist/walk.mjs +export {}; + +// @Filename: /index.ts +import { simple } from 'acorn-walk';