Skip to content

Commit 3e9703f

Browse files
authored
Add additional diagnostic for packages that only resolve under non-exports-respecting modes (#52173)
1 parent 9cdba99 commit 3e9703f

13 files changed

+546
-28
lines changed

src/compiler/checker.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,7 @@ import {
819819
nodeIsPresent,
820820
nodeIsSynthesized,
821821
NodeLinks,
822+
nodeModulesPathPart,
822823
nodeStartsNewLexicalEnvironment,
823824
NodeWithTypeArguments,
824825
NonNullChain,
@@ -4772,7 +4773,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
47724773

47734774
if (sourceFile.symbol) {
47744775
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
4775-
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
4776+
errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference);
47764777
}
47774778
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
47784779
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
@@ -4860,7 +4861,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
48604861
error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName);
48614862
}
48624863
else {
4863-
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference);
4864+
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, currentSourceFile, mode, resolvedModule!, moduleReference);
48644865
}
48654866
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
48664867
return undefined;
@@ -4930,25 +4931,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
49304931
}
49314932
}
49324933

4933-
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
4934-
const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId
4935-
? typesPackageExists(packageId.name)
4934+
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
4935+
let errorInfo;
4936+
if (!isExternalModuleNameRelative(moduleReference) && packageId) {
4937+
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
4938+
errorInfo = node10Result
49364939
? chainDiagnosticMessages(
49374940
/*details*/ undefined,
4938-
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
4939-
packageId.name, mangleScopedPackageName(packageId.name))
4940-
: packageBundlesTypes(packageId.name)
4941+
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,
4942+
node10Result,
4943+
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageId.name)}` : packageId.name)
4944+
: typesPackageExists(packageId.name)
49414945
? chainDiagnosticMessages(
49424946
/*details*/ undefined,
4943-
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
4944-
packageId.name,
4945-
moduleReference)
4946-
: chainDiagnosticMessages(
4947-
/*details*/ undefined,
4948-
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
4949-
moduleReference,
4950-
mangleScopedPackageName(packageId.name))
4951-
: undefined;
4947+
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
4948+
packageId.name, mangleScopedPackageName(packageId.name))
4949+
: packageBundlesTypes(packageId.name)
4950+
? chainDiagnosticMessages(
4951+
/*details*/ undefined,
4952+
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
4953+
packageId.name,
4954+
moduleReference)
4955+
: chainDiagnosticMessages(
4956+
/*details*/ undefined,
4957+
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
4958+
moduleReference,
4959+
mangleScopedPackageName(packageId.name));
4960+
}
49524961
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
49534962
errorInfo,
49544963
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5235,6 +5235,14 @@
52355235
"category": "Message",
52365236
"code": 6276
52375237
},
5238+
"Resolution of non-relative name failed; trying with modern Node resolution features disabled to see if npm library needs configuration update.": {
5239+
"category": "Message",
5240+
"code": 6277
5241+
},
5242+
"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.": {
5243+
"category": "Message",
5244+
"code": 6278
5245+
},
52385246

52395247
"Enable project compilation": {
52405248
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ function createResolvedModuleWithFailedLookupLocations(
212212
failedLookupLocations: string[],
213213
affectingLocations: string[],
214214
diagnostics: Diagnostic[],
215-
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined
215+
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined,
216+
legacyResult?: string,
216217
): ResolvedModuleWithFailedLookupLocations {
217218
if (resultFromCache) {
218219
resultFromCache.failedLookupLocations = updateResolutionField(resultFromCache.failedLookupLocations, failedLookupLocations);
@@ -232,6 +233,7 @@ function createResolvedModuleWithFailedLookupLocations(
232233
failedLookupLocations: initializeResolutionField(failedLookupLocations),
233234
affectingLocations: initializeResolutionField(affectingLocations),
234235
resolutionDiagnostics: initializeResolutionField(diagnostics),
236+
node10Result: legacyResult,
235237
};
236238
}
237239
function initializeResolutionField<T>(value: T[]): T[] | undefined {
@@ -1666,12 +1668,37 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
16661668
const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration);
16671669
const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration);
16681670
result =
1669-
priorityExtensions && tryResolve(priorityExtensions) ||
1670-
secondaryExtensions && tryResolve(secondaryExtensions) ||
1671+
priorityExtensions && tryResolve(priorityExtensions, state) ||
1672+
secondaryExtensions && tryResolve(secondaryExtensions, state) ||
16711673
undefined;
16721674
}
16731675
else {
1674-
result = tryResolve(extensions);
1676+
result = tryResolve(extensions, state);
1677+
}
1678+
1679+
// For non-relative names that resolved to JS but no types in modes that look up an "import" condition in package.json "exports",
1680+
// try again with "exports" disabled to try to detect if this is likely a configuration error in a dependency's package.json.
1681+
let legacyResult;
1682+
if (result?.value?.isExternalLibraryImport
1683+
&& !isConfigLookup
1684+
&& extensions & (Extensions.TypeScript | Extensions.Declaration)
1685+
&& features & NodeResolutionFeatures.Exports
1686+
&& !isExternalModuleNameRelative(moduleName)
1687+
&& !extensionIsOk(Extensions.TypeScript | Extensions.Declaration, result.value.resolved.extension)
1688+
&& conditions.indexOf("import") > -1
1689+
) {
1690+
traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update);
1691+
const diagnosticState = {
1692+
...state,
1693+
features: state.features & ~NodeResolutionFeatures.Exports,
1694+
failedLookupLocations: [],
1695+
affectingLocations: [],
1696+
reportDiagnostic: noop,
1697+
};
1698+
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
1699+
if (diagnosticResult?.value?.isExternalLibraryImport) {
1700+
legacyResult = diagnosticResult.value.resolved.path;
1701+
}
16751702
}
16761703

16771704
return createResolvedModuleWithFailedLookupLocations(
@@ -1680,10 +1707,11 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
16801707
failedLookupLocations,
16811708
affectingLocations,
16821709
diagnostics,
1683-
state.resultFromCache
1710+
state.resultFromCache,
1711+
legacyResult,
16841712
);
16851713

1686-
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
1714+
function tryResolve(extensions: Extensions, state: ModuleResolutionState): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
16871715
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true);
16881716
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state);
16891717
if (resolved) {
@@ -2279,7 +2307,7 @@ function resolvedIfExtensionMatches(extensions: Extensions, path: string, resolv
22792307
}
22802308

22812309
/** True if `extension` is one of the supported `extensions`. */
2282-
function extensionIsOk(extensions: Extensions, extension: Extension): boolean {
2310+
function extensionIsOk(extensions: Extensions, extension: string): boolean {
22832311
return extensions & Extensions.JavaScript && (extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs)
22842312
|| extensions & Extensions.TypeScript && (extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts)
22852313
|| extensions & Extensions.Declaration && (extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts)

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7599,6 +7599,12 @@ export interface ResolvedModuleWithFailedLookupLocations {
75997599
affectingLocations?: string[];
76007600
/** @internal */
76017601
resolutionDiagnostics?: Diagnostic[]
7602+
/**
7603+
* @internal
7604+
* Used to issue a diagnostic if typings for a non-relative import couldn't be found
7605+
* while respecting package.json `exports`, but were found when disabling `exports`.
7606+
*/
7607+
node10Result?: string;
76027608
}
76037609

76047610
export interface ResolvedTypeReferenceDirective {

tests/baselines/reference/nodeModulesExportsBlocksTypesVersions(module=node16).errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ error TS6504: File '/node_modules/exports-and-types-versions/dist/foo.js' is a J
66
/main.cts(2,16): error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.
77
/main.cts(5,16): error TS2307: Cannot find module 'exports-and-types-versions/versioned-nah' or its corresponding type declarations.
88
/main.mts(1,16): error TS7016: Could not find a declaration file for module 'exports-and-types-versions/foo'. '/node_modules/exports-and-types-versions/dist/foo.js' implicitly has an 'any' type.
9-
If the 'exports-and-types-versions' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'exports-and-types-versions/foo';`
9+
There are types at '/node_modules/exports-and-types-versions/types/foo.d.ts', but this result could not be resolved when respecting package.json "exports". The 'exports-and-types-versions' library may need to update its package.json or typings.
1010
/main.mts(2,16): error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.
1111
/main.mts(5,16): error TS2307: Cannot find module 'exports-and-types-versions/versioned-nah' or its corresponding type declarations.
1212

