Skip to content

Commit f472868

Browse files
committed
Watch generated file if it doesnt exist when trying to translate it to to source generated position
1 parent 1810288 commit f472868

File tree

5 files changed

+226
-81
lines changed

5 files changed

+226
-81
lines changed

src/server/editorServices.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2240,7 +2240,13 @@ namespace ts.server {
22402240
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
22412241
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
22422242
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
2243-
if (!declarationInfo) return undefined;
2243+
if (!declarationInfo) {
2244+
if (sourceFileName) {
2245+
// Project contains source file and it generates the generated file name
2246+
project.addGeneratedFileWatch(generatedFileName, sourceFileName);
2247+
}
2248+
return undefined;
2249+
}
22442250

22452251
// Try to get from cache
22462252
declarationInfo.getSnapshot(); // Ensure synchronized

src/server/project.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,22 @@ namespace ts.server {
109109
return value instanceof ScriptInfo;
110110
}
111111

112+
interface GeneratedFileWatcher {
113+
generatedFilePath: Path;
114+
watcher: FileWatcher;
115+
}
116+
type GeneratedFileWatcherMap = GeneratedFileWatcher | Map<GeneratedFileWatcher>;
117+
function isGeneratedFileWatcher(watch: GeneratedFileWatcherMap): watch is GeneratedFileWatcher {
118+
return (watch as GeneratedFileWatcher).generatedFilePath !== undefined;
119+
}
120+
112121
export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
113122
private rootFiles: ScriptInfo[] = [];
114123
private rootFilesMap: Map<ProjectRoot> = createMap<ProjectRoot>();
115124
private program: Program | undefined;
116125
private externalFiles: SortedReadonlyArray<string> | undefined;
117126
private missingFilesMap: Map<FileWatcher> | undefined;
127+
private generatedFilesMap: GeneratedFileWatcherMap | undefined;
118128
private plugins: PluginModuleWithName[] = [];
119129

120130
/*@internal*/
@@ -573,6 +583,7 @@ namespace ts.server {
573583
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
574584
this.builderState = undefined;
575585
this.resolutionCache.closeTypeRootsWatch();
586+
this.clearGeneratedFileWatch();
576587
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
577588
}
578589

@@ -654,6 +665,7 @@ namespace ts.server {
654665
clearMap(this.missingFilesMap, closeFileWatcher);
655666
this.missingFilesMap = undefined!;
656667
}
668+
this.clearGeneratedFileWatch();
657669

658670
// signal language service to release source files acquired from document registry
659671
this.languageService.dispose();
@@ -947,6 +959,39 @@ namespace ts.server {
947959
missingFilePath => this.addMissingFileWatcher(missingFilePath)
948960
);
949961

962+
if (this.generatedFilesMap) {
963+
const outPath = this.compilerOptions.outFile && this.compilerOptions.out;
964+
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
965+
// --out
966+
if (!outPath || !this.isValidGeneratedFileWatcher(
967+
removeFileExtension(outPath) + Extension.Dts,
968+
this.generatedFilesMap,
969+
)) {
970+
this.clearGeneratedFileWatch();
971+
}
972+
}
973+
else {
974+
// MultiFile
975+
if (outPath) {
976+
this.clearGeneratedFileWatch();
977+
}
978+
else {
979+
this.generatedFilesMap.forEach((watcher, source) => {
980+
const sourceFile = this.program!.getSourceFileByPath(source as Path);
981+
if (!sourceFile ||
982+
sourceFile.resolvedPath !== source ||
983+
!this.isValidGeneratedFileWatcher(
984+
getDeclarationEmitOutputFilePathWorker(sourceFile.fileName, this.compilerOptions, this.currentDirectory, this.program!.getCommonSourceDirectory(), this.getCanonicalFileName),
985+
watcher
986+
)) {
987+
closeFileWatcherOf(watcher);
988+
(this.generatedFilesMap as Map<GeneratedFileWatcher>).delete(source);
989+
}
990+
});
991+
}
992+
}
993+
}
994+
950995
// Watch the type locations that would be added to program as part of automatic type resolutions
951996
if (this.languageServiceEnabled) {
952997
this.resolutionCache.updateTypeRootsWatch();
@@ -1011,6 +1056,61 @@ namespace ts.server {
10111056
return !!this.missingFilesMap && this.missingFilesMap.has(path);
10121057
}
10131058

1059+
/* @internal */
1060+
addGeneratedFileWatch(generatedFile: string, sourceFile: string) {
1061+
if (this.compilerOptions.outFile || this.compilerOptions.out) {
1062+
// Single watcher
1063+
if (!this.generatedFilesMap) {
1064+
this.generatedFilesMap = this.createGeneratedFileWatcher(generatedFile);
1065+
}
1066+
}
1067+
else {
1068+
// Map
1069+
const path = this.toPath(sourceFile);
1070+
if (this.generatedFilesMap) {
1071+
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
1072+
Debug.fail(`${this.projectName} Expected not to have --out watcher for generated file with options: ${JSON.stringify(this.compilerOptions)}`);
1073+
return;
1074+
}
1075+
if (this.generatedFilesMap.has(path)) return;
1076+
}
1077+
else {
1078+
this.generatedFilesMap = createMap();
1079+
}
1080+
this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile));
1081+
}
1082+
}
1083+
1084+
private createGeneratedFileWatcher(generatedFile: string): GeneratedFileWatcher {
1085+
return {
1086+
generatedFilePath: this.toPath(generatedFile),
1087+
watcher: this.projectService.watchFactory.watchFile(
1088+
this.projectService.host,
1089+
generatedFile,
1090+
() => this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this),
1091+
PollingInterval.High,
1092+
WatchType.MissingGeneratedFile,
1093+
this
1094+
)
1095+
};
1096+
}
1097+
1098+
private isValidGeneratedFileWatcher(generateFile: string, watcher: GeneratedFileWatcher) {
1099+
return this.toPath(generateFile) === watcher.generatedFilePath;
1100+
}
1101+
1102+
private clearGeneratedFileWatch() {
1103+
if (this.generatedFilesMap) {
1104+
if (isGeneratedFileWatcher(this.generatedFilesMap)) {
1105+
closeFileWatcherOf(this.generatedFilesMap);
1106+
}
1107+
else {
1108+
clearMap(this.generatedFilesMap, closeFileWatcherOf);
1109+
}
1110+
this.generatedFilesMap = undefined;
1111+
}
1112+
}
1113+
10141114
getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined {
10151115
const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName));
10161116
if (scriptInfo && !scriptInfo.isAttached(this)) {

src/server/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,6 @@ namespace ts {
227227
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
228228
MissingSourceMapFile = "Missing source map file",
229229
NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root",
230+
MissingGeneratedFile = "Missing generated file"
230231
}
231232
}

0 commit comments

Comments
 (0)