diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b1cc4ef5b27..1c6265c9454 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1294,6 +1294,8 @@ namespace ts { const result = parseJsonText(configFileName, configFileText); const cwd = host.getCurrentDirectory(); result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e7caa7048cb..db621f4e214 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -487,7 +487,7 @@ namespace ts { function sourceFileNotUptoDate(sourceFile: SourceFile) { return !sourceFileVersionUptoDate(sourceFile) || - hasInvalidatedResolution(sourceFile.resolvedPath); + hasInvalidatedResolution(sourceFile.path); } function sourceFileVersionUptoDate(sourceFile: SourceFile) { @@ -752,10 +752,18 @@ namespace ts { if (oldProgram && host.onReleaseOldSourceFile) { const oldSourceFiles = oldProgram.getSourceFiles(); for (const oldSourceFile of oldSourceFiles) { - if (!getSourceFile(oldSourceFile.path) || shouldCreateNewSourceFile) { - host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions()); + const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); + if (shouldCreateNewSourceFile || !newFile || + // old file wasnt redirect but new file is + (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { + host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); } } + oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => { + if (resolvedProjectReference && !getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) { + host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); + } + }); } // unconditionally set oldProgram to undefined to prevent it from being captured in closure @@ -798,7 +806,10 @@ namespace ts { getResolvedModuleWithFailedLookupLocationsFromCache, getProjectReferences, getResolvedProjectReferences, - getProjectReferenceRedirect + getProjectReferenceRedirect, + getResolvedProjectReferenceToRedirect, + getResolvedProjectReferenceByPath, + forEachResolvedProjectReference }; verifyCompilerOptions(); @@ -881,7 +892,7 @@ namespace ts { if (structuralIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, // the best we can do is fallback to the default logic. - return resolveModuleNamesWorker(moduleNames, containingFile, /*reusedNames*/ undefined, getProjectReferenceRedirectProject(file.originalFileName)); + return resolveModuleNamesWorker(moduleNames, containingFile, /*reusedNames*/ undefined, getResolvedProjectReferenceToRedirect(file.originalFileName)); } const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile); @@ -961,7 +972,7 @@ namespace ts { } const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames, getProjectReferenceRedirectProject(file.originalFileName)) + ? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames, getResolvedProjectReferenceToRedirect(file.originalFileName)) : emptyArray; // Combine results of resolutions and predicted results @@ -1021,46 +1032,28 @@ namespace ts { } } - function canReuseProjectReferences( - newProjectReferences: ReadonlyArray | undefined, - oldProjectReferences: ReadonlyArray | undefined, - oldResolvedReferences: ReadonlyArray | undefined): boolean { - // If array of references is changed, we cant resue old program - if (!arrayIsEqualTo(oldProjectReferences!, newProjectReferences, projectReferenceIsEqualTo)) { - return false; - } - - // Check the json files for the project references - if (newProjectReferences) { - // Resolved project referenced should be array if projectReferences provided are array - Debug.assert(!!oldResolvedReferences); - for (let i = 0; i < newProjectReferences.length; i++) { - const oldRef = oldResolvedReferences![i]; - const newRef = parseProjectReferenceConfigFile(newProjectReferences[i]); - if (oldRef) { - if (!newRef || newRef.sourceFile !== oldRef.sourceFile) { - // Resolved project reference has gone missing or changed - return false; - } - - // If the transitive references can be reused then only this reference can be reused - if (!canReuseProjectReferences(newRef.commandLine.projectReferences, oldRef.commandLine.projectReferences, oldRef.references)) { - return false; - } + function canReuseProjectReferences(): boolean { + return !forEachProjectReference( + oldProgram!.getProjectReferences(), + oldProgram!.getResolvedProjectReferences(), + (oldResolvedRef, index, parent) => { + const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const newResolvedRef = parseProjectReferenceConfigFile(newRef); + if (oldResolvedRef) { + // Resolved project reference has gone missing or changed + return !newResolvedRef || newResolvedRef.sourceFile !== oldResolvedRef.sourceFile; } else { // A previously-unresolved reference may be resolved now - if (newRef !== undefined) { - return false; - } + return newResolvedRef !== undefined; } + }, + (oldProjectReferences, parent) => { + // If array of references is changed, we cant resue old program + const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; + return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); } - } - else { - // Resolved project referenced should be undefined if projectReferences is undefined - Debug.assert(!oldResolvedReferences); - } - return true; + ); } function tryReuseStructureFromOldProgram(): StructureIsReused { @@ -1088,7 +1081,7 @@ namespace ts { } // Check if any referenced project tsconfig files are different - if (!canReuseProjectReferences(projectReferences, oldProgram.getProjectReferences(), oldProgram.getResolvedProjectReferences())) { + if (!canReuseProjectReferences()) { return oldProgram.structureIsReused = StructureIsReused.Not; } resolvedProjectReferences = oldProgram.getResolvedProjectReferences(); @@ -1240,7 +1233,7 @@ namespace ts { if (resolveTypeReferenceDirectiveNamesWorker) { // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getProjectReferenceRedirectProject(newSourceFile.originalFileName)); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getResolvedProjectReferenceToRedirect(newSourceFile.originalFileName)); // ensure that types resolutions are still correct const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); if (resolutionsChanged) { @@ -2215,7 +2208,7 @@ namespace ts { // If this file is produced by a referenced project, we need to rewrite it to // look in the output folder of the referenced project rather than the input - const referencedProject = getProjectReferenceRedirectProject(fileName); + const referencedProject = getResolvedProjectReferenceToRedirect(fileName); if (!referencedProject) { return undefined; } @@ -2229,16 +2222,11 @@ namespace ts { /** * Get the referenced project if the file is input file from that reference project */ - function getProjectReferenceRedirectProject(fileName: string) { - if (!resolvedProjectReferences || !resolvedProjectReferences.length) { - return undefined; - } - - // If this file is input file of the referenced projec - return forEachEntry(projectReferenceRedirects!, referencedProject => { + function getResolvedProjectReferenceToRedirect(fileName: string) { + return forEachResolvedProjectReference((referencedProject, referenceProjectPath) => { // not input file from the referenced project, ignore if (!referencedProject || - options.configFilePath === referencedProject.commandLine.options.configFilePath || + toPath(options.configFilePath!) === referenceProjectPath || !contains(referencedProject.commandLine.fileNames, fileName, isSameFile)) { return undefined; } @@ -2247,6 +2235,67 @@ namespace ts { }); } + function forEachResolvedProjectReference( + cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined + ): T | undefined { + return forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { + const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const resolvedRefPath = toPath(resolveProjectReferencePath(ref)); + return cb(resolvedRef, resolvedRefPath); + }); + } + + function forEachProjectReference( + projectReferences: ReadonlyArray | undefined, + resolvedProjectReferences: ReadonlyArray | undefined, + cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, + cbRef?: (projectReferences: ReadonlyArray | undefined, parent: ResolvedProjectReference | undefined) => T | undefined + ): T | undefined { + let seenResolvedRefs: ResolvedProjectReference[] | undefined; + + return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined, cbResolvedRef, cbRef); + + function worker( + projectReferences: ReadonlyArray | undefined, + resolvedProjectReferences: ReadonlyArray | undefined, + parent: ResolvedProjectReference | undefined, + cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, + cbRef?: (projectReferences: ReadonlyArray | undefined, parent: ResolvedProjectReference | undefined) => T | undefined, + ): T | undefined { + + // Visit project references first + if (cbRef) { + const result = cbRef(projectReferences, parent); + if (result) { return result; } + } + + return forEach(resolvedProjectReferences, (resolvedRef, index) => { + if (contains(seenResolvedRefs, resolvedRef)) { + // ignore recursives + return undefined; + } + + const result = cbResolvedRef(resolvedRef, index, parent); + if (result) { + return result; + } + + if (!resolvedRef) return undefined; + + (seenResolvedRefs || (seenResolvedRefs = [])).push(resolvedRef); + return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef, cbResolvedRef, cbRef); + }); + } + } + + function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + return undefined; + } + + return projectReferenceRedirects.get(projectReferencePath) || undefined; + } + function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { forEach(file.referencedFiles, ref => { const referencedFileName = resolveTripleslashReference(ref.fileName, file.originalFileName); @@ -2261,7 +2310,7 @@ namespace ts { return; } - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName, getProjectReferenceRedirectProject(file.originalFileName)); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName, getResolvedProjectReferenceToRedirect(file.originalFileName)); for (let i = 0; i < typeDirectives.length; i++) { const ref = file.typeReferenceDirectives[i]; @@ -2450,11 +2499,14 @@ namespace ts { // An absolute path pointing to the containing directory of the config file const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); if (sourceFile === undefined) { projectReferenceRedirects.set(sourceFilePath, false); return undefined; } sourceFile.path = sourceFilePath; + sourceFile.resolvedPath = sourceFilePath; + sourceFile.originalFileName = refPath; const commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; projectReferenceRedirects.set(sourceFilePath, resolvedRef); @@ -2506,12 +2558,13 @@ namespace ts { } } + //TODO:: Errors on transitive references if (projectReferences) { for (let i = 0; i < projectReferences.length; i++) { const ref = projectReferences[i]; const resolvedRefOpts = resolvedProjectReferences![i] && resolvedProjectReferences![i]!.commandLine.options; if (resolvedRefOpts === undefined) { - createDiagnosticForReference(i, Diagnostics.File_0_does_not_exist, ref.path); + createDiagnosticForReference(i, Diagnostics.File_0_not_found, ref.path); continue; } if (!resolvedRefOpts.composite) { diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 42df1eb9a24..b462ab96b1a 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -11,6 +11,7 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; removeResolutionsOfFile(filePath: Path): void; + removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map>): void; createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; @@ -128,6 +129,7 @@ namespace ts { resolveModuleNames, getResolvedModuleWithFailedLookupLocationsFromCache, resolveTypeReferenceDirectives, + removeResolutionsFromProjectReferenceRedirects, removeResolutionsOfFile, invalidateResolutionOfFile, setFilesWithInvalidatedNonRelativeUnresolvedImports, @@ -262,12 +264,20 @@ namespace ts { const resolvedModules: R[] = []; const compilerOptions = resolutionHost.getCompilationSettings(); const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); + + // All the resolutions in this file are invalidated if this file wasnt resolved using same redirect + const program = resolutionHost.getCurrentProgram(); + const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); + const unmatchedRedirects = oldRedirect ? + !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : + !!redirectedReference; + const seenNamesInFile = createMap(); for (const name of names) { let resolution = resolutionsInFile.get(name); // Resolution is valid if it is present and not invalidated if (!seenNamesInFile.has(name) && - allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated || + allFilesHaveInvalidatedResolution || unmatchedRedirects || !resolution || resolution.isInvalidated || // If the name is unresolved import that was invalidated, recalculate (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) { const existingResolution = resolution; @@ -596,6 +606,20 @@ namespace ts { } } + function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { + if (!fileExtensionIs(filePath, Extension.Json)) { return; } + + const program = resolutionHost.getCurrentProgram(); + if (!program) { return; } + + // If this file is input file for the referenced project, get it + const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); + if (!resolvedProjectReference) { return; } + + // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution + resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); + } + function removeResolutionsOfFile(filePath: Path) { removeResolutionsOfFileFromCache(resolvedModuleNames, filePath); removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index eb5ab188796..8d8282615fa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2831,6 +2831,9 @@ namespace ts { getProjectReferences(): ReadonlyArray | undefined; getResolvedProjectReferences(): ReadonlyArray | undefined; /*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined; + /*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined; + /*@internal*/ forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined; + /*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined; } /* @internal */ @@ -4882,7 +4885,7 @@ namespace ts { */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string | undefined; - /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; + /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 34cd1a4a107..ff39b61e481 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -764,8 +764,8 @@ namespace ts { return !hostSourceFile || isFileMissingOnHost(hostSourceFile) ? undefined : hostSourceFile.version.toString(); } - function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { - const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); + function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) { + const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath); // If this is the source file thats in the cache and new program doesnt need it, // remove the cached entry. // Note we arent deleting entry if file became missing in new program or @@ -779,8 +779,10 @@ namespace ts { if ((hostSourceFileInfo as FilePresentOnHost).fileWatcher) { (hostSourceFileInfo as FilePresentOnHost).fileWatcher.close(); } - sourceFilesCache.delete(oldSourceFile.path); - resolutionCache.removeResolutionsOfFile(oldSourceFile.path); + sourceFilesCache.delete(oldSourceFile.resolvedPath); + if (oldSourceFile.resolvedPath === oldSourceFile.path || !hasSourceFileByPath) { + resolutionCache.removeResolutionsOfFile(oldSourceFile.path); + } } } } @@ -875,6 +877,7 @@ namespace ts { if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.get(path)) { resolutionCache.invalidateResolutionOfFile(path); } + resolutionCache.removeResolutionsFromProjectReferenceRedirects(path); nextSourceFileVersion(path); // Update the program diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 32916f8308a..043c44f4834 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -871,15 +871,20 @@ namespace ts.server { if (!info) { this.logger.msg(`Error: got watch notification for unknown file: ${fileName}`); } - else if (eventKind === FileWatcherEventKind.Deleted) { - // File was deleted - this.handleDeletedFile(info); - } - else if (!info.isScriptOpen()) { - // file has been changed which might affect the set of referenced files in projects that include - // this file and set of inferred projects - info.delayReloadNonMixedContentFile(); - this.delayUpdateProjectGraphs(info.containingProjects); + else { + if (info.containingProjects) { + info.containingProjects.forEach(project => project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(info.path)); + } + if (eventKind === FileWatcherEventKind.Deleted) { + // File was deleted + this.handleDeletedFile(info); + } + else if (!info.isScriptOpen()) { + // file has been changed which might affect the set of referenced files in projects that include + // this file and set of inferred projects + info.delayReloadNonMixedContentFile(); + this.delayUpdateProjectGraphs(info.containingProjects); + } } } @@ -2434,17 +2439,14 @@ namespace ts.server { } else { // If the configured project for project reference has more than zero references, keep it alive - const resolvedProjectReferences = project.getResolvedProjectReferences(); - if (resolvedProjectReferences) { - for (const ref of resolvedProjectReferences) { - if (ref) { - const refProject = this.configuredProjects.get(ref.sourceFile.path); - if (refProject && refProject.hasOpenRef()) { - toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath); - } + project.forEachResolvedProjectReference(ref => { + if (ref) { + const refProject = this.configuredProjects.get(ref.sourceFile.path); + if (refProject && refProject.hasOpenRef()) { + toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath); } } - } + }); } }); diff --git a/src/server/project.ts b/src/server/project.ts index 2f3a0c203d4..dcdac0e201d 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -583,14 +583,11 @@ namespace ts.server { for (const f of this.program.getSourceFiles()) { this.detachScriptInfoIfNotRoot(f.fileName); } - const projectReferences = this.program.getResolvedProjectReferences(); - if (projectReferences) { - for (const ref of projectReferences) { - if (ref) { - this.detachScriptInfoFromProject(ref.sourceFile.fileName); - } + this.program.forEachResolvedProjectReference(ref => { + if (ref) { + this.detachScriptInfoFromProject(ref.sourceFile.fileName); } - } + }); } // Release external files forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile)); @@ -925,12 +922,19 @@ namespace ts.server { if (hasNewProgram) { if (oldProgram) { for (const f of oldProgram.getSourceFiles()) { - if (this.program.getSourceFileByPath(f.path)) { - continue; + const newFile = this.program.getSourceFileByPath(f.resolvedPath); + if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) { + // new program does not contain this file - detach it from the project + // - remove resolutions only if this is undirected file or doesnt have source file with the path in new program + this.detachScriptInfoFromProject(f.fileName, f.path !== f.resolvedPath && !!this.program.getSourceFileByPath(f.path)); } - // new program does not contain this file - detach it from the project - this.detachScriptInfoFromProject(f.fileName); } + + oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => { + if (resolvedProjectReference && !this.program.getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) { + this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName); + } + }); } // Update the missing file paths watcher @@ -964,11 +968,13 @@ namespace ts.server { return hasNewProgram; } - private detachScriptInfoFromProject(uncheckedFileName: string) { + private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) { const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName); if (scriptInfoToDetach) { scriptInfoToDetach.detachFromProject(this); - this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path); + if (!noRemoveResolution) { + this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path); + } } } @@ -1400,9 +1406,9 @@ namespace ts.server { } /*@internal*/ - getResolvedProjectReferences() { + forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined { const program = this.getCurrentProgram(); - return program && program.getResolvedProjectReferences(); + return program && program.forEachResolvedProjectReference(cb); } enablePlugins() { diff --git a/src/services/services.ts b/src/services/services.ts index 662b471fe75..27a458bf1ef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1276,7 +1276,7 @@ namespace ts { // not part of the new program. function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); - documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey); + documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey); } function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { diff --git a/src/testRunner/unittests/projectReferences.ts b/src/testRunner/unittests/projectReferences.ts index 4702c0c26ce..53fcd4a9dd0 100644 --- a/src/testRunner/unittests/projectReferences.ts +++ b/src/testRunner/unittests/projectReferences.ts @@ -184,7 +184,7 @@ namespace ts { }; testProjectReferences(spec, "/primary/tsconfig.json", program => { const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_does_not_exist); + assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_not_found); }); }); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 242c55f8200..eed69251db5 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -543,8 +543,8 @@ export function gfoo() { } function verifyProgram(host: WatchedSystem, watch: () => BuilderProgram) { - verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive); checkProgramActualFiles(watch().getProgram(), expectedProgramFiles); + verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive); verifyDependencies(watch, aDts, [aDts]); verifyDependencies(watch, bDts, [bDts, aDts]); verifyDependencies(watch, refs.path, [refs.path]); @@ -588,12 +588,74 @@ export function gfoo() { checkOutputErrorsIncremental(host, emptyArray); const nrefReplacer = (f: string) => f.replace("refs", "nrefs"); - verifyWatchesOfProject(host, expectedWatchedFiles.map(nrefReplacer), expectedWatchedDirectoriesRecursive.map(nrefReplacer)); checkProgramActualFiles(watch().getProgram(), expectedProgramFiles.map(nrefReplacer)); + verifyWatchesOfProject(host, expectedWatchedFiles.map(nrefReplacer), expectedWatchedDirectoriesRecursive.map(nrefReplacer)); verifyDependencies(watch, aDts, [aDts]); verifyDependencies(watch, bDts, [bDts, aDts]); verifyDependencies(watch, nrefs.path, [nrefs.path]); verifyDependencies(watch, cTs.path, [cTs.path, nrefs.path, bDts]); + + // revert the update + host.writeFile(cTsconfig.path, cTsconfig.content); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + verifyProgram(host, watch); + }); + + it("edit in referenced config file", () => { + const { host, watch } = createSolutionAndWatchMode(); + + const nrefs: File = { + path: getFilePathInProject(project, "nrefs/a.d.ts"), + content: host.readFile(aDts)! + }; + const bTsConfigJson = JSON.parse(bTsconfig.content); + host.ensureFileOrFolder(nrefs); + bTsConfigJson.compilerOptions.paths = { "@ref/*": ["./nrefs/*"] }; + host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson)); + + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + + const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path]; + checkProgramActualFiles(watch().getProgram(), expectedProgramFiles); + verifyWatchesOfProject(host, expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()), expectedWatchedDirectoriesRecursive.concat(getFilePathInProject(project, "nrefs").toLowerCase())); + verifyDependencies(watch, nrefs.path, [nrefs.path]); + verifyDependencies(watch, bDts, [bDts, nrefs.path]); + verifyDependencies(watch, refs.path, [refs.path]); + verifyDependencies(watch, cTs.path, [cTs.path, refs.path, bDts]); + + // revert the update + host.writeFile(bTsconfig.path, bTsconfig.content); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + verifyProgram(host, watch); + }); + + it("deleting referenced config file", () => { + const { host, watch } = createSolutionAndWatchMode(); + + // Resolutions should change now + // Should map to b.ts instead with options from our own config + host.deleteFile(bTsconfig.path); + + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, [ + "tsconfig.c.json(9,21): error TS6053: File '/user/username/projects/transitiveReferences/tsconfig.b.json' not found.\n" + ]); + + const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path]; + checkProgramActualFiles(watch().getProgram(), expectedProgramFiles); + verifyWatchesOfProject(host, expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase()), expectedWatchedDirectoriesRecursive); + verifyDependencies(watch, bTs.path, [bTs.path, refs.path]); + verifyDependencies(watch, refs.path, [refs.path]); + verifyDependencies(watch, cTs.path, [cTs.path, refs.path, bTs.path]); + + // revert the update + host.writeFile(bTsconfig.path, bTsconfig.content); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + verifyProgram(host, watch); }); }); }); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index e031786c3aa..b4a87405ef9 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -5070,7 +5070,7 @@ namespace ts.projectSystem { function getFileNotFoundDiagnostic(configFile: File, relativeFileName: string): ConfigFileDiagnostic { const findString = `{"path":"./${relativeFileName}"}`; - const d = Diagnostics.File_0_does_not_exist; + const d = Diagnostics.File_0_not_found; const start = configFile.content.indexOf(findString); return { fileName: configFile.path,