Skip to content

Commit 69f2e2a

Browse files
authored
Issue better error when unresolvable package in --moduleResolution node10 resolves with --moduleResolution bundler (#56949)
1 parent a673958 commit 69f2e2a

30 files changed

+722
-125
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5185,7 +5185,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
51855185
}
51865186
}
51875187
else {
5188-
error(errorNode, moduleNotFoundError, moduleReference);
5188+
if (host.getResolvedModule(currentSourceFile, moduleReference, mode)?.alternateResult) {
5189+
const errorInfo = createModuleNotFoundChain(currentSourceFile, host, moduleReference, mode, moduleReference);
5190+
errorOrSuggestion(/*isError*/ true, errorNode, chainDiagnosticMessages(errorInfo, moduleNotFoundError, moduleReference));
5191+
}
5192+
else {
5193+
error(errorNode, moduleNotFoundError, moduleReference);
5194+
}
51895195
}
51905196
}
51915197
}

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5447,6 +5447,14 @@
54475447
"category": "Message",
54485448
"code": 6278
54495449
},
5450+
"Resolution of non-relative name failed; trying with '--moduleResolution bundler' to see if project may need configuration update.": {
5451+
"category": "Message",
5452+
"code": 6279
5453+
},
5454+
"There are types at '{0}', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.": {
5455+
"category": "Message",
5456+
"code": 6280
5457+
},
54505458

