mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #25464 from Microsoft/cacheRecursiveDirectoryWatchers
Caches the recursive directory watchers so we do not have to traverse and recreate more children watches
This commit is contained in:
+65
-31
@@ -332,9 +332,9 @@ namespace ts {
|
||||
/*@internal*/
|
||||
export interface RecursiveDirectoryWatcherHost {
|
||||
watchDirectory: HostWatchDirectory;
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
getAccessibleSortedChildDirectories(path: string): ReadonlyArray<string>;
|
||||
directoryExists(dir: string): boolean;
|
||||
filePathComparer: Comparer<string>;
|
||||
realpath(s: string): string;
|
||||
}
|
||||
|
||||
@@ -345,60 +345,94 @@ namespace ts {
|
||||
*/
|
||||
/*@internal*/
|
||||
export function createRecursiveDirectoryWatcher(host: RecursiveDirectoryWatcherHost): (directoryName: string, callback: DirectoryWatcherCallback) => FileWatcher {
|
||||
type ChildWatches = ReadonlyArray<DirectoryWatcher>;
|
||||
interface DirectoryWatcher extends FileWatcher {
|
||||
childWatches: ChildWatches;
|
||||
interface ChildDirectoryWatcher extends FileWatcher {
|
||||
dirName: string;
|
||||
}
|
||||
type ChildWatches = ReadonlyArray<ChildDirectoryWatcher>;
|
||||
interface HostDirectoryWatcher {
|
||||
watcher: FileWatcher;
|
||||
childWatches: ChildWatches;
|
||||
refCount: number;
|
||||
}
|
||||
|
||||
const cache = createMap<HostDirectoryWatcher>();
|
||||
const callbackCache = createMultiMap<DirectoryWatcherCallback>();
|
||||
const filePathComparer = getStringComparer(!host.useCaseSensitiveFileNames);
|
||||
const toCanonicalFilePath = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
|
||||
return createDirectoryWatcher;
|
||||
|
||||
/**
|
||||
* Create the directory watcher for the dirPath.
|
||||
*/
|
||||
function createDirectoryWatcher(dirName: string, callback: DirectoryWatcherCallback): DirectoryWatcher {
|
||||
const watcher = host.watchDirectory(dirName, fileName => {
|
||||
// Call the actual callback
|
||||
callback(fileName);
|
||||
function createDirectoryWatcher(dirName: string, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher {
|
||||
const dirPath = toCanonicalFilePath(dirName) as Path;
|
||||
let directoryWatcher = cache.get(dirPath);
|
||||
if (directoryWatcher) {
|
||||
directoryWatcher.refCount++;
|
||||
}
|
||||
else {
|
||||
directoryWatcher = {
|
||||
watcher: host.watchDirectory(dirName, fileName => {
|
||||
// Call the actual callback
|
||||
callbackCache.forEach((callbacks, rootDirName) => {
|
||||
if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) {
|
||||
callbacks.forEach(callback => callback(fileName));
|
||||
}
|
||||
});
|
||||
|
||||
// Iterate through existing children and update the watches if needed
|
||||
updateChildWatches(result, callback);
|
||||
});
|
||||
// Iterate through existing children and update the watches if needed
|
||||
updateChildWatches(dirName, dirPath);
|
||||
}),
|
||||
refCount: 1,
|
||||
childWatches: emptyArray
|
||||
};
|
||||
cache.set(dirPath, directoryWatcher);
|
||||
updateChildWatches(dirName, dirPath);
|
||||
}
|
||||
|
||||
let result: DirectoryWatcher = {
|
||||
close: () => {
|
||||
watcher.close();
|
||||
result.childWatches.forEach(closeFileWatcher);
|
||||
result = undefined!;
|
||||
},
|
||||
if (callback) {
|
||||
callbackCache.add(dirPath, callback);
|
||||
}
|
||||
|
||||
return {
|
||||
dirName,
|
||||
childWatches: emptyArray
|
||||
close: () => {
|
||||
const directoryWatcher = Debug.assertDefined(cache.get(dirPath));
|
||||
if (callback) callbackCache.remove(dirPath, callback);
|
||||
directoryWatcher.refCount--;
|
||||
|
||||
if (directoryWatcher.refCount) return;
|
||||
|
||||
cache.delete(dirPath);
|
||||
closeFileWatcherOf(directoryWatcher);
|
||||
directoryWatcher.childWatches.forEach(closeFileWatcher);
|
||||
}
|
||||
};
|
||||
updateChildWatches(result, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateChildWatches(watcher: DirectoryWatcher, callback: DirectoryWatcherCallback) {
|
||||
function updateChildWatches(dirName: string, dirPath: Path) {
|
||||
// Iterate through existing children and update the watches if needed
|
||||
if (watcher) {
|
||||
watcher.childWatches = watchChildDirectories(watcher.dirName, watcher.childWatches, callback);
|
||||
const parentWatcher = cache.get(dirPath);
|
||||
if (parentWatcher) {
|
||||
parentWatcher.childWatches = watchChildDirectories(dirName, parentWatcher.childWatches);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the directories in the parentDir
|
||||
*/
|
||||
function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, callback: DirectoryWatcherCallback): ChildWatches {
|
||||
let newChildWatches: DirectoryWatcher[] | undefined;
|
||||
enumerateInsertsAndDeletes<string, DirectoryWatcher>(
|
||||
function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches): ChildWatches {
|
||||
let newChildWatches: ChildDirectoryWatcher[] | undefined;
|
||||
enumerateInsertsAndDeletes<string, ChildDirectoryWatcher>(
|
||||
host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => {
|
||||
const childFullName = getNormalizedAbsolutePath(child, parentDir);
|
||||
// Filter our the symbolic link directories since those arent included in recursive watch
|
||||
// which is same behaviour when recursive: true is passed to fs.watch
|
||||
return host.filePathComparer(childFullName, host.realpath(childFullName)) === Comparison.EqualTo ? childFullName : undefined;
|
||||
return filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined;
|
||||
}) : emptyArray,
|
||||
existingChildWatches,
|
||||
(child, childWatcher) => host.filePathComparer(child, childWatcher.dirName),
|
||||
(child, childWatcher) => filePathComparer(child, childWatcher.dirName),
|
||||
createAndAddChildDirectoryWatcher,
|
||||
closeFileWatcher,
|
||||
addChildDirectoryWatcher
|
||||
@@ -410,14 +444,14 @@ namespace ts {
|
||||
* Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list
|
||||
*/
|
||||
function createAndAddChildDirectoryWatcher(childName: string) {
|
||||
const result = createDirectoryWatcher(childName, callback);
|
||||
const result = createDirectoryWatcher(childName);
|
||||
addChildDirectoryWatcher(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add child directory watcher to the new ChildDirectoryWatcher list
|
||||
*/
|
||||
function addChildDirectoryWatcher(childWatcher: DirectoryWatcher) {
|
||||
function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) {
|
||||
(newChildWatches || (newChildWatches = [])).push(childWatcher);
|
||||
}
|
||||
}
|
||||
@@ -710,7 +744,7 @@ namespace ts {
|
||||
createWatchDirectoryUsing(dynamicPollingWatchFile || createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout })) :
|
||||
watchDirectoryUsingFsWatch;
|
||||
const watchDirectoryRecursively = createRecursiveDirectoryWatcher({
|
||||
filePathComparer: getStringComparer(!useCaseSensitiveFileNames),
|
||||
useCaseSensitiveFileNames,
|
||||
directoryExists,
|
||||
getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories,
|
||||
watchDirectory,
|
||||
|
||||
@@ -398,7 +398,7 @@ namespace ts {
|
||||
case WatchLogLevel.TriggerOnly:
|
||||
return createFileWatcherWithTriggerLogging;
|
||||
case WatchLogLevel.Verbose:
|
||||
return createFileWatcherWithLogging;
|
||||
return addWatch === <any>watchDirectory ? createDirectoryWatcherWithLogging : createFileWatcherWithLogging;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,6 +413,25 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function createDirectoryWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, undefined>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
|
||||
const watchInfo = `${watchCaption}:: Added:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
|
||||
log(watchInfo);
|
||||
const start = timestamp();
|
||||
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
|
||||
const elapsed = timestamp() - start;
|
||||
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
|
||||
return {
|
||||
close: () => {
|
||||
const watchInfo = `${watchCaption}:: Close:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
|
||||
log(watchInfo);
|
||||
const start = timestamp();
|
||||
watcher.close();
|
||||
const elapsed = timestamp() - start;
|
||||
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createFileWatcherWithTriggerLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, undefined>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
|
||||
return addWatch(host, file, (fileName, cbOptional) => {
|
||||
const triggerredInfo = `${watchCaption}:: Triggered with ${fileName}${cbOptional !== undefined ? cbOptional : ""}:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
|
||||
|
||||
@@ -352,9 +352,9 @@ interface Array<T> {}`
|
||||
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
|
||||
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium);
|
||||
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
|
||||
directoryExists: path => this.directoryExists(path),
|
||||
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
|
||||
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
|
||||
watchDirectory,
|
||||
realpath: s => this.realpath(s)
|
||||
});
|
||||
@@ -362,9 +362,9 @@ interface Array<T> {}`
|
||||
else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) {
|
||||
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false);
|
||||
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
|
||||
directoryExists: path => this.directoryExists(path),
|
||||
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
|
||||
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
|
||||
watchDirectory,
|
||||
realpath: s => this.realpath(s)
|
||||
});
|
||||
@@ -373,9 +373,9 @@ interface Array<T> {}`
|
||||
const watchFile = createDynamicPriorityPollingWatchFile(this);
|
||||
const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium);
|
||||
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
|
||||
directoryExists: path => this.directoryExists(path),
|
||||
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
|
||||
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
|
||||
watchDirectory,
|
||||
realpath: s => this.realpath(s)
|
||||
});
|
||||
|
||||
@@ -8589,10 +8589,10 @@ export const x = 10;`
|
||||
tscWatchDirectory === Tsc_WatchDirectory.WatchFile ?
|
||||
expectedWatchedFiles :
|
||||
createMap();
|
||||
// For failed resolution lookup and tsconfig files
|
||||
mapOfDirectories.set(projectFolder, 2);
|
||||
// For failed resolution lookup and tsconfig files => cached so only watched only once
|
||||
mapOfDirectories.set(projectFolder, 1);
|
||||
// Through above recursive watches
|
||||
mapOfDirectories.set(projectSrcFolder, 2);
|
||||
mapOfDirectories.set(projectSrcFolder, 1);
|
||||
// node_modules/@types folder
|
||||
mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1);
|
||||
const expectedCompletions = ["file1"];
|
||||
|
||||
@@ -1933,9 +1933,6 @@ declare namespace ts {
|
||||
getAmbientModules(): Symbol[];
|
||||
tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined;
|
||||
getApparentType(type: Type): Type;
|
||||
getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined;
|
||||
getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
|
||||
getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined;
|
||||
getBaseConstraintOfType(type: Type): Type | undefined;
|
||||
getDefaultFromTypeParameter(type: Type): Type | undefined;
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user