diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f0a78e8da4b..00905a28579 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -326,6 +326,13 @@ namespace ts.server { syntaxOnly?: boolean; } + interface OriginalFileInfo { fileName: NormalizedPath; path: Path; } + type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo; + + function isOpenScriptInfo(infoOrFileName: OpenScriptInfoOrClosedFileInfo): infoOrFileName is ScriptInfo { + return !!(infoOrFileName as ScriptInfo).containingProjects; + } + function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) { return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`; } @@ -1044,12 +1051,12 @@ namespace ts.server { } } - private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { + private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: OpenScriptInfoOrClosedFileInfo) { let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { // By default the info would get impacted by presence of config file since its in the detection path // Only adding the info as a root to inferred project will need the existence to be watched by file watcher - if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { + if (isOpenScriptInfo(info) && !configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false); this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd); } @@ -1066,9 +1073,11 @@ namespace ts.server { // Or the whole chain of config files for the roots of the inferred projects // Cache the host value of file exists and add the info to map of open files impacted by this config file - const openFilesImpactedByConfigFile = createMap(); - openFilesImpactedByConfigFile.set(info.path, false); const exists = this.host.fileExists(configFileName); + const openFilesImpactedByConfigFile = createMap(); + if (isOpenScriptInfo(info)) { + openFilesImpactedByConfigFile.set(info.path, false); + } configFileExistenceInfo = { exists, openFilesImpactedByConfigFile }; this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo); this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd); @@ -1180,7 +1189,7 @@ namespace ts.server { */ private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.isScriptOpen()); - this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => { + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path); @@ -1214,7 +1223,7 @@ namespace ts.server { /* @internal */ startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); - this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => { + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (!configFileExistenceInfo) { // Create the cache @@ -1242,7 +1251,7 @@ namespace ts.server { */ /* @internal */ stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { - this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => { + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { Debug.assert(info.isScriptOpen()); @@ -1265,12 +1274,12 @@ namespace ts.server { * The server must start searching from the directory containing * the newly opened file. */ - private forEachConfigFileLocation(info: ScriptInfo, infoShouldBeOpen: boolean, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) { + private forEachConfigFileLocation(info: OpenScriptInfoOrClosedFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) { if (this.syntaxOnly) { return undefined; } - Debug.assert(!infoShouldBeOpen || this.openFiles.has(info.path)); + Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path)); const projectRootPath = this.openFiles.get(info.path); let searchPath = asNormalizedPath(getDirectoryPath(info.fileName)); @@ -1309,11 +1318,13 @@ namespace ts.server { * current directory (the directory in which tsc was invoked). * The server must start searching from the directory containing * the newly opened file. + * If script info is passed in, it is asserted to be open script info + * otherwise just file name */ - private getConfigFileNameForFile(info: ScriptInfo, infoShouldBeOpen: boolean) { - if (infoShouldBeOpen) Debug.assert(info.isScriptOpen()); + private getConfigFileNameForFile(info: OpenScriptInfoOrClosedFileInfo) { + if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen()); this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); - const configFileName = this.forEachConfigFileLocation(info, infoShouldBeOpen, (configFileName, canonicalConfigFilePath) => + const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => this.configFileExists(configFileName, canonicalConfigFilePath, info)); if (configFileName) { this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`); @@ -2005,7 +2016,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, /*infoShouldBeOpen*/ true); + const configFileName = this.getConfigFileNameForFile(info); if (configFileName) { const project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { @@ -2093,17 +2104,25 @@ namespace ts.server { return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined); } - /** @internal */ - getProjectForFileWithoutOpening(fileName: NormalizedPath): { readonly scriptInfo: ScriptInfo, readonly projects: ReadonlyArray } | undefined { - const scriptInfo = this.filenameToScriptInfo.get(fileName) || - this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName, this.currentDirectory, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined); - if (!scriptInfo) return undefined; - if (scriptInfo.containingProjects.length) { - return { scriptInfo, projects: scriptInfo.containingProjects }; + /*@internal*/ + getOriginalLocationEnsuringConfiguredProject(project: Project, location: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined { + const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location); + if (!originalLocation) return undefined; + + const { fileName } = originalLocation; + const originalScriptInfo = this.getScriptInfo(fileName); + if (originalScriptInfo && originalScriptInfo.containingProjects.length) { + return originalLocation; } - const configFileName = this.getConfigFileNameForFile(scriptInfo, /*infoShouldBeOpen*/ false); - const project = configFileName === undefined ? undefined : this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName); - return project && project.containsScriptInfo(scriptInfo) ? { scriptInfo, projects: [project] } : undefined; + + const info: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) }; + const configFileName = this.getConfigFileNameForFile(info); + if (!configFileName) return undefined; + + const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName); + updateProjectIfDirty(configuredProject); + + return configuredProject.containsFile(info.fileName) ? originalLocation : undefined; } /** @internal */ @@ -2128,7 +2147,7 @@ namespace ts.server { this.openFiles.set(info.path, projectRootPath); let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info); if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization - configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true); + configFileName = this.getConfigFileNameForFile(info); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { diff --git a/src/server/project.ts b/src/server/project.ts index 08b08eae04c..decb31097a9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -711,7 +711,7 @@ namespace ts.server { } containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean { - const info = this.projectService.getScriptInfoForPath(this.toPath(filename)); + const info = this.projectService.getScriptInfoForNormalizedPath(filename); if (info && (info.isScriptOpen() || !requireOpen)) { return this.containsScriptInfo(info); } diff --git a/src/server/session.ts b/src/server/session.ts index 7dccda3c82c..d2fefb16430 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -427,23 +427,20 @@ namespace ts.server { if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled cb(projectAndLocation, (project, location) => { seenProjects.set(projectAndLocation.project.projectName, true); - const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location); + const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location); if (!originalLocation) return false; - const originalProjectAndScriptInfo = projectService.getProjectForFileWithoutOpening(toNormalizedPath(originalLocation.fileName)); - if (!originalProjectAndScriptInfo) return false; - if (originalProjectAndScriptInfo) { - toDo = toDo || []; + const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!; + toDo = toDo || []; - for (const project of originalProjectAndScriptInfo.projects) { - addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects); - } - const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalProjectAndScriptInfo.scriptInfo); - if (symlinkedProjectsMap) { - symlinkedProjectsMap.forEach((symlinkedProjects) => { - for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects); - }); - } + for (const project of originalScriptInfo.containingProjects) { + addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects); + } + const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo); + if (symlinkedProjectsMap) { + symlinkedProjectsMap.forEach((symlinkedProjects) => { + for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects); + }); } return true; }); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 6d39ecb76d1..d23c2f288f3 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -9458,7 +9458,7 @@ export function Test2() { }); }); - describe("Untitled files", () => { + describe("tsserverProjectSystem Untitled files", () => { it("Can convert positions to locations", () => { const aTs: File = { path: "/proj/a.ts", content: "" }; const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };