mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Initial refactoring so that watch from tsc follows the tsserver projects
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user