From 4ca46ff20a542f91562d2465f85501add7c016e2 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Fri, 9 Jun 2017 02:31:11 -0700 Subject: [PATCH] A TSServer implementation that just uses the files list to avoid looking all over the file system. Improves performance when running on a network file system. --- src/server/g3ServerHostProxy.ts | 123 ++++++++++++++++++++++++++++++++ src/server/project.ts | 18 ++++- src/server/tsconfig.json | 1 + 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/server/g3ServerHostProxy.ts diff --git a/src/server/g3ServerHostProxy.ts b/src/server/g3ServerHostProxy.ts new file mode 100644 index 0000000000000..ff89251edd577 --- /dev/null +++ b/src/server/g3ServerHostProxy.ts @@ -0,0 +1,123 @@ +/// + +namespace ts.server { + function isGenerated(path: string) { + const GEN_EXT = ['ngsummary', 'ngstyle', 'ngfactory']; + const TS_EXT = ['ts', 'tsx', 'd.ts']; + + for (const gen of GEN_EXT) { + for (const ext of TS_EXT) { + if (fileExtensionIs(path, gen + '.' + ext)) { + return true; + } + } + } + return false; + } + + export function getG3ServerHostProxy( + tsconfigPath: string, + host: ServerHost, + logger: Logger): ServerHost { + + let proxyHost:ServerHost = (Object).assign({}, host); + + const {config, error} = readConfigFile(tsconfigPath, proxyHost.readFile); + if (error) { + return host; + } + const projectDir = getDirectoryPath(tsconfigPath); + + // Get the files list from the tsconfig. + let {options, errors, fileNames} = + parseJsonConfigFileContent(config, proxyHost, projectDir); + + if (errors && errors.length !== 0) { + return host; + } + + // All google3 projects have rootDits set. Don't proxy if rootDirs is + // not set. + if (!options.rootDirs) { + return host; + } + + // Get the list of files into a map. + let fileMap: {[k: string]: boolean} = {}; + + // Just put the directory of the files in the known directories list. + // We don't rely on the behavior of walking up the directories to find + // the node_modules. (This part may not work for opensource) + let directoryMap: {[k: string]: boolean} = {}; + let rootDirs = options.rootDirs; + + // Add all the rootDirs to the known directories list. + rootDirs.forEach(d => { + logger.info('Adding rootdir: ' + d); + directoryMap[d] = true; + }); + + // Add the tsconfig.json as a valid project file. + fileMap[tsconfigPath] = true; + + // For each file add to the filesMap and add their directory + // (and few directories above them) to the directoryMap. + fileNames.forEach(f => { + f = proxyHost.resolvePath(f); + logger.info('Adding file: ' + f); + fileMap[f] = true; + // TODO(viks): How deep should we go? Is 2 enough? + for (let i = 0; i < 2; i++) { + f = getDirectoryPath(f); + if (f) { + logger.info('Adding dir: ' + getDirectoryPath(f)); + directoryMap[f] = true; + } else { + break; + } + } + }); + + // Override the fileExists in the ServerHost to reply using the fileMap + // instead of hitting the (network) file system. + proxyHost.fileExists = (path: string) => { + path = proxyHost.resolvePath(path); + if (path in fileMap) { + // File found in map! + logger.info('Found: ' + path); + return true; + } else { + // Only ever allow looking in the filesystem for files inside + // the project dir. Allows for discovery of new source files + // in the project without having to rebuild tsconfig. + // Skip generated files since the tsconfig would have to generated anyways + // while regenerating these. + if (!isGenerated(path)) { + for (const rootDir of rootDirs) { + if (path.indexOf(rootDir) === 0) { + logger.info('Search: ' + path); + return host.fileExists(path); + } + } + } + } + // File not in map. Just return false without hitting file system. + logger.info('Did not find: ' + path); + return false; + } + + // Override the directoryExists in the ServerHost to reply using the + // directoryMap without hitting the file system. + proxyHost.directoryExists = (path: string) => { + path = proxyHost.resolvePath(path); + if (path in directoryMap) { + logger.info('Dir Found: ' + path); + return true; + } + logger.info('Dir NOT Found: ' + path); + return false; + } + + return proxyHost; + } +} diff --git a/src/server/project.ts b/src/server/project.ts index ac56a6e3b9802..a8b7570abcb21 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -4,6 +4,7 @@ /// /// /// +/// namespace ts.server { @@ -119,6 +120,8 @@ namespace ts.server { // wrapper over the real language service that will suppress all semantic operations protected languageService: LanguageService; + public readonly proxyHost: ServerHost; + public languageServiceEnabled = true; protected readonly lsHost: LSHost; @@ -202,7 +205,18 @@ namespace ts.server { this.setInternalCompilerOptionsForEmittingJsFiles(); - this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken); + // Create a proxy server host that uses the files list to respond to + // fileExists and directoryExists so as to avoid going to the + // file system every time. + this.proxyHost = this.projectService.host; + if (projectKind === ProjectKind.Configured && hasExplicitListOfFiles) { + this.proxyHost = getG3ServerHostProxy( + projectName, + this.projectService.host, + this.projectService.logger); + } + + this.lsHost = new LSHost(this.proxyHost, this, this.projectService.cancellationToken); this.lsHost.setCompilationSettings(this.compilerOptions); this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry); @@ -727,7 +741,7 @@ namespace ts.server { } const allFileNames = arrayFrom(referencedFiles.keys()) as Path[]; - return filter(allFileNames, file => this.projectService.host.fileExists(file)); + return filter(allFileNames, file => this.proxyHost.fileExists(file)); } // remove a root file from project diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index b0eb4965ea687..14a2a58da16ff 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -17,6 +17,7 @@ "scriptInfo.ts", "lsHost.ts", "typingsCache.ts", + "g3ServerHostProxy.ts", "project.ts", "editorServices.ts", "protocol.ts",