Skip to content

Commit c80a83e

Browse files
committed
Schedule failed lookup updates
1 parent 9725d62 commit c80a83e

File tree

12 files changed

+149
-56
lines changed

12 files changed

+149
-56
lines changed

src/compiler/program.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,11 +554,11 @@ namespace ts {
554554
getSourceVersion: (path: Path, fileName: string) => string | undefined,
555555
fileExists: (fileName: string) => boolean,
556556
hasInvalidatedResolution: HasInvalidatedResolution,
557-
hasChangedAutomaticTypeDirectiveNames: boolean,
557+
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
558558
projectReferences: readonly ProjectReference[] | undefined
559559
): boolean {
560560
// If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date
561-
if (!program || hasChangedAutomaticTypeDirectiveNames) {
561+
if (!program || hasChangedAutomaticTypeDirectiveNames?.()) {
562562
return false;
563563
}
564564

@@ -1424,7 +1424,7 @@ namespace ts {
14241424
return oldProgram.structureIsReused;
14251425
}
14261426

1427-
if (host.hasChangedAutomaticTypeDirectiveNames) {
1427+
if (host.hasChangedAutomaticTypeDirectiveNames?.()) {
14281428
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
14291429
}
14301430

src/compiler/resolutionCache.ts

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ namespace ts {
99
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined;
1010
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
1111

12+
invalidateResolutionsOfFailedLookupLocations(): boolean;
1213
invalidateResolutionOfFile(filePath: Path): void;
1314
removeResolutionsOfFile(filePath: Path): void;
1415
removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
1516
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map<readonly string[]>): void;
1617
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
18+
hasChangedAutomaticTypeDirectiveNames(): boolean;
1719

1820
startCachingPerDirectoryResolution(): void;
1921
finishCachingPerDirectoryResolution(): void;
@@ -50,6 +52,7 @@ namespace ts {
5052
onInvalidatedResolution(): void;
5153
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
5254
onChangedAutomaticTypeDirectiveNames(): void;
55+
scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
5356
getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
5457
projectName?: string;
5558
getGlobalCache?(): string | undefined;
@@ -147,6 +150,11 @@ namespace ts {
147150
const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = [];
148151
const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();
149152

153+
let hasChangedAutomaticTypeDirectiveNames = false;
154+
const failedLookupChecks: Path[] = [];
155+
const startsWithPathChecks: Path[] = [];
156+
const isInDirectoryChecks: Path[] = [];
157+
150158
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
151159
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();
152160

@@ -195,7 +203,9 @@ namespace ts {
195203
resolveTypeReferenceDirectives,
196204
removeResolutionsFromProjectReferenceRedirects,
197205
removeResolutionsOfFile,
206+
hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames,
198207
invalidateResolutionOfFile,
208+
invalidateResolutionsOfFailedLookupLocations,
199209
setFilesWithInvalidatedNonRelativeUnresolvedImports,
200210
createHasInvalidatedResolution,
201211
updateTypeRootsWatch,
@@ -227,9 +237,13 @@ namespace ts {
227237
resolvedTypeReferenceDirectives.clear();
228238
resolvedFileToResolution.clear();
229239
resolutionsWithFailedLookups.length = 0;
240+
failedLookupChecks.length = 0;
241+
startsWithPathChecks.length = 0;
242+
isInDirectoryChecks.length = 0;
230243
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
231244
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
232245
clearPerDirectoryResolutions();
246+
hasChangedAutomaticTypeDirectiveNames = false;
233247
}
234248

235249
function startRecordingFilesWithChangedResolutions() {
@@ -253,6 +267,8 @@ namespace ts {
253267
}
254268

255269
function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
270+
// Ensure pending resolutions are applied
271+
invalidateResolutionsOfFailedLookupLocations();
256272
if (forceAllFilesAsInvalidated) {
257273
// Any file asked would have invalidated resolution
258274
filesWithInvalidatedResolutions = undefined;
@@ -281,6 +297,7 @@ namespace ts {
281297
watcher.watcher.close();
282298
}
283299
});
300+
hasChangedAutomaticTypeDirectiveNames = false;
284301
}
285302

286303
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations {
@@ -662,9 +679,7 @@ namespace ts {
662679
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
663680
}
664681

665-
if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
666-
resolutionHost.onInvalidatedResolution();
667-
}
682+
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
668683
}, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive);
669684
}
670685

@@ -700,36 +715,42 @@ namespace ts {
700715
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
701716
}
702717

703-
function invalidateResolution(resolution: ResolutionWithFailedLookupLocations) {
704-
resolution.isInvalidated = true;
705-
let changedInAutoTypeReferenced = false;
706-
for (const containingFilePath of Debug.assertDefined(resolution.files)) {
707-
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
708-
// When its a file with inferred types resolution, invalidate type reference directive resolution
709-
changedInAutoTypeReferenced = changedInAutoTypeReferenced || containingFilePath.endsWith(inferredTypesContainingFile);
710-
}
711-
if (changedInAutoTypeReferenced) {
712-
resolutionHost.onChangedAutomaticTypeDirectiveNames();
718+
function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) {
719+
if (!resolutions) return false;
720+
let invalidated = false;
721+
for (const resolution of resolutions) {
722+
if (resolution.isInvalidated || !canInvalidate(resolution)) continue;
723+
resolution.isInvalidated = invalidated = true;
724+
for (const containingFilePath of Debug.assertDefined(resolution.files)) {
725+
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
726+
// When its a file with inferred types resolution, invalidate type reference directive resolution
727+
hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || containingFilePath.endsWith(inferredTypesContainingFile);
728+
}
713729
}
730+
return invalidated;
714731
}
715732

716733
function invalidateResolutionOfFile(filePath: Path) {
717734
removeResolutionsOfFile(filePath);
718735
// Resolution is invalidated if the resulting file name is same as the deleted file path
719-
forEach(resolvedFileToResolution.get(filePath), invalidateResolution);
736+
const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
737+
if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) &&
738+
hasChangedAutomaticTypeDirectiveNames &&
739+
!prevHasChangedAutomaticTypeDirectiveNames) {
740+
resolutionHost.onChangedAutomaticTypeDirectiveNames();
741+
}
720742
}
721743

722744
function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyMap<readonly string[]>) {
723745
Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
724746
filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
725747
}
726748