@@ -79,7 +79,7 @@ error TS6504: File '/node_modules/exports-and-types-versions/dist/foo.js' is a J
7979
import {} from "exports-and-types-versions/foo";
8080
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8181
!!! error TS7016: Could not find a declaration file for module 'exports-and-types-versions/foo'. '/node_modules/exports-and-types-versions/dist/foo.js' implicitly has an 'any' type.
82-
!!! error TS7016: If the 'exports-and-types-versions' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'exports-and-types-versions/foo';`
82+
!!! error TS7016: There are types at '/node_modules/exports-and-types-versions/types/foo.d.ts', but this result could not be resolved when respecting package.json "exports". The 'exports-and-types-versions' library may need to update its package.json or typings.
8383
import {} from "exports-and-types-versions/nope";
8484
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8585
!!! error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.

tests/baselines/reference/nodeModulesExportsBlocksTypesVersions(module=node16).trace.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@
9999
"File name '/node_modules/exports-and-types-versions/dist/foo.js' has a '.js' extension - stripping it.",
100100
"File '/node_modules/exports-and-types-versions/dist/foo.js' exist - use it as a name resolution result.",
101101
"Resolving real path for '/node_modules/exports-and-types-versions/dist/foo.js', result '/node_modules/exports-and-types-versions/dist/foo.js'.",
102+
"Resolution of non-relative name failed; trying with modern Node resolution features disabled to see if npm library needs configuration update.",
103+
"File '/package.json' does not exist according to earlier cached lookups.",
104+
"Loading module 'exports-and-types-versions/foo' from 'node_modules' folder, target file types: TypeScript, Declaration.",
105+
"File '/node_modules/exports-and-types-versions/package.json' exists according to earlier cached lookups.",
106+
"'package.json' has a 'typesVersions' field with version-specific path mappings.",
107+
"'package.json' has a 'typesVersions' entry '*' that matches compiler version '3.1.0-dev', looking for a pattern to match module name 'foo'.",
108+
"Module name 'foo', matched pattern 'foo'.",
109+
"Trying substitution './types/foo.d.ts', candidate module location: './types/foo.d.ts'.",
110+
"File '/node_modules/exports-and-types-versions/types/foo.d.ts' exist - use it as a name resolution result.",
111+
"Resolving real path for '/node_modules/exports-and-types-versions/types/foo.d.ts', result '/node_modules/exports-and-types-versions/types/foo.d.ts'.",
102112
"======== Module name 'exports-and-types-versions/foo' was successfully resolved to '/node_modules/exports-and-types-versions/dist/foo.js' with Package ID 'exports-and-types-versions/dist/foo.js@1.0.0'. ========",
103113
"======== Resolving module 'exports-and-types-versions/nope' from '/main.mts'. ========",
104114
"Module resolution kind is not specified, using 'Node16'.",

tests/baselines/reference/nodeModulesExportsBlocksTypesVersions(module=nodenext).errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ error TS6504: File '/node_modules/exports-and-types-versions/dist/foo.js' is a J
66
/main.cts(2,16): error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.
77
/main.cts(5,16): error TS2307: Cannot find module 'exports-and-types-versions/versioned-nah' or its corresponding type declarations.
88
/main.mts(1,16): error TS7016: Could not find a declaration file for module 'exports-and-types-versions/foo'. '/node_modules/exports-and-types-versions/dist/foo.js' implicitly has an 'any' type.
9-
If the 'exports-and-types-versions' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'exports-and-types-versions/foo';`
9+
There are types at '/node_modules/exports-and-types-versions/types/foo.d.ts', but this result could not be resolved when respecting package.json "exports". The 'exports-and-types-versions' library may need to update its package.json or typings.
1010
/main.mts(2,16): error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.
1111
/main.mts(5,16): error TS2307: Cannot find module 'exports-and-types-versions/versioned-nah' or its corresponding type declarations.
1212

