diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0e60926c93d50..b813d7aa7f3f5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -8519,5 +8519,9 @@ "'{0}' is not a valid meta-property for keyword 'import'. Did you mean 'meta' or 'defer'?": { "category": "Error", "code": 18061 + }, + "This import could not be resolved using the '{0}' exports condition in its package.json. Types were found using the condition '{1}', but this lower-priority condition will not be used at runtime. This is a bug in the '{2}' library's typings configuration.": { + "category": "Error", + "code": 18062 } } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 3fa7da5d5c960..00aac6112108c 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -37,7 +37,6 @@ import { forEachAncestorDirectory, formatMessage, getAllowImportingTsExtensions, - getAllowJSCompilerOption, getAnyExtensionFromPath, getBaseFileName, GetCanonicalFileName, @@ -327,6 +326,8 @@ export interface ModuleResolutionState { isConfigLookup: boolean; candidateIsFromPackageJsonField: boolean; resolvedPackageDirectory: boolean; + /** When beginning a fallback lookup in exports conditions, value is set to the object containing the conditions */ + inFallbackConditionsLookup?: object; } /** Just the fields that we use for module resolution. @@ -2586,26 +2587,7 @@ function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: str } const trailingParts = parts.slice(nameParts.length); 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. See the selfNameModuleAugmentation.ts test for why this - // matters. - // - // However, there's an exception. If the user has `allowJs` and `declaration`, we need - // to ensure that self-name imports of their own package can resolve back to their - // input JS files via `tryLoadInputFileForPath` at a higher priority than their output - // declaration files, so we need to do a single pass with all extensions for that case. - if (getAllowJSCompilerOption(state.compilerOptions) && !pathContainsNodeModules(directory)) { - return loadModuleFromExports(scope, extensions, subpath, state, cache, redirectedReference); - } - 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); + return loadModuleFromExports(scope, extensions, subpath, state, cache, redirectedReference); } function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { @@ -2806,6 +2788,7 @@ function getLoadModuleFromTargetExportOrImport(extensions: Extensions, state: Mo else if (typeof target === "object" && target !== null) { // eslint-disable-line no-restricted-syntax if (!Array.isArray(target)) { traceIfEnabled(state, Diagnostics.Entering_conditional_exports); + const saveInFallbackConditionsLookup = state.inFallbackConditionsLookup; for (const condition of getOwnKeys(target as MapLike)) { if (condition === "default" || state.conditions.includes(condition) || isApplicableVersionedTypesKey(state.conditions, condition)) { traceIfEnabled(state, Diagnostics.Matched_0_condition_1, isImports ? "imports" : "exports", condition); @@ -2814,10 +2797,25 @@ function getLoadModuleFromTargetExportOrImport(extensions: Extensions, state: Mo if (result) { traceIfEnabled(state, Diagnostics.Resolved_under_condition_0, condition); traceIfEnabled(state, Diagnostics.Exiting_conditional_exports); + if (state.inFallbackConditionsLookup === target) { + const firstMatchingCondition = getOwnKeys(target as MapLike).find(c => c === "default" || state.conditions.includes(c) || isApplicableVersionedTypesKey(state.conditions, c))!; + const packageName = scope.contents.packageJsonContent.name || scope.packageDirectory; + state.reportDiagnostic(createCompilerDiagnostic( + Diagnostics.This_import_could_not_be_resolved_using_the_0_exports_condition_in_its_package_json_Types_were_found_using_the_condition_1_but_this_lower_priority_condition_will_not_be_used_at_runtime_This_is_a_bug_in_the_2_library_s_typings_configuration, + firstMatchingCondition, + condition, + packageName, + )); + } return result; } else { traceIfEnabled(state, Diagnostics.Failed_to_resolve_under_condition_0, condition); + if (isImports) { + // Don't use fallback conditions for imports, since the user can fix their own package.json + return { value: undefined }; + } + state.inFallbackConditionsLookup = target; } } else { @@ -2825,6 +2823,7 @@ function getLoadModuleFromTargetExportOrImport(extensions: Extensions, state: Mo } } traceIfEnabled(state, Diagnostics.Exiting_conditional_exports); + state.inFallbackConditionsLookup = saveInFallbackConditionsLookup; return undefined; } else { diff --git a/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=bundler).errors.txt b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=bundler).errors.txt new file mode 100644 index 0000000000000..0259bdbe96499 --- /dev/null +++ b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=bundler).errors.txt @@ -0,0 +1,25 @@ +error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. + + +!!! error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. +==== /node_modules/dep/package.json (0 errors) ==== + { + "name": "dep", + "version": "1.0.0", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + } + } + +==== /node_modules/dep/dist/index.d.ts (0 errors) ==== + export {}; + +==== /node_modules/dep/dist/index.mjs (0 errors) ==== + export {}; + +==== /index.mts (0 errors) ==== + import {} from "dep"; \ No newline at end of file diff --git a/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=node16).errors.txt b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=node16).errors.txt index e205e8b437d53..6a65a94463c8b 100644 --- a/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=node16).errors.txt +++ b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=node16).errors.txt @@ -1,7 +1,9 @@ error TS5110: Option 'module' must be set to 'Node16' when option 'moduleResolution' is set to 'Node16'. +error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. !!! error TS5110: Option 'module' must be set to 'Node16' when option 'moduleResolution' is set to 'Node16'. +!!! error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. ==== /node_modules/dep/package.json (0 errors) ==== { "name": "dep", @@ -22,9 +24,4 @@ error TS5110: Option 'module' must be set to 'Node16' when option 'moduleResolut export {}; ==== /index.mts (0 errors) ==== - import {} from "dep"; - // Should be an untyped resolution to dep/dist/index.mjs, - // but the first search is only for TS files, and when - // there's no dist/index.d.mts, it continues looking for - // matching conditions and resolves via `types`. - \ No newline at end of file + import {} from "dep"; \ No newline at end of file diff --git a/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=nodenext).errors.txt b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=nodenext).errors.txt index 85cf980ae498c..284f9c3eb90e2 100644 --- a/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=nodenext).errors.txt +++ b/tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=nodenext).errors.txt @@ -1,7 +1,9 @@ error TS5110: Option 'module' must be set to 'NodeNext' when option 'moduleResolution' is set to 'NodeNext'. +error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. !!! error TS5110: Option 'module' must be set to 'NodeNext' when option 'moduleResolution' is set to 'NodeNext'. +!!! error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'types', but this lower-priority condition will not be used at runtime. This is a bug in the 'dep' library's typings configuration. ==== /node_modules/dep/package.json (0 errors) ==== { "name": "dep", @@ -22,9 +24,4 @@ error TS5110: Option 'module' must be set to 'NodeNext' when option 'moduleResol export {}; ==== /index.mts (0 errors) ==== - import {} from "dep"; - // Should be an untyped resolution to dep/dist/index.mjs, - // but the first search is only for TS files, and when - // there's no dist/index.d.mts, it continues looking for - // matching conditions and resolves via `types`. - \ No newline at end of file + import {} from "dep"; \ No newline at end of file diff --git a/tests/baselines/reference/selfNameModuleAugmentation.errors.txt b/tests/baselines/reference/selfNameModuleAugmentation.errors.txt new file mode 100644 index 0000000000000..53b89593faeaf --- /dev/null +++ b/tests/baselines/reference/selfNameModuleAugmentation.errors.txt @@ -0,0 +1,38 @@ +error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'default', but this lower-priority condition will not be used at runtime. This is a bug in the 'acorn-walk' library's typings configuration. +/index.ts(1,10): error TS2305: Module '"acorn-walk"' has no exported member 'simple'. + + +!!! error TS18062: This import could not be resolved using the 'import' exports condition in its package.json. Types were found using the condition 'default', but this lower-priority condition will not be used at runtime. This is a bug in the 'acorn-walk' library's typings configuration. +==== /node_modules/acorn-walk/package.json (0 errors) ==== + { + "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" + } + } + +==== /node_modules/acorn-walk/dist/walk.d.ts (0 errors) ==== + export {}; + declare module 'acorn-walk' { + export function simple(node: any, visitors: any, base?: any, state?: any): any; + } + +==== /node_modules/acorn-walk/dist/walk.mjs (0 errors) ==== + export {}; + +==== /index.ts (1 errors) ==== + import { simple } from 'acorn-walk'; + ~~~~~~ +!!! error TS2305: Module '"acorn-walk"' has no exported member 'simple'. + \ No newline at end of file diff --git a/tests/baselines/reference/selfNameModuleAugmentation.symbols b/tests/baselines/reference/selfNameModuleAugmentation.symbols index eb78bcbda7236..99dfad0e7bf2e 100644 --- a/tests/baselines/reference/selfNameModuleAugmentation.symbols +++ b/tests/baselines/reference/selfNameModuleAugmentation.symbols @@ -3,7 +3,7 @@ === /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)) +>'acorn-walk' : Symbol("/node_modules/acorn-walk/dist/walk", Decl(walk.mjs, 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)) diff --git a/tests/baselines/reference/selfNameModuleAugmentation.trace.json b/tests/baselines/reference/selfNameModuleAugmentation.trace.json index bdb1889706548..e3ec9ab01e598 100644 --- a/tests/baselines/reference/selfNameModuleAugmentation.trace.json +++ b/tests/baselines/reference/selfNameModuleAugmentation.trace.json @@ -12,18 +12,11 @@ "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.", - "Failed to resolve under condition 'import'.", - "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' exists - use it as a name resolution result.", + "File '/node_modules/acorn-walk/dist/walk.mjs' exists - use it as a name resolution result.", "'package.json' does not have a 'peerDependencies' field.", - "Resolved under condition 'default'.", + "Resolved under condition 'import'.", "Exiting conditional exports.", - "======== 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'. ========", + "======== Module name 'acorn-walk' was successfully resolved to '/node_modules/acorn-walk/dist/walk.mjs' with Package ID 'acorn-walk/dist/walk.mjs@8.2.0'. ========", "======== Resolving module 'acorn-walk' from '/index.ts'. ========", "Explicitly specified module resolution kind: 'Bundler'.", "Resolving in CJS mode with conditions 'import', 'types'.", diff --git a/tests/baselines/reference/selfNameModuleAugmentation.types b/tests/baselines/reference/selfNameModuleAugmentation.types index 3728786e5272e..73f65385f5c4e 100644 --- a/tests/baselines/reference/selfNameModuleAugmentation.types +++ b/tests/baselines/reference/selfNameModuleAugmentation.types @@ -10,9 +10,13 @@ declare module 'acorn-walk' { >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 === @@ -21,6 +25,6 @@ export {}; === /index.ts === import { simple } from 'acorn-walk'; ->simple : (node: any, visitors: any, base?: any, state?: any) => any -> : ^ ^^ ^^ ^^ ^^ ^^^ ^^ ^^^ ^^^^^ +>simple : any +> : ^^^ diff --git a/tests/baselines/reference/tsserver/fourslashServer/pathCompletionsPackageJsonImportsSrcNoDistWildcard2.js b/tests/baselines/reference/tsserver/fourslashServer/pathCompletionsPackageJsonImportsSrcNoDistWildcard2.js index 90fe6d8133481..597ce1b604820 100644 --- a/tests/baselines/reference/tsserver/fourslashServer/pathCompletionsPackageJsonImportsSrcNoDistWildcard2.js +++ b/tests/baselines/reference/tsserver/fourslashServer/pathCompletionsPackageJsonImportsSrcNoDistWildcard2.js @@ -1118,10 +1118,6 @@ Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspa Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/src 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/dist 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/dist 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations -Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations -Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations -Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations -Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /home/src/workspaces/project/tsconfig.json projectStateVersion: 2 projectProgramVersion: 1 structureChanged: true structureIsReused:: SafeModules Elapsed:: *ms Info seq [hh:mm:ss:mss] Project '/home/src/workspaces/project/tsconfig.json' (Configured) Info seq [hh:mm:ss:mss] Files (2) @@ -1183,7 +1179,6 @@ watchedFiles:: watchedDirectoriesRecursive:: /home/src/workspaces/node_modules: {} - {} *new* /home/src/workspaces/node_modules/@types: {} {} @@ -1193,7 +1188,6 @@ watchedDirectoriesRecursive:: {} /home/src/workspaces/project/node_modules: {} - {} *new* /home/src/workspaces/project/node_modules/@types: {} {} diff --git a/tests/cases/conformance/moduleResolution/conditionalExportsResolutionFallback.ts b/tests/cases/conformance/moduleResolution/conditionalExportsResolutionFallback.ts index 2076f434272cd..e703022dd68fd 100644 --- a/tests/cases/conformance/moduleResolution/conditionalExportsResolutionFallback.ts +++ b/tests/cases/conformance/moduleResolution/conditionalExportsResolutionFallback.ts @@ -27,8 +27,4 @@ export {}; export {}; // @Filename: /index.mts -import {} from "dep"; -// Should be an untyped resolution to dep/dist/index.mjs, -// but the first search is only for TS files, and when -// there's no dist/index.d.mts, it continues looking for -// matching conditions and resolves via `types`. +import {} from "dep"; \ No newline at end of file