Skip to content

Commit 94df516

Browse files
committed
Handle resolution caching when referenced tsconfig changes
1 parent d4e4b43 commit 94df516

File tree

11 files changed

+253
-98
lines changed

11 files changed

+253
-98
lines changed

src/compiler/commandLineParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,8 @@ namespace ts {
12941294
const result = parseJsonText(configFileName, configFileText);
12951295
const cwd = host.getCurrentDirectory();
12961296
result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames));
1297+
result.resolvedPath = result.path;
1298+
result.originalFileName = result.fileName;
12971299
return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
12981300
}
12991301

src/compiler/program.ts

Lines changed: 107 additions & 54 deletions
Large diffs are not rendered by default.

src/compiler/resolutionCache.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace ts {
1111

1212
invalidateResolutionOfFile(filePath: Path): void;
1313
removeResolutionsOfFile(filePath: Path): void;
14+
removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
1415
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map<ReadonlyArray<string>>): void;
1516
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
1617

@@ -128,6 +129,7 @@ namespace ts {
128129
resolveModuleNames,
129130
getResolvedModuleWithFailedLookupLocationsFromCache,
130131
resolveTypeReferenceDirectives,
132+
removeResolutionsFromProjectReferenceRedirects,
131133
removeResolutionsOfFile,
132134
invalidateResolutionOfFile,
133135
setFilesWithInvalidatedNonRelativeUnresolvedImports,
@@ -262,12 +264,20 @@ namespace ts {
262264
const resolvedModules: R[] = [];
263265
const compilerOptions = resolutionHost.getCompilationSettings();
264266
const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
267+
268+
// All the resolutions in this file are invalidated if this file wasnt resolved using same redirect
269+
const program = resolutionHost.getCurrentProgram();
270+
const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile);
271+
const unmatchedRedirects = oldRedirect ?
272+
!redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path :
273+
!!redirectedReference;
274+
265275
const seenNamesInFile = createMap<true>();
266276
for (const name of names) {
267277
let resolution = resolutionsInFile.get(name);
268278
// Resolution is valid if it is present and not invalidated
269279
if (!seenNamesInFile.has(name) &&
270-
allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated ||
280+
allFilesHaveInvalidatedResolution || unmatchedRedirects || !resolution || resolution.isInvalidated ||
271281
// If the name is unresolved import that was invalidated, recalculate
272282
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) {
273283
const existingResolution = resolution;
@@ -596,6 +606,20 @@ namespace ts {
596606
}
597607
}
598608

609+
function removeResolutionsFromProjectReferenceRedirects(filePath: Path) {
610+
if (!fileExtensionIs(filePath, Extension.Json)) { return; }
611+
612+
const program = resolutionHost.getCurrentProgram();
613+
if (!program) { return; }
614+
615+
// If this file is input file for the referenced project, get it
616+
const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath);
617+
if (!resolvedProjectReference) { return; }
618+
619+
// filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution
620+
resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f)));
621+
}
622+
599623
function removeResolutionsOfFile(filePath: Path) {
600624
removeResolutionsOfFileFromCache(resolvedModuleNames, filePath);
601625
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath);