54515459
"Enable project compilation": {
54525460
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ function createResolvedModuleWithFailedLookupLocationsHandlingSymlink(
226226
diagnostics: Diagnostic[],
227227
state: ModuleResolutionState,
228228
cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined,
229-
legacyResult?: string,
229+
alternateResult?: string,
230230
): ResolvedModuleWithFailedLookupLocations {
231231
// If this is from node_modules for non relative name, always respect preserveSymlinks
232232
if (
@@ -248,7 +248,7 @@ function createResolvedModuleWithFailedLookupLocationsHandlingSymlink(
248248
diagnostics,
249249
state.resultFromCache,
250250
cache,
251-
legacyResult,
251+
alternateResult,
252252
);
253253
}
254254

@@ -260,7 +260,7 @@ function createResolvedModuleWithFailedLookupLocations(
260260
diagnostics: Diagnostic[],
261261
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined,
262262
cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined,
263-
legacyResult?: string,
263+
alternateResult?: string,
264264
): ResolvedModuleWithFailedLookupLocations {
265265
if (resultFromCache) {
266266
if (!cache?.isReadonly) {
@@ -290,7 +290,7 @@ function createResolvedModuleWithFailedLookupLocations(
290290
failedLookupLocations: initializeResolutionField(failedLookupLocations),
291291
affectingLocations: initializeResolutionField(affectingLocations),
292292
resolutionDiagnostics: initializeResolutionField(diagnostics),
293-
node10Result: legacyResult,
293+
alternateResult,
294294
};
295295
}
296296
function initializeResolutionField<T>(value: T[]): T[] | undefined {
@@ -325,6 +325,7 @@ export interface ModuleResolutionState {
325325
reportDiagnostic: DiagnosticReporter;
326326
isConfigLookup: boolean;
327327
candidateIsFromPackageJsonField: boolean;
328+
resolvedPackageDirectory: boolean;
328329
}
329330

330331
/** Just the fields that we use for module resolution.
@@ -602,6 +603,7 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
602603
reportDiagnostic: diag => void diagnostics.push(diag),
603604
isConfigLookup: false,
604605
candidateIsFromPackageJsonField: false,
606+
resolvedPackageDirectory: false,
605607
};
606608
let resolved = primaryLookup();
607609
let primary = true;
@@ -1839,6 +1841,7 @@ function nodeModuleNameResolverWorker(
18391841
reportDiagnostic: diag => void diagnostics.push(diag),
18401842
isConfigLookup,
18411843
candidateIsFromPackageJsonField: false,
1844+
resolvedPackageDirectory: false,
18421845
};
18431846
if (traceEnabled && moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
18441847
trace(host, Diagnostics.Resolving_in_0_mode_with_conditions_1, features & NodeResolutionFeatures.EsmMode ? "ESM" : "CJS", state.conditions.map(c => `'${c}'`).join(", "));
@@ -1856,27 +1859,45 @@ function nodeModuleNameResolverWorker(
18561859
result = tryResolve(extensions, state);
18571860
}
18581861

1859-
// For non-relative names that resolved to JS but no types in modes that look up an "import" condition in package.json "exports",
1860-
// try again with "exports" disabled to try to detect if this is likely a configuration error in a dependency's package.json.
1861-
let legacyResult;
1862-
if (
1863-
result?.value?.isExternalLibraryImport
1864-
&& !isConfigLookup
1865-
&& extensions & (Extensions.TypeScript | Extensions.Declaration)
1866-
&& features & NodeResolutionFeatures.Exports
1867-
&& !isExternalModuleNameRelative(moduleName)
1868-
&& !extensionIsOk(Extensions.TypeScript | Extensions.Declaration, result.value.resolved.extension)
1869-
&& conditions?.includes("import")
1870-
) {
1871-
traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update);
1872-
const diagnosticState = {
1873-
...state,
1874-
features: state.features & ~NodeResolutionFeatures.Exports,
1875-
reportDiagnostic: noop,
1876-
};
1877-
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
1878-
if (diagnosticResult?.value?.isExternalLibraryImport) {
1879-
legacyResult = diagnosticResult.value.resolved.path;
1862+
let alternateResult;
1863+
if (state.resolvedPackageDirectory && !isConfigLookup && !isExternalModuleNameRelative(moduleName)) {
1864+
const wantedTypesButGotJs = result?.value
1865+
&& extensions & (Extensions.TypeScript | Extensions.Declaration)
1866+
&& !extensionIsOk(Extensions.TypeScript | Extensions.Declaration, result.value.resolved.extension);
1867+
if (
1868+
result?.value?.isExternalLibraryImport
1869+
&& wantedTypesButGotJs
1870+
&& features & NodeResolutionFeatures.Exports
1871+
&& conditions?.includes("import")
1872+
) {
1873+
traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update);
1874+
const diagnosticState = {
1875+
...state,
1876+
features: state.features & ~NodeResolutionFeatures.Exports,
1877+
reportDiagnostic: noop,
1878+
};
1879+
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
1880+
if (diagnosticResult?.value?.isExternalLibraryImport) {
1881+
alternateResult = diagnosticResult.value.resolved.path;
1882+
}
1883+
}
1884+
else if (
1885+
(!result?.value || wantedTypesButGotJs)
1886+
&& moduleResolution === ModuleResolutionKind.Node10
1887+
) {
1888+
traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_moduleResolution_bundler_to_see_if_project_may_need_configuration_update);
1889+
const diagnosticsCompilerOptions = { ...state.compilerOptions, moduleResolution: ModuleResolutionKind.Bundler };
1890+
const diagnosticState = {
1891+
...state,
1892+
compilerOptions: diagnosticsCompilerOptions,
1893+
features: NodeResolutionFeatures.BundlerDefault,
1894+
conditions: getConditions(diagnosticsCompilerOptions),
1895+
reportDiagnostic: noop,
1896+
};
1897+
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
1898+
if (diagnosticResult?.value?.isExternalLibraryImport) {
1899+
alternateResult = diagnosticResult.value.resolved.path;
1900+
}
18801901
}
18811902
}
18821903

@@ -1889,7 +1910,7 @@ function nodeModuleNameResolverWorker(
18891910
diagnostics,
18901911
state,
18911912
cache,
1892-
legacyResult,
1913+
alternateResult,
18931914
);
18941915

18951916
function tryResolve(extensions: Extensions, state: ModuleResolutionState): SearchResult<{ resolved: Resolved; isExternalLibraryImport: boolean; }> {
@@ -2350,6 +2371,7 @@ export function getTemporaryModuleResolutionState(packageJsonInfoCache: PackageJ
23502371
reportDiagnostic: noop,
23512372
isConfigLookup: false,
23522373
candidateIsFromPackageJsonField: false,
2374+
resolvedPackageDirectory: false,
23532375
};
23542376
}
23552377

@@ -3072,6 +3094,9 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
30723094
// Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now.
30733095
packageInfo = rootPackageInfo ?? getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state);
30743096
}
3097+
if (packageInfo) {
3098+
state.resolvedPackageDirectory = true;
3099+
}
30753100
// package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them)
30763101
if (packageInfo && packageInfo.contents.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) {
30773102
return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value;
@@ -3202,6 +3227,7 @@ export function classicNameResolver(moduleName: string, containingFile: string,
32023227
reportDiagnostic: diag => void diagnostics.push(diag),
32033228
isConfigLookup: false,
32043229
candidateIsFromPackageJsonField: false,
3230+
resolvedPackageDirectory: false,
32053231
};
32063232

32073233
const resolved = tryResolve(Extensions.TypeScript | Extensions.Declaration) ||
@@ -3302,6 +3328,7 @@ export function loadModuleFromGlobalCache(moduleName: string, projectName: strin
33023328
reportDiagnostic: diag => void diagnostics.push(diag),
33033329
isConfigLookup: false,
33043330
candidateIsFromPackageJsonField: false,
3331+
resolvedPackageDirectory: false,
33053332
};
33063333
const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.Declaration, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined);
33073334
return createResolvedModuleWithFailedLookupLocations(

src/compiler/resolutionCache.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export interface ResolutionWithFailedLookupLocations {
168168
refCount?: number;
169169
// Files that have this resolution using
170170
files?: Set<Path>;
171-
node10Result?: string;
171+
alternateResult?: string;
172172
}
173173

174174
/** @internal */
@@ -1092,22 +1092,22 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
10921092
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
10931093
Debug.assert(!!resolution.refCount);
10941094

1095-
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
1096-
if (!failedLookupLocations?.length && !affectingLocations?.length && !node10Result) return;
1097-
if (failedLookupLocations?.length || node10Result) resolutionsWithFailedLookups.add(resolution);
1095+
const { failedLookupLocations, affectingLocations, alternateResult } = resolution;
1096+
if (!failedLookupLocations?.length && !affectingLocations?.length && !alternateResult) return;
1097+
if (failedLookupLocations?.length || alternateResult) resolutionsWithFailedLookups.add(resolution);
10981098

10991099
let setAtRoot = false;
11001100
if (failedLookupLocations) {
11011101
for (const failedLookupLocation of failedLookupLocations) {
11021102
setAtRoot = watchFailedLookupLocation(failedLookupLocation, setAtRoot);
11031103
}
11041104
}
1105-
if (node10Result) setAtRoot = watchFailedLookupLocation(node10Result, setAtRoot);
1105+
if (alternateResult) setAtRoot = watchFailedLookupLocation(alternateResult, setAtRoot);
11061106
if (setAtRoot) {
11071107
// This is always non recursive
11081108
setDirectoryWatcher(rootDir, rootPath, /*nonRecursive*/ true);
11091109
}
1110-
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length && !node10Result);
1110+
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length && !alternateResult);
11111111
}
11121112

11131113
function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) {
@@ -1247,15 +1247,15 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
12471247
if (resolutions?.delete(resolution) && !resolutions.size) resolvedFileToResolution.delete(key);
12481248
}
12491249

1250-
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
1250+
const { failedLookupLocations, affectingLocations, alternateResult } = resolution;
12511251
if (resolutionsWithFailedLookups.delete(resolution)) {
12521252
let removeAtRoot = false;
12531253
if (failedLookupLocations) {
12541254
for (const failedLookupLocation of failedLookupLocations) {
12551255
removeAtRoot = stopWatchFailedLookupLocation(failedLookupLocation, removeAtRoot, syncDirWatcherRemove);
12561256
}
12571257
}
1258-
if (node10Result) removeAtRoot = stopWatchFailedLookupLocation(node10Result, removeAtRoot, syncDirWatcherRemove);
1258+
if (alternateResult) removeAtRoot = stopWatchFailedLookupLocation(alternateResult, removeAtRoot, syncDirWatcherRemove);
12591259
if (removeAtRoot) removeDirectoryWatcher(rootPath, syncDirWatcherRemove);
12601260
}
12611261
else if (affectingLocations?.length) {
@@ -1462,7 +1462,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
14621462
if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true;
14631463
if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false;
14641464
return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))) ||
1465-
(!!resolution.node10Result && isInvalidatedFailedLookup(resolutionHost.toPath(resolution.node10Result)));
1465+
(!!resolution.alternateResult && isInvalidatedFailedLookup(resolutionHost.toPath(resolution.alternateResult)));
14661466
}
14671467

