Watch subdirectories in project root instead of watching project root so as to avoid watching excluded directories that are not part of config file

Fixes #25629 and Microsoft/vscode#51139
This commit is contained in:
Sheetal Nandi 2018-07-19 13:28:55 -07:00
parent feca91c67b
commit 4ee3f2b3ea
6 changed files with 136 additions and 112 deletions

View file

@ -398,8 +398,17 @@ namespace ts {
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
// Always watch root directory recursively
return { dir: rootDir!, dirPath: rootPath }; // TODO: GH#18217
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? failedLookupLocation : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); // tslint:disable-line
const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1);
if (subDirectoryInRoot !== -1) {
// Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
return { dir: failedLookupLocation.substr(0, subDirectoryInRoot), dirPath: failedLookupLocationPath.substr(0, subDirectoryInRoot) as Path };
}
else {
// Always watch root directory non recursively
return { dir: rootDir!, dirPath: rootPath, nonRecursive: false }; // TODO: GH#18217
}
}
return getDirectoryToWatchFromFailedLookupLocationDirectory(
@ -478,6 +487,7 @@ namespace ts {
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
}
if (dirPath === rootPath) {
Debug.assert(!nonRecursive);
setAtRoot = true;
}
else {
@ -487,8 +497,8 @@ namespace ts {
}
if (setAtRoot) {
// This is always recursive
setDirectoryWatcher(rootDir!, rootPath); // TODO: GH#18217
// This is always non recursive
setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
}
}

View file

@ -583,6 +583,10 @@ interface Array<T> {}`
return;
}
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
if (isFsFolder(fileOrDirectory)) {
this.invokeDirectoryWatcher(fileOrDirectory.fullPath, "");
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, "");
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
@ -599,12 +603,11 @@ interface Array<T> {}`
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (isFsFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
// Invoke directory and recursive directory watcher for the folder
// Here we arent invoking recursive directory watchers for the base folders
// since that is something we would want to do for both file as well as folder we are deleting
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, relativePath);
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, relativePath);
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, "");
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, "");
}
if (basePath !== fileOrDirectory.path) {

View file

@ -2262,8 +2262,11 @@ declare module "fs" {
const files = [file, module, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfFilesAndCompilerOptions([file.path], host);
checkProgramActualFiles(watch(), [file.path, libFile.path]);
checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true);
host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`);
host.runQueuedTimeoutCallbacks();
@ -2622,7 +2625,7 @@ declare module "fs" {
createWatchOfConfigFile("tsconfig.json", host);
checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, [mainPackageRoot, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
});
});

View file

@ -622,8 +622,8 @@ namespace ts.projectSystem {
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));
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, ["/a/b/c", combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false);
checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
});
it("can handle tsconfig file name with difference casing", () => {
@ -3035,21 +3035,18 @@ namespace ts.projectSystem {
assert.isDefined(project);
checkProjectActualFiles(project, map(files, file => file.path));
checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
const watchedRecursiveDirectories = ["/a/b/node_modules/@types"];
watchedRecursiveDirectories.push("/a/b");
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
files.push(file2);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
watchedRecursiveDirectories.pop();
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path));
checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
// On next file open the files file2a should be closed and not watched any more
projectService.openClientFile(file2.path);
@ -3057,8 +3054,8 @@ namespace ts.projectSystem {
assert.strictEqual(projectService.configuredProjects.get(configFile.path), project);
checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path));
checkWatchedFiles(host, [libFile.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true);
});
@ -3098,7 +3095,7 @@ namespace ts.projectSystem {
checkWatchedFiles(host, [libFile.path, module1.path, module2.path, configFile.path]);
checkWatchedDirectories(host, [], /*recursive*/ false);
const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src");
watchedRecursiveDirectories.push(`${root}/a/b/src`, `${root}/a/b/node_modules`);
watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
});
@ -6780,7 +6777,7 @@ namespace ts.projectSystem {
const { configFileName } = projectService.openClientFile(file1.path);
assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217)
checkNumberOfConfiguredProjects(projectService, 1);
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, canonicalFrontendDir].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
const project = projectService.configuredProjects.get(canonicalConfigPath)!;
verifyProjectAndWatchedDirectories();
@ -6934,7 +6931,7 @@ namespace ts.projectSystem {
const projectService = createProjectService(host);
const { configFileName } = projectService.openClientFile(app.path);
assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217
const recursiveWatchedDirectories: string[] = [appFolder].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
verifyProject();
let timeoutAfterReloadFs = timeoutDuringPartialInstallation;
@ -7014,7 +7011,7 @@ namespace ts.projectSystem {
const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts";
projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!);
// we would now not have failed lookup in the parent of appFolder since lodash is available
recursiveWatchedDirectories.length = 1;
recursiveWatchedDirectories.length = 2;
// npm installation complete, timeout after reload fs
timeoutAfterReloadFs = true;
verifyAfterPartialOrCompleteNpmInstall(2);
@ -7534,9 +7531,9 @@ namespace ts.projectSystem {
const openFiles = [file1.path];
const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ?
// Folders of node_modules lookup not in changedRoot
["a/b/project", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) :
["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) :
// Folder of tsconfig
["/a/b/project"];
["/a/b/project", "/a/b/project/node_modules"];
const host = createServerHost(projectFiles);
const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
const projectService = session.getProjectService();
@ -7565,7 +7562,7 @@ namespace ts.projectSystem {
host.reloadFS(projectFiles);
host.runQueuedTimeoutCallbacks();
if (useSlashRootAsSomeNotRootFolderInUserDirectory) {
watchedRecursiveDirectories.length = 2;
watchedRecursiveDirectories.length = 3;
}
else {
// file2 addition wont be detected
@ -7993,10 +7990,10 @@ new C();`
checkCompleteEvent(session, 2, expectedSequenceId);
}
function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: string[], nonRecursiveDirectories: string[]) {
function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: ReadonlyMap<number>, nonRecursiveDirectories: string[]) {
checkWatchedFilesDetailed(host, files.filter(f => f !== recognizersDateTimeSrcFile.path), 1);
checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, recursiveDirectories, 1, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false);
checkWatchedDirectoriesDetailed(host, recursiveDirectories, /*recursive*/ true);
}
function createSessionAndOpenFile(host: TestServerHost) {
@ -8017,8 +8014,16 @@ new C();`
const filesWithNodeModulesSetup = [...filesWithSources, nodeModulesRecorgnizersText];
const filesAfterCompilation = [...filesWithNodeModulesSetup, recongnizerTextDistTypingFile];
const watchedDirectoriesWithResolvedModule = [`${recognizersDateTime}/src`, ...(withPathMapping ? emptyArray : [recognizersDateTime]), ...getTypeRootsFromLocation(recognizersDateTime)];
const watchedDirectoriesWithUnresolvedModule = [recognizersDateTime, ...(withPathMapping ? [recognizersText] : emptyArray), ...watchedDirectoriesWithResolvedModule, ...getNodeModuleDirectories(packages)];
const watchedDirectoriesWithResolvedModule = arrayToMap(getTypeRootsFromLocation(recognizersDateTime), k => k, () => 1);
watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/src`, withPathMapping ? 1 : 2); // wild card + failed lookups
if (!withPathMapping) {
watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups
}
const watchedDirectoriesWithUnresolvedModule = cloneMap(watchedDirectoriesWithResolvedModule);
watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups
[`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => {
watchedDirectoriesWithUnresolvedModule.set(d, 1);
});
const nonRecursiveWatchedDirectories = withPathMapping ? [packages] : emptyArray;
function verifyProjectWithResolvedModule(session: TestSession) {
@ -8251,11 +8256,10 @@ new C();`
return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`;
}
function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File) {
function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: ReadonlyArray<string>) {
checkWatchedFiles(host, mapDefined(files, f => f === openFile ? undefined : f.path));
checkWatchedDirectories(host, [], /*recursive*/ false);
const configDirectory = getDirectoryPath(configFile.path);
checkWatchedDirectories(host, [configDirectory, `${configDirectory}/${nodeModulesAtTypes}`], /*recursive*/ true);
checkWatchedDirectories(host, [projectLocation, `${projectLocation}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)], /*recursive*/ true);
}
describe("from files in same folder", () => {
@ -8299,6 +8303,7 @@ new C();`
});
it("non relative module name", () => {
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`];
const module1Name = "module1";
const module2Name = "module2";
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
@ -8312,7 +8317,7 @@ new C();`
const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name);
getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
file1.content += fileContent;
file2.content += fileContent;
@ -8322,7 +8327,7 @@ new C();`
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
]);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
});
});
@ -8391,6 +8396,7 @@ new C();`
});
it("non relative module name", () => {
const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/product`];
const module1Name = "module1";
const module2Name = "module2";
const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`;
@ -8410,7 +8416,7 @@ new C();`
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace);
getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace);
verifyTrace(resolutionTrace, expectedTrace);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
file1.content += fileContent;
file2.content += fileContent;
@ -8423,7 +8429,7 @@ new C();`
getExpectedReusingResolutionFromOldProgram(file1, module1Name),
getExpectedReusingResolutionFromOldProgram(file1, module2Name)
]);
verifyWatchesWithConfigFile(host, files, file1);
verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories);
});
it("non relative module name from inferred project", () => {
@ -8460,7 +8466,7 @@ new C();`
watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json"));
});
const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([
currentDirectory, `${projectLocation}/product/${nodeModules}`,
`${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectLocation}/product/${nodeModules}`,
`${projectLocation}/${nodeModules}`, `${projectLocation}/product/test/${nodeModules}`,
`${projectLocation}/product/test/src/${nodeModules}`
]);
@ -8530,7 +8536,6 @@ export const x = 10;`
outDir: "../out",
baseUrl: "./",
typeRoots: ["typings"]
}
})
};
@ -8546,13 +8551,15 @@ export const x = 10;`
checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup
}
else {
checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs
checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs
}
const expectedWatchedDirectories = createMap<number>();
expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup
expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card
expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2
expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs
expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory
expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true);
}

