Skip to content

Commit 3e49556

Browse files
committed
Watch generated file if it doesnt exist when trying to translate it to to source generated position
1 parent 903527e commit 3e49556

File tree

5 files changed

+201
-58
lines changed

5 files changed

+201
-58
lines changed

src/server/editorServices.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2234,7 +2234,13 @@ namespace ts.server {
22342234
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
22352235
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
22362236
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
2237-
if (!declarationInfo) return undefined;
2237+
if (!declarationInfo) {
2238+
if (sourceFileName) {
2239+
// Project contains source file and it generates the generated file name
2240+
project.addGeneratedFileWatch(generatedFileName, sourceFileName);
2241+
}
2242+
return undefined;
2243+
}
22382244

22392245
// Try to get from cache
22402246
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*/
@@ -568,6 +578,7 @@ namespace ts.server {
568578
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
569579
this.builderState = undefined;
570580
this.resolutionCache.closeTypeRootsWatch();
581+
this.clearGeneratedFileWatch();
571582
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
572583
}
573584

@@ -649,6 +660,7 @@ namespace ts.server {
649660
clearMap(this.missingFilesMap, closeFileWatcher);
650661
this.missingFilesMap = undefined!;
651662
}
663+
this.clearGeneratedFileWatch();
652664

653665
// signal language service to release source files acquired from document registry
654666
this.languageService.dispose();
@@ -942,6 +954,39 @@ namespace ts.server {
942954
missingFilePath => this.addMissingFileWatcher(missingFilePath)
943955
);
944956

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

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