From 6515d375bd602e7c711101e5b44356636b583647 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 8 Nov 2019 14:23:49 -0800 Subject: [PATCH 1/3] Optionally stop looking for the default configured project at node_modules --- src/compiler/path.ts | 4 +++ src/compiler/resolutionCache.ts | 4 --- src/server/editorServices.ts | 45 ++++++++++++++++++++++++--------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index d7d8d964d542b..a550e1a0e9b57 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -852,4 +852,8 @@ namespace ts { directory = parentPath; } } + + export function isNodeModulesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules"); + } } \ No newline at end of file diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 011baa788b1a2..d512087154473 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -428,10 +428,6 @@ namespace ts { return cache && cache.get(moduleName); } - function isNodeModulesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules"); - } - function isNodeModulesAtTypesDirectory(dirPath: Path) { return endsWith(dirPath, "/node_modules/@types"); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3412c1c1598f7..1355c9fd1a313 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -362,6 +362,12 @@ namespace ts.server { RootOfInferredProjectFalse = "Open file was set as not inferred root", } + const enum ConfigFileActionResult { + Continue, + Return, + Break, + } + /*@internal*/ interface ConfigFileExistenceInfo { /** @@ -1637,7 +1643,7 @@ namespace ts.server { * The server must start searching from the directory containing * the newly opened file. */ - private forEachConfigFileLocation(info: OpenScriptInfoOrClosedOrConfigFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) { + private forEachConfigFileLocation(info: OpenScriptInfoOrClosedOrConfigFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => ConfigFileActionResult | void) { if (this.syntaxOnly) { return undefined; } @@ -1658,12 +1664,23 @@ namespace ts.server { if (searchInDirectory) { const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName); const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); - let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json")); - if (result) return tsconfigFileName; + const tsResult = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json")); + if (tsResult === ConfigFileActionResult.Return) { + return tsconfigFileName; + } const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); - result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json")); - if (result) return jsconfigFileName; + const jsResult = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json")); + if (jsResult === ConfigFileActionResult.Return) { + return jsconfigFileName; + } + + if (tsResult === ConfigFileActionResult.Break || jsResult === ConfigFileActionResult.Break) { + break; + } + + Debug.assert(!tsResult); + Debug.assert(!jsResult); } const parentPath = asNormalizedPath(getDirectoryPath(searchPath)); @@ -1678,7 +1695,7 @@ namespace ts.server { /*@internal*/ findDefaultConfiguredProject(info: ScriptInfo) { if (!info.isScriptOpen()) return undefined; - const configFileName = this.getConfigFileNameForFile(info); + const configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ false); return configFileName && this.findConfiguredProjectByProjectName(configFileName); } @@ -1693,11 +1710,15 @@ namespace ts.server { * If script info is passed in, it is asserted to be open script info * otherwise just file name */ - private getConfigFileNameForFile(info: OpenScriptInfoOrClosedOrConfigFileInfo) { + private getConfigFileNameForFile(info: OpenScriptInfoOrClosedOrConfigFileInfo, stopAtNodeModules: boolean) { if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen()); this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => - this.configFileExists(configFileName, canonicalConfigFilePath, info)); + this.configFileExists(configFileName, canonicalConfigFilePath, info) + ? ConfigFileActionResult.Return + : stopAtNodeModules && isNodeModulesDirectory(getDirectoryPath(canonicalConfigFilePath) as Path) + ? ConfigFileActionResult.Break + : ConfigFileActionResult.Continue); if (configFileName) { this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`); } @@ -2706,7 +2727,7 @@ namespace ts.server { // we first detect if there is already a configured project created for it: if so, // we re- read the tsconfig file content and update the project only if we havent already done so // otherwise we create a new one. - const configFileName = this.getConfigFileNameForFile(info); + const configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ true); if (configFileName) { const project = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName); if (!updatedProjects.has(configFileName)) { @@ -2803,7 +2824,7 @@ namespace ts.server { if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined; const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) }; - const configFileName = this.getConfigFileNameForFile(originalFileInfo); + const configFileName = this.getConfigFileNameForFile(originalFileInfo, /*stopAtNodeModules*/ false); if (!configFileName) return undefined; const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || @@ -2857,7 +2878,7 @@ namespace ts.server { let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info); let defaultConfigProject: ConfiguredProject | undefined; if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization - configFileName = this.getConfigFileNameForFile(info); + configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ true); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { @@ -2920,7 +2941,7 @@ namespace ts.server { fileName: project.getConfigFilePath(), path: info.path, configFileInfo: true - }); + }, /*stopAtNodeModules*/ true); if (!configFileName) return; // find or delay load the project From c2a412c50221b55d8916c2671f6e807ae3bdfb21 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 2 Mar 2020 10:58:38 -0800 Subject: [PATCH 2/3] Make stopping at node_modules non-optional --- src/server/editorServices.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1355c9fd1a313..764443179f5d8 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1695,7 +1695,7 @@ namespace ts.server { /*@internal*/ findDefaultConfiguredProject(info: ScriptInfo) { if (!info.isScriptOpen()) return undefined; - const configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ false); + const configFileName = this.getConfigFileNameForFile(info); return configFileName && this.findConfiguredProjectByProjectName(configFileName); } @@ -1710,13 +1710,13 @@ namespace ts.server { * If script info is passed in, it is asserted to be open script info * otherwise just file name */ - private getConfigFileNameForFile(info: OpenScriptInfoOrClosedOrConfigFileInfo, stopAtNodeModules: boolean) { + private getConfigFileNameForFile(info: OpenScriptInfoOrClosedOrConfigFileInfo) { if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen()); this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => this.configFileExists(configFileName, canonicalConfigFilePath, info) ? ConfigFileActionResult.Return - : stopAtNodeModules && isNodeModulesDirectory(getDirectoryPath(canonicalConfigFilePath) as Path) + : isNodeModulesDirectory(getDirectoryPath(canonicalConfigFilePath) as Path) ? ConfigFileActionResult.Break : ConfigFileActionResult.Continue); if (configFileName) { @@ -2727,7 +2727,7 @@ namespace ts.server { // we first detect if there is already a configured project created for it: if so, // we re- read the tsconfig file content and update the project only if we havent already done so // otherwise we create a new one. - const configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ true); + const configFileName = this.getConfigFileNameForFile(info); if (configFileName) { const project = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName); if (!updatedProjects.has(configFileName)) { @@ -2824,7 +2824,7 @@ namespace ts.server { if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined; const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) }; - const configFileName = this.getConfigFileNameForFile(originalFileInfo, /*stopAtNodeModules*/ false); + const configFileName = this.getConfigFileNameForFile(originalFileInfo); if (!configFileName) return undefined; const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || @@ -2878,7 +2878,7 @@ namespace ts.server { let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info); let defaultConfigProject: ConfiguredProject | undefined; if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization - configFileName = this.getConfigFileNameForFile(info, /*stopAtNodeModules*/ true); + configFileName = this.getConfigFileNameForFile(info); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { @@ -2941,7 +2941,7 @@ namespace ts.server { fileName: project.getConfigFilePath(), path: info.path, configFileInfo: true - }, /*stopAtNodeModules*/ true); + }); if (!configFileName) return; // find or delay load the project From cb4a0a5f074fc77095ff27e7a582fd0c1297a56c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 2 Mar 2020 14:02:42 -0800 Subject: [PATCH 3/3] Generalize and simplify the change - node_modules files don't have default configured projects --- src/server/editorServices.ts | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 764443179f5d8..c0a9b9530abe5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -362,12 +362,6 @@ namespace ts.server { RootOfInferredProjectFalse = "Open file was set as not inferred root", } - const enum ConfigFileActionResult { - Continue, - Return, - Break, - } - /*@internal*/ interface ConfigFileExistenceInfo { /** @@ -1643,7 +1637,7 @@ namespace ts.server { * The server must start searching from the directory containing * the newly opened file. */ - private forEachConfigFileLocation(info: OpenScriptInfoOrClosedOrConfigFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => ConfigFileActionResult | void) { + private forEachConfigFileLocation(info: OpenScriptInfoOrClosedOrConfigFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) { if (this.syntaxOnly) { return undefined; } @@ -1664,23 +1658,19 @@ namespace ts.server { if (searchInDirectory) { const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName); const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); - const tsResult = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json")); - if (tsResult === ConfigFileActionResult.Return) { - return tsconfigFileName; - } + let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json")); + if (result) return tsconfigFileName; const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); - const jsResult = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json")); - if (jsResult === ConfigFileActionResult.Return) { - return jsconfigFileName; - } + result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json")); + if (result) return jsconfigFileName; - if (tsResult === ConfigFileActionResult.Break || jsResult === ConfigFileActionResult.Break) { + // If we started within node_modules, don't look outside node_modules. + // Otherwise, we might pick up a very large project and pull in the world, + // causing an editor delay. + if (isNodeModulesDirectory(canonicalSearchPath)) { break; } - - Debug.assert(!tsResult); - Debug.assert(!jsResult); } const parentPath = asNormalizedPath(getDirectoryPath(searchPath)); @@ -1714,11 +1704,7 @@ namespace ts.server { if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen()); this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => - this.configFileExists(configFileName, canonicalConfigFilePath, info) - ? ConfigFileActionResult.Return - : isNodeModulesDirectory(getDirectoryPath(canonicalConfigFilePath) as Path) - ? ConfigFileActionResult.Break - : ConfigFileActionResult.Continue); + this.configFileExists(configFileName, canonicalConfigFilePath, info)); if (configFileName) { this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`); }