diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index f624ed9098f..0f019b3f9c6 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -254,6 +254,7 @@ namespace ts { readonly allWatchedWildcardDirectories: ESMap>; readonly allWatchedInputFiles: ESMap>; readonly allWatchedConfigFiles: ESMap; + readonly allWatchedExtendedConfigFiles: ESMap>; timerToBuildInvalidatedProject: any; reportFileChangeDetected: boolean; @@ -325,6 +326,7 @@ namespace ts { allWatchedWildcardDirectories: new Map(), allWatchedInputFiles: new Map(), allWatchedConfigFiles: new Map(), + allWatchedExtendedConfigFiles: new Map(), timerToBuildInvalidatedProject: undefined, reportFileChangeDetected: false, @@ -462,6 +464,15 @@ namespace ts { { onDeleteValue: closeFileWatcher } ); + state.allWatchedExtendedConfigFiles.forEach(watcher => { + watcher.projects.forEach(project => { + if (!currentProjects.has(project)) { + watcher.projects.delete(project); + } + }); + watcher.close(); + }); + mutateMapSkippingNewValues( state.allWatchedWildcardDirectories, currentProjects, @@ -1165,6 +1176,7 @@ namespace ts { if (reloadLevel === ConfigFileProgramReloadLevel.Full) { watchConfigFile(state, project, projectPath, config); + watchExtendedConfigFiles(state, projectPath, config); watchWildCardDirectories(state, project, projectPath, config); watchInputFiles(state, project, projectPath, config); } @@ -1789,6 +1801,24 @@ namespace ts { )); } + function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { + updateSharedExtendedConfigFileWatcher( + resolvedPath, + parsed, + state.allWatchedExtendedConfigFiles, + (extendedConfigFileName, extendedConfigFilePath) => state.watchFile( + extendedConfigFileName, + () => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath => + invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full) + ), + PollingInterval.High, + parsed?.watchOptions, + WatchType.ExtendedConfigFile, + ), + fileName => toPath(state, fileName), + ); + } + function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { if (!state.watch) return; updateWatchingWildcardDirectories( @@ -1846,6 +1876,7 @@ namespace ts { const cfg = parseConfigFile(state, resolved, resolvedPath); // Watch this file watchConfigFile(state, resolved, resolvedPath, cfg); + watchExtendedConfigFiles(state, resolvedPath, cfg); if (cfg) { // Update watchers for wildcard directories watchWildCardDirectories(state, resolved, resolvedPath, cfg); @@ -1858,6 +1889,10 @@ namespace ts { function stopWatching(state: SolutionBuilderState) { clearMap(state.allWatchedConfigFiles, closeFileWatcher); + clearMap(state.allWatchedExtendedConfigFiles, watcher => { + watcher.projects.clear(); + watcher.close(); + }); clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf)); clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher)); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ea9d20da84e..85594b103f0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -409,6 +409,7 @@ namespace ts { export type WatchType = WatchTypeRegistry[keyof WatchTypeRegistry]; export const WatchType: WatchTypeRegistry = { ConfigFile: "Config file", + ExtendedConfigFile: "Extended config file", SourceFile: "Source file", MissingFile: "Missing file", WildcardDirectory: "Wild card directory", @@ -418,6 +419,7 @@ namespace ts { export interface WatchTypeRegistry { ConfigFile: "Config file", + ExtendedConfigFile: "Extended config file", SourceFile: "Source file", MissingFile: "Missing file", WildcardDirectory: "Wild card directory", diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 8face54ee3a..25bb26332d4 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -246,6 +246,7 @@ namespace ts { let builderProgram: T; let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc + let extendedConfigFilesMap: ESMap; // Map of file watchers for the extended config files let missingFilesMap: ESMap; // Map of file watchers for the missing files let watchedWildcardDirectories: ESMap; // map of watchers for the wild card directories in the config file let timerToUpdateProgram: any; // timer callback to recompile the program @@ -337,6 +338,9 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); + // Update extended config file watch + watchExtendedConfigFiles(); + return configFileName ? { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close } : { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close }; @@ -354,6 +358,10 @@ namespace ts { configFileWatcher.close(); configFileWatcher = undefined; } + if (extendedConfigFilesMap) { + clearMap(extendedConfigFilesMap, closeFileWatcher); + extendedConfigFilesMap = undefined!; + } if (watchedWildcardDirectories) { clearMap(watchedWildcardDirectories, closeFileWatcherOf); watchedWildcardDirectories = undefined!; @@ -657,6 +665,9 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); + + // Update extended config file watch + watchExtendedConfigFiles(); } function parseConfigFile() { @@ -777,5 +788,23 @@ namespace ts { WatchType.WildcardDirectory ); } + + function watchExtendedConfigFiles() { + // Update the extended config files watcher + mutateMap( + extendedConfigFilesMap ||= new Map(), + arrayToMap(compilerOptions.configFile?.extendedSourceFiles || emptyArray, toPath), + { + // Watch the extended config files + createNewValue: watchExtendedConfigFile, + // Config files that are no longer extended should no longer be watched. + onDeleteValue: closeFileWatcher + } + ); + } + + function watchExtendedConfigFile(extendedConfigFile: Path) { + return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ExtendedConfigFile); + } } } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 861c64c1eb3..80fd5fe7a8f 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -257,6 +257,51 @@ namespace ts { Full } + export interface SharedExtendedConfigFileWatcher extends FileWatcher { + fileWatcher: FileWatcher; + projects: Set; + } + + /** + * Updates the map of shared extended config file watches with a new set of extended config files from a base config file of the project + */ + export function updateSharedExtendedConfigFileWatcher( + projectPath: T, + parsed: ParsedCommandLine | undefined, + extendedConfigFilesMap: ESMap>, + createExtendedConfigFileWatch: (extendedConfigPath: string, extendedConfigFilePath: Path) => FileWatcher, + toPath: (fileName: string) => Path, + ) { + const extendedConfigs = arrayToMap(parsed?.options.configFile?.extendedSourceFiles || emptyArray, toPath); + // remove project from all unrelated watchers + extendedConfigFilesMap.forEach((watcher, extendedConfigFilePath) => { + if (!extendedConfigs.has(extendedConfigFilePath)) { + watcher.projects.delete(projectPath); + watcher.close(); + } + }); + // Update the extended config files watcher + extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { + const existing = extendedConfigFilesMap.get(extendedConfigFilePath); + if (existing) { + existing.projects.add(projectPath); + } + else { + // start watching previously unseen extended config + extendedConfigFilesMap.set(extendedConfigFilePath, { + projects: new Set([projectPath]), + fileWatcher: createExtendedConfigFileWatch(extendedConfigFileName, extendedConfigFilePath), + close: () => { + const existing = extendedConfigFilesMap.get(extendedConfigFilePath); + if (!existing || existing.projects.size !== 0) return; + existing.fileWatcher.close(); + extendedConfigFilesMap.delete(extendedConfigFilePath); + }, + }); + } + }); + } + /** * Updates the existing missing file watches with the new set of missing files after new program is created */ diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 368a3c03bce..5787a5f01a6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -756,6 +756,9 @@ namespace ts.server { /*@internal*/ readonly watchFactory: WatchFactory; + /*@internal*/ + private readonly sharedExtendedConfigFileWatchers = new Map>(); + /*@internal*/ readonly packageJsonCache: PackageJsonCache; /*@internal*/ @@ -1350,6 +1353,43 @@ namespace ts.server { } } + /*@internal*/ + updateSharedExtendedConfigFileMap({ canonicalConfigFilePath }: ConfiguredProject, parsedCommandLine: ParsedCommandLine) { + updateSharedExtendedConfigFileWatcher( + canonicalConfigFilePath, + parsedCommandLine, + this.sharedExtendedConfigFileWatchers, + (extendedConfigFileName, extendedConfigFilePath) => this.watchFactory.watchFile( + extendedConfigFileName, + () => { + let ensureProjectsForOpenFiles = false; + this.sharedExtendedConfigFileWatchers.get(extendedConfigFilePath)?.projects.forEach(canonicalPath => { + const project = this.configuredProjects.get(canonicalPath); + // Skip refresh if project is not yet loaded + if (!project || project.isInitialLoadPending()) return; + project.pendingReload = ConfigFileProgramReloadLevel.Full; + project.pendingReloadReason = `Change in extended config file ${extendedConfigFileName} detected`; + this.delayUpdateProjectGraph(project); + ensureProjectsForOpenFiles = true; + }); + if (ensureProjectsForOpenFiles) this.delayEnsureProjectForOpenFiles(); + }, + PollingInterval.High, + this.hostConfiguration.watchOptions, + WatchType.ExtendedConfigFile + ), + fileName => this.toPath(fileName), + ); + } + + /*@internal*/ + removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) { + this.sharedExtendedConfigFileWatchers.forEach(watcher => { + watcher.projects.delete(project.canonicalConfigFilePath); + watcher.close(); + }); + } + /** * This is the callback function for the config file add/remove/change at any location * that matters to open script info but doesnt have configured project open @@ -2051,7 +2091,6 @@ namespace ts.server { this, this.documentRegistry, cachedDirectoryStructureHost); - // TODO: We probably should also watch the configFiles that are extended project.createConfigFileWatcher(); this.configuredProjects.set(project.canonicalConfigFilePath, project); this.setConfigFileExistenceByNewConfiguredProject(project); @@ -2134,12 +2173,14 @@ namespace ts.server { if (lastFileExceededProgramSize) { project.disableLanguageService(lastFileExceededProgramSize); project.stopWatchingWildCards(); + this.removeProjectFromSharedExtendedConfigFileMap(project); } else { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 + this.updateSharedExtendedConfigFileMap(project, parsedCommandLine); } project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides); const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles()); diff --git a/src/server/project.ts b/src/server/project.ts index 3f41ef9de50..6323e458227 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2292,6 +2292,7 @@ namespace ts.server { } this.stopWatchingWildCards(); + this.projectService.removeProjectFromSharedExtendedConfigFileMap(this); this.projectErrors = undefined; this.openFileWatchTriggered.clear(); this.compilerHost = undefined; diff --git a/src/testRunner/unittests/tsbuild/watchMode.ts b/src/testRunner/unittests/tsbuild/watchMode.ts index a0442229ba8..a4167b84497 100644 --- a/src/testRunner/unittests/tsbuild/watchMode.ts +++ b/src/testRunner/unittests/tsbuild/watchMode.ts @@ -1219,6 +1219,169 @@ export function someFn() { }`), noopChange ] }); + + verifyTscWatch({ + scenario, + subScenario: "works with extended source files", + commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"], + sys: () => { + const alphaExtendedConfigFile: File = { + path: "/a/b/alpha.tsconfig.json", + content: "{}" + }; + const project1Config: File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [commonFile1.path, commonFile2.path] + }) + }; + const bravoExtendedConfigFile: File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) + }; + const otherFile: File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + return createWatchedSystem([ + libFile, + alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Modify alpha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({ + compilerOptions: { strict: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1 + }, + { + caption: "Build project 2", + change: noop, + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "change bravo config", + change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { strict: false } + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "project 2 extends alpha", + change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "update aplha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"), + timeouts: checkSingleTimeoutQueueLengthAndRun, // build project1 + }, + { + caption: "Build project 2", + change: noop, + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "works correctly when project with extended config is removed", + commandLineArgs: ["-b", "-w", "-v"], + sys: () => { + const alphaExtendedConfigFile: File = { + path: "/a/b/alpha.tsconfig.json", + content: JSON.stringify({ + strict: true + }) + }; + const project1Config: File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [commonFile1.path, commonFile2.path] + }) + }; + const bravoExtendedConfigFile: File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + strict: true + }) + }; + const otherFile: File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + { + path: "./project2.tsconfig.json", + }, + ], + files: [], + }) + }; + return createWatchedSystem([ + libFile, configFile, + alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Remove project2 from base config", + change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + ], + files: [], + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] + }); }); describe("unittests:: tsbuild:: watchMode:: with demo project", () => { diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index 63f2cdcd1d8..80a4d60cf70 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1626,5 +1626,75 @@ import { x } from "../b";`), }, ] }); + + verifyTscWatch({ + scenario, + subScenario: "extended source files are watched", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const firstExtendedConfigFile: File = { + path: "/a/b/first.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const secondExtendedConfigFile: File = { + path: "/a/b/second.tsconfig.json", + content: JSON.stringify({ + extends: "./first.tsconfig.json" + }) + }; + const configFile: File = { + path: configFilePath, + content: JSON.stringify({ + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + }) + }; + return createWatchedSystem([ + libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile + ]); + }, + changes: [ + { + caption: "Change config to extend another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + extends: "./second.tsconfig.json", + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change first extended config", + change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ + compilerOptions: { + strict: false, + } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change second extended config", + change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ + extends: "./first.tsconfig.json", + compilerOptions: { + strictNullChecks: true, + } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change config to stop extending another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + ] + }); }); } diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index 5c8acc00fb0..eac227348c9 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1093,6 +1093,137 @@ foo();` }); assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); }); + + describe("watches extended config files", () => { + function getService(additionalFiles?: File[]) { + const alphaExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, + content: "{}" + }; + const bravoExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) + }; + const aConfig: File = { + path: `${tscWatch.projectRoot}/a/tsconfig.json`, + content: JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + files: ["a.ts"] + }) + }; + const aFile: File = { + path: `${tscWatch.projectRoot}/a/a.ts`, + content: `let a = 1;` + }; + const bConfig: File = { + path: `${tscWatch.projectRoot}/b/tsconfig.json`, + content: JSON.stringify({ + extends: "../extended/bravo.tsconfig.json", + files: ["b.ts"] + }) + }; + const bFile: File = { + path: `${tscWatch.projectRoot}/b/b.ts`, + content: `let b = 1;` + }; + + const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); + const projectService = createProjectService(host); + return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; + } + + it("should watch the extended configs of multiple projects", () => { + const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); + + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + checkNumberOfConfiguredProjects(projectService, 2); + const aProject = projectService.configuredProjects.get(aConfig.path)!; + const bProject = projectService.configuredProjects.get(bConfig.path)!; + checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); + checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + assert.isUndefined(aProject.getCompilerOptions().strict); + assert.isUndefined(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(alphaExtendedConfig.path, JSON.stringify({ + compilerOptions: { + strict: true + } + })); + assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(3); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isTrue(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(bravoExtendedConfig.path, JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + strict: false + } + })); + assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(2); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isFalse(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(bConfig.path, JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + })); + assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(2); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isTrue(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); + + host.writeFile(alphaExtendedConfig.path, "{}"); + assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(3); + assert.isUndefined(aProject.getCompilerOptions().strict); + assert.isUndefined(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); + }); + + it("should stop watching the extended configs of closed projects", () => { + const dummy: File = { + path: `${tscWatch.projectRoot}/dummy/dummy.ts`, + content: `let dummy = 1;` + }; + const dummyConfig: File = { + path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, + content: "{}" + }; + const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService([dummy, dummyConfig]); + + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + projectService.openClientFile(dummy.path); + checkNumberOfConfiguredProjects(projectService, 3); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path, dummyConfig.path]); + + projectService.closeClientFile(bFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); + + checkNumberOfConfiguredProjects(projectService, 2); + checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, dummyConfig.path]); + + projectService.closeClientFile(aFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); + + checkNumberOfConfiguredProjects(projectService, 1); + checkWatchedFiles(host, [libFile.path, dummyConfig.path]); + }); + }); }); describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index c4c7d78b740..76015c3fec5 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -72,6 +72,26 @@ namespace ts.projectSystem { verifyEvent(project, `Change in config file detected`); }); + it("when change is detected in an extended config file", () => { + const bTs: File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: File = { + path: configBPath, + content: JSON.stringify({ + extends: "../a/tsconfig.json", + }) + }; + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); + + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configB.path)!; + verifyEvent(project, `Change in extended config file ${configA.path} detected`); + }); + describe("when opening original location project", () => { it("with project references", () => { verify(); diff --git a/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-bad-reference.js b/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-bad-reference.js index fee6acfb283..755664b4174 100644 --- a/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-bad-reference.js +++ b/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-bad-reference.js @@ -241,6 +241,8 @@ Semantic diagnostics in builder refreshed for:: WatchedFiles:: /user/username/projects/demo/core/tsconfig.json: {"fileName":"/user/username/projects/demo/core/tsconfig.json","pollingInterval":250} +/user/username/projects/demo/tsconfig-base.json: + {"fileName":"/user/username/projects/demo/tsconfig-base.json","pollingInterval":250} /user/username/projects/demo/core/utilities.ts: {"fileName":"/user/username/projects/demo/core/utilities.ts","pollingInterval":250} /user/username/projects/demo/animals/tsconfig.json: @@ -478,6 +480,8 @@ Semantic diagnostics in builder refreshed for:: WatchedFiles:: /user/username/projects/demo/core/tsconfig.json: {"fileName":"/user/username/projects/demo/core/tsconfig.json","pollingInterval":250} +/user/username/projects/demo/tsconfig-base.json: + {"fileName":"/user/username/projects/demo/tsconfig-base.json","pollingInterval":250} /user/username/projects/demo/core/utilities.ts: {"fileName":"/user/username/projects/demo/core/utilities.ts","pollingInterval":250} /user/username/projects/demo/animals/tsconfig.json: diff --git a/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-circular-reference.js b/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-circular-reference.js index e17c8d56528..58d286de318 100644 --- a/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-circular-reference.js +++ b/tests/baselines/reference/tsbuild/watchMode/demo/updates-with-circular-reference.js @@ -170,6 +170,8 @@ Output:: WatchedFiles:: /user/username/projects/demo/animals/tsconfig.json: {"fileName":"/user/username/projects/demo/animals/tsconfig.json","pollingInterval":250} +/user/username/projects/demo/tsconfig-base.json: + {"fileName":"/user/username/projects/demo/tsconfig-base.json","pollingInterval":250} /user/username/projects/demo/animals/animal.ts: {"fileName":"/user/username/projects/demo/animals/animal.ts","pollingInterval":250} /user/username/projects/demo/animals/dog.ts: @@ -281,6 +283,8 @@ Semantic diagnostics in builder refreshed for:: WatchedFiles:: /user/username/projects/demo/animals/tsconfig.json: {"fileName":"/user/username/projects/demo/animals/tsconfig.json","pollingInterval":250} +/user/username/projects/demo/tsconfig-base.json: + {"fileName":"/user/username/projects/demo/tsconfig-base.json","pollingInterval":250} /user/username/projects/demo/animals/animal.ts: {"fileName":"/user/username/projects/demo/animals/animal.ts","pollingInterval":250} /user/username/projects/demo/animals/dog.ts: diff --git a/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js new file mode 100644 index 00000000000..eafc6e97367 --- /dev/null +++ b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js @@ -0,0 +1,233 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/a/b/tsconfig.json] +{"references":[{"path":"./project1.tsconfig.json"},{"path":"./project2.tsconfig.json"}],"files":[]} + +//// [/a/b/alpha.tsconfig.json] +{"strict":true} + +//// [/a/b/project1.tsconfig.json] +{"extends":"./alpha.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} + +//// [/a/b/commonFile1.ts] +let x = 1 + +//// [/a/b/commonFile2.ts] +let y = 1 + +//// [/a/b/bravo.tsconfig.json] +{"strict":true} + +//// [/a/b/project2.tsconfig.json] +{"extends":"./bravo.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/other.ts"]} + +//// [/a/b/other.ts] +let z = 0; + + +/a/lib/tsc.js -b -w -v +Output:: +>> Screen clear +[12:00:27 AM] Starting compilation in watch mode... + +[12:00:28 AM] Projects in this build: + * project1.tsconfig.json + * project2.tsconfig.json + * tsconfig.json + +[12:00:29 AM] Project 'project1.tsconfig.json' is out of date because output file 'commonFile1.js' does not exist + +[12:00:30 AM] Building project '/a/b/project1.tsconfig.json'... + +[12:00:41 AM] Project 'project2.tsconfig.json' is out of date because output file 'other.js' does not exist + +[12:00:42 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:00:49 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Program root files: ["/a/b/other.ts"] +Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/bravo.tsconfig.json: + {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile1.d.ts] +declare let x: number; + + +//// [/a/b/commonFile2.js] +var y = 1; + + +//// [/a/b/commonFile2.d.ts] +declare let y: number; + + +//// [/a/b/project1.tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../lib/lib.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true + }, + "./commonfile1.ts": { + "version": "2167136208-let x = 1", + "signature": "2842409786-declare let x: number;\n", + "affectsGlobalScope": true + }, + "./commonfile2.ts": { + "version": "2168322129-let y = 1", + "signature": "784887931-declare let y: number;\n", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "watch": true, + "configFilePath": "./project1.tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "./commonfile1.ts", + "./commonfile2.ts", + "../lib/lib.d.ts" + ] + }, + "version": "FakeTSVersion" +} + +//// [/a/b/other.js] +var z = 0; + + +//// [/a/b/other.d.ts] +declare let z: number; + + +//// [/a/b/project2.tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../lib/lib.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true + }, + "./other.ts": { + "version": "2874288940-let z = 0;", + "signature": "-1272633924-declare let z: number;\n", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "watch": true, + "configFilePath": "./project2.tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "./other.ts", + "../lib/lib.d.ts" + ] + }, + "version": "FakeTSVersion" +} + + +Change:: Remove project2 from base config + +Input:: +//// [/a/b/tsconfig.json] +{"references":[{"path":"./project1.tsconfig.json"}],"files":[]} + + +Output:: +>> Screen clear +[12:00:52 AM] File change detected. Starting incremental compilation... + +[12:00:53 AM] Found 0 errors. Watching for file changes. + + + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-with-extended-source-files.js b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-with-extended-source-files.js new file mode 100644 index 00000000000..0c4c2339200 --- /dev/null +++ b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-with-extended-source-files.js @@ -0,0 +1,557 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/a/b/alpha.tsconfig.json] +{} + +//// [/a/b/project1.tsconfig.json] +{"extends":"./alpha.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} + +//// [/a/b/commonFile1.ts] +let x = 1 + +//// [/a/b/commonFile2.ts] +let y = 1 + +//// [/a/b/bravo.tsconfig.json] +{"extends":"./alpha.tsconfig.json"} + +//// [/a/b/project2.tsconfig.json] +{"extends":"./bravo.tsconfig.json","compilerOptions":{"composite":true},"files":["/a/b/other.ts"]} + +//// [/a/b/other.ts] +let z = 0; + + +/a/lib/tsc.js -b -w -v project1.tsconfig.json project2.tsconfig.json +Output:: +>> Screen clear +[12:00:25 AM] Starting compilation in watch mode... + +[12:00:26 AM] Projects in this build: + * project1.tsconfig.json + * project2.tsconfig.json + +[12:00:27 AM] Project 'project1.tsconfig.json' is out of date because output file 'commonFile1.js' does not exist + +[12:00:28 AM] Building project '/a/b/project1.tsconfig.json'... + +[12:00:39 AM] Project 'project2.tsconfig.json' is out of date because output file 'other.js' does not exist + +[12:00:40 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:00:47 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Program root files: ["/a/b/other.ts"] +Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/bravo.tsconfig.json: + {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile1.d.ts] +declare let x: number; + + +//// [/a/b/commonFile2.js] +var y = 1; + + +//// [/a/b/commonFile2.d.ts] +declare let y: number; + + +//// [/a/b/project1.tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../lib/lib.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true + }, + "./commonfile1.ts": { + "version": "2167136208-let x = 1", + "signature": "2842409786-declare let x: number;\n", + "affectsGlobalScope": true + }, + "./commonfile2.ts": { + "version": "2168322129-let y = 1", + "signature": "784887931-declare let y: number;\n", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "watch": true, + "configFilePath": "./project1.tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "./commonfile1.ts", + "./commonfile2.ts", + "../lib/lib.d.ts" + ] + }, + "version": "FakeTSVersion" +} + +//// [/a/b/other.js] +var z = 0; + + +//// [/a/b/other.d.ts] +declare let z: number; + + +//// [/a/b/project2.tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "../lib/lib.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true + }, + "./other.ts": { + "version": "2874288940-let z = 0;", + "signature": "-1272633924-declare let z: number;\n", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "watch": true, + "configFilePath": "./project2.tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "./other.ts", + "../lib/lib.d.ts" + ] + }, + "version": "FakeTSVersion" +} + + +Change:: Modify alpha config + +Input:: +//// [/a/b/alpha.tsconfig.json] +{"compilerOptions":{"strict":true}} + + +Output:: +>> Screen clear +[12:00:51 AM] File change detected. Starting incremental compilation... + +[12:00:52 AM] Project 'project1.tsconfig.json' is out of date because oldest output 'commonFile1.js' is older than newest input 'alpha.tsconfig.json' + +[12:00:53 AM] Building project '/a/b/project1.tsconfig.json'... + +[12:00:55 AM] Updating unchanged output timestamps of project '/a/b/project1.tsconfig.json'... + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"strict":true,"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/bravo.tsconfig.json: + {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] file changed its modified time +//// [/a/b/commonFile1.d.ts] file changed its modified time +//// [/a/b/commonFile2.js] file changed its modified time +//// [/a/b/commonFile2.d.ts] file changed its modified time +//// [/a/b/project1.tsconfig.tsbuildinfo] file changed its modified time + +Change:: Build project 2 + +Input:: + +Output:: +[12:00:56 AM] Project 'project2.tsconfig.json' is out of date because oldest output 'other.js' is older than newest input 'alpha.tsconfig.json' + +[12:00:57 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:00:59 AM] Updating unchanged output timestamps of project '/a/b/project2.tsconfig.json'... + +[12:01:00 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/other.ts"] +Program options: {"strict":true,"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/bravo.tsconfig.json: + {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/other.js] file changed its modified time +//// [/a/b/other.d.ts] file changed its modified time +//// [/a/b/project2.tsconfig.tsbuildinfo] file changed its modified time + +Change:: change bravo config + +Input:: +//// [/a/b/bravo.tsconfig.json] +{"extends":"./alpha.tsconfig.json","compilerOptions":{"strict":false}} + + +Output:: +>> Screen clear +[12:01:04 AM] File change detected. Starting incremental compilation... + +[12:01:05 AM] Project 'project2.tsconfig.json' is out of date because oldest output 'other.js' is older than newest input 'bravo.tsconfig.json' + +[12:01:06 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:01:08 AM] Updating unchanged output timestamps of project '/a/b/project2.tsconfig.json'... + +[12:01:09 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/other.ts"] +Program options: {"strict":false,"composite":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/bravo.tsconfig.json: + {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/other.js] file changed its modified time +//// [/a/b/other.d.ts] file changed its modified time +//// [/a/b/project2.tsconfig.tsbuildinfo] file changed its modified time + +Change:: project 2 extends alpha + +Input:: +//// [/a/b/project2.tsconfig.json] +{"extends":"./alpha.tsconfig.json"} + + +Output:: +>> Screen clear +[12:01:13 AM] File change detected. Starting incremental compilation... + +[12:01:14 AM] Project 'project2.tsconfig.json' is out of date because oldest output 'commonFile1.js' is older than newest input 'project2.tsconfig.json' + +[12:01:15 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:01:25 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts"] +Program options: {"strict":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +"use strict"; +var x = 1; + + +//// [/a/b/commonFile2.js] +"use strict"; +var y = 1; + + +//// [/a/b/other.js] +"use strict"; +var z = 0; + + + +Change:: update aplha config + +Input:: +//// [/a/b/alpha.tsconfig.json] +{} + + +Output:: +>> Screen clear +[12:01:29 AM] File change detected. Starting incremental compilation... + +[12:01:30 AM] Project 'project1.tsconfig.json' is out of date because oldest output 'commonFile1.d.ts' is older than newest input 'alpha.tsconfig.json' + +[12:01:31 AM] Building project '/a/b/project1.tsconfig.json'... + +[12:01:33 AM] Updating unchanged output timestamps of project '/a/b/project1.tsconfig.json'... + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"composite":true,"watch":true,"configFilePath":"/a/b/project1.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] file changed its modified time +//// [/a/b/commonFile1.d.ts] file changed its modified time +//// [/a/b/commonFile2.js] file changed its modified time +//// [/a/b/commonFile2.d.ts] file changed its modified time +//// [/a/b/project1.tsconfig.tsbuildinfo] file changed its modified time + +Change:: Build project 2 + +Input:: + +Output:: +[12:01:34 AM] Project 'project2.tsconfig.json' is out of date because oldest output 'other.js' is older than newest input 'alpha.tsconfig.json' + +[12:01:35 AM] Building project '/a/b/project2.tsconfig.json'... + +[12:01:37 AM] Updating unchanged output timestamps of project '/a/b/project2.tsconfig.json'... + +[12:01:38 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts"] +Program options: {"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts +/a/b/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts +/a/b/other.ts + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] file changed its modified time +//// [/a/b/commonFile2.js] file changed its modified time +//// [/a/b/other.js] file changed its modified time diff --git a/tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js b/tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js new file mode 100644 index 00000000000..892ea32f15b --- /dev/null +++ b/tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js @@ -0,0 +1,278 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/a/b/commonFile1.ts] +let x = 1 + +//// [/a/b/commonFile2.ts] +let y = 1 + +//// [/a/b/tsconfig.json] +{"compilerOptions":{},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} + +//// [/a/b/first.tsconfig.json] +{"compilerOptions":{"strict":true}} + +//// [/a/b/second.tsconfig.json] +{"extends":"./first.tsconfig.json"} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:21 AM] Starting compilation in watch mode... + +[12:00:26 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + + +Change:: Change config to extend another config + +Input:: +//// [/a/b/tsconfig.json] +{"extends":"./second.tsconfig.json","compilerOptions":{},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} + + +Output:: +>> Screen clear +[12:00:29 AM] File change detected. Starting incremental compilation... + +[12:00:30 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"strict":true,"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program structureReused: Completely +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} +/a/b/second.tsconfig.json: + {"fileName":"/a/b/second.tsconfig.json","pollingInterval":250} +/a/b/first.tsconfig.json: + {"fileName":"/a/b/first.tsconfig.json","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + + +Change:: Change first extended config + +Input:: +//// [/a/b/first.tsconfig.json] +{"compilerOptions":{"strict":false}} + + +Output:: +>> Screen clear +[12:00:33 AM] File change detected. Starting incremental compilation... + +[12:00:34 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"strict":false,"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program structureReused: Completely +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} +/a/b/second.tsconfig.json: + {"fileName":"/a/b/second.tsconfig.json","pollingInterval":250} +/a/b/first.tsconfig.json: + {"fileName":"/a/b/first.tsconfig.json","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + + +Change:: Change second extended config + +Input:: +//// [/a/b/second.tsconfig.json] +{"extends":"./first.tsconfig.json","compilerOptions":{"strictNullChecks":true}} + + +Output:: +>> Screen clear +[12:00:37 AM] File change detected. Starting incremental compilation... + +[12:00:38 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"strict":false,"strictNullChecks":true,"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program structureReused: Completely +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} +/a/b/second.tsconfig.json: + {"fileName":"/a/b/second.tsconfig.json","pollingInterval":250} +/a/b/first.tsconfig.json: + {"fileName":"/a/b/first.tsconfig.json","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + + +Change:: Change config to stop extending another config + +Input:: +//// [/a/b/tsconfig.json] +{"compilerOptions":{},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} + + +Output:: +>> Screen clear +[12:00:41 AM] File change detected. Starting incremental compilation... + +[12:00:42 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts"] +Program options: {"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program structureReused: Completely +Program files:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/commonFile1.ts +/a/b/commonFile2.ts + +WatchedFiles:: +/a/b/tsconfig.json: + {"fileName":"/a/b/tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined +