mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-16 15:51:35 -05:00
TypeReference directive reuse (#49750)
This commit is contained in:
@@ -58,7 +58,7 @@ import {
|
||||
targetOptionDeclaration, toFileNameLowerCase, tokenToString, trace, tracing, trimStringEnd, TsConfigSourceFile,
|
||||
TypeChecker, typeDirectiveIsEqualTo, TypeReferenceDirectiveResolutionCache, UnparsedSource, VariableDeclaration,
|
||||
VariableStatement, walkUpParenthesizedExpressions, WriteFileCallback, WriteFileCallbackData,
|
||||
writeFileEnsuringDirectories, zipToModeAwareCache,
|
||||
writeFileEnsuringDirectories, zipToModeAwareCache, TypeReferenceDirectiveResolutionInfo, getResolvedTypeReferenceDirective,
|
||||
} from "./_namespaces/ts";
|
||||
import * as performance from "./_namespaces/ts.performance";
|
||||
|
||||
@@ -564,8 +564,7 @@ export function loadWithTypeDirectiveCache<T>(names: string[] | readonly FileRef
|
||||
for (const name of names) {
|
||||
let result: T;
|
||||
const mode = getModeForFileReference(name, containingFileMode);
|
||||
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
|
||||
const strName = isString(name) ? name : name.fileName.toLowerCase();
|
||||
const strName = getResolutionName(name);
|
||||
const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName;
|
||||
if (cache.has(cacheKey)) {
|
||||
result = cache.get(cacheKey)!;
|
||||
@@ -1202,9 +1201,16 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader);
|
||||
}
|
||||
|
||||
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
|
||||
let actualResolveTypeReferenceDirectiveNamesWorker: (
|
||||
typeDirectiveNames: string[] | readonly FileReference[],
|
||||
containingFile: string,
|
||||
redirectedReference: ResolvedProjectReference | undefined,
|
||||
containingFileMode: SourceFile["impliedNodeFormat"] | undefined,
|
||||
resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined,
|
||||
) => (ResolvedTypeReferenceDirective | undefined)[];
|
||||
if (host.resolveTypeReferenceDirectives) {
|
||||
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode);
|
||||
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode, resolutionInfo) =>
|
||||
host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode, resolutionInfo);
|
||||
}
|
||||
else {
|
||||
typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache());
|
||||
@@ -1318,7 +1324,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
// This containingFilename needs to match with the one used in managed-side
|
||||
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory();
|
||||
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
|
||||
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename);
|
||||
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeReferences, containingFilename);
|
||||
for (let i = 0; i < typeReferences.length; i++) {
|
||||
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
|
||||
processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId });
|
||||
@@ -1516,14 +1522,14 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
return result;
|
||||
}
|
||||
|
||||
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
|
||||
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile, resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined): readonly (ResolvedTypeReferenceDirective | undefined)[] {
|
||||
if (!typeDirectiveNames.length) return [];
|
||||
const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile;
|
||||
const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined;
|
||||
const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined;
|
||||
tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName });
|
||||
performance.mark("beforeResolveTypeReference");
|
||||
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode);
|
||||
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode, resolutionInfo);
|
||||
performance.mark("afterResolveTypeReference");
|
||||
performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference");
|
||||
tracing?.pop();
|
||||
@@ -1759,6 +1765,96 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[];
|
||||
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[], containingFile: string): readonly (ResolvedTypeReferenceDirective | undefined)[];
|
||||
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
|
||||
if (structureIsReused === StructureIsReused.Not) {
|
||||
// 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 resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFile, /*resolutionInfo*/ undefined);
|
||||
}
|
||||
|
||||
const oldSourceFile = !isString(containingFile) ? oldProgram && oldProgram.getSourceFile(containingFile.fileName) : undefined;
|
||||
if (!isString(containingFile)) {
|
||||
if (oldSourceFile !== containingFile && containingFile.resolvedTypeReferenceDirectiveNames) {
|
||||
// `file` was created for the new program.
|
||||
//
|
||||
// We only set `file.resolvedTypeReferenceDirectiveNames` via work from the current function,
|
||||
// so it is defined iff we already called the current function on `file`.
|
||||
// That call happened no later than the creation of the `file` object,
|
||||
// which per above occurred during the current program creation.
|
||||
// Since we assume the filesystem does not change during program creation,
|
||||
// it is safe to reuse resolutions from the earlier call.
|
||||
const result: (ResolvedTypeReferenceDirective | undefined)[] = [];
|
||||
for (const typeDirectiveName of typeDirectiveNames as readonly FileReference[]) {
|
||||
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
|
||||
const resolvedTypeReferenceDirective = containingFile.resolvedTypeReferenceDirectiveNames.get(getResolutionName(typeDirectiveName), typeDirectiveName.resolutionMode || containingFile.impliedNodeFormat);
|
||||
result.push(resolvedTypeReferenceDirective);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** An ordered list of module names for which we cannot recover the resolution. */
|
||||
let unknownTypeReferenceDirectiveNames: string[] | FileReference[] | undefined;
|
||||
let result: (ResolvedTypeReferenceDirective | undefined)[] | undefined;
|
||||
let reusedNames: (string | FileReference)[] | undefined;
|
||||
const containingSourceFile = !isString(containingFile) ? containingFile : undefined;
|
||||
const canReuseResolutions = !isString(containingFile) ?
|
||||
containingFile === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path) :
|
||||
!hasInvalidatedResolutions(toPath(containingFile));
|
||||
for (let i = 0; i < typeDirectiveNames.length; i++) {
|
||||
const entry = typeDirectiveNames[i];
|
||||
if (canReuseResolutions) {
|
||||
const typeDirectiveName = getResolutionName(entry);
|
||||
const mode = getModeForFileReference(entry, containingSourceFile?.impliedNodeFormat);
|
||||
const oldResolvedTypeReferenceDirective = getResolvedTypeReferenceDirective(oldSourceFile, typeDirectiveName, mode);
|
||||
if (oldResolvedTypeReferenceDirective) {
|
||||
if (isTraceEnabled(options, host)) {
|
||||
trace(host,
|
||||
oldResolvedTypeReferenceDirective.packageId ?
|
||||
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
|
||||
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2,
|
||||
typeDirectiveName,
|
||||
!isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile,
|
||||
oldResolvedTypeReferenceDirective.resolvedFileName,
|
||||
oldResolvedTypeReferenceDirective.packageId && packageIdToString(oldResolvedTypeReferenceDirective.packageId)
|
||||
);
|
||||
}
|
||||
(result ??= new Array(typeDirectiveNames.length))[i] = oldResolvedTypeReferenceDirective;
|
||||
(reusedNames ??= []).push(entry);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
|
||||
(unknownTypeReferenceDirectiveNames ??= []).push(entry as FileReference & string);
|
||||
}
|
||||
|
||||
if (!unknownTypeReferenceDirectiveNames) return result || emptyArray;
|
||||
const resolutions = resolveTypeReferenceDirectiveNamesWorker(
|
||||
unknownTypeReferenceDirectiveNames,
|
||||
containingFile,
|
||||
{ names: unknownTypeReferenceDirectiveNames, reusedNames }
|
||||
);
|
||||
|
||||
// Combine results of resolutions
|
||||
if (!result) {
|
||||
// There were no unresolved resolutions.
|
||||
Debug.assert(resolutions.length === typeDirectiveNames.length);
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (!result[i]) {
|
||||
result[i] = resolutions[j];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
Debug.assert(j === resolutions.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
function canReuseProjectReferences(): boolean {
|
||||
return !forEachProjectReference(
|
||||
oldProgram!.getProjectReferences(),
|
||||
@@ -1962,7 +2058,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
|
||||
}
|
||||
const typesReferenceDirectives = newSourceFile.typeReferenceDirectives;
|
||||
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile);
|
||||
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typesReferenceDirectives, newSourceFile);
|
||||
// ensure that types resolutions are still correct
|
||||
const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
|
||||
if (typeReferenceResolutionsChanged) {
|
||||
@@ -3229,13 +3325,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
return;
|
||||
}
|
||||
|
||||
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file);
|
||||
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectives, file);
|
||||
for (let index = 0; index < typeDirectives.length; index++) {
|
||||
const ref = file.typeReferenceDirectives[index];
|
||||
const resolvedTypeReferenceDirective = resolutions[index];
|
||||
// store resolved type directive on the file
|
||||
const fileName = toFileNameLowerCase(ref.fileName);
|
||||
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective);
|
||||
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective, getModeForFileReference(ref, file.impliedNodeFormat));
|
||||
const mode = ref.resolutionMode || file.impliedNodeFormat;
|
||||
if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) {
|
||||
programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext));
|
||||
|
||||
Reference in New Issue
Block a user