Skip to content

Commit 3dc0d5a

Browse files
committed
Watch missing map file and update the source mapping accordingly
1 parent 8f3d2d9 commit 3dc0d5a

File tree

4 files changed

+224
-66
lines changed

4 files changed

+224
-66
lines changed

src/server/editorServices.ts

Lines changed: 112 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ namespace ts.server {
342342
FailedLookupLocation = "Directory of Failed lookup locations in module resolution",
343343
TypeRoots = "Type root directory",
344344
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
345+
MissingSourceMapFile = "Missing source map file"
345346
}
346347

347348
const enum ConfigFileWatcherStatus {
@@ -953,22 +954,25 @@ namespace ts.server {
953954
private handleSourceMapProjects(info: ScriptInfo) {
954955
// Change in d.ts, update source projects as well
955956
if (info.sourceMapFilePath) {
956-
const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
957-
if (sourceMapFileInfo && sourceMapFileInfo.sourceInfos) {
958-
this.delayUpdateSourceInfoProjects(sourceMapFileInfo.sourceInfos);
957+
if (isString(info.sourceMapFilePath)) {
958+
const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
959+
this.delayUpdateSourceInfoProjects(sourceMapFileInfo && sourceMapFileInfo.sourceInfos);
960+
}
961+
else {
962+
this.delayUpdateSourceInfoProjects(info.sourceMapFilePath.sourceInfos);
959963
}
960964
}
961965
// Change in mapInfo, update declarationProjects and source projects
962-
if (info.sourceInfos) {
963-
this.delayUpdateSourceInfoProjects(info.sourceInfos);
964-
}
966+
this.delayUpdateSourceInfoProjects(info.sourceInfos);
965967
if (info.declarationInfoPath) {
966968
this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath);
967969
}
968970
}
969971

970-
private delayUpdateSourceInfoProjects(sourceInfos: Map<true>) {
971-
sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path));
972+
private delayUpdateSourceInfoProjects(sourceInfos: Map<true> | undefined) {
973+
if (sourceInfos) {
974+
sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path));
975+
}
972976
}
973977

974978
private delayUpdateProjectsOfScriptInfoPath(path: Path) {
@@ -992,6 +996,14 @@ namespace ts.server {
992996
// update projects to make sure that set of referenced files is correct
993997
this.delayUpdateProjectGraphs(containingProjects);
994998
this.handleSourceMapProjects(info);
999+
info.closeSourceMapFileWatcher();
1000+
// need to recalculate source map from declaration file
1001+
if (info.declarationInfoPath) {
1002+
const declarationInfo = this.getScriptInfoForPath(info.declarationInfoPath);
1003+
if (declarationInfo) {
1004+
declarationInfo.sourceMapFilePath = undefined;
1005+
}
1006+
}
9951007
}
9961008
}
9971009

@@ -2228,32 +2240,43 @@ namespace ts.server {
22282240

22292241
/*@internal*/
22302242
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
2231-
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, project.directoryStructureHost);
2243+
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
2244+
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
22322245
if (!declarationInfo) return undefined;
22332246

22342247
// Try to get from cache
22352248
declarationInfo.getSnapshot(); // Ensure synchronized
2236-
if (declarationInfo.sourceMapFilePath !== undefined) {
2237-
// Doesnt have sourceMap
2238-
if (!declarationInfo.sourceMapFilePath) return undefined;
2239-
2249+
if (isString(declarationInfo.sourceMapFilePath)) {
22402250
// Ensure mapper is synchronized
2241-
const mapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
2242-
if (mapFileInfo) {
2243-
mapFileInfo.getSnapshot();
2244-
if (mapFileInfo.documentPositionMapper !== undefined) {
2245-
return mapFileInfo.documentPositionMapper ? mapFileInfo.documentPositionMapper : undefined;
2251+
const sourceMapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
2252+
if (sourceMapFileInfo) {
2253+
sourceMapFileInfo.getSnapshot();
2254+
if (sourceMapFileInfo.documentPositionMapper !== undefined) {
2255+
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
2256+
return sourceMapFileInfo.documentPositionMapper ? sourceMapFileInfo.documentPositionMapper : undefined;
22462257
}
22472258
}
2259+
declarationInfo.sourceMapFilePath = undefined;
2260+
}
2261+
else if (declarationInfo.sourceMapFilePath) {
2262+
declarationInfo.sourceMapFilePath.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, declarationInfo.sourceMapFilePath.sourceInfos);
2263+
return undefined;
2264+
}
2265+
else if (declarationInfo.sourceMapFilePath !== undefined) {
2266+
// Doesnt have sourceMap
2267+
return undefined;
22482268
}
22492269

22502270
// Create the mapper
2251-
declarationInfo.sourceMapFilePath = undefined;
22522271
let sourceMapFileInfo: ScriptInfo | undefined;
2272+
let mapFileNameFromDeclarationInfo: string | undefined;
22532273

2254-
let readMapFile: ReadMapFile | undefined = mapFileName => {
2255-
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, project.directoryStructureHost);
2256-
if (!mapInfo) return undefined;
2274+
let readMapFile: ReadMapFile | undefined = (mapFileName, mapFileNameFromDts) => {
2275+
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.host);
2276+
if (!mapInfo) {
2277+
mapFileNameFromDeclarationInfo = mapFileNameFromDts;
2278+
return undefined;
2279+
}
22572280
sourceMapFileInfo = mapInfo;
22582281
const snap = mapInfo.getSnapshot();
22592282
if (mapInfo.documentPositionMapper !== undefined) return mapInfo.documentPositionMapper;
@@ -2271,18 +2294,54 @@ namespace ts.server {
22712294
declarationInfo.sourceMapFilePath = sourceMapFileInfo.path;
22722295
sourceMapFileInfo.declarationInfoPath = declarationInfo.path;
22732296
sourceMapFileInfo.documentPositionMapper = documentPositionMapper || false;
2274-
if (sourceFileName && documentPositionMapper) {
2275-
// Attach as source
2276-
const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!;
2277-
(sourceMapFileInfo.sourceInfos || (sourceMapFileInfo.sourceInfos = createMap())).set(sourceInfo.path, true);
2278-
}
2297+
sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos);
2298+
}
2299+
else if (mapFileNameFromDeclarationInfo) {
2300+
declarationInfo.sourceMapFilePath = {
2301+
declarationInfoPath: declarationInfo.path,
2302+
watcher: this.addMissingSourceMapFile(
2303+
project.currentDirectory === this.currentDirectory ?
2304+
mapFileNameFromDeclarationInfo :
2305+
getNormalizedAbsolutePath(mapFileNameFromDeclarationInfo, project.currentDirectory),
2306+
declarationInfo.path
2307+
),
2308+
sourceInfos: this.addSourceInfoToSourceMap(sourceFileName, project)
2309+
};
22792310
}
22802311
else {
22812312
declarationInfo.sourceMapFilePath = false;
22822313
}
22832314
return documentPositionMapper;
22842315
}
22852316