@@ -79,7 +79,7 @@ error TS6504: File '/node_modules/exports-and-types-versions/dist/foo.js' is a J
7979
import {} from "exports-and-types-versions/foo";
8080
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8181
!!! error TS7016: Could not find a declaration file for module 'exports-and-types-versions/foo'. '/node_modules/exports-and-types-versions/dist/foo.js' implicitly has an 'any' type.
82-
!!! error TS7016: If the 'exports-and-types-versions' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'exports-and-types-versions/foo';`
82+
!!! error TS7016: There are types at '/node_modules/exports-and-types-versions/types/foo.d.ts', but this result could not be resolved when respecting package.json "exports". The 'exports-and-types-versions' library may need to update its package.json or typings.
8383
import {} from "exports-and-types-versions/nope";
8484
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8585
!!! error TS2307: Cannot find module 'exports-and-types-versions/nope' or its corresponding type declarations.

tests/baselines/reference/nodeModulesExportsBlocksTypesVersions(module=nodenext).trace.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@
9999
"File name '/node_modules/exports-and-types-versions/dist/foo.js' has a '.js' extension - stripping it.",
100100
"File '/node_modules/exports-and-types-versions/dist/foo.js' exist - use it as a name resolution result.",
101101
"Resolving real path for '/node_modules/exports-and-types-versions/dist/foo.js', result '/node_modules/exports-and-types-versions/dist/foo.js'.",
102+
"Resolution of non-relative name failed; trying with modern Node resolution features disabled to see if npm library needs configuration update.",
103+
"File '/package.json' does not exist according to earlier cached lookups.",
104+
"Loading module 'exports-and-types-versions/foo' from 'node_modules' folder, target file types: TypeScript, Declaration.",
105+
"File '/node_modules/exports-and-types-versions/package.json' exists according to earlier cached lookups.",
106+
"'package.json' has a 'typesVersions' field with version-specific path mappings.",
107+
"'package.json' has a 'typesVersions' entry '*' that matches compiler version '3.1.0-dev', looking for a pattern to match module name 'foo'.",
108+
"Module name 'foo', matched pattern 'foo'.",
109+
"Trying substitution './types/foo.d.ts', candidate module location: './types/foo.d.ts'.",
110+
"File '/node_modules/exports-and-types-versions/types/foo.d.ts' exist - use it as a name resolution result.",
111+
"Resolving real path for '/node_modules/exports-and-types-versions/types/foo.d.ts', result '/node_modules/exports-and-types-versions/types/foo.d.ts'.",
102112
"======== Module name 'exports-and-types-versions/foo' was successfully resolved to '/node_modules/exports-and-types-versions/dist/foo.js' with Package ID 'exports-and-types-versions/dist/foo.js@1.0.0'. ========",
103113
"======== Resolving module 'exports-and-types-versions/nope' from '/main.mts'. ========",
104114
"Module resolution kind is not specified, using 'NodeNext'.",

0 commit comments

Comments
 (0)