Add additional diagnostic for packages that only resolve under non-exports-respecting modes (#52173)

This commit is contained in:
Andrew Branch
2023-01-12 16:30:35 -08:00
committed by GitHub
parent 9cdba994ac
commit 3e9703f126
13 changed files with 546 additions and 28 deletions
+26 -17
View File
@@ -819,6 +819,7 @@ import {
nodeIsPresent,
nodeIsSynthesized,
NodeLinks,
nodeModulesPathPart,
nodeStartsNewLexicalEnvironment,
NodeWithTypeArguments,
NonNullChain,
@@ -4772,7 +4773,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (sourceFile.symbol) {
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference);
}
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
@@ -4860,7 +4861,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName);
}
else {
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference);
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, currentSourceFile, mode, resolvedModule!, moduleReference);
}
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
return undefined;
@@ -4930,25 +4931,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId
? typesPackageExists(packageId.name)
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
let errorInfo;
if (!isExternalModuleNameRelative(moduleReference) && packageId) {
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
errorInfo = node10Result
? chainDiagnosticMessages(
/*details*/ undefined,
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,
packageId.name, mangleScopedPackageName(packageId.name))
: packageBundlesTypes(packageId.name)
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,
node10Result,
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageId.name)}` : packageId.name)
: typesPackageExists(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageId.name,
moduleReference)
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
mangleScopedPackageName(packageId.name))
: undefined;
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,
packageId.name, mangleScopedPackageName(packageId.name))
: packageBundlesTypes(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageId.name,
moduleReference)
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
mangleScopedPackageName(packageId.name));
}
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
errorInfo,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
+8
View File
@@ -5235,6 +5235,14 @@
"category": "Message",
"code": 6276
},
"Resolution of non-relative name failed; trying with modern Node resolution features disabled to see if npm library needs configuration update.": {
"category": "Message",
"code": 6277
},
"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.": {
"category": "Message",
"code": 6278
},
"Enable project compilation": {
"category": "Message",
+35 -7
View File
@@ -212,7 +212,8 @@ function createResolvedModuleWithFailedLookupLocations(
failedLookupLocations: string[],
affectingLocations: string[],
diagnostics: Diagnostic[],
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined,
legacyResult?: string,
): ResolvedModuleWithFailedLookupLocations {
if (resultFromCache) {
resultFromCache.failedLookupLocations = updateResolutionField(resultFromCache.failedLookupLocations, failedLookupLocations);
@@ -232,6 +233,7 @@ function createResolvedModuleWithFailedLookupLocations(
failedLookupLocations: initializeResolutionField(failedLookupLocations),
affectingLocations: initializeResolutionField(affectingLocations),
resolutionDiagnostics: initializeResolutionField(diagnostics),
node10Result: legacyResult,
};
}
function initializeResolutionField<T>(value: T[]): T[] | undefined {
@@ -1666,12 +1668,37 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
const priorityExtensions = extensions & (Extensions.TypeScript | Extensions.Declaration);
const secondaryExtensions = extensions & ~(Extensions.TypeScript | Extensions.Declaration);
result =
priorityExtensions && tryResolve(priorityExtensions) ||
secondaryExtensions && tryResolve(secondaryExtensions) ||
priorityExtensions && tryResolve(priorityExtensions, state) ||
secondaryExtensions && tryResolve(secondaryExtensions, state) ||
undefined;
}
else {
result = tryResolve(extensions);
result = tryResolve(extensions, state);
}
// For non-relative names that resolved to JS but no types in modes that look up an "import" condition in package.json "exports",
// try again with "exports" disabled to try to detect if this is likely a configuration error in a dependency's package.json.
let legacyResult;
if (result?.value?.isExternalLibraryImport
&& !isConfigLookup
&& extensions & (Extensions.TypeScript | Extensions.Declaration)
&& features & NodeResolutionFeatures.Exports
&& !isExternalModuleNameRelative(moduleName)
&& !extensionIsOk(Extensions.TypeScript | Extensions.Declaration, result.value.resolved.extension)
&& conditions.indexOf("import") > -1
) {
traceIfEnabled(state, Diagnostics.Resolution_of_non_relative_name_failed_trying_with_modern_Node_resolution_features_disabled_to_see_if_npm_library_needs_configuration_update);
const diagnosticState = {
...state,
features: state.features & ~NodeResolutionFeatures.Exports,
failedLookupLocations: [],
affectingLocations: [],
reportDiagnostic: noop,
};
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
if (diagnosticResult?.value?.isExternalLibraryImport) {
legacyResult = diagnosticResult.value.resolved.path;
}
}
return createResolvedModuleWithFailedLookupLocations(
@@ -1680,10 +1707,11 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
failedLookupLocations,
affectingLocations,
diagnostics,
state.resultFromCache
state.resultFromCache,
legacyResult,
);
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
function tryResolve(extensions: Extensions, state: ModuleResolutionState): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state);
if (resolved) {
@@ -2279,7 +2307,7 @@ function resolvedIfExtensionMatches(extensions: Extensions, path: string, resolv
}
/** True if `extension` is one of the supported `extensions`. */
function extensionIsOk(extensions: Extensions, extension: Extension): boolean {
function extensionIsOk(extensions: Extensions, extension: string): boolean {
return extensions & Extensions.JavaScript && (extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs)
|| extensions & Extensions.TypeScript && (extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts)
|| extensions & Extensions.Declaration && (extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts)
+6
View File
@@ -7599,6 +7599,12 @@ export interface ResolvedModuleWithFailedLookupLocations {
affectingLocations?: string[];
/** @internal */
resolutionDiagnostics?: Diagnostic[]
/**
* @internal
* Used to issue a diagnostic if typings for a non-relative import couldn't be found
* while respecting package.json `exports`, but were found when disabling `exports`.
*/
node10Result?: string;
}
export interface ResolvedTypeReferenceDirective {