///
///
///
namespace ts.server {
export class CachedServerHost implements ServerHost {
args: string[];
newLine: string;
useCaseSensitiveFileNames: boolean;
readonly trace: (s: string) => void;
readonly realpath?: (path: string) => string;
private getCanonicalFileName: (fileName: string) => string;
private cachedReadDirectoryResult = createMap();
private readonly currentDirectory: string;
constructor(private readonly host: ServerHost) {
this.args = host.args;
this.newLine = host.newLine;
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
this.getCanonicalFileName = createGetCanonicalFileName(this.useCaseSensitiveFileNames);
if (host.trace) {
this.trace = s => host.trace(s);
}
if (this.host.realpath) {
this.realpath = path => this.host.realpath(path);
}
this.currentDirectory = this.host.getCurrentDirectory();
}
private getFileSystemEntries(rootDir: string) {
const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName);
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 = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName);
if (this.cachedReadDirectoryResult.get(path)) {
return true;
}
try {
return this.getFileSystemEntries(rootDir);
}
catch (_e) {
return false;
}
}
write(s: string) {
return this.host.write(s);
}
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
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);
}
resolvePath(path: string) {
return this.host.resolvePath(path);
}
createDirectory(path: string) {
Debug.fail(`Why is createDirectory called on the cached server for ${path}`);
}
getExecutingFilePath() {
return this.host.getExecutingFilePath();
}
getCurrentDirectory() {
return this.currentDirectory;
}
exit(exitCode?: number) {
Debug.fail(`Why is exit called on the cached server: ${exitCode}`);
}
getEnvironmentVariable(name: string) {
Debug.fail(`Why is getEnvironmentVariable called on the cached server: ${name}`);
return this.host.getEnvironmentVariable(name);
}
getDirectories(rootDir: string) {
if (this.canWorkWithCacheForDir(rootDir)) {
return this.getFileSystemEntries(rootDir).directories.slice();
}
return this.host.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);
}
fileExists(fileName: string): boolean {
const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
const baseName = getBaseFileName(toNormalizedPath(fileName));
return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName);
}
directoryExists(dirPath: string) {
const path = toPath(dirPath, this.currentDirectory, this.getCanonicalFileName);
return this.cachedReadDirectoryResult.has(path) || this.host.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, name: string) {
return some(entries, file => this.fileNameEqual(file, name));
}
private updateFileSystemEntry(entries: ReadonlyArray, 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 = toPath(fileOrFolder, this.currentDirectory, this.getCanonicalFileName);
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));
}
}
}
}
clearCache() {
this.cachedReadDirectoryResult = createMap();
}
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
return this.host.setTimeout(callback, ms, ...args);
}
clearTimeout(timeoutId: any) {
return this.host.clearTimeout(timeoutId);
}
setImmediate(callback: (...args: any[]) => void, ...args: any[]) {
this.host.setImmediate(callback, ...args);
}
clearImmediate(timeoutId: any) {
this.host.clearImmediate(timeoutId);
}
}
type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean };
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
private compilationSettings: CompilerOptions;
private readonly resolvedModuleNames = createMap