Initial refactoring so that watch from tsc follows the tsserver projects

This commit is contained in:
Sheetal Nandi
2017-07-24 16:57:49 -07:00
parent 94a589b3bb
commit ef5935b52c
12 changed files with 970 additions and 518 deletions
+1 -1
View File
@@ -1350,7 +1350,7 @@ namespace ts.server {
}
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName);
const cachedServerHost = new CachedServerHost(this.host);
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
this.logger.info(`Opened configuration file ${configFileName}`);
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName);
+12 -99
View File
@@ -8,13 +8,12 @@ namespace ts.server {
newLine: string;
useCaseSensitiveFileNames: boolean;
private readonly cachedHost: CachedHost;
readonly trace: (s: string) => void;
readonly realpath?: (path: string) => string;
private cachedReadDirectoryResult = createMap<FileSystemEntries>();
private readonly currentDirectory: string;
constructor(private readonly host: ServerHost, private getCanonicalFileName: (fileName: string) => string) {
constructor(private readonly host: ServerHost) {
this.args = host.args;
this.newLine = host.newLine;
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
@@ -24,41 +23,7 @@ namespace ts.server {
if (this.host.realpath) {
this.realpath = path => this.host.realpath(path);
}
this.currentDirectory = this.host.getCurrentDirectory();
}
private toPath(fileName: string) {
return toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
}
private getFileSystemEntries(rootDir: string) {
const path = this.toPath(rootDir);
const cachedResult = this.cachedReadDirectoryResult.get(path);
if (cachedResult) {
return cachedResult;
}
const resultFromHost: FileSystemEntries = {
files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
directories: this.host.getDirectories(rootDir) || []
};
this.cachedReadDirectoryResult.set(path, resultFromHost);
return resultFromHost;
}
private canWorkWithCacheForDir(rootDir: string) {
// Some of the hosts might not be able to handle read directory or getDirectories
const path = this.toPath(rootDir);
if (this.cachedReadDirectoryResult.get(path)) {
return true;
}
try {
return this.getFileSystemEntries(rootDir);
}
catch (_e) {
return false;
}
this.cachedHost = createCachedHost(host);
}
write(s: string) {
@@ -66,13 +31,7 @@ namespace ts.server {
}
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
const path = this.toPath(fileName);
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
const baseFileName = getBaseFileName(toNormalizedPath(fileName));
if (result) {
result.files = this.updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true);
}
return this.host.writeFile(fileName, data, writeByteOrderMark);
this.cachedHost.writeFile(fileName, data, writeByteOrderMark);
}
resolvePath(path: string) {
@@ -88,7 +47,7 @@ namespace ts.server {
}
getCurrentDirectory() {
return this.currentDirectory;
return this.cachedHost.getCurrentDirectory();
}
exit(exitCode?: number) {
@@ -101,78 +60,32 @@ namespace ts.server {
}
getDirectories(rootDir: string) {
if (this.canWorkWithCacheForDir(rootDir)) {
return this.getFileSystemEntries(rootDir).directories.slice();
}
return this.host.getDirectories(rootDir);
return this.cachedHost.getDirectories(rootDir);
}
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
if (this.canWorkWithCacheForDir(rootDir)) {
return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path));
}
return this.host.readDirectory(rootDir, extensions, excludes, includes, depth);
return this.cachedHost.readDirectory(rootDir, extensions, excludes, includes, depth);
}
fileExists(fileName: string): boolean {
const path = this.toPath(fileName);
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
const baseName = getBaseFileName(toNormalizedPath(fileName));
return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName);
return this.cachedHost.fileExists(fileName);
}
directoryExists(dirPath: string) {
const path = this.toPath(dirPath);
return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath);
return this.cachedHost.directoryExists(dirPath);
}
readFile(path: string, encoding?: string): string {
return this.host.readFile(path, encoding);
}
private fileNameEqual(name1: string, name2: string) {
return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2);
}
private hasEntry(entries: ReadonlyArray<string>, name: string) {
return some(entries, file => this.fileNameEqual(file, name));
}
private updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
if (this.hasEntry(entries, baseName)) {
if (!isValid) {
return filter(entries, entry => !this.fileNameEqual(entry, baseName));
}
}
else if (isValid) {
return entries.concat(baseName);
}
return entries;
}
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) {
const path = this.toPath(fileOrFolder);
const existingResult = this.cachedReadDirectoryResult.get(path);
if (existingResult) {
if (!this.host.directoryExists(fileOrFolder)) {
this.cachedReadDirectoryResult.delete(path);
}
}
else {
// Was this earlier file
const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
if (parentResult) {
const baseName = getBaseFileName(fileOrFolder);
if (parentResult) {
parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path));
parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path));
}
}
}
return this.cachedHost.addOrDeleteFileOrFolder(fileOrFolder);
}
clearCache() {
this.cachedReadDirectoryResult = createMap<FileSystemEntries>();
return this.cachedHost.clearCache();
}
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
+40 -54
View File
@@ -639,38 +639,13 @@ namespace ts.server {
}
}
const missingFilePaths = this.program.getMissingFilePaths();
const newMissingFilePathMap = arrayToSet(missingFilePaths);
// Update the missing file paths watcher
this.missingFilesMap = mutateExistingMapWithNewSet(
this.missingFilesMap, newMissingFilePathMap,
this.missingFilesMap = updateMissingFilePathsWatch(this.program, this.missingFilesMap,
// Watch the missing files
missingFilePath => {
const fileWatcher = this.projectService.addFileWatcher(
WatchType.MissingFilePath, this, missingFilePath,
(filename, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
if (this.projectKind === ProjectKind.Configured) {
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
}
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
}
);
return fileWatcher;
},
missingFilePath => this.addMissingFileWatcher(missingFilePath),
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
(missingFilePath, fileWatcher) => {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
}
(missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded)
);
}
@@ -694,6 +669,32 @@ namespace ts.server {
return hasChanges;
}
private addMissingFileWatcher(missingFilePath: Path) {
const fileWatcher = this.projectService.addFileWatcher(
WatchType.MissingFilePath, this, missingFilePath,
(filename, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
if (this.projectKind === ProjectKind.Configured) {
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
}
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
}
);
return fileWatcher;
}
private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason);
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap && this.missingFilesMap.has(path);
}
@@ -1135,33 +1136,18 @@ namespace ts.server {
}
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
this.directoriesWatchedForWildcards = mutateExistingMap(
this.directoriesWatchedForWildcards, wildcardDirectories,
// Create new watch and recursive info
(directory, flag) => {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return {
watcher: this.projectService.addDirectoryWatcher(
WatchType.WildCardDirectories, this, directory,
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
recursive
),
recursive
};
},
// Close existing watch thats not needed any more
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded
this.directoriesWatchedForWildcards = updateWatchingWildcardDirectories(this.directoriesWatchedForWildcards,
wildcardDirectories,
// Create new directory watcher
(directory, recursive) => this.projectService.addDirectoryWatcher(
WatchType.WildCardDirectories, this, directory,
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
recursive
),
// Watcher is same if the recursive flags match
({ recursive: existingRecursive }, flag) => {
// If the recursive dont match, it needs update
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return existingRecursive !== recursive;
},
// Close existing watch that doesnt match in recursive flag
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged
// Close directory watcher
(directory, watcher, recursive, recursiveChanged) => this.projectService.closeDirectoryWatcher(
WatchType.WildCardDirectories, this, directory, watcher, recursive,
recursiveChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded
)
);
}
-73
View File
@@ -292,77 +292,4 @@ namespace ts.server {
deleted(oldItems[oldIndex++]);
}
}
export function cleanExistingMap<T>(
existingMap: Map<T>,
onDeleteExistingValue: (key: string, existingValue: T) => void) {
if (existingMap) {
// Remove all
existingMap.forEach((existingValue, key) => {
existingMap.delete(key);
onDeleteExistingValue(key, existingValue);
});
}
}
export function mutateExistingMapWithNewSet<T>(
existingMap: Map<T>, newMap: Map<true>,
createNewValue: (key: string) => T,
onDeleteExistingValue: (key: string, existingValue: T) => void
): Map<T> {
return mutateExistingMap(
existingMap, newMap,
/*createNewValue*/(key, _valueInNewMap) => createNewValue(key),
onDeleteExistingValue,
);
}
export function mutateExistingMap<T, U>(
existingMap: Map<T>, newMap: Map<U>,
createNewValue: (key: string, valueInNewMap: U) => T,
onDeleteExistingValue: (key: string, existingValue: T) => void,
isSameValue?: (existingValue: T, valueInNewMap: U) => boolean,
OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void,
onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void
): Map<T> {
// If there are new values update them
if (newMap) {
if (existingMap) {
// Needs update
existingMap.forEach((existingValue, key) => {
const valueInNewMap = newMap.get(key);
// Existing value - remove it
if (valueInNewMap === undefined) {
existingMap.delete(key);
onDeleteExistingValue(key, existingValue);
}
// different value - remove it
else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) {
existingMap.delete(key);
OnDeleteExistingMismatchValue(key, existingValue);
}
else if (onSameExistingValue) {
onSameExistingValue(existingValue, valueInNewMap);
}
});
}
else {
// Create new
existingMap = createMap<T>();
}
// Add new values that are not already present
newMap.forEach((valueInNewMap, key) => {
if (!existingMap.has(key)) {
// New values
existingMap.set(key, createNewValue(key, valueInNewMap));
}
});
return existingMap;
}
cleanExistingMap(existingMap, onDeleteExistingValue);
return undefined;
}
}