src/compiler/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2831,6 +2831,9 @@ namespace ts {
28312831
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
28322832
getResolvedProjectReferences(): ReadonlyArray<ResolvedProjectReference | undefined> | undefined;
28332833
/*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined;
2834+
/*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
2835+
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
2836+
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
28342837
}
28352838

28362839
/* @internal */
@@ -4882,7 +4885,7 @@ namespace ts {
48824885
*/
48834886
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[];
48844887
getEnvironmentVariable?(name: string): string | undefined;
4885-
/* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
4888+
/* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void;
48864889
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
48874890
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
48884891
createHash?(data: string): string;

src/compiler/watch.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,8 @@ namespace ts {
764764
return !hostSourceFile || isFileMissingOnHost(hostSourceFile) ? undefined : hostSourceFile.version.toString();
765765
}
766766

767-
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) {
768-
const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path);
767+
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) {
768+
const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath);
769769
// If this is the source file thats in the cache and new program doesnt need it,
770770
// remove the cached entry.
771771
// Note we arent deleting entry if file became missing in new program or
@@ -779,8 +779,10 @@ namespace ts {
779779
if ((hostSourceFileInfo as FilePresentOnHost).fileWatcher) {
780780
(hostSourceFileInfo as FilePresentOnHost).fileWatcher.close();
781781
}
782-
sourceFilesCache.delete(oldSourceFile.path);
783-
resolutionCache.removeResolutionsOfFile(oldSourceFile.path);
782+
sourceFilesCache.delete(oldSourceFile.resolvedPath);
783+
if (oldSourceFile.resolvedPath === oldSourceFile.path || !hasSourceFileByPath) {
784+
resolutionCache.removeResolutionsOfFile(oldSourceFile.path);
785+
}
784786
}
785787
}
786788
}
@@ -875,6 +877,7 @@ namespace ts {
875877
if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.get(path)) {
876878
resolutionCache.invalidateResolutionOfFile(path);
877879
}
880+
resolutionCache.removeResolutionsFromProjectReferenceRedirects(path);
878881
nextSourceFileVersion(path);
879882

880883
// Update the program

src/server/editorServices.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -871,15 +871,20 @@ namespace ts.server {
871871
if (!info) {
872872
this.logger.msg(`Error: got watch notification for unknown file: ${fileName}`);
873873
}
874-
else if (eventKind === FileWatcherEventKind.Deleted) {
875-
// File was deleted
876-
this.handleDeletedFile(info);
877-
}
878-
else if (!info.isScriptOpen()) {
879-
// file has been changed which might affect the set of referenced files in projects that include
880-
// this file and set of inferred projects
881-
info.delayReloadNonMixedContentFile();
882-
this.delayUpdateProjectGraphs(info.containingProjects);
874+
else {
875+
if (info.containingProjects) {
876+
info.containingProjects.forEach(project => project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(info.path));
877+
}
878+
if (eventKind === FileWatcherEventKind.Deleted) {
879+
// File was deleted
880+
this.handleDeletedFile(info);
881+
}
882+
else if (!info.isScriptOpen()) {
883+
// file has been changed which might affect the set of referenced files in projects that include
884+
// this file and set of inferred projects
885+
info.delayReloadNonMixedContentFile();
886+
this.delayUpdateProjectGraphs(info.containingProjects);
887+
}
883888
}
884889
}
885890

@@ -2434,17 +2439,14 @@ namespace ts.server {
24342439
}
24352440
else {
24362441
// If the configured project for project reference has more than zero references, keep it alive
2437-
const resolvedProjectReferences = project.getResolvedProjectReferences();
2438-
if (resolvedProjectReferences) {
2439-
for (const ref of resolvedProjectReferences) {
2440-
if (ref) {
2441-
const refProject = this.configuredProjects.get(ref.sourceFile.path);
2442-
if (refProject && refProject.hasOpenRef()) {
2443-
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
2444-
}
2442+
project.forEachResolvedProjectReference(ref => {
2443+
if (ref) {
2444+
const refProject = this.configuredProjects.get(ref.sourceFile.path);
2445+
if (refProject && refProject.hasOpenRef()) {
2446+
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
24452447
}
24462448
}
2447-
}
2449+
});
24482450
}
24492451
});
24502452

src/server/project.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -583,14 +583,11 @@ namespace ts.server {
583583
for (const f of this.program.getSourceFiles()) {
584584
this.detachScriptInfoIfNotRoot(f.fileName);
585585
}
586-
const projectReferences = this.program.getResolvedProjectReferences();
587-
if (projectReferences) {
588-
for (const ref of projectReferences) {
589-
if (ref) {
590-
this.detachScriptInfoFromProject(ref.sourceFile.fileName);
591-
}
586+
this.program.forEachResolvedProjectReference(ref => {
587+
if (ref) {
588+
this.detachScriptInfoFromProject(ref.sourceFile.fileName);
592589
}
593-
}
590+
});
594591
}
595592
// Release external files
596593
forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile));
@@ -925,12 +922,19 @@ namespace ts.server {
925922
if (hasNewProgram) {
926923
if (oldProgram) {
927924
for (const f of oldProgram.getSourceFiles()) {
928-
if (this.program.getSourceFileByPath(f.path)) {
929-
continue;
925+
const newFile = this.program.getSourceFileByPath(f.resolvedPath);
926+
if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) {
927+
// new program does not contain this file - detach it from the project
928+
// - remove resolutions only if this is undirected file or doesnt have source file with the path in new program
929+
this.detachScriptInfoFromProject(f.fileName, f.path !== f.resolvedPath && !!this.program.getSourceFileByPath(f.path));
930930
}
931-
// new program does not contain this file - detach it from the project
932-
this.detachScriptInfoFromProject(f.fileName);
933931
}
932+
933+
oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => {
934+
if (resolvedProjectReference && !this.program.getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) {
935+
this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName);
936+
}
937+
});
934938
}
935939

936940
// Update the missing file paths watcher
@@ -964,11 +968,13 @@ namespace ts.server {
964968
return hasNewProgram;
965969
}
966970

967-
private detachScriptInfoFromProject(uncheckedFileName: string) {
971+
private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) {
968972
const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName);
969973
if (scriptInfoToDetach) {
970974
scriptInfoToDetach.detachFromProject(this);
971-
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
975+
if (!noRemoveResolution) {
976+
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
977+
}
972978
}
973979
}
974980

@@ -1400,9 +1406,9 @@ namespace ts.server {
14001406
}
14011407

14021408
/*@internal*/
1403-
getResolvedProjectReferences() {
1409+
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined {
14041410
const program = this.getCurrentProgram();
1405-
return program && program.getResolvedProjectReferences();
1411+
return program && program.forEachResolvedProjectReference(cb);
14061412
}
14071413

14081414
enablePlugins() {

src/services/services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ namespace ts {
12761276
// not part of the new program.
12771277
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
12781278
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
1279-
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
1279+
documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey);
12801280
}
12811281

12821282
function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {

src/testRunner/unittests/projectReferences.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ namespace ts {
184184
};
185185
testProjectReferences(spec, "/primary/tsconfig.json", program => {
186186
const errs = program.getOptionsDiagnostics();
187-
assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_does_not_exist);
187+
assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_not_found);
188188
});
189189
});
190190

0 commit comments

Comments
 (0)