2317+
private addSourceInfoToSourceMap(sourceFileName: string | undefined, project: Project, sourceInfos?: Map<true>) {
2318+
if (sourceFileName) {
2319+
// Attach as source
2320+
const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!;
2321+
(sourceInfos || (sourceInfos = createMap())).set(sourceInfo.path, true);
2322+
}
2323+
return sourceInfos;
2324+
}
2325+
2326+
private addMissingSourceMapFile(mapFileName: string, declarationInfoPath: Path) {
2327+
const fileWatcher = this.watchFactory.watchFile(
2328+
this.host,
2329+
mapFileName,
2330+
() => {
2331+
const declarationInfo = this.getScriptInfoForPath(declarationInfoPath);
2332+
if (declarationInfo && declarationInfo.sourceMapFilePath && !isString(declarationInfo.sourceMapFilePath)) {
2333+
// Update declaration and source projects
2334+
this.delayUpdateProjectGraphs(declarationInfo.containingProjects);
2335+
this.delayUpdateSourceInfoProjects(declarationInfo.sourceMapFilePath.sourceInfos);
2336+
declarationInfo.closeSourceMapFileWatcher();
2337+
}
2338+
},
2339+
PollingInterval.High,
2340+
WatchType.MissingSourceMapFile,
2341+
);
2342+
return fileWatcher;
2343+
}
2344+
22862345
/*@internal*/
22872346
getSourceFileLike(fileName: string, projectNameOrProject: string | Project, declarationInfo?: ScriptInfo) {
22882347
const project = (projectNameOrProject as Project).projectName ? projectNameOrProject as Project : this.findProject(projectNameOrProject as string);
@@ -2297,7 +2356,7 @@ namespace ts.server {
22972356
if (!info) return undefined;
22982357

22992358
// Attach as source
2300-
if (declarationInfo && declarationInfo.sourceMapFilePath && info !== declarationInfo) {
2359+
if (declarationInfo && isString(declarationInfo.sourceMapFilePath) && info !== declarationInfo) {
23012360
const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath);
23022361
if (sourceMapInfo) {
23032362
(sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true);
@@ -2669,11 +2728,18 @@ namespace ts.server {
26692728
if (!info.isScriptOpen() && info.isOrphan()) {
26702729
// Otherwise if there is any source info that is alive, this alive too
26712730
if (!info.sourceMapFilePath) return;
2672-
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
2673-
if (!sourceMapInfo || !sourceMapInfo.sourceInfos) return;
2674-
if (!forEachKey(sourceMapInfo.sourceInfos, path => {
2675-
const info = this.getScriptInfoForPath(path as Path)!;
2676-
return info.isScriptOpen() || !info.isOrphan();
2731+
let sourceInfos: Map<true> | undefined;
2732+
if (isString(info.sourceMapFilePath)) {
2733+
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
2734+
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
2735+
}
2736+
else {
2737+
sourceInfos = info.sourceMapFilePath.sourceInfos;
2738+
}
2739+
if (!sourceInfos) return;
2740+
if (!forEachKey(sourceInfos, path => {
2741+
const info = this.getScriptInfoForPath(path as Path);
2742+
return !!info && (info.isScriptOpen() || !info.isOrphan());
26772743
})) {
26782744
return;
26792745
}
@@ -2682,11 +2748,18 @@ namespace ts.server {
26822748
// Retain this script info
26832749
toRemoveScriptInfos.delete(info.path);
26842750
if (info.sourceMapFilePath) {
2685-
// And map file info and source infos
2686-
toRemoveScriptInfos.delete(info.sourceMapFilePath);
2687-
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
2688-
if (sourceMapInfo && sourceMapInfo.sourceInfos) {
2689-
sourceMapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path));
2751+
let sourceInfos: Map<true> | undefined;
2752+
if (isString(info.sourceMapFilePath)) {
2753+
// And map file info and source infos
2754+
toRemoveScriptInfos.delete(info.sourceMapFilePath);
2755+
const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath);
2756+
sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos;
2757+
}
2758+
else {
2759+
sourceInfos = info.sourceMapFilePath.sourceInfos;
2760+
}
2761+
if (sourceInfos) {
2762+
sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path));
26902763
}
26912764
}
26922765
});
@@ -2695,6 +2768,7 @@ namespace ts.server {
26952768
// if there are not projects that include this script info - delete it
26962769
this.stopWatchingScriptInfo(info);
26972770
this.deleteScriptInfo(info);
2771+
info.closeSourceMapFileWatcher();
26982772
});
26992773
}
27002774

src/server/scriptInfo.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ namespace ts.server {
6666

6767
private resetSourceMapInfo() {
6868
this.info.sourceFileLike = undefined;
69+
this.info.closeSourceMapFileWatcher();
6970
this.info.sourceMapFilePath = undefined;
7071
this.info.declarationInfoPath = undefined;
7172
this.info.sourceInfos = undefined;
@@ -280,6 +281,13 @@ namespace ts.server {
280281
sourceFile: SourceFile;
281282
}
282283

284+
/*@internal*/
285+
export interface SourceMapFileWatcher {
286+
declarationInfoPath: Path;
287+
watcher: FileWatcher;
288+
sourceInfos?: Map<true>;
289+
}
290+
283291
export class ScriptInfo {
284292
/**
285293
* All projects that include this file
@@ -309,7 +317,7 @@ namespace ts.server {
309317
sourceFileLike?: SourceFileLike;
310318

311319
/*@internal*/
312-
sourceMapFilePath?: Path | false;
320+
sourceMapFilePath?: Path | SourceMapFileWatcher | false;
313321

314322
// Present on sourceMapFile info
315323
/*@internal*/
@@ -606,5 +614,13 @@ namespace ts.server {
606614
getLineInfo(): LineInfo {
607615
return this.textStorage.getLineInfo();
608616
}
617+
618+
/*@internal*/
619+
closeSourceMapFileWatcher() {
620+
if (this.sourceMapFilePath && !isString(this.sourceMapFilePath)) {
621+
closeFileWatcherOf(this.sourceMapFilePath);
622+
this.sourceMapFilePath = undefined;
623+
}
624+
}
609625
}
610626
}

src/services/sourcemaps.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ namespace ts {
131131
* string | undefined to contents of map file to create DocumentPositionMapper from it
132132
* DocumentPositionMapper | false to give back cached DocumentPositionMapper
133133
*/
134-
export type ReadMapFile = (mapFileName: string) => string | undefined | DocumentPositionMapper | false;
134+
export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false;
135135

136136
export function getDocumentPositionMapper(
137137
host: DocumentPositionMapperHost,
@@ -155,9 +155,10 @@ namespace ts {
155155
possibleMapLocations.push(mapFileName);
156156
}
157157
possibleMapLocations.push(generatedFileName + ".map");
158+
const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName));
158159
for (const location of possibleMapLocations) {
159160
const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName));
160-
const mapFileContents = readMapFile(mapFileName);
161+
const mapFileContents = readMapFile(mapFileName, originalMapFileName);
161162
if (isString(mapFileContents)) {
162163
return convertDocumentToSourceMapper(host, mapFileContents, mapFileName);
163164
}

0 commit comments

Comments
 (0)