Partial implementation for invalidating the program (instead of source file as that would involve more work) so the files are picked up

This commit is contained in:
Sheetal Nandi 2017-08-05 02:27:27 -07:00
parent d55150cbd3
commit 8dc62484ec
5 changed files with 91 additions and 21 deletions

View File

@ -9,6 +9,7 @@ namespace ts {
resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[];
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
invalidateResolutionOfDeletedFile(filePath: Path): void;
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void;
clear(): void;
}
@ -33,8 +34,7 @@ namespace ts {
export function createResolutionCache(
toPath: (fileName: string) => Path,
getCompilerOptions: () => CompilerOptions,
clearProgramAndScheduleUpdate: () => void,
watchForFailedLookupLocation: (fileName: string, callback: FileWatcherCallback) => FileWatcher,
watchForFailedLookupLocation: (failedLookupLocation: string, containingFile: string, name: string) => FileWatcher,
log: (s: string) => void,
resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache {
@ -54,6 +54,7 @@ namespace ts {
resolveModuleNames,
resolveTypeReferenceDirectives,
invalidateResolutionOfDeletedFile,
invalidateResolutionOfChangedFailedLookupLocation,
clear
};
@ -185,11 +186,8 @@ namespace ts {
log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`);
}
else {
const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, (__fileName, eventKind) => {
log(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
// There is some kind of change in the failed lookup location, update the program
clearProgramAndScheduleUpdate();
});
log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, containingFile, name);
failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 });
}
}
@ -260,7 +258,7 @@ namespace ts {
});
}
else if (value) {
value.forEach((resolution) => {
value.forEach((resolution, __name) => {
if (resolution && !resolution.isInvalidated) {
const result = getResult(resolution);
if (result) {
@ -274,9 +272,31 @@ namespace ts {
});
}
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
failedLookupLocation: string,
cache: Map<Map<T>>) {
cache.forEach((value, _containingFilePath) => {
if (value) {
value.forEach((resolution, __name) => {
if (resolution && !resolution.isInvalidated && contains(resolution.failedLookupLocations, failedLookupLocation)) {
// TODO: mark the file as needing re-evaluation of module resolution instead of using it blindly.
// Note: Right now this invalidation path is not used at all as it doesnt matter as we are anyways clearing the program,
// which means all the resolutions will be discarded.
resolution.isInvalidated = true;
}
});
}
});
}
function invalidateResolutionOfDeletedFile(filePath: Path) {
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName);
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
}
function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string) {
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedModuleNames);
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedTypeReferenceDirectives);
}
}
}

View File

@ -255,6 +255,7 @@ namespace ts {
let timerToUpdateProgram: any; // timer callback to recompile the program
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
@ -274,8 +275,7 @@ namespace ts {
const resolutionCache = createResolutionCache(
fileName => toPath(fileName),
() => compilerOptions,
() => clearExistingProgramAndScheduleProgramUpdate(),
(fileName, callback) => system.watchFile(fileName, callback),
watchFailedLookupLocation,
s => writeLog(s)
);
@ -310,6 +310,19 @@ namespace ts {
// Update watches
missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher);
if (missingFilePathsRequestedForRelease) {
// These are the paths that program creater told us as not in use any more but were missing on the disk.
// We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
// if there is already watcher for it (for missing files)
// At that point our watches were updated, hence now we know that these paths are not tracked and need to be removed
// so that at later time we have correct result of their presence
for (const missingFilePath of missingFilePathsRequestedForRelease) {
if (!missingFilesMap.has(missingFilePath)) {
sourceFilesCache.delete(missingFilePath);
}
}
missingFilePathsRequestedForRelease = undefined;
}
afterCompile(host, program, builder);
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
@ -446,8 +459,14 @@ namespace ts {
// remove the cached entry.
// Note we arent deleting entry if file became missing in new program or
// there was version update and new source file was created.
if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) {
sourceFilesCache.delete(oldSourceFile.path);
if (hostSourceFileInfo) {
// record the missing file paths so they can be removed later if watchers arent tracking them
if (isString(hostSourceFileInfo)) {
(missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
}
else if (hostSourceFileInfo.sourceFile === oldSourceFile) {
sourceFilesCache.delete(oldSourceFile.path);
}
}
}
@ -471,11 +490,6 @@ namespace ts {
scheduleProgramUpdate();
}
function clearExistingProgramAndScheduleProgramUpdate() {
program = undefined;
scheduleProgramUpdate();
}
function updateProgram() {
timerToUpdateProgram = undefined;
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
@ -547,6 +561,24 @@ namespace ts {
}
}
function watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) {
return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, containingFile, name));
}
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, containingFile: string, name: string) {
writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`);
const path = toPath(failedLookupLocation);
updateCachedSystem(failedLookupLocation, path);
// TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file
// refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions.
// For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again.
// resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
program = undefined;
scheduleProgramUpdate();
}
function watchMissingFilePath(missingFilePath: Path) {
return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind));
}

View File

@ -335,7 +335,8 @@ namespace ts.projectSystem {
checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
const moduleLookupLocations = ["/a/b/c/module.ts", "/a/b/c/module.tsx"];
checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path, ...moduleLookupLocations));
});
it("can handle tsconfig file name with difference casing", () => {

View File

@ -247,7 +247,8 @@ namespace ts.server {
WildCardDirectories = "Wild card directory",
TypeRoot = "Type root of the project",
ClosedScriptInfo = "Closed Script info",
ConfigFileForInferredRoot = "Config file for the inferred project root"
ConfigFileForInferredRoot = "Config file for the inferred project root",
FailedLookupLocation = "Failed lookup locations in module resolution"
}
/* @internal */

View File

@ -217,8 +217,7 @@ namespace ts.server {
this.resolutionCache = createResolutionCache(
fileName => this.projectService.toPath(fileName),
() => this.compilerOptions,
() => this.markAsDirty(),
(fileName, callback) => host.watchFile(fileName, callback),
(failedLookupLocation, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, containingFile, name),
s => this.projectService.logger.info(s),
(primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host,
this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName())
@ -235,6 +234,23 @@ namespace ts.server {
this.markAsDirty();
}
private watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) {
// There is some kind of change in the failed lookup location, update the program
return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (__fileName, eventKind) => {
this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
if (this.projectKind === ProjectKind.Configured) {
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(failedLookupLocation));
}
this.updateTypes();
// TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file
// refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions.
// For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again.
// this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
// this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
});
}
private setInternalCompilerOptionsForEmittingJsFiles() {
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
this.compilerOptions.noEmitForJsFiles = true;