mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Pass in information for the module name resolution when resolutions from file are partially used (#49738)
* Test showing wrong resolution is returned because of incorrect mode calculation Test for #48229 * Pass in information for the module name resolution when resolutions from file are partially used Fixes #48229 * Make the resolution info complete
This commit is contained in:
+65
-50
@@ -623,17 +623,20 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function loadWithModeAwareCache<T>(names: string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] {
|
||||
export function loadWithModeAwareCache<T>(names: readonly StringLiteralLike[] | readonly string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, resolutionInfo: ModuleResolutionInfo | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] {
|
||||
if (names.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const resolutions: T[] = [];
|
||||
const cache = new Map<string, T>();
|
||||
let i = 0;
|
||||
for (const name of names) {
|
||||
for (const entry of resolutionInfo ? resolutionInfo.names : names) {
|
||||
let result: T;
|
||||
const mode = getModeForResolutionAtIndex(containingFile, i);
|
||||
const mode = !isString(entry) ?
|
||||
getModeForUsageLocation(containingFile, entry) :
|
||||
getModeForResolutionAtIndex(containingFile, i);
|
||||
i++;
|
||||
const name = isString(entry) ? entry : entry.text;
|
||||
const cacheKey = mode !== undefined ? `${mode}|${name}` : name;
|
||||
if (cache.has(cacheKey)) {
|
||||
result = cache.get(cacheKey)!;
|
||||
@@ -1098,24 +1101,41 @@ namespace ts {
|
||||
|
||||
let moduleResolutionCache: ModuleResolutionCache | undefined;
|
||||
let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined;
|
||||
let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[];
|
||||
let actualResolveModuleNamesWorker: (
|
||||
moduleNames: readonly StringLiteralLike[],
|
||||
containingFile: SourceFile,
|
||||
containingFileName: string,
|
||||
redirectedReference: ResolvedProjectReference | undefined,
|
||||
resolutionInfo: ModuleResolutionInfo | undefined,
|
||||
) => (ResolvedModuleFull | undefined)[];
|
||||
const hasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse;
|
||||
if (host.resolveModuleNames) {
|
||||
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => {
|
||||
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
||||
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
|
||||
return resolved as ResolvedModuleFull;
|
||||
}
|
||||
const withExtension = clone(resolved) as ResolvedModuleFull;
|
||||
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
|
||||
return withExtension;
|
||||
});
|
||||
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) =>
|
||||
host.resolveModuleNames!(
|
||||
moduleNames.map(literal => literal.text),
|
||||
containingFileName,
|
||||
resolutionInfo?.reusedNames?.map(literal => literal.text),
|
||||
redirectedReference,
|
||||
options,
|
||||
containingFile,
|
||||
resolutionInfo,
|
||||
).map(resolved => {
|
||||
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
||||
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
|
||||
return resolved as ResolvedModuleFull;
|
||||
}
|
||||
const withExtension = clone(resolved) as ResolvedModuleFull;
|
||||
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
|
||||
return withExtension;
|
||||
});
|
||||
moduleResolutionCache = host.getModuleResolutionCache?.();
|
||||
}
|
||||
else {
|
||||
moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options);
|
||||
const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217
|
||||
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader);
|
||||
const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) =>
|
||||
resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule;
|
||||
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) =>
|
||||
loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader);
|
||||
}
|
||||
|
||||
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
|
||||
@@ -1398,18 +1418,15 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function pullDiagnosticsFromCache(names: string[] | readonly FileReference[], containingFile: SourceFile) {
|
||||
function pullDiagnosticsFromCache(names: readonly StringLiteralLike[] | readonly FileReference[], containingFile: SourceFile) {
|
||||
if (!moduleResolutionCache) return;
|
||||
const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory);
|
||||
const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined;
|
||||
const containingDir = getDirectoryPath(containingFileName);
|
||||
const redirectedReference = getRedirectReferenceForResolution(containingFile);
|
||||
let i = 0;
|
||||
for (const n of names) {
|
||||
// mimics logic done in the resolution cache, should be resilient to upgrading it to use `FileReference`s for non-type-reference modal lookups to make it rely on the index in the list less
|
||||
const mode = typeof n === "string" ? getModeForResolutionAtIndex(containingFile, i) : getModeForFileReference(n, containingFileMode);
|
||||
const name = typeof n === "string" ? n : n.fileName;
|
||||
i++;
|
||||
const mode = getResolutionMode(n, containingFile);
|
||||
const name = getResolutionName(n);
|
||||
// only nonrelative names hit the cache, and, at least as of right now, only nonrelative names can issue diagnostics
|
||||
// (Since diagnostics are only issued via import or export map lookup)
|
||||
// This may totally change if/when the issue of output paths not mapping to input files is fixed in a broader context
|
||||
@@ -1421,13 +1438,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] {
|
||||
function resolveModuleNamesWorker(moduleNames: readonly StringLiteralLike[], containingFile: SourceFile, resolutionInfo: ModuleResolutionInfo | undefined): readonly (ResolvedModuleFull | undefined)[] {
|
||||
if (!moduleNames.length) return emptyArray;
|
||||
const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory);
|
||||
const redirectedReference = getRedirectReferenceForResolution(containingFile);
|
||||
tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName });
|
||||
performance.mark("beforeResolveModule");
|
||||
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference);
|
||||
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo);
|
||||
performance.mark("afterResolveModule");
|
||||
performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule");
|
||||
tracing?.pop();
|
||||
@@ -1530,11 +1547,11 @@ namespace ts {
|
||||
return classifiableNames;
|
||||
}
|
||||
|
||||
function resolveModuleNamesReusingOldState(moduleNames: string[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] {
|
||||
function resolveModuleNamesReusingOldState(moduleNames: readonly StringLiteralLike[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] {
|
||||
if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) {
|
||||
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
|
||||
// the best we can do is fallback to the default logic.
|
||||
return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined);
|
||||
return resolveModuleNamesWorker(moduleNames, file, /*resolutionInfo*/ undefined);
|
||||
}
|
||||
|
||||
const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName);
|
||||
@@ -1548,10 +1565,8 @@ namespace ts {
|
||||
// Since we assume the filesystem does not change during program creation,
|
||||
// it is safe to reuse resolutions from the earlier call.
|
||||
const result: (ResolvedModuleFull | undefined)[] = [];
|
||||
let i = 0;
|
||||
for (const moduleName of moduleNames) {
|
||||
const resolvedModule = file.resolvedModules.get(moduleName, getModeForResolutionAtIndex(file, i));
|
||||
i++;
|
||||
const resolvedModule = file.resolvedModules.get(moduleName.text, getModeForUsageLocation(file, moduleName));
|
||||
result.push(resolvedModule);
|
||||
}
|
||||
return result;
|
||||
@@ -1562,7 +1577,7 @@ namespace ts {
|
||||
// With this information, we can infer some module resolutions without performing resolution.
|
||||
|
||||
/** An ordered list of module names for which we cannot recover the resolution. */
|
||||
let unknownModuleNames: string[] | undefined;
|
||||
let unknownModuleNames: StringLiteralLike[] | undefined;
|
||||
/**
|
||||
* The indexing of elements in this list matches that of `moduleNames`.
|
||||
*
|
||||
@@ -1573,7 +1588,7 @@ namespace ts {
|
||||
* * ResolvedModuleFull instance: can be reused.
|
||||
*/
|
||||
let result: (ResolvedModuleFull | undefined)[] | undefined;
|
||||
let reusedNames: string[] | undefined;
|
||||
let reusedNames: StringLiteralLike[] | undefined;
|
||||
/** A transient placeholder used to mark predicted resolution in the result list. */
|
||||
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any;
|
||||
|
||||
@@ -1581,21 +1596,22 @@ namespace ts {
|
||||
const moduleName = moduleNames[i];
|
||||
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
|
||||
if (file === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path)) {
|
||||
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i));
|
||||
const mode = getModeForUsageLocation(file, moduleName);
|
||||
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName.text, mode);
|
||||
if (oldResolvedModule) {
|
||||
if (isTraceEnabled(options, host)) {
|
||||
trace(host,
|
||||
oldResolvedModule.packageId ?
|
||||
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
|
||||
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2,
|
||||
moduleName,
|
||||
moduleName.text,
|
||||
getNormalizedAbsolutePath(file.originalFileName, currentDirectory),
|
||||
oldResolvedModule.resolvedFileName,
|
||||
oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId)
|
||||
);
|
||||
}
|
||||
(result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule;
|
||||
(reusedNames || (reusedNames = [])).push(moduleName);
|
||||
(result ??= new Array(moduleNames.length))[i] = oldResolvedModule;
|
||||
(reusedNames ??= []).push(moduleName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1604,14 +1620,14 @@ namespace ts {
|
||||
// - resolved to an ambient module in the old program whose declaration is in an unmodified file
|
||||
// (so the same module declaration will land in the new program)
|
||||
let resolvesToAmbientModuleInNonModifiedFile = false;
|
||||
if (contains(file.ambientModuleNames, moduleName)) {
|
||||
if (contains(file.ambientModuleNames, moduleName.text)) {
|
||||
resolvesToAmbientModuleInNonModifiedFile = true;
|
||||
if (isTraceEnabled(options, host)) {
|
||||
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory));
|
||||
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory));
|
||||
}
|
||||
}
|
||||
else {
|
||||
resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, i);
|
||||
resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName);
|
||||
}
|
||||
|
||||
if (resolvesToAmbientModuleInNonModifiedFile) {
|
||||
@@ -1619,12 +1635,12 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
|
||||
(unknownModuleNames || (unknownModuleNames = [])).push(moduleName);
|
||||
(unknownModuleNames ??= []).push(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
const resolutions = unknownModuleNames && unknownModuleNames.length
|
||||
? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames)
|
||||
? resolveModuleNamesWorker(unknownModuleNames, file, { names: unknownModuleNames, reusedNames })
|
||||
: emptyArray;
|
||||
|
||||
// Combine results of resolutions and predicted results
|
||||
@@ -1654,9 +1670,8 @@ namespace ts {
|
||||
|
||||
// If we change our policy of rechecking failed lookups on each program create,
|
||||
// we should adjust the value returned here.
|
||||
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, index: number): boolean {
|
||||
if (index >= length(oldSourceFile?.imports) + length(oldSourceFile?.moduleAugmentations)) return false; // mode index out of bounds, don't reuse resolution
|
||||
const resolutionToFile = getResolvedModule(oldSourceFile, moduleName, oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, index));
|
||||
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: StringLiteralLike): boolean {
|
||||
const resolutionToFile = getResolvedModule(oldSourceFile, moduleName.text, getModeForUsageLocation(file, moduleName));
|
||||
const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName);
|
||||
if (resolutionToFile && resolvedFile) {
|
||||
// In the old program, we resolved to an ambient module that was in the same
|
||||
@@ -1667,14 +1682,14 @@ namespace ts {
|
||||
}
|
||||
|
||||
// at least one of declarations should come from non-modified source file
|
||||
const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName);
|
||||
const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName.text);
|
||||
|
||||
if (!unmodifiedFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isTraceEnabled(options, host)) {
|
||||
trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile);
|
||||
trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName.text, unmodifiedFile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1874,7 +1889,7 @@ namespace ts {
|
||||
const moduleNames = getModuleNames(newSourceFile);
|
||||
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile);
|
||||
// ensure that module resolution results are still correct
|
||||
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, oldSourceFile, moduleResolutionIsEqualTo);
|
||||
const resolutionsChanged = hasChangesInResolutions(moduleNames, newSourceFile, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
|
||||
if (resolutionsChanged) {
|
||||
structureIsReused = StructureIsReused.SafeModules;
|
||||
newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions);
|
||||
@@ -1885,7 +1900,7 @@ namespace ts {
|
||||
const typesReferenceDirectives = newSourceFile.typeReferenceDirectives;
|
||||
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile);
|
||||
// ensure that types resolutions are still correct
|
||||
const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, oldSourceFile, typeDirectiveIsEqualTo);
|
||||
const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
|
||||
if (typeReferenceResolutionsChanged) {
|
||||
structureIsReused = StructureIsReused.SafeModules;
|
||||
newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions);
|
||||
@@ -3288,7 +3303,7 @@ namespace ts {
|
||||
const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options;
|
||||
for (let index = 0; index < moduleNames.length; index++) {
|
||||
const resolution = resolutions[index];
|
||||
setResolvedModule(file, moduleNames[index], resolution, getModeForResolutionAtIndex(file, index));
|
||||
setResolvedModule(file, moduleNames[index].text, resolution, getModeForUsageLocation(file, moduleNames[index]));
|
||||
|
||||
if (!resolution) {
|
||||
continue;
|
||||
@@ -4379,11 +4394,11 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] {
|
||||
const res = imports.map(i => i.text);
|
||||
function getModuleNames({ imports, moduleAugmentations }: SourceFile): StringLiteralLike[] {
|
||||
const res = imports.map(i => i);
|
||||
for (const aug of moduleAugmentations) {
|
||||
if (aug.kind === SyntaxKind.StringLiteral) {
|
||||
res.push(aug.text);
|
||||
res.push(aug);
|
||||
}
|
||||
// Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user