727-
function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
728-
let isChangedFailedLookupLocation: (location: string) => boolean;
749+
function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
729750
if (isCreatingWatchedDirectory) {
730751
// Watching directory is created
731752
// Invalidate any resolution has failed lookup in this directory
732-
isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location));
753+
isInDirectoryChecks.push(fileOrDirectoryPath);
733754
}
734755
else {
735756
// If something to do with folder/file starting with "." in node_modules folder, skip it
@@ -748,10 +769,8 @@ namespace ts {
748769
if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) ||
749770
isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
750771
// Invalidate any resolution from this directory
751-
isChangedFailedLookupLocation = location => {
752-
const locationPath = resolutionHost.toPath(location);
753-
return locationPath === fileOrDirectoryPath || startsWith(resolutionHost.toPath(location), fileOrDirectoryPath);
754-
};
772+
failedLookupChecks.push(fileOrDirectoryPath);
773+
startsWithPathChecks.push(fileOrDirectoryPath);
755774
}
756775
else {
757776
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
@@ -762,20 +781,33 @@ namespace ts {
762781
return false;
763782
}
764783
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
765-
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
784+
failedLookupChecks.push(fileOrDirectoryPath);
766785
}
767786
}
768-
let invalidated = false;
769-
// Resolution is invalidated if the resulting file name is same as the deleted file path
770-
for (const resolution of resolutionsWithFailedLookups) {
771-
if (resolution.failedLookupLocations.some(isChangedFailedLookupLocation)) {
772-
invalidateResolution(resolution);
773-
invalidated = true;
774-
}
787+
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
788+
}
789+
790+
function invalidateResolutionsOfFailedLookupLocations() {
791+
if (!failedLookupChecks.length && !startsWithPathChecks.length && !isInDirectoryChecks.length) {
792+
return false;
775793
}
794+
795+
const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution);
796+
failedLookupChecks.length = 0;
797+
startsWithPathChecks.length = 0;
798+
isInDirectoryChecks.length = 0;
776799
return invalidated;
777800
}
778801

802+
function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
803+
return resolution.failedLookupLocations.some(location => {
804+
const locationPath = resolutionHost.toPath(location);
805+
return contains(failedLookupChecks, locationPath) ||
806+
startsWithPathChecks.some(fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath)) ||
807+
isInDirectoryChecks.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
808+
});
809+
}
810+
779811
function closeTypeRootsWatch() {
780812
clearMap(typeRootsWatches, closeFileWatcher);
781813
}
@@ -800,13 +832,14 @@ namespace ts {
800832
// For now just recompile
801833
// We could potentially store more data here about whether it was/would be really be used or not
802834
// and with that determine to trigger compilation but for now this is enough
835+
hasChangedAutomaticTypeDirectiveNames = true;
803836
resolutionHost.onChangedAutomaticTypeDirectiveNames();
804837

805838
// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
806839
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
807840
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
808-
if (dirPath && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
809-
resolutionHost.onInvalidatedResolution();
841+
if (dirPath) {
842+
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
810843
}
811844
}, WatchDirectoryFlags.Recursive);
812845
}

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5690,6 +5690,8 @@ namespace ts {
56905690

56915691
/* @internal */
56925692
export type HasInvalidatedResolution = (sourceFile: Path) => boolean;
5693+
/* @internal */
5694+
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;
56935695

56945696
export interface CompilerHost extends ModuleResolutionHost {
56955697
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
@@ -5719,7 +5721,7 @@ namespace ts {
57195721
getEnvironmentVariable?(name: string): string | undefined;
57205722
/* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void;
57215723
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
5722-
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
5724+
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames;
57235725
createHash?(data: string): string;
57245726
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
57255727
/* @internal */ useSourceOfProjectReferenceRedirect?(): boolean;

0 commit comments

Comments
 (0)