mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-20 13:45:34 -05:00
Track missing files
1. Expose missing files from the `Program`. 2. In `tsc --watch` and `tsserver`, add file watchers to missing files. 3. When missing files are created, schedule compilation (tsc) or refresh the containing projects (tsserver).
This commit is contained in:
@@ -513,6 +513,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const missingFilePaths = filesByName.getKeys().filter(p => !filesByName.get(p));
|
||||
|
||||
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
|
||||
moduleResolutionCache = undefined;
|
||||
|
||||
@@ -524,6 +526,7 @@ namespace ts {
|
||||
getSourceFile,
|
||||
getSourceFileByPath,
|
||||
getSourceFiles: () => files,
|
||||
getMissingFilePaths: () => missingFilePaths,
|
||||
getCompilerOptions: () => options,
|
||||
getSyntacticDiagnostics,
|
||||
getOptionsDiagnostics,
|
||||
@@ -862,11 +865,31 @@ namespace ts {
|
||||
return oldProgram.structureIsReused;
|
||||
}
|
||||
|
||||
// If a file has ceased to be missing, then we need to discard some of the old
|
||||
// structure in order to pick it up.
|
||||
// Caution: if the file has created and then deleted between since it was discovered to
|
||||
// be missing, then the corresponding file watcher will have been closed and no new one
|
||||
// will be created until we encounter a change that prevents complete structure reuse.
|
||||
// During this interval, creation of the file will go unnoticed. We expect this to be
|
||||
// both rare and low-impact.
|
||||
if (oldProgram.getMissingFilePaths) {
|
||||
const missingFilePaths: Path[] = oldProgram.getMissingFilePaths() || emptyArray;
|
||||
for (const missingFilePath of missingFilePaths) {
|
||||
if (host.fileExists(missingFilePath)) {
|
||||
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update fileName -> file mapping
|
||||
for (let i = 0; i < newSourceFiles.length; i++) {
|
||||
filesByName.set(filePaths[i], newSourceFiles[i]);
|
||||
}
|
||||
|
||||
for (const p of oldProgram.getMissingFilePaths()) {
|
||||
filesByName.set(p, undefined);
|
||||
}
|
||||
|
||||
files = newSourceFiles;
|
||||
fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics();
|
||||
|
||||
|
||||
@@ -340,11 +340,20 @@ namespace ts {
|
||||
}
|
||||
|
||||
function fileChanged(curr: any, prev: any) {
|
||||
if (+curr.mtime <= +prev.mtime) {
|
||||
const isCurrZero = +curr.mtime === 0;
|
||||
const isPrevZero = +prev.mtime === 0;
|
||||
const added = !isCurrZero && isPrevZero;
|
||||
const deleted = isCurrZero && !isPrevZero;
|
||||
|
||||
// This value is consistent with poll() in createPollingWatchedFileSet()
|
||||
// and depended upon by the file watchers created in Project.updateGraphWorker.
|
||||
const removed = deleted ? true : (added ? false : undefined);
|
||||
|
||||
if (!added && !deleted && +curr.mtime <= +prev.mtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(fileName);
|
||||
callback(fileName, removed);
|
||||
}
|
||||
},
|
||||
watchDirectory: (directoryName, callback, recursive) => {
|
||||
|
||||
@@ -285,6 +285,19 @@ namespace ts {
|
||||
|
||||
setCachedProgram(compileResult.program);
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
||||
|
||||
if (compileResult.program.getMissingFilePaths) {
|
||||
const missingPaths = compileResult.program.getMissingFilePaths() || [];
|
||||
missingPaths.forEach((path: Path): void => {
|
||||
const fileWatcher = sys.watchFile(path, (_fileName: string, removed?: boolean) => {
|
||||
// removed = deleted ? true : (added ? false : undefined)
|
||||
if (removed === false) {
|
||||
fileWatcher.close();
|
||||
startTimerForRecompilation();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cachedFileExists(fileName: string): boolean {
|
||||
|
||||
@@ -2426,6 +2426,12 @@ namespace ts {
|
||||
*/
|
||||
getSourceFiles(): SourceFile[];
|
||||
|
||||
/**
|
||||
* Get a list of file names that were passed to 'createProgram' or referenced in a
|
||||
* program source file but could not be located.
|
||||
*/
|
||||
getMissingFilePaths?(): Path[];
|
||||
|
||||
/**
|
||||
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then
|
||||
* the JavaScript and declaration files will be produced for all the files in this program.
|
||||
|
||||
@@ -210,8 +210,11 @@ namespace ts.server {
|
||||
return this.host.resolvePath(path);
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
return this.host.fileExists(path);
|
||||
fileExists(file: string): boolean {
|
||||
// As an optimization, don't hit the disks for files we already know don't exist
|
||||
// (because we're watching for their creation).
|
||||
const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace ts.server {
|
||||
private rootFilesMap: Map<ScriptInfo> = createMap<ScriptInfo>();
|
||||
private program: ts.Program;
|
||||
private externalFiles: SortedReadonlyArray<string>;
|
||||
private missingFilesMap: FileMap<FileWatcher> = createFileMap<FileWatcher>();
|
||||
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
@@ -606,6 +607,39 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges && this.program.getMissingFilePaths) {
|
||||
const missingFilePaths = this.program.getMissingFilePaths() || emptyArray;
|
||||
const missingFilePathsSet = createMap<true>();
|
||||
missingFilePaths.forEach(p => missingFilePathsSet.set(p, true));
|
||||
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
this.missingFilesMap.getKeys().forEach(p => {
|
||||
if (!missingFilePathsSet.has(p)) {
|
||||
this.missingFilesMap.get(p).close();
|
||||
this.missingFilesMap.remove(p);
|
||||
}
|
||||
});
|
||||
|
||||
// Missing files that are not yet watched should be added to the map.
|
||||
missingFilePaths.forEach(p => {
|
||||
if (!this.missingFilesMap.contains(p)) {
|
||||
const fileWatcher = ts.sys.watchFile(p, (_filename: string, removed?: boolean) => {
|
||||
// removed = deleted ? true : (added ? false : undefined)
|
||||
if (removed === false && this.missingFilesMap.contains(p)) {
|
||||
fileWatcher.close();
|
||||
this.missingFilesMap.remove(p);
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.updateGraph();
|
||||
}
|
||||
});
|
||||
this.missingFilesMap.set(p, fileWatcher);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
|
||||
this.externalFiles = this.getExternalFiles();
|
||||
enumerateInsertsAndDeletes(this.externalFiles, oldExternalFiles,
|
||||
@@ -626,6 +660,10 @@ namespace ts.server {
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
isWatchedMissingFile(path: Path) {
|
||||
return this.missingFilesMap.contains(path);
|
||||
}
|
||||
|
||||
getScriptInfoLSHost(fileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
||||
if (scriptInfo) {
|
||||
|
||||
@@ -522,6 +522,9 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
// removed = deleted ? true : (added ? false : undefined)
|
||||
// This value is consistent with sys.watchFile()
|
||||
// and depended upon by the file watchers created in performCompilation() in tsc's executeCommandLine().
|
||||
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
|
||||
if (err) {
|
||||
watchedFile.callback(watchedFile.fileName);
|
||||
@@ -560,7 +563,9 @@ namespace ts.server {
|
||||
const file: WatchedFile = {
|
||||
fileName,
|
||||
callback,
|
||||
mtime: getModifiedTime(fileName)
|
||||
mtime: sys.fileExists(fileName)
|
||||
? getModifiedTime(fileName)
|
||||
: new Date(0) // Any subsequent modification will occur after this time
|
||||
};
|
||||
|
||||
watchedFiles.push(file);
|
||||
|
||||
Reference in New Issue
Block a user