Skip to content

Schedule failed lookup updates #38560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,11 @@ namespace ts {
getSourceVersion: (path: Path, fileName: string) => string | undefined,
fileExists: (fileName: string) => boolean,
hasInvalidatedResolution: HasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: boolean,
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
projectReferences: readonly ProjectReference[] | undefined
): boolean {
// If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date
if (!program || hasChangedAutomaticTypeDirectiveNames) {
if (!program || hasChangedAutomaticTypeDirectiveNames?.()) {
return false;
}

Expand Down Expand Up @@ -1424,7 +1424,7 @@ namespace ts {
return oldProgram.structureIsReused;
}

if (host.hasChangedAutomaticTypeDirectiveNames) {
if (host.hasChangedAutomaticTypeDirectiveNames?.()) {
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
}

Expand Down
95 changes: 64 additions & 31 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ namespace ts {
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined;
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];

invalidateResolutionsOfFailedLookupLocations(): boolean;
invalidateResolutionOfFile(filePath: Path): void;
removeResolutionsOfFile(filePath: Path): void;
removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map<readonly string[]>): void;
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
hasChangedAutomaticTypeDirectiveNames(): boolean;

startCachingPerDirectoryResolution(): void;
finishCachingPerDirectoryResolution(): void;
Expand Down Expand Up @@ -50,6 +52,7 @@ namespace ts {
onInvalidatedResolution(): void;
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
onChangedAutomaticTypeDirectiveNames(): void;
scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
projectName?: string;
getGlobalCache?(): string | undefined;
Expand Down Expand Up @@ -147,6 +150,11 @@ namespace ts {
const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = [];
const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();

let hasChangedAutomaticTypeDirectiveNames = false;
const failedLookupChecks: Path[] = [];
const startsWithPathChecks: Path[] = [];
const isInDirectoryChecks: Path[] = [];

const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();

Expand Down Expand Up @@ -195,7 +203,9 @@ namespace ts {
resolveTypeReferenceDirectives,
removeResolutionsFromProjectReferenceRedirects,
removeResolutionsOfFile,
hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames,
invalidateResolutionOfFile,
invalidateResolutionsOfFailedLookupLocations,
setFilesWithInvalidatedNonRelativeUnresolvedImports,
createHasInvalidatedResolution,
updateTypeRootsWatch,
Expand Down Expand Up @@ -227,9 +237,13 @@ namespace ts {
resolvedTypeReferenceDirectives.clear();
resolvedFileToResolution.clear();
resolutionsWithFailedLookups.length = 0;
failedLookupChecks.length = 0;
startsWithPathChecks.length = 0;
isInDirectoryChecks.length = 0;
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
clearPerDirectoryResolutions();
hasChangedAutomaticTypeDirectiveNames = false;
}

function startRecordingFilesWithChangedResolutions() {
Expand All @@ -253,6 +267,8 @@ namespace ts {
}

function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
// Ensure pending resolutions are applied
invalidateResolutionsOfFailedLookupLocations();
if (forceAllFilesAsInvalidated) {
// Any file asked would have invalidated resolution
filesWithInvalidatedResolutions = undefined;
Expand Down Expand Up @@ -281,6 +297,7 @@ namespace ts {
watcher.watcher.close();
}
});
hasChangedAutomaticTypeDirectiveNames = false;
}

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

if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
resolutionHost.onInvalidatedResolution();
}
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
}, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive);
}

Expand Down Expand Up @@ -700,36 +715,42 @@ namespace ts {
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
}