14681468
function isInvalidatedFailedLookup(locationPath: Path) {

src/compiler/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7752,10 +7752,10 @@ export interface ResolvedModuleWithFailedLookupLocations {
77527752
resolutionDiagnostics?: Diagnostic[];
77537753
/**
77547754
* @internal
7755-
* Used to issue a diagnostic if typings for a non-relative import couldn't be found
7756-
* while respecting package.json `exports`, but were found when disabling `exports`.
7755+
* Used to issue a better diagnostic when an unresolvable module may
7756+
* have been resolvable under different module resolution settings.
77577757
*/
7758-
node10Result?: string;
7758+
alternateResult?: string;
77597759
}
77607760

77617761
export interface ResolvedTypeReferenceDirective {

src/compiler/utilities.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -767,18 +767,23 @@ export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleWithFaile
767767
oldResolution.resolvedModule.resolvedFileName === newResolution.resolvedModule.resolvedFileName &&
768768
oldResolution.resolvedModule.originalPath === newResolution.resolvedModule.originalPath &&
769769
packageIdIsEqual(oldResolution.resolvedModule.packageId, newResolution.resolvedModule.packageId) &&
770-
oldResolution.node10Result === newResolution.node10Result;
770+
oldResolution.alternateResult === newResolution.alternateResult;
771771
}
772772

773773
/** @internal */
774774
export function createModuleNotFoundChain(sourceFile: SourceFile, host: TypeCheckerHost, moduleReference: string, mode: ResolutionMode, packageName: string) {
775-
const node10Result = host.getResolvedModule(sourceFile, moduleReference, mode)?.node10Result;
776-
const result = node10Result
775+
const alternateResult = host.getResolvedModule(sourceFile, moduleReference, mode)?.alternateResult;
776+
const alternateResultMessage = alternateResult && (getEmitModuleResolutionKind(host.getCompilerOptions()) === ModuleResolutionKind.Node10
777+
? [Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_under_your_current_moduleResolution_setting_Consider_updating_to_node16_nodenext_or_bundler, [alternateResult]] as const
778+
: [
779+
Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
780+
[alternateResult, alternateResult.includes(nodeModulesPathPart + "@types/") ? `@types/${mangleScopedPackageName(packageName)}` : packageName],
781+
] as const);
782+
const result = alternateResultMessage
777783
? chainDiagnosticMessages(
778784
/*details*/ undefined,
779-
Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
780-
node10Result,
781-
node10Result.includes(nodeModulesPathPart + "@types/") ? `@types/${mangleScopedPackageName(packageName)}` : packageName,
785+
alternateResultMessage[0],
786+
...alternateResultMessage[1],
782787
)
783788
: host.typesPackageExists(packageName)
784789
? chainDiagnosticMessages(

src/harness/incrementalUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export function verifyResolutionCache(
331331
resolvedTypeReferenceDirective: (resolved as any).resolvedTypeReferenceDirective,
332332
failedLookupLocations: resolved.failedLookupLocations,
333333
affectingLocations: resolved.affectingLocations,
334-
node10Result: resolved.node10Result,
334+
alternateResult: resolved.alternateResult,
335335
};
336336
expectedToResolution.set(expectedResolution, resolved);
337337
resolutionToExpected.set(resolved, expectedResolution);

0 commit comments

Comments
 (0)