View file

@ -146,8 +146,10 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const expectedWatchedDirectoriesRecursive = createMap<number>();
expectedWatchedDirectoriesRecursive.set("/a/b", 2); // TypingInstaller and wild card
expectedWatchedDirectoriesRecursive.set("/a/b", 1); // wild card
expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch
expectedWatchedDirectoriesRecursive.set("/a/b/node_modules", 1); // TypingInstaller
expectedWatchedDirectoriesRecursive.set("/a/b/bower_components", 1); // TypingInstaller
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);
@ -844,9 +846,7 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const watchedRecursiveDirectoriesExpected = createMap<number>();
watchedRecursiveDirectoriesExpected.set("/", 2); // wild card + type installer
checkWatchedDirectoriesDetailed(host, watchedRecursiveDirectoriesExpected, /*recursive*/ true);
checkWatchedDirectoriesDetailed(host, ["/", "/node_modules", "/bower_components"], 1, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);

View file

@ -64,18 +64,24 @@ namespace ts.server.typingsInstaller {
onRequestCompleted: RequestCompletedAction;
}
function isPackageOrBowerJson(fileName: string) {
const base = getBaseFileName(fileName);
return base === "package.json" || base === "bower.json";
function endsWith(str: string, suffix: string, caseSensitive: boolean): boolean {
const expectedPos = str.length - suffix.length;
return expectedPos >= 0 &&
(str.indexOf(suffix, expectedPos) === expectedPos ||
(!caseSensitive && compareStringsCaseInsensitive(str.substr(expectedPos), suffix) === Comparison.EqualTo));
}
function getDirectoryExcludingNodeModulesOrBowerComponents(f: string) {
const indexOfNodeModules = f.indexOf("/node_modules/");
const indexOfBowerComponents = f.indexOf("/bower_components/");
const subStrLength = indexOfNodeModules === -1 || indexOfBowerComponents === -1 ?
Math.max(indexOfNodeModules, indexOfBowerComponents) :
Math.min(indexOfNodeModules, indexOfBowerComponents);
return subStrLength === -1 ? f : f.substr(0, subStrLength);
function isPackageOrBowerJson(fileName: string, caseSensitive: boolean) {
return endsWith(fileName, "/package.json", caseSensitive) || endsWith(fileName, "/bower.json", caseSensitive);
}
function sameFiles(a: string, b: string, caseSensitive: boolean) {
return a === b || (!caseSensitive && compareStringsCaseInsensitive(a, b) === Comparison.EqualTo);
}
const enum ProjectWatcherType {
FileWatcher = "FileWatcher",
DirectoryWatcher = "DirectoryWatcher"
}
type ProjectWatchers = Map<FileWatcher> & { isInvoked?: boolean; };
@ -88,7 +94,7 @@ namespace ts.server.typingsInstaller {
private safeList: JsTyping.SafeList | undefined;
readonly pendingRunRequests: PendingRequest[] = [];
private readonly toCanonicalFileName: GetCanonicalFileName;
private readonly globalCacheCanonicalPackageJsonPath: string;
private readonly globalCachePackageJsonPath: string;
private installRunCount = 1;
private inFlightRequestCount = 0;
@ -103,7 +109,7 @@ namespace ts.server.typingsInstaller {
private readonly throttleLimit: number,
protected readonly log = nullLog) {
this.toCanonicalFileName = createGetCanonicalFileName(installTypingHost.useCaseSensitiveFileNames);
this.globalCacheCanonicalPackageJsonPath = combinePaths(this.toCanonicalFileName(globalCachePath), "package.json");
this.globalCachePackageJsonPath = combinePaths(globalCachePath, "package.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`);
}
@ -406,84 +412,79 @@ namespace ts.server.typingsInstaller {
watchers.isInvoked = false;
const isLoggingEnabled = this.log.isEnabled();
const createProjectWatcher = (path: string, createWatch: (path: string) => FileWatcher) => {
toRemove.delete(path);
if (watchers.has(path)) {
const createProjectWatcher = (path: string, projectWatcherType: ProjectWatcherType) => {
const canonicalPath = this.toCanonicalFileName(path);
toRemove.delete(canonicalPath);
if (watchers.has(canonicalPath)) {
return;
}
watchers.set(path, createWatch(path));
};
const createProjectFileWatcher = (file: string): FileWatcher => {
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Added:: WatchInfo: ${file}`);
this.log.writeLine(`${projectWatcherType}:: Added:: WatchInfo: ${path}`);
}
const watcher = this.installTypingHost.watchFile!(file, (f, eventKind) => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${watchers.isInvoked}'`);
}
if (!watchers.isInvoked) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*pollingInterval*/ 2000);
const watcher = projectWatcherType === ProjectWatcherType.FileWatcher ?
this.installTypingHost.watchFile!(path, (f, eventKind) => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${path}:: handler is already invoked '${watchers.isInvoked}'`);
}
if (!watchers.isInvoked) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*pollingInterval*/ 2000) :
this.installTypingHost.watchDirectory!(path, f => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${path} recursive :: handler is already invoked '${watchers.isInvoked}'`);
}
if (watchers.isInvoked || !fileExtensionIs(f, Extension.Json)) {
return;
}
return isLoggingEnabled ? {
if (isPackageOrBowerJson(f, this.installTypingHost.useCaseSensitiveFileNames) &&
!sameFiles(f, this.globalCachePackageJsonPath, this.installTypingHost.useCaseSensitiveFileNames)) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*recursive*/ true);
watchers.set(canonicalPath, isLoggingEnabled ? {
close: () => {
this.log.writeLine(`FileWatcher:: Closed:: WatchInfo: ${file}`);
this.log.writeLine(`${projectWatcherType}:: Closed:: WatchInfo: ${path}`);
watcher.close();
}
} : watcher;
};
const createProjectDirectoryWatcher = (dir: string): FileWatcher => {
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Added:: WatchInfo: ${dir} recursive`);
}
const watcher = this.installTypingHost.watchDirectory!(dir, f => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${dir} recursive :: handler is already invoked '${watchers.isInvoked}'`);
}
if (watchers.isInvoked) {
return;
}
f = this.toCanonicalFileName(f);
if (f !== this.globalCacheCanonicalPackageJsonPath && isPackageOrBowerJson(f)) {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*recursive*/ true);
return isLoggingEnabled ? {
close: () => {
this.log.writeLine(`DirectoryWatcher:: Closed:: WatchInfo: ${dir} recursive`);
watcher.close();
}
} : watcher;
} : watcher);
};
// Create watches from list of files
for (const file of files) {
const filePath = this.toCanonicalFileName(file);
if (isPackageOrBowerJson(filePath)) {
if (file.endsWith("/package.json") || file.endsWith("/bower.json")) {
// package.json or bower.json exists, watch the file to detect changes and update typings
createProjectWatcher(filePath, createProjectFileWatcher);
createProjectWatcher(file, ProjectWatcherType.FileWatcher);
continue;
}
// path in projectRoot, watch project root
if (containsPath(projectRootPath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(projectRootPath, createProjectDirectoryWatcher);
if (containsPath(projectRootPath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
const subDirectory = file.indexOf(directorySeparator, projectRootPath.length + 1);
if (subDirectory !== -1) {
// Watch subDirectory
createProjectWatcher(file.substr(0, subDirectory), ProjectWatcherType.DirectoryWatcher);
}
else {
// Watch the directory itself
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
}
continue;
}
// path in global cache, watch global cache
if (containsPath(this.globalCachePath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(this.globalCachePath, createProjectDirectoryWatcher);
if (containsPath(this.globalCachePath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
createProjectWatcher(this.globalCachePath, ProjectWatcherType.DirectoryWatcher);
continue;
}
// Get path without node_modules and bower_components
createProjectWatcher(getDirectoryExcludingNodeModulesOrBowerComponents(getDirectoryPath(filePath)), createProjectDirectoryWatcher);
// watch node_modules or bower_components
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
}
// Remove unused watches