Skip to content

Commit 114d2bd

Browse files
author
zhengbli
committed
Separate directory watching and file watching again to reduce logic complexity, because reference counting is a lot easier in this case
1 parent 697644c commit 114d2bd

1 file changed

Lines changed: 62 additions & 100 deletions

File tree

src/compiler/sys.ts

Lines changed: 62 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ namespace ts {
3535
}
3636

3737
export interface DirectoryWatcher extends FileWatcher {
38+
directoryPath: Path;
3839
referenceCount: number;
3940
}
4041

@@ -306,123 +307,71 @@ namespace ts {
306307

307308
function createWatchedFileSet() {
308309
const dirWatchers = createFileMap<DirectoryWatcher>();
309-
const recursiveDirWatchers = createFileMap<DirectoryWatcher>();
310310
// One file can have multiple watchers
311311
const fileWatcherCallbacks = createFileMap<FileWatcherCallback[]>();
312-
const dirWatcherCallbacks = createFileMap<DirectoryWatcherCallback[]>();
313-
314312
const currentDirectory = process.cwd();
315-
return { addFile, removeFile, addDir };
313+
return { addFile, removeFile };
316314

317-
function addDir(dirName: string, callback: DirectoryWatcherCallback, recursive?: boolean) {
318-
const dirPath = toPath(dirName, currentDirectory, getCanonicalPath);
319-
if (!dirWatcherCallbacks.contains(dirPath)) {
320-
dirWatcherCallbacks.set(dirPath, [callback]);
321-
}
322-
else {
323-
dirWatcherCallbacks.get(dirPath).push(callback);
324-
}
325-
const { watcher, isRecursive } = addDirWatcher(dirPath, recursive);
326-
return {
327-
close: () => reduceDirWatcherRefCount(watcher, dirPath, isRecursive)
328-
};
329-
}
330-
331-
function reduceDirWatcherRefCount(watcher: DirectoryWatcher, dirPath: Path, isRecursive: boolean) {
315+
function reduceDirWatcherRefCount(dirPath: Path) {
316+
const watcher = dirWatchers.get(dirPath);
332317
watcher.referenceCount -= 1;
333318
if (watcher.referenceCount <= 0) {
334319
watcher.close();
335-
if (isRecursive) {
336-
recursiveDirWatchers.remove(dirPath);
337-
}
338-
else {
339-
dirWatchers.remove(dirPath);
340-
}
320+
dirWatchers.remove(dirPath);
341321
}
342322
}
343323

344-
function addDirWatcher(dirPath: Path, recursive?: boolean): { watcher: DirectoryWatcher, isRecursive: boolean } {
345-
let watchers: FileMap<DirectoryWatcher>;
346-
const options: { persistent: boolean, recursive?: boolean } = { persistent: true };
324+
function addDirWatcher(dirPath: Path): void {
325+
if (dirWatchers.contains(dirPath)) {
326+
const watcher = dirWatchers.get(dirPath);
327+
watcher.referenceCount += 1;
328+
return;
329+
}
330+
331+
const watcher: DirectoryWatcher = _fs.watch(
332+
dirPath,
333+
{ persistent: true },
334+
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
335+
);
336+
watcher.referenceCount = 1;
337+
dirWatchers.set(dirPath, watcher);
338+
return;
339+
}
347340

348-
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
349-
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
350-
if (isNode4OrLater() && recursive === true &&
351-
(process.platform === "win32" || process.platform === "darwin")) {
352-
if (recursiveDirWatchers.contains(dirPath)) {
353-
const watcher = recursiveDirWatchers.get(dirPath);
354-
watcher.referenceCount += 1;
355-
return { watcher, isRecursive: true };
356-
}
357-
watchers = recursiveDirWatchers;
358-
options.recursive = true;
341+
function addFileWatcherCallback(filePath: Path, callback: FileWatcherCallback): void {
342+
if (fileWatcherCallbacks.contains(filePath)) {
343+
fileWatcherCallbacks.get(filePath).push(callback);
359344
}
360345
else {
361-
if (dirWatchers.contains(dirPath)) {
362-
const watcher = dirWatchers.get(dirPath);
363-
watcher.referenceCount += 1;
364-
return { watcher, isRecursive: false };
365-
}
366-
watchers = dirWatchers;
367-
options.recursive = false;
346+
fileWatcherCallbacks.set(filePath, [callback]);
368347
}
369-
370-
const watcher: DirectoryWatcher = _fs.watch(dirPath, options, (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath));
371-
watcher.referenceCount = 1;
372-
watchers.set(dirPath, watcher);
373-
return { watcher, isRecursive: options.recursive };
374348
}
375349

376-
function findDirWatcherForFile(filePath: Path): { watcher: DirectoryWatcher, watcherPath: Path, isRecursive: boolean } {
377-
let watcher: DirectoryWatcher;
378-
let watcherPath: Path;
379-
let isRecursive = false;
380-
recursiveDirWatchers.forEachValue(dirPath => {
381-
if (filePath.indexOf(dirPath) === 0) {
382-
watcherPath = dirPath;
383-
watcher = recursiveDirWatchers.get(dirPath);
384-
isRecursive = true;
385-
return;
386-
}
387-
});
388-
if (!watcher) {
389-
const parentDirPath = getDirectoryPath(filePath);
390-
if (dirWatchers.contains(parentDirPath)) {
391-
watcherPath = parentDirPath;
392-
watcher = dirWatchers.get(parentDirPath);
393-
}
350+
function findWatchedDirForFile(filePath: Path): Path {
351+
const dirPath = getDirectoryPath(filePath);
352+
if (dirWatchers.contains(dirPath)) {
353+
return dirPath;
394354
}
395-
return { watcher, watcherPath, isRecursive };
355+
return undefined;
396356
}
397357

398358
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
399359
const filePath = toPath(fileName, currentDirectory, getCanonicalPath);
360+
addFileWatcherCallback(filePath, callback);
361+
addDirWatcher(getDirectoryPath(filePath));
400362

401-
if (fileWatcherCallbacks.contains(filePath)) {
402-
fileWatcherCallbacks.get(filePath).push(callback);
403-
}
404-
else {
405-
const { watcher } = findDirWatcherForFile(filePath);
406-
if (!watcher) {
407-
addDirWatcher(getDirectoryPath(filePath));
408-
}
409-
else {
410-
watcher.referenceCount += 1;
411-
}
412-
fileWatcherCallbacks.set(filePath, [callback]);
413-
}
414363
return { fileName, callback };
415364
}
416365

417-
function removeFile(file: WatchedFile) {
418-
const filePath = toPath(file.fileName, currentDirectory, getCanonicalPath);
366+
function removeFile(watchedFile: WatchedFile) {
367+
const filePath = toPath(watchedFile.fileName, currentDirectory, getCanonicalPath);
419368
if (fileWatcherCallbacks.contains(filePath)) {
420-
const newCallbacks = copyListRemovingItem(file.callback, fileWatcherCallbacks.get(filePath));
369+
const newCallbacks = copyListRemovingItem(watchedFile.callback, fileWatcherCallbacks.get(filePath));
421370
if (newCallbacks.length === 0) {
422371
fileWatcherCallbacks.remove(filePath);
423-
const { watcher, watcherPath, isRecursive } = findDirWatcherForFile(filePath);
424-
if (watcher) {
425-
reduceDirWatcherRefCount(watcher, watcherPath, isRecursive);
372+
const watchedDir = findWatchedDirForFile(filePath);
373+
if (watchedDir) {
374+
reduceDirWatcherRefCount(watchedDir);
426375
}
427376
}
428377
else {
@@ -437,14 +386,7 @@ namespace ts {
437386
function fileEventHandler(eventName: string, relativefileName: string, baseDirPath: Path) {
438387
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
439388
const filePath = relativefileName === undefined ? undefined : toPath(relativefileName, baseDirPath, getCanonicalPath);
440-
// Directory callbacks are not set for file content changes, they are more often used for
441-
// adding/removing/renaming files, which corresponds to the "rename" event
442-
if (eventName === "rename" && dirWatcherCallbacks.contains(baseDirPath)) {
443-
for (const dirCallback of dirWatcherCallbacks.get(baseDirPath)) {
444-
dirCallback(filePath);
445-
}
446-
}
447-
if (fileWatcherCallbacks.contains(filePath)) {
389+
if (eventName === "change" && fileWatcherCallbacks.contains(filePath)) {
448390
for (const fileCallback of fileWatcherCallbacks.get(filePath)) {
449391
fileCallback(filePath);
450392
}
@@ -577,7 +519,29 @@ namespace ts {
577519
};
578520
},
579521
watchDirectory: (path, callback, recursive) => {
580-
return watchedFileSet.addDir(path, callback, recursive);
522+
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
523+
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
524+
let options: any;
525+
if (isNode4OrLater() && (process.platform === "win32" || process.platform === "darwin")) {
526+
options = { persistent: true, recursive: !!recursive };
527+
}
528+
else {
529+
options = { persistent: true };
530+
}
531+
532+
return _fs.watch(
533+
path,
534+
options,
535+
(eventName: string, relativeFileName: string) => {
536+
// In watchDirectory we only care about adding and removing files (when event name is
537+
// "rename"); changes made within files are handled by corresponding fileWatchers (when
538+
// event name is "change")
539+
if (eventName === "rename") {
540+
// When deleting a file, the passed baseFileName is null
541+
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(path, relativeFileName)));
542+
};
543+
}
544+
);
581545
},
582546
resolvePath: function (path: string): string {
583547
return _path.resolve(path);
@@ -658,5 +622,3 @@ namespace ts {
658622
}
659623
})();
660624
}
661-
662-

0 commit comments

Comments
 (0)