Merge pull request #23484 from Microsoft/typingInstallerWatch
Use watch recursive directories instead of watchFile for node_modules and bower components
This commit is contained in:
commit
0526ff5fad
5 changed files with 153 additions and 38 deletions
|
@ -13,7 +13,9 @@ namespace ts.projectSystem {
|
|||
export import checkArray = TestFSWithWatch.checkArray;
|
||||
export import libFile = TestFSWithWatch.libFile;
|
||||
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
|
||||
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
||||
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
|
||||
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
||||
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
|
||||
import safeList = TestFSWithWatch.safeList;
|
||||
|
||||
export const customTypesMap = {
|
||||
|
@ -7821,8 +7823,8 @@ namespace ts.projectSystem {
|
|||
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
|
||||
TestFSWithWatch.checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedWatchedFiles);
|
||||
TestFSWithWatch.checkMultiMapKeyCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories);
|
||||
checkWatchedFilesDetailed(host, expectedWatchedFiles);
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
|
||||
checkProjectActualFiles(project, fileNames);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,7 +141,19 @@ namespace ts.projectSystem {
|
|||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = configuredProjectAt(projectService, 0);
|
||||
checkProjectActualFiles(p, [file1.path, tsconfig.path]);
|
||||
checkWatchedFiles(host, [tsconfig.path, libFile.path, packageJson.path, "/a/b/bower_components", "/a/b/node_modules"]);
|
||||
|
||||
const expectedWatchedFiles = createMap<number>();
|
||||
expectedWatchedFiles.set(tsconfig.path, 1); // tsserver
|
||||
expectedWatchedFiles.set(libFile.path, 1); // tsserver
|
||||
expectedWatchedFiles.set(packageJson.path, 1); // typing installer
|
||||
checkWatchedFilesDetailed(host, expectedWatchedFiles);
|
||||
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
|
||||
const expectedWatchedDirectoriesRecursive = createMap<number>();
|
||||
expectedWatchedDirectoriesRecursive.set("/a/b", 2); // TypingInstaller and wild card
|
||||
expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
|
@ -149,7 +161,9 @@ namespace ts.projectSystem {
|
|||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]);
|
||||
// should not watch jquery
|
||||
checkWatchedFiles(host, [tsconfig.path, libFile.path, packageJson.path, "/a/b/bower_components", "/a/b/node_modules"]);
|
||||
checkWatchedFilesDetailed(host, expectedWatchedFiles);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("inferred project (typings installed)", () => {
|
||||
|
@ -827,7 +841,17 @@ namespace ts.projectSystem {
|
|||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = configuredProjectAt(projectService, 0);
|
||||
checkProjectActualFiles(p, [app.path, jsconfig.path]);
|
||||
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
|
||||
|
||||
const watchedFilesExpected = createMap<number>();
|
||||
watchedFilesExpected.set(jsconfig.path, 1); // project files
|
||||
watchedFilesExpected.set(libFile.path, 1); // project files
|
||||
checkWatchedFilesDetailed(host, watchedFilesExpected);
|
||||
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
|
||||
const watchedRecursiveDirectoriesExpected = createMap<number>();
|
||||
watchedRecursiveDirectoriesExpected.set("/", 2); // wild card + type installer
|
||||
checkWatchedDirectoriesDetailed(host, watchedRecursiveDirectoriesExpected, /*recursive*/ true);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
|
||||
|
|
|
@ -179,10 +179,18 @@ interface Array<T> {}`
|
|||
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
|
||||
}
|
||||
|
||||
export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) {
|
||||
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: Map<number>) {
|
||||
checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedFiles);
|
||||
}
|
||||
|
||||
export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive: boolean) {
|
||||
checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||
}
|
||||
|
||||
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: Map<number>, recursive: boolean) {
|
||||
checkMultiMapKeyCount(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
|
||||
}
|
||||
|
||||
export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray<string>) {
|
||||
const mapExpected = arrayToSet(expected);
|
||||
const mapSeen = createMap<true>();
|
||||
|
|
|
@ -119,8 +119,10 @@ declare namespace ts.server {
|
|||
|
||||
/* @internal */
|
||||
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
writeFile(path: string, content: string): void;
|
||||
createDirectory(path: string): void;
|
||||
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
||||
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,20 @@ namespace ts.server.typingsInstaller {
|
|||
onRequestCompleted: RequestCompletedAction;
|
||||
}
|
||||
|
||||
function isPackageOrBowerJson(fileName: string) {
|
||||
const base = getBaseFileName(fileName);
|
||||
return base === "package.json" || base === "bower.json";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
type ProjectWatchers = Map<FileWatcher> & { isInvoked?: boolean; };
|
||||
|
||||
export abstract class TypingsInstaller {
|
||||
|
@ -73,6 +87,8 @@ namespace ts.server.typingsInstaller {
|
|||
private readonly projectWatchers = createMap<ProjectWatchers>();
|
||||
private safeList: JsTyping.SafeList | undefined;
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
private readonly toCanonicalFileName: GetCanonicalFileName;
|
||||
private readonly globalCacheCanonicalPackageJsonPath: string;
|
||||
|
||||
private installRunCount = 1;
|
||||
private inFlightRequestCount = 0;
|
||||
|
@ -86,6 +102,8 @@ namespace ts.server.typingsInstaller {
|
|||
private readonly typesMapLocation: Path,
|
||||
private readonly throttleLimit: number,
|
||||
protected readonly log = nullLog) {
|
||||
this.toCanonicalFileName = createGetCanonicalFileName(installTypingHost.useCaseSensitiveFileNames);
|
||||
this.globalCacheCanonicalPackageJsonPath = combinePaths(this.toCanonicalFileName(globalCachePath), "package.json");
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`);
|
||||
}
|
||||
|
@ -147,7 +165,7 @@ namespace ts.server.typingsInstaller {
|
|||
}
|
||||
|
||||
// start watching files
|
||||
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch);
|
||||
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch, req.projectRootPath);
|
||||
|
||||
// install typings
|
||||
if (discoverTypingsResult.newTypingNames.length) {
|
||||
|
@ -367,7 +385,7 @@ namespace ts.server.typingsInstaller {
|
|||
}
|
||||
}
|
||||
|
||||
private watchFiles(projectName: string, files: string[]) {
|
||||
private watchFiles(projectName: string, files: string[], projectRootPath: Path) {
|
||||
if (!files.length) {
|
||||
// shut down existing watchers
|
||||
this.closeWatchers(projectName);
|
||||
|
@ -375,43 +393,104 @@ namespace ts.server.typingsInstaller {
|
|||
}
|
||||
|
||||
let watchers = this.projectWatchers.get(projectName);
|
||||
const toRemove = createMap<FileWatcher>();
|
||||
if (!watchers) {
|
||||
watchers = createMap();
|
||||
this.projectWatchers.set(projectName, watchers);
|
||||
}
|
||||
else {
|
||||
copyEntries(watchers, toRemove);
|
||||
}
|
||||
|
||||
watchers.isInvoked = false;
|
||||
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
||||
watchers.isInvoked = false;
|
||||
|
||||
const isLoggingEnabled = this.log.isEnabled();
|
||||
mutateMap(
|
||||
watchers,
|
||||
arrayToSet(files),
|
||||
{
|
||||
// Watch the missing files
|
||||
createNewValue: file => {
|
||||
if (isLoggingEnabled) {
|
||||
this.log.writeLine(`FileWatcher:: Added:: WatchInfo: ${file}`);
|
||||
}
|
||||
const watcher = this.installTypingHost.watchFile(file, (f, eventKind) => {
|
||||
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);
|
||||
return isLoggingEnabled ? {
|
||||
close: () => {
|
||||
this.log.writeLine(`FileWatcher:: Closed:: WatchInfo: ${file}`);
|
||||
}
|
||||
} : watcher;
|
||||
},
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
onDeleteValue: closeFileWatcher
|
||||
const createProjectWatcher = (path: string, createWatch: (path: string) => FileWatcher) => {
|
||||
toRemove.delete(path);
|
||||
if (watchers.has(path)) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
watchers.set(path, createWatch(path));
|
||||
};
|
||||
const createProjectFileWatcher = (file: string): FileWatcher => {
|
||||
if (isLoggingEnabled) {
|
||||
this.log.writeLine(`FileWatcher:: Added:: WatchInfo: ${file}`);
|
||||
}
|
||||
const watcher = this.installTypingHost.watchFile(file, (f, eventKind) => {
|
||||
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);
|
||||
|
||||
return isLoggingEnabled ? {
|
||||
close: () => {
|
||||
this.log.writeLine(`FileWatcher:: Closed:: WatchInfo: ${file}`);
|
||||
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 => {
|
||||
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;
|
||||
};
|
||||
|
||||
// Create watches from list of files
|
||||
for (const file of files) {
|
||||
const filePath = this.toCanonicalFileName(file);
|
||||
if (isPackageOrBowerJson(filePath)) {
|
||||
// package.json or bower.json exists, watch the file to detect changes and update typings
|
||||
createProjectWatcher(filePath, createProjectFileWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in projectRoot, watch project root
|
||||
if (containsPath(projectRootPath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
|
||||
createProjectWatcher(projectRootPath, createProjectDirectoryWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in global cache, watch global cache
|
||||
if (containsPath(this.globalCachePath, filePath, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
|
||||
createProjectWatcher(this.globalCachePath, createProjectDirectoryWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get path without node_modules and bower_components
|
||||
createProjectWatcher(getDirectoryExcludingNodeModulesOrBowerComponents(getDirectoryPath(filePath)), createProjectDirectoryWatcher);
|
||||
}
|
||||
|
||||
// Remove unused watches
|
||||
toRemove.forEach((watch, path) => {
|
||||
watch.close();
|
||||
watchers.delete(path);
|
||||
});
|
||||
}
|
||||
|
||||
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {
|
||||
|
|
Loading…
Reference in a new issue