function invalidateResolution(resolution: ResolutionWithFailedLookupLocations) {
resolution.isInvalidated = true;
let changedInAutoTypeReferenced = false;
for (const containingFilePath of Debug.assertDefined(resolution.files)) {
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
// When its a file with inferred types resolution, invalidate type reference directive resolution
changedInAutoTypeReferenced = changedInAutoTypeReferenced || containingFilePath.endsWith(inferredTypesContainingFile);
}
if (changedInAutoTypeReferenced) {
resolutionHost.onChangedAutomaticTypeDirectiveNames();
function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) {
if (!resolutions) return false;
let invalidated = false;
for (const resolution of resolutions) {
if (resolution.isInvalidated || !canInvalidate(resolution)) continue;
resolution.isInvalidated = invalidated = true;
for (const containingFilePath of Debug.assertDefined(resolution.files)) {
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFilePath, true);
// When its a file with inferred types resolution, invalidate type reference directive resolution
hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || containingFilePath.endsWith(inferredTypesContainingFile);
}
}
return invalidated;
}

function invalidateResolutionOfFile(filePath: Path) {
removeResolutionsOfFile(filePath);
// Resolution is invalidated if the resulting file name is same as the deleted file path
forEach(resolvedFileToResolution.get(filePath), invalidateResolution);
const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) &&
hasChangedAutomaticTypeDirectiveNames &&
!prevHasChangedAutomaticTypeDirectiveNames) {
resolutionHost.onChangedAutomaticTypeDirectiveNames();
}
}

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

function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
let isChangedFailedLookupLocation: (location: string) => boolean;
function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
if (isCreatingWatchedDirectory) {
// Watching directory is created
// Invalidate any resolution has failed lookup in this directory
isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location));
isInDirectoryChecks.push(fileOrDirectoryPath);
}
else {
// If something to do with folder/file starting with "." in node_modules folder, skip it
Expand All @@ -748,10 +769,8 @@ namespace ts {
if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) ||
isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
// Invalidate any resolution from this directory
isChangedFailedLookupLocation = location => {
const locationPath = resolutionHost.toPath(location);
return locationPath === fileOrDirectoryPath || startsWith(resolutionHost.toPath(location), fileOrDirectoryPath);
};
failedLookupChecks.push(fileOrDirectoryPath);
startsWithPathChecks.push(fileOrDirectoryPath);
}
else {
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
Expand All @@ -762,20 +781,33 @@ namespace ts {
return false;
}
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
failedLookupChecks.push(fileOrDirectoryPath);
}
}
let invalidated = false;
// Resolution is invalidated if the resulting file name is same as the deleted file path
for (const resolution of resolutionsWithFailedLookups) {
if (resolution.failedLookupLocations.some(isChangedFailedLookupLocation)) {
invalidateResolution(resolution);
invalidated = true;
}
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
}

function invalidateResolutionsOfFailedLookupLocations() {
if (!failedLookupChecks.length && !startsWithPathChecks.length && !isInDirectoryChecks.length) {
return false;
}

const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution);
failedLookupChecks.length = 0;
startsWithPathChecks.length = 0;
isInDirectoryChecks.length = 0;
return invalidated;
}

function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
return resolution.failedLookupLocations.some(location => {
const locationPath = resolutionHost.toPath(location);
return contains(failedLookupChecks, locationPath) ||
startsWithPathChecks.some(fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath)) ||
isInDirectoryChecks.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
});
}

function closeTypeRootsWatch() {
clearMap(typeRootsWatches, closeFileWatcher);
}
Expand All @@ -800,13 +832,14 @@ namespace ts {
// For now just recompile
// We could potentially store more data here about whether it was/would be really be used or not
// and with that determine to trigger compilation but for now this is enough
hasChangedAutomaticTypeDirectiveNames = true;
resolutionHost.onChangedAutomaticTypeDirectiveNames();

// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
if (dirPath && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
resolutionHost.onInvalidatedResolution();
if (dirPath) {
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
}
}, WatchDirectoryFlags.Recursive);
}
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5688,6 +5688,8 @@ namespace ts {

/* @internal */
export type HasInvalidatedResolution = (sourceFile: Path) => boolean;
/* @internal */
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;

export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
Expand Down Expand Up @@ -5717,7 +5719,7 @@ namespace ts {
getEnvironmentVariable?(name: string): string | undefined;
/* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void;
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames;
createHash?(data: string): string;
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
/* @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
Expand Down
Loading