From 751cb9e2c3e63fdbb7f373427744ce5a266be60f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 30 Nov 2018 16:29:21 -0800 Subject: [PATCH] Update source and declaration projects on update to declaration file or map file TODO: add tests --- src/server/editorServices.ts | 89 +++++++++++++++---- src/server/scriptInfo.ts | 16 ++-- .../reference/api/tsserverlibrary.d.ts | 4 + 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5c70c7be8bf..5eb009fcb52 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -945,10 +945,39 @@ namespace ts.server { // this file and set of inferred projects info.delayReloadNonMixedContentFile(); this.delayUpdateProjectGraphs(info.containingProjects); + this.handleSourceMapProjects(info); } } } + private handleSourceMapProjects(info: ScriptInfo) { + // Change in d.ts, update source projects as well + if (info.sourceMapFilePath) { + const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (sourceMapFileInfo && sourceMapFileInfo.sourceInfos) { + this.delayUpdateSourceInfoProjects(sourceMapFileInfo.sourceInfos); + } + } + // Change in mapInfo, update declarationProjects and source projects + if (info.sourceInfos) { + this.delayUpdateSourceInfoProjects(info.sourceInfos); + } + if (info.declarationInfoPath) { + this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath); + } + } + + private delayUpdateSourceInfoProjects(sourceInfos: Map) { + sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path)); + } + + private delayUpdateProjectsOfScriptInfoPath(path: Path) { + const info = this.getScriptInfoForPath(path); + if (info) { + this.delayUpdateProjectGraphs(info.containingProjects); + } + } + private handleDeletedFile(info: ScriptInfo) { this.stopWatchingScriptInfo(info); @@ -962,6 +991,7 @@ namespace ts.server { // update projects to make sure that set of referenced files is correct this.delayUpdateProjectGraphs(containingProjects); + this.handleSourceMapProjects(info); } } @@ -2201,19 +2231,30 @@ namespace ts.server { const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, project.directoryStructureHost); if (!declarationInfo) return undefined; + // Try to get from cache declarationInfo.getSnapshot(); // Ensure synchronized - const existingMapper = declarationInfo.mapper; - if (existingMapper !== undefined) { - return existingMapper ? existingMapper : undefined; + if (declarationInfo.sourceMapFilePath !== undefined) { + // Doesnt have sourceMap + if (!declarationInfo.sourceMapFilePath) return undefined; + + // Ensure mapper is synchronized + const mapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); + if (mapFileInfo) { + mapFileInfo.getSnapshot(); + if (mapFileInfo.mapper !== undefined) { + return mapFileInfo.mapper ? mapFileInfo.mapper : undefined; + } + } } // Create the mapper - declarationInfo.mapInfo = undefined; + declarationInfo.sourceMapFilePath = undefined; + let sourceMapFileInfo: ScriptInfo | undefined; let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); if (!mapInfo) return undefined; - declarationInfo.mapInfo = mapInfo; + sourceMapFileInfo = mapInfo; const snap = mapInfo.getSnapshot(); return snap.getText(0, snap.getLength()); }; @@ -2225,11 +2266,17 @@ namespace ts.server { readMapFile ); readMapFile = undefined; // Remove ref to project - declarationInfo.mapper = mapper || false; - if (sourceFileName && mapper) { - // Attach as source - const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; - (declarationInfo.mapInfo!.sourceInfos || (declarationInfo.mapInfo!.sourceInfos = createMap())).set(sourceInfo.path, true); + if (sourceMapFileInfo) { + declarationInfo.sourceMapFilePath = sourceMapFileInfo.path; + sourceMapFileInfo.mapper = mapper || false; + if (sourceFileName && mapper) { + // Attach as source + const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; + (sourceMapFileInfo.sourceInfos || (sourceMapFileInfo.sourceInfos = createMap())).set(sourceInfo.path, true); + } + } + else { + declarationInfo.sourceMapFilePath = false; } return mapper; } @@ -2248,8 +2295,11 @@ namespace ts.server { if (!info) return undefined; // Attach as source - if (declarationInfo && declarationInfo.mapInfo && info !== declarationInfo) { - (declarationInfo.mapInfo.sourceInfos || (declarationInfo.mapInfo.sourceInfos = createMap())).set(info.path, true); + if (declarationInfo && declarationInfo.sourceMapFilePath && info !== declarationInfo) { + const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); + if (sourceMapInfo) { + (sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true); + } } // Key doesnt matter since its only for text and lines @@ -2616,8 +2666,10 @@ namespace ts.server { // If script info is open or orphan, retain it and its dependencies if (!info.isScriptOpen() && info.isOrphan()) { // Otherwise if there is any source info that is alive, this alive too - if (!info.mapInfo || !info.mapInfo.sourceInfos) return; - if (!forEachKey(info.mapInfo.sourceInfos, path => { + if (!info.sourceMapFilePath) return; + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (!sourceMapInfo || !sourceMapInfo.sourceInfos) return; + if (!forEachKey(sourceMapInfo.sourceInfos, path => { const info = this.getScriptInfoForPath(path as Path)!; return info.isScriptOpen() || !info.isOrphan(); })) { @@ -2627,11 +2679,12 @@ namespace ts.server { // Retain this script info toRemoveScriptInfos.delete(info.path); - if (info.mapInfo) { + if (info.sourceMapFilePath) { // And map file info and source infos - toRemoveScriptInfos.delete(info.mapInfo.path); - if (info.mapInfo.sourceInfos) { - info.mapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); + toRemoveScriptInfos.delete(info.sourceMapFilePath); + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (sourceMapInfo && sourceMapInfo.sourceInfos) { + sourceMapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); } } }); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index d2f230178d3..29685112188 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -65,10 +65,11 @@ namespace ts.server { } private resetSourceMapInfo() { - this.info.mapper = undefined; this.info.sourceFileLike = undefined; - this.info.mapInfo = undefined; + this.info.sourceMapFilePath = undefined; + this.info.declarationInfoPath = undefined; this.info.sourceInfos = undefined; + this.info.mapper = undefined; } /** Public for testing */ @@ -305,13 +306,18 @@ namespace ts.server { mTime?: number; /*@internal*/ - mapInfo?: ScriptInfo; + sourceFileLike?: SourceFileLike; + + /*@internal*/ + sourceMapFilePath?: Path | false; + + // Present on sourceMapFile info + /*@internal*/ + declarationInfoPath?: Path; /*@internal*/ sourceInfos?: Map; /*@internal*/ mapper?: DocumentPositionMapper | false; - /*@internal*/ - sourceFileLike: SourceFileLike | undefined; constructor( private readonly host: ServerHost, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2bf91fac4f4..fa6990c07f4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8595,6 +8595,9 @@ declare namespace ts.server { getHostFormatCodeOptions(): FormatCodeSettings; getHostPreferences(): protocol.UserPreferences; private onSourceFileChanged; + private handleSourceMapProjects; + private delayUpdateSourceInfoProjects; + private delayUpdateProjectsOfScriptInfoPath; private handleDeletedFile; private onConfigChangedForConfiguredProject; /** @@ -8722,6 +8725,7 @@ declare namespace ts.server { private findExternalProjectContainingOpenScriptInfo; openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; private removeOrphanConfiguredProjects; + private removeOrphanScriptInfos; private telemetryOnOpenFile; /** * Close file whose contents is managed by the client