Skip to content

Commit a9e619c

Browse files
committed
TypeReference directive reuse
1 parent 7b0df1f commit a9e619c

File tree

13 files changed

+621
-68
lines changed

13 files changed

+621
-68
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,9 +773,9 @@ namespace ts {
773773
}
774774

775775
/* @internal */
776-
export function getResolutionName(entry: FileReference | StringLiteralLike) {
776+
export function getResolutionName(entry: string | FileReference | StringLiteralLike) {
777777
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
778-
return isStringLiteralLike(entry) ? entry.text : entry.fileName.toLowerCase();
778+
return !isString(entry) ? isStringLiteralLike(entry) ? entry.text : toFileNameLowerCase(entry.fileName) : entry;
779779
}
780780

781781
/* @internal */

src/compiler/program.ts

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,7 @@ namespace ts {
501501
for (const name of names) {
502502
let result: T;
503503
const mode = getModeForFileReference(name, containingFileMode);
504-
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
505-
const strName = isString(name) ? name : name.fileName.toLowerCase();
504+
const strName = getResolutionName(name);
506505
const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName;
507506
if (cache.has(cacheKey)) {
508507
result = cache.get(cacheKey)!;
@@ -1138,9 +1137,16 @@ namespace ts {
11381137
loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader);
11391138
}
11401139

1141-
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
1140+
let actualResolveTypeReferenceDirectiveNamesWorker: (
1141+
typeDirectiveNames: string[] | readonly FileReference[],
1142+
containingFile: string,
1143+
redirectedReference: ResolvedProjectReference | undefined,
1144+
containingFileMode: SourceFile["impliedNodeFormat"] | undefined,
1145+
resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined,
1146+
) => (ResolvedTypeReferenceDirective | undefined)[];
11421147
if (host.resolveTypeReferenceDirectives) {
1143-
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode);
1148+
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode, resolutionInfo) =>
1149+
host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode, resolutionInfo);
11441150
}
11451151
else {
11461152
typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache());
@@ -1254,7 +1260,7 @@ namespace ts {
12541260
// This containingFilename needs to match with the one used in managed-side
12551261
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory();
12561262
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
1257-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename);
1263+
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeReferences, containingFilename);
12581264
for (let i = 0; i < typeReferences.length; i++) {
12591265
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
12601266
processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId });
@@ -1452,14 +1458,14 @@ namespace ts {
14521458
return result;
14531459
}
14541460

1455-
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1461+
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile, resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined): readonly (ResolvedTypeReferenceDirective | undefined)[] {
14561462
if (!typeDirectiveNames.length) return [];
14571463
const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile;
14581464
const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined;
14591465
const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined;
14601466
tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName });
14611467
performance.mark("beforeResolveTypeReference");
1462-
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode);
1468+
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode, resolutionInfo);
14631469
performance.mark("afterResolveTypeReference");
14641470
performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference");
14651471
tracing?.pop();
@@ -1695,6 +1701,96 @@ namespace ts {
16951701
}
16961702
}
16971703

1704+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[];
1705+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[], containingFile: string): readonly (ResolvedTypeReferenceDirective | undefined)[];
1706+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1707+
if (structureIsReused === StructureIsReused.Not) {
1708+
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
1709+
// the best we can do is fallback to the default logic.
1710+
return resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFile, /*resolutionInfo*/ undefined);
1711+
}
1712+
1713+
const oldSourceFile = !isString(containingFile) ? oldProgram && oldProgram.getSourceFile(containingFile.fileName) : undefined;
1714+
if (!isString(containingFile)) {
1715+
if (oldSourceFile !== containingFile && containingFile.resolvedTypeReferenceDirectiveNames) {
1716+
// `file` was created for the new program.
1717+
//
1718+
// We only set `file.resolvedTypeReferenceDirectiveNames` via work from the current function,
1719+
// so it is defined iff we already called the current function on `file`.
1720+
// That call happened no later than the creation of the `file` object,
1721+
// which per above occurred during the current program creation.
1722+
// Since we assume the filesystem does not change during program creation,
1723+
// it is safe to reuse resolutions from the earlier call.
1724+
const result: (ResolvedTypeReferenceDirective | undefined)[] = [];
1725+
for (const typeDirectiveName of typeDirectiveNames as readonly FileReference[]) {
1726+
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1727+
const resolvedTypeReferenceDirective = containingFile.resolvedTypeReferenceDirectiveNames.get(getResolutionName(typeDirectiveName), typeDirectiveName.resolutionMode || containingFile.impliedNodeFormat);
1728+
result.push(resolvedTypeReferenceDirective);
1729+
}
1730+
return result;
1731+
}
1732+
}
1733+
1734+
/** An ordered list of module names for which we cannot recover the resolution. */
1735+
let unknownTypeReferenceDirectiveNames: string[] | FileReference[] | undefined;
1736+
let result: (ResolvedTypeReferenceDirective | undefined)[] | undefined;
1737+
let reusedNames: (string | FileReference)[] | undefined;
1738+
const containingSourceFile = !isString(containingFile) ? containingFile : undefined;
1739+
const canReuseResolutions = !isString(containingFile) ?
1740+
containingFile === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path) :
1741+
!hasInvalidatedResolutions(toPath(containingFile));
1742+
for (let i = 0; i < typeDirectiveNames.length; i++) {
1743+
const entry = typeDirectiveNames[i];
1744+
if (canReuseResolutions) {
1745+
const typeDirectiveName = getResolutionName(entry);
1746+
const mode = getModeForFileReference(entry, containingSourceFile?.impliedNodeFormat);
1747+
const oldResolvedTypeReferenceDirective = getResolvedTypeReferenceDirective(oldSourceFile, typeDirectiveName, mode);
1748+
if (oldResolvedTypeReferenceDirective) {
1749+
if (isTraceEnabled(options, host)) {
1750+
trace(host,
1751+
oldResolvedTypeReferenceDirective.packageId ?
1752+
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
1753+
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2,
1754+
typeDirectiveName,
1755+
!isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile,
1756+
oldResolvedTypeReferenceDirective.resolvedFileName,
1757+
oldResolvedTypeReferenceDirective.packageId && packageIdToString(oldResolvedTypeReferenceDirective.packageId)
1758+
);
1759+
}
1760+
(result ??= new Array(typeDirectiveNames.length))[i] = oldResolvedTypeReferenceDirective;
1761+
(reusedNames ??= []).push(entry);
1762+
continue;
1763+
}
1764+
}
1765+
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
1766+
(unknownTypeReferenceDirectiveNames ??= []).push(entry as FileReference & string);
1767+
}
1768+
1769+
if (!unknownTypeReferenceDirectiveNames) return result || emptyArray;
1770+
const resolutions = resolveTypeReferenceDirectiveNamesWorker(
1771+
unknownTypeReferenceDirectiveNames,
1772+
containingFile,
1773+
{ names: unknownTypeReferenceDirectiveNames, reusedNames }
1774+
);
1775+
1776+
// Combine results of resolutions
1777+
if (!result) {
1778+
// There were no unresolved resolutions.
1779+
Debug.assert(resolutions.length === typeDirectiveNames.length);
1780+
return resolutions;
1781+
}
1782+
1783+
let j = 0;
1784+
for (let i = 0; i < result.length; i++) {
1785+
if (!result[i]) {
1786+
result[i] = resolutions[j];
1787+
j++;
1788+
}
1789+
}
1790+
Debug.assert(j === resolutions.length);
1791+
return result;
1792+
}
1793+
16981794
function canReuseProjectReferences(): boolean {
16991795
return !forEachProjectReference(
17001796
oldProgram!.getProjectReferences(),
@@ -1898,7 +1994,7 @@ namespace ts {
18981994
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
18991995
}
19001996
const typesReferenceDirectives = newSourceFile.typeReferenceDirectives;
1901-
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile);
1997+
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typesReferenceDirectives, newSourceFile);
19021998
// ensure that types resolutions are still correct
19031999
const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
19042000
if (typeReferenceResolutionsChanged) {
@@ -3165,13 +3261,13 @@ namespace ts {
31653261
return;
31663262
}
31673263

3168-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file);
3264+
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectives, file);
31693265
for (let index = 0; index < typeDirectives.length; index++) {
31703266
const ref = file.typeReferenceDirectives[index];
31713267
const resolvedTypeReferenceDirective = resolutions[index];
31723268
// store resolved type directive on the file
31733269
const fileName = toFileNameLowerCase(ref.fileName);
3174-
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective);
3270+
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective, getModeForFileReference(ref, file.impliedNodeFormat));
31753271
const mode = ref.resolutionMode || file.impliedNodeFormat;
31763272
if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) {
31773273
programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext));

src/compiler/resolutionCache.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ namespace ts {
1414
resolutionInfo: ModuleResolutionInfo | undefined
1515
): (ResolvedModuleFull | undefined)[];
1616
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined;
17-
resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[];
17+
resolveTypeReferenceDirectives(
18+
typeDirectiveNames: string[] | readonly FileReference[],
19+
containingFile: string,
20+
redirectedReference: ResolvedProjectReference | undefined,
21+
containingFileMode: SourceFile["impliedNodeFormat"] | undefined,
22+
resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined,
23+
): (ResolvedTypeReferenceDirective | undefined)[];
1824

1925
invalidateResolutionsOfFailedLookupLocations(): boolean;
2026
invalidateResolutionOfFile(filePath: Path): void;
@@ -416,7 +422,7 @@ namespace ts {
416422
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
417423
shouldRetryResolution: (t: T) => boolean;
418424
reusedNames?: readonly string[];
419-
resolutionInfo?: ModuleResolutionInfo;
425+
resolutionInfo?: ModuleResolutionInfo | TypeReferenceDirectiveResolutionInfo;
420426
logChanges?: boolean;
421427
containingSourceFile?: SourceFile;
422428
containingSourceFileMode?: SourceFile["impliedNodeFormat"];
@@ -450,7 +456,7 @@ namespace ts {
450456
const seenNamesInFile = createModeAwareCache<true>();
451457
let i = 0;
452458
for (const entry of containingSourceFile && resolutionInfo ? resolutionInfo.names : names) {
453-
const name = !isString(entry) ? getResolutionName(entry) : entry;
459+
const name = getResolutionName(entry);
454460
// Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant
455461
// they require calculating the mode for a given import from it's position in the resolution table, since a given
456462
// import's syntax may override the file's default mode.
@@ -547,7 +553,13 @@ namespace ts {
547553
}
548554

549555
if (containingSourceFile && resolutionInfo) {
550-
resolutionInfo.reusedNames?.forEach(literal => seenNamesInFile.set(literal.text, getModeForUsageLocation(containingSourceFile, literal), true));
556+
resolutionInfo.reusedNames?.forEach(entry => seenNamesInFile.set(
557+
getResolutionName(entry),
558+
!isString(entry) && isStringLiteralLike(entry) ?
559+
getModeForUsageLocation(containingSourceFile, entry) :
560+
getModeForFileReference(entry, containingSourceFile.impliedNodeFormat),
561+
true,
562+
));
551563
reusedNames = undefined;
552564
}
553565
if (resolutionsInFile.size() !== seenNamesInFile.size()) {
@@ -581,7 +593,13 @@ namespace ts {
581593
}
582594
}
583595

584-
function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] {
596+
function resolveTypeReferenceDirectives(
597+
typeDirectiveNames: string[] | readonly FileReference[],
598+
containingFile: string,
599+
redirectedReference?: ResolvedProjectReference,
600+
containingFileMode?: SourceFile["impliedNodeFormat"],
601+
resolutionInfo?: TypeReferenceDirectiveResolutionInfo,
602+
): (ResolvedTypeReferenceDirective | undefined)[] {
585603
return resolveNamesWithLocalCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective>({
586604
names: typeDirectiveNames,
587605
containingFile,
@@ -591,7 +609,8 @@ namespace ts {
591609
loader: resolveTypeReferenceDirective,
592610
getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
593611
shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
594-
containingSourceFileMode: containingFileMode
612+
containingSourceFileMode: containingFileMode,
613+
resolutionInfo,
595614
});
596615
}
597616

0 commit comments

Comments
 (0)