From c0b6fb590f081c3376a63d8fcf42655925374dbf Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Tue, 3 Nov 2020 11:21:24 -0500 Subject: [PATCH 01/10] Watch extended configs if present --- src/compiler/watchPublic.ts | 10 ++ src/compiler/watchUtilities.ts | 24 +++ .../unittests/tscWatch/watchEnvironment.ts | 156 ++++++++++++++++++ .../with-fallbackPolling-option.js | 79 +++++++++ .../with-several-extended-configs.js | 94 +++++++++++ .../with-watchDirectory-option.js | 79 +++++++++ ...th-watchFile-as-watch-options-to-extend.js | 79 +++++++++ .../watchExtends/with-watchFile-option.js | 79 +++++++++ 8 files changed, 600 insertions(+) create mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js create mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js create mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js create mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js create mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 8face54ee3a4b..08fd082d34faa 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 @@ -354,6 +355,10 @@ namespace ts { configFileWatcher.close(); configFileWatcher = undefined; } + if (extendedConfigFilesMap) { + clearMap(extendedConfigFilesMap, closeFileWatcher); + extendedConfigFilesMap = undefined!; + } if (watchedWildcardDirectories) { clearMap(watchedWildcardDirectories, closeFileWatcherOf); watchedWildcardDirectories = undefined!; @@ -419,6 +424,7 @@ namespace ts { resolutionCache.finishCachingPerDirectoryResolution(); // Update watches + updateExtendedConfigFilePathsWatch(builderProgram.getProgram(), extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), watchExtendedConfigFile); updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath); if (needsUpdateInTypeRootWatch) { resolutionCache.updateTypeRootsWatch(); @@ -705,6 +711,10 @@ namespace ts { } } + function watchExtendedConfigFile(extendedConfigFile: string) { + return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile); + } + function watchMissingFilePath(missingFilePath: Path) { return watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile); } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 861c64c1eb3ec..c5588e07e79f5 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -257,6 +257,30 @@ namespace ts { Full } + /** + * Updates the extended config file watches with the new set of extended config files after new program is created + */ + export function updateExtendedConfigFilePathsWatch( + program: Program, + extendedConfigFilesMap: ESMap, + createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, + ) { + const extendedSourceFiles = program.getCompilerOptions().configFile?.extendedSourceFiles || []; + // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` + const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); + // Update the extended config files watcher + mutateMap( + extendedConfigFilesMap, + newExtendedConfigFilesMap, + { + // Watch the extended config files + createNewValue: createExtendedConfigFileWatch, + // Config files that are no longer extended should no longer be watched. + onDeleteValue: closeFileWatcher + } + ); + } + /** * Updates the existing missing file watches with the new set of missing files after new program is created */ diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index b510cf6d2f673..b709798060bec 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -497,5 +497,161 @@ namespace ts.tscWatch { verifyWorker("-extendedDiagnostics"); }); }); + + describe("handles watch for extended configs", () => { + verifyTscWatch({ + scenario, + subScenario: "watchExtends/with several extended configs", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const firstExtendedFile: File = { + path: "/a/b/first.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: false, + } + }) + }; + const secondExtendedFile: File = { + path: "/a/b/second.tsconfig.json", + content: JSON.stringify({ + extends: "./first.tsconfig.json", + compilerOptions: { + strictNullChecks: true, + } + }) + }; + const thirdExtendedFile: File = { + path: "/a/b/third.tsconfig.json", + content: JSON.stringify({ + extends: "./second.tsconfig.json", + compilerOptions: { + strictBindCallApply: true, + } + }) + }; + const fourthExtendedFile: File = { + path: "/a/b/fourth.tsconfig.json", + content: JSON.stringify({ + extends: "./third.tsconfig.json", + compilerOptions: { + strictFunctionTypes: true, + } + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + extends: "./fourth.tsconfig.json", + compilerOptions: { + strictPropertyInitialization: true, + } + }) + }; + const files = [ + libFile, commonFile1, commonFile2, firstExtendedFile, secondExtendedFile, thirdExtendedFile, + fourthExtendedFile, configFile + ]; + return createWatchedSystem(files); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchExtends/with watchFile option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const extendedFile: File = { + path: "/a/b/extended.tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchFile: "UseFsEvents" + } + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + extends: "./extended.tsconfig.json" + }) + }; + const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; + return createWatchedSystem(files); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchExtends/with watchDirectory option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const extendedFile: File = { + path: "/a/b/extended.tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchDirectory: "UseFsEvents" + } + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + extends: "./extended.tsconfig.json" + }) + }; + const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchExtends/with fallbackPolling option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const extendedFile: File = { + path: "/a/b/extended.tsconfig.json", + content: JSON.stringify({ + watchOptions: { + fallbackPolling: "PriorityInterval" + } + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + extends: "./extended.tsconfig.json" + }) + }; + const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchExtends/with watchFile as watch options to extend", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], + sys: () => { + const extendedFile: File = { + path: "/a/b/extended.tsconfig.json", + content: "{}" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + extends: "./extended.tsconfig.json" + }) + }; + const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; + return createWatchedSystem(files); + }, + changes: emptyArray + }); + }); }); } diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js new file mode 100644 index 0000000000000..780b6e3f7bfb1 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js @@ -0,0 +1,79 @@ +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/extended.tsconfig.json] +{"watchOptions":{"fallbackPolling":"PriorityInterval"}} + +//// [/a/b/tsconfig.json] +{"extends":"./extended.tsconfig.json"} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:19 AM] Starting compilation in watch mode... + +[12:00:24 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} +/a/b/extended.tsconfig.json: + {"fileName":"/a/b/extended.tsconfig.json","pollingInterval":250} +/a/b/node_modules/@types: + {"fileName":"/a/b/node_modules/@types","pollingInterval":500} +/a/b: + {"fileName":"/a/b","pollingInterval":500} + +FsWatches:: + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js new file mode 100644 index 0000000000000..04e4568950c34 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js @@ -0,0 +1,94 @@ +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/first.tsconfig.json] +{"compilerOptions":{"strict":false}} + +//// [/a/b/second.tsconfig.json] +{"extends":"./first.tsconfig.json","compilerOptions":{"strictNullChecks":true}} + +//// [/a/b/third.tsconfig.json] +{"extends":"./second.tsconfig.json","compilerOptions":{"strictBindCallApply":true}} + +//// [/a/b/fourth.tsconfig.json] +{"extends":"./third.tsconfig.json","compilerOptions":{"strictFunctionTypes":true}} + +//// [/a/b/tsconfig.json] +{"extends":"./fourth.tsconfig.json","compilerOptions":{"strictPropertyInitialization":true}} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:25 AM] Starting compilation in watch mode... + +[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":false,"strictNullChecks":true,"strictBindCallApply":true,"strictFunctionTypes":true,"strictPropertyInitialization":true,"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} +/a/b/fourth.tsconfig.json: + {"fileName":"/a/b/fourth.tsconfig.json","pollingInterval":250} +/a/b/third.tsconfig.json: + {"fileName":"/a/b/third.tsconfig.json","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"}} +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js new file mode 100644 index 0000000000000..24d529effc81e --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js @@ -0,0 +1,79 @@ +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/extended.tsconfig.json] +{"watchOptions":{"watchDirectory":"UseFsEvents"}} + +//// [/a/b/tsconfig.json] +{"extends":"./extended.tsconfig.json"} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:19 AM] Starting compilation in watch mode... + +[12:00:24 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} +/a/b/extended.tsconfig.json: + {"fileName":"/a/b/extended.tsconfig.json","pollingInterval":250} + +FsWatches:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js new file mode 100644 index 0000000000000..5fb00648bdafd --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js @@ -0,0 +1,79 @@ +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/extended.tsconfig.json] +{} + +//// [/a/b/tsconfig.json] +{"extends":"./extended.tsconfig.json"} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json --watchFile UseFsEvents +Output:: +>> Screen clear +[12:00:19 AM] Starting compilation in watch mode... + +[12:00:24 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:: + +FsWatches:: +/a/b/tsconfig.json: + {"directoryName":"/a/b/tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/commonfile1.ts: + {"directoryName":"/a/b/commonFile1.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/commonfile2.ts: + {"directoryName":"/a/b/commonFile2.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/lib/lib.d.ts: + {"directoryName":"/a/lib/lib.d.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/extended.tsconfig.json: + {"directoryName":"/a/b/extended.tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js new file mode 100644 index 0000000000000..10a97c9e14b50 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js @@ -0,0 +1,79 @@ +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/extended.tsconfig.json] +{"watchOptions":{"watchFile":"UseFsEvents"}} + +//// [/a/b/tsconfig.json] +{"extends":"./extended.tsconfig.json"} + + +/a/lib/tsc.js -w -p /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:19 AM] Starting compilation in watch mode... + +[12:00:24 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:: + +FsWatches:: +/a/b/tsconfig.json: + {"directoryName":"/a/b/tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/commonfile1.ts: + {"directoryName":"/a/b/commonFile1.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/commonfile2.ts: + {"directoryName":"/a/b/commonFile2.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/lib/lib.d.ts: + {"directoryName":"/a/lib/lib.d.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b/extended.tsconfig.json: + {"directoryName":"/a/b/extended.tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: +/a/b/node_modules/@types: + {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/a/b: + {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] +var x = 1; + + +//// [/a/b/commonFile2.js] +var y = 1; + + From 55d4e3ed54002e6cadf42afbbf9968cae2b9f9b6 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Wed, 11 Nov 2020 17:02:44 -0500 Subject: [PATCH 02/10] Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. --- src/compiler/watch.ts | 2 + src/compiler/watchPublic.ts | 23 ++- src/compiler/watchUtilities.ts | 2 +- .../unittests/tscWatch/programUpdates.ts | 140 +++++++++++++ .../unittests/tscWatch/watchEnvironment.ts | 156 --------------- ...le-is-changed-to-extend-another-config.js} | 83 +++++--- ...nged-to-no-longer-extend-another-config.js | 131 ++++++++++++ ...-when-extended-config-files-are-changed.js | 186 ++++++++++++++++++ .../with-fallbackPolling-option.js | 79 -------- .../with-watchDirectory-option.js | 79 -------- ...th-watchFile-as-watch-options-to-extend.js | 79 -------- .../watchExtends/with-watchFile-option.js | 79 -------- 12 files changed, 537 insertions(+), 502 deletions(-) rename tests/baselines/reference/tscWatch/{watchEnvironment/watchExtends/with-several-extended-configs.js => programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js} (50%) create mode 100644 tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js create mode 100644 tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js delete mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js delete mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js delete mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js delete mode 100644 tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ea9d20da84ee6..85594b103f01c 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 08fd082d34faa..b6730740c935e 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -338,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 }; @@ -424,7 +427,6 @@ namespace ts { resolutionCache.finishCachingPerDirectoryResolution(); // Update watches - updateExtendedConfigFilePathsWatch(builderProgram.getProgram(), extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), watchExtendedConfigFile); updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath); if (needsUpdateInTypeRootWatch) { resolutionCache.updateTypeRootsWatch(); @@ -663,6 +665,9 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); + + // Update extended config file watch + watchExtendedConfigFiles(); } function parseConfigFile() { @@ -711,10 +716,6 @@ namespace ts { } } - function watchExtendedConfigFile(extendedConfigFile: string) { - return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile); - } - function watchMissingFilePath(missingFilePath: Path) { return watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile); } @@ -787,5 +788,17 @@ namespace ts { WatchType.WildcardDirectory ); } + + function watchExtendedConfigFiles() { + updateExtendedConfigFilePathsWatch( + builderProgram.getProgram(), + extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), + watchExtendedConfigFile + ); + } + + function watchExtendedConfigFile(extendedConfigFile: string) { + return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ExtendedConfigFile); + } } } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index c5588e07e79f5..bba56ffe08fe8 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -265,7 +265,7 @@ namespace ts { extendedConfigFilesMap: ESMap, createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, ) { - const extendedSourceFiles = program.getCompilerOptions().configFile?.extendedSourceFiles || []; + const extendedSourceFiles = program.getCompilerOptions().configFile?.extendedSourceFiles || emptyArray; // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); // Update the extended config files watcher diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index 63f2cdcd1d8a9..cf5ec38ea089e 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1626,5 +1626,145 @@ import { x } from "../b";`), }, ] }); + + verifyTscWatch({ + scenario, + subScenario: "works correctly when config file is changed to extend another config", + 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, + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "works correctly when config file is changed to no longer extend another config", + 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({ + extends: "./second.tsconfig.json", + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + }) + }; + return createWatchedSystem([ + libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile + ]); + }, + changes: [ + { + caption: "Change config to stop extending another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "works correctly when extended config files are changed", + 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({ + extends: "./second.tsconfig.json", + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + }) + }; + return createWatchedSystem([ + libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile + ]); + }, + changes: [ + { + 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, + } + ] + }); }); } diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index b709798060bec..b510cf6d2f673 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -497,161 +497,5 @@ namespace ts.tscWatch { verifyWorker("-extendedDiagnostics"); }); }); - - describe("handles watch for extended configs", () => { - verifyTscWatch({ - scenario, - subScenario: "watchExtends/with several extended configs", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const firstExtendedFile: File = { - path: "/a/b/first.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: false, - } - }) - }; - const secondExtendedFile: File = { - path: "/a/b/second.tsconfig.json", - content: JSON.stringify({ - extends: "./first.tsconfig.json", - compilerOptions: { - strictNullChecks: true, - } - }) - }; - const thirdExtendedFile: File = { - path: "/a/b/third.tsconfig.json", - content: JSON.stringify({ - extends: "./second.tsconfig.json", - compilerOptions: { - strictBindCallApply: true, - } - }) - }; - const fourthExtendedFile: File = { - path: "/a/b/fourth.tsconfig.json", - content: JSON.stringify({ - extends: "./third.tsconfig.json", - compilerOptions: { - strictFunctionTypes: true, - } - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - extends: "./fourth.tsconfig.json", - compilerOptions: { - strictPropertyInitialization: true, - } - }) - }; - const files = [ - libFile, commonFile1, commonFile2, firstExtendedFile, secondExtendedFile, thirdExtendedFile, - fourthExtendedFile, configFile - ]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchExtends/with watchFile option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const extendedFile: File = { - path: "/a/b/extended.tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchFile: "UseFsEvents" - } - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - extends: "./extended.tsconfig.json" - }) - }; - const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchExtends/with watchDirectory option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const extendedFile: File = { - path: "/a/b/extended.tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchDirectory: "UseFsEvents" - } - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - extends: "./extended.tsconfig.json" - }) - }; - const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchExtends/with fallbackPolling option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const extendedFile: File = { - path: "/a/b/extended.tsconfig.json", - content: JSON.stringify({ - watchOptions: { - fallbackPolling: "PriorityInterval" - } - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - extends: "./extended.tsconfig.json" - }) - }; - const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchExtends/with watchFile as watch options to extend", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], - sys: () => { - const extendedFile: File = { - path: "/a/b/extended.tsconfig.json", - content: "{}" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - extends: "./extended.tsconfig.json" - }) - }; - const files = [libFile, commonFile1, commonFile2, extendedFile, configFile]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - }); }); } diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js similarity index 50% rename from tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js rename to tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js index 04e4568950c34..579194112fb44 100644 --- a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-several-extended-configs.js +++ b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js @@ -18,33 +18,27 @@ 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":false}} +{"compilerOptions":{"strict":true}} //// [/a/b/second.tsconfig.json] -{"extends":"./first.tsconfig.json","compilerOptions":{"strictNullChecks":true}} - -//// [/a/b/third.tsconfig.json] -{"extends":"./second.tsconfig.json","compilerOptions":{"strictBindCallApply":true}} - -//// [/a/b/fourth.tsconfig.json] -{"extends":"./third.tsconfig.json","compilerOptions":{"strictFunctionTypes":true}} - -//// [/a/b/tsconfig.json] -{"extends":"./fourth.tsconfig.json","compilerOptions":{"strictPropertyInitialization":true}} +{"extends":"./first.tsconfig.json"} /a/lib/tsc.js -w -p /a/b/tsconfig.json Output:: >> Screen clear -[12:00:25 AM] Starting compilation in watch mode... +[12:00:21 AM] Starting compilation in watch mode... -[12:00:30 AM] Found 0 errors. Watching for file changes. +[12:00:26 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,"strictBindCallApply":true,"strictFunctionTypes":true,"strictPropertyInitialization":true,"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} +Program options: {"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts @@ -65,22 +59,12 @@ WatchedFiles:: {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} /a/lib/lib.d.ts: {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} -/a/b/fourth.tsconfig.json: - {"fileName":"/a/b/fourth.tsconfig.json","pollingInterval":250} -/a/b/third.tsconfig.json: - {"fileName":"/a/b/third.tsconfig.json","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"}} -/a/b: - {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} exitCode:: ExitStatus.undefined @@ -92,3 +76,54 @@ var x = 1; 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 + diff --git a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js new file mode 100644 index 0000000000000..f3efefeec59d7 --- /dev/null +++ b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js @@ -0,0 +1,131 @@ +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] +{"extends":"./second.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: {"strict":true,"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} +/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 + +//// [/a/b/commonFile1.js] +"use strict"; +var x = 1; + + +//// [/a/b/commonFile2.js] +"use strict"; +var y = 1; + + + +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: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: {"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 + diff --git a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js new file mode 100644 index 0000000000000..76a19d085cdea --- /dev/null +++ b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js @@ -0,0 +1,186 @@ +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] +{"extends":"./second.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: {"strict":true,"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} +/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 + +//// [/a/b/commonFile1.js] +"use strict"; +var x = 1; + + +//// [/a/b/commonFile2.js] +"use strict"; +var y = 1; + + + +Change:: Change first extended config + +Input:: +//// [/a/b/first.tsconfig.json] +{"compilerOptions":{"strict":false}} + + +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":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: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,"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 + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js deleted file mode 100644 index 780b6e3f7bfb1..0000000000000 --- a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-fallbackPolling-option.js +++ /dev/null @@ -1,79 +0,0 @@ -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/extended.tsconfig.json] -{"watchOptions":{"fallbackPolling":"PriorityInterval"}} - -//// [/a/b/tsconfig.json] -{"extends":"./extended.tsconfig.json"} - - -/a/lib/tsc.js -w -p /a/b/tsconfig.json -Output:: ->> Screen clear -[12:00:19 AM] Starting compilation in watch mode... - -[12:00:24 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} -/a/b/extended.tsconfig.json: - {"fileName":"/a/b/extended.tsconfig.json","pollingInterval":250} -/a/b/node_modules/@types: - {"fileName":"/a/b/node_modules/@types","pollingInterval":500} -/a/b: - {"fileName":"/a/b","pollingInterval":500} - -FsWatches:: - -FsWatchesRecursive:: - -exitCode:: ExitStatus.undefined - -//// [/a/b/commonFile1.js] -var x = 1; - - -//// [/a/b/commonFile2.js] -var y = 1; - - diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js deleted file mode 100644 index 24d529effc81e..0000000000000 --- a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchDirectory-option.js +++ /dev/null @@ -1,79 +0,0 @@ -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/extended.tsconfig.json] -{"watchOptions":{"watchDirectory":"UseFsEvents"}} - -//// [/a/b/tsconfig.json] -{"extends":"./extended.tsconfig.json"} - - -/a/lib/tsc.js -w -p /a/b/tsconfig.json -Output:: ->> Screen clear -[12:00:19 AM] Starting compilation in watch mode... - -[12:00:24 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} -/a/b/extended.tsconfig.json: - {"fileName":"/a/b/extended.tsconfig.json","pollingInterval":250} - -FsWatches:: -/a/b/node_modules/@types: - {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b: - {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} - -FsWatchesRecursive:: - -exitCode:: ExitStatus.undefined - -//// [/a/b/commonFile1.js] -var x = 1; - - -//// [/a/b/commonFile2.js] -var y = 1; - - diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js deleted file mode 100644 index 5fb00648bdafd..0000000000000 --- a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-as-watch-options-to-extend.js +++ /dev/null @@ -1,79 +0,0 @@ -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/extended.tsconfig.json] -{} - -//// [/a/b/tsconfig.json] -{"extends":"./extended.tsconfig.json"} - - -/a/lib/tsc.js -w -p /a/b/tsconfig.json --watchFile UseFsEvents -Output:: ->> Screen clear -[12:00:19 AM] Starting compilation in watch mode... - -[12:00:24 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:: - -FsWatches:: -/a/b/tsconfig.json: - {"directoryName":"/a/b/tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/commonfile1.ts: - {"directoryName":"/a/b/commonFile1.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/commonfile2.ts: - {"directoryName":"/a/b/commonFile2.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/lib/lib.d.ts: - {"directoryName":"/a/lib/lib.d.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/extended.tsconfig.json: - {"directoryName":"/a/b/extended.tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} - -FsWatchesRecursive:: -/a/b/node_modules/@types: - {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b: - {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} - -exitCode:: ExitStatus.undefined - -//// [/a/b/commonFile1.js] -var x = 1; - - -//// [/a/b/commonFile2.js] -var y = 1; - - diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js deleted file mode 100644 index 10a97c9e14b50..0000000000000 --- a/tests/baselines/reference/tscWatch/watchEnvironment/watchExtends/with-watchFile-option.js +++ /dev/null @@ -1,79 +0,0 @@ -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/extended.tsconfig.json] -{"watchOptions":{"watchFile":"UseFsEvents"}} - -//// [/a/b/tsconfig.json] -{"extends":"./extended.tsconfig.json"} - - -/a/lib/tsc.js -w -p /a/b/tsconfig.json -Output:: ->> Screen clear -[12:00:19 AM] Starting compilation in watch mode... - -[12:00:24 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:: - -FsWatches:: -/a/b/tsconfig.json: - {"directoryName":"/a/b/tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/commonfile1.ts: - {"directoryName":"/a/b/commonFile1.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/commonfile2.ts: - {"directoryName":"/a/b/commonFile2.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/lib/lib.d.ts: - {"directoryName":"/a/lib/lib.d.ts","fallbackPollingInterval":250,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b/extended.tsconfig.json: - {"directoryName":"/a/b/extended.tsconfig.json","fallbackPollingInterval":2000,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} - -FsWatchesRecursive:: -/a/b/node_modules/@types: - {"directoryName":"/a/b/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} -/a/b: - {"directoryName":"/a/b","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} - -exitCode:: ExitStatus.undefined - -//// [/a/b/commonFile1.js] -var x = 1; - - -//// [/a/b/commonFile2.js] -var y = 1; - - From a6a3bc2dfaa9dbb12856415557ac8d300d06af69 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Thu, 12 Nov 2020 22:12:08 -0500 Subject: [PATCH 03/10] Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. --- src/compiler/watchPublic.ts | 13 ++-- src/compiler/watchUtilities.ts | 9 +-- src/server/editorServices.ts | 19 +++++- src/server/project.ts | 35 +++++++++++ .../unittests/tsserver/configuredProjects.ts | 61 +++++++++++++++++++ .../tsserver/events/projectLoading.ts | 20 ++++++ 6 files changed, 147 insertions(+), 10 deletions(-) diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index b6730740c935e..a403132aac2b3 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -790,11 +790,14 @@ namespace ts { } function watchExtendedConfigFiles() { - updateExtendedConfigFilePathsWatch( - builderProgram.getProgram(), - extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), - watchExtendedConfigFile - ); + const configFile = builderProgram.getCompilerOptions().configFile; + if (configFile) { + updateExtendedConfigFilesWatch( + configFile, + extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), + watchExtendedConfigFile + ); + } } function watchExtendedConfigFile(extendedConfigFile: string) { diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index bba56ffe08fe8..852a15f5bf741 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -258,14 +258,15 @@ namespace ts { } /** - * Updates the extended config file watches with the new set of extended config files after new program is created + * Updates the extended config file watches with the new set of extended config files from the config file + * after new program is created because the config file was reloaded or program was created first time from the config file */ - export function updateExtendedConfigFilePathsWatch( - program: Program, + export function updateExtendedConfigFilesWatch( + configFile: TsConfigSourceFile, extendedConfigFilesMap: ESMap, createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, ) { - const extendedSourceFiles = program.getCompilerOptions().configFile?.extendedSourceFiles || emptyArray; + const extendedSourceFiles = configFile.extendedSourceFiles || emptyArray; // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); // Update the extended config files watcher diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 368a3c03bce7d..967e03278502e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1350,6 +1350,22 @@ namespace ts.server { } } + /*@internal*/ + onExtendedConfigChangedForConfiguredProject(project: ConfiguredProject, extendedConfigFile: string) { + const configFileExistenceInfo = this.getConfigFileExistenceInfo(project); + + this.logConfigFileWatchUpdate(asNormalizedPath(extendedConfigFile), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + + // Skip refresh if project is not yet loaded + if (project.isInitialLoadPending()) return; + project.pendingReload = ConfigFileProgramReloadLevel.Full; + project.pendingReloadReason = "Change in extended config file detected"; + this.delayUpdateProjectGraph(project); + // As we scheduled the update on configured project graph, + // we would need to schedule the project reload for only the root of inferred projects + this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true); + } + /** * 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 +2067,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); @@ -2133,12 +2148,14 @@ namespace ts.server { const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader); if (lastFileExceededProgramSize) { project.disableLanguageService(lastFileExceededProgramSize); + project.stopWatchingExtendedConfigFiles(); project.stopWatchingWildCards(); } else { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); + project.watchExtendedConfigFiles(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 } project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides); diff --git a/src/server/project.ts b/src/server/project.ts index 3f41ef9de5050..1be47089fff38 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2057,6 +2057,8 @@ namespace ts.server { export class ConfiguredProject extends Project { /* @internal */ configFileWatcher: FileWatcher | undefined; + /* @internal */ + private extendedConfigFileWatchers: ESMap | undefined; private directoriesWatchedForWildcards: ESMap | undefined; readonly canonicalConfigFilePath: NormalizedPath; @@ -2267,6 +2269,38 @@ namespace ts.server { this.projectErrors = projectErrors; } + /* @internal */ + createExtendedConfigFileWatcher(extendedConfigFile: string): FileWatcher { + return this.projectService.watchFactory.watchFile( + extendedConfigFile, + (fileName) => this.projectService.onExtendedConfigChangedForConfiguredProject(this, fileName), + PollingInterval.High, + this.projectService.getWatchOptions(this), + WatchType.ExtendedConfigFile, + this + ); + } + + /* @internal */ + watchExtendedConfigFiles() { + const configFile = this.getCompilerOptions().configFile; + if (configFile) { + updateExtendedConfigFilesWatch( + configFile, + this.extendedConfigFileWatchers || (this.extendedConfigFileWatchers = new Map()), + (extendedConfigFile) => this.createExtendedConfigFileWatcher(extendedConfigFile), + ); + } + } + + /* @internal */ + stopWatchingExtendedConfigFiles() { + if (this.extendedConfigFileWatchers) { + clearMap(this.extendedConfigFileWatchers, closeFileWatcher); + this.extendedConfigFileWatchers = undefined; + } + } + /*@internal*/ watchWildcards(wildcardDirectories: ESMap) { updateWatchingWildcardDirectories( @@ -2291,6 +2325,7 @@ namespace ts.server { this.configFileWatcher = undefined; } + this.stopWatchingExtendedConfigFiles(); this.stopWatchingWildCards(); this.projectErrors = undefined; this.openFileWatchTriggered.clear(); diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index 5c8acc00fb071..c4537f0aa97ac 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1093,6 +1093,67 @@ foo();` }); assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); }); + + it("should watch the extended configs of multiple projects", () => { + const alphaExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + strict: false, + } + }) + }; + const bravoExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + strictNullChecks: true, + } + }) + }; + 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 + ]); + const projectService = createProjectService(host); + + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [aFile.path, aConfig.path, alphaExtendedConfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(alphaExtendedConfig.path, alphaExtendedConfig.content); + host.checkTimeoutQueueLengthAndRun(3); + host.writeFile(bravoExtendedConfig.path, bravoExtendedConfig.content); + host.checkTimeoutQueueLengthAndRun(2); + }); }); 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 c4c7d78b7402e..d7d10c64961a9 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 detected`); + }); + describe("when opening original location project", () => { it("with project references", () => { verify(); From fa918ed61a9c679f7616eeb000186794cad3f852 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Fri, 13 Nov 2020 22:52:06 -0500 Subject: [PATCH 04/10] Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. --- src/server/editorServices.ts | 21 ++++++--- .../unittests/tsserver/configuredProjects.ts | 45 +++++++++++++------ .../tsserver/events/projectLoading.ts | 4 +- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 967e03278502e..9ef64dcdd4a32 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1352,18 +1352,13 @@ namespace ts.server { /*@internal*/ onExtendedConfigChangedForConfiguredProject(project: ConfiguredProject, extendedConfigFile: string) { - const configFileExistenceInfo = this.getConfigFileExistenceInfo(project); - - this.logConfigFileWatchUpdate(asNormalizedPath(extendedConfigFile), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.logExtendedConfigFileWatchUpdate(asNormalizedPath(extendedConfigFile), project.canonicalConfigFilePath, ConfigFileWatcherStatus.ReloadingFiles); // Skip refresh if project is not yet loaded if (project.isInitialLoadPending()) return; project.pendingReload = ConfigFileProgramReloadLevel.Full; - project.pendingReloadReason = "Change in extended config file detected"; + project.pendingReloadReason = `Change in extended config file ${extendedConfigFile} detected`; this.delayUpdateProjectGraph(project); - // As we scheduled the update on configured project graph, - // we would need to schedule the project reload for only the root of inferred projects - this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true); } /** @@ -1689,6 +1684,18 @@ namespace ts.server { this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } + /*@internal*/ + private logExtendedConfigFileWatchUpdate(extendedConfigFile: NormalizedPath, canonicalConfigFilePath: string, status: ConfigFileWatcherStatus) { + if (!this.logger.hasLevel(LogLevel.verbose)) { + return; + } + const watches: WatchType[] = []; + if (this.configuredProjects.has(canonicalConfigFilePath)) { + watches.push(WatchType.ExtendedConfigFile); + } + this.logger.info(`ExtendedConfigFilePresence:: Current Watches: ${watches}:: File: ${extendedConfigFile} Status: ${status}`); + } + /** * Create the watcher for the configFileExistenceInfo */ diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index c4537f0aa97ac..2a27cf9f7ca6c 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1097,19 +1097,12 @@ foo();` it("should watch the extended configs of multiple projects", () => { const alphaExtendedConfig: File = { path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - strict: false, - } - }) + content: "{}" }; const bravoExtendedConfig: File = { path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, content: JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - strictNullChecks: true, - } + extends: "./alpha.tsconfig.json" }) }; const aConfig: File = { @@ -1144,15 +1137,39 @@ foo();` projectService.openClientFile(aFile.path); projectService.openClientFile(bFile.path); checkNumberOfProjects(projectService, { configuredProjects: 2 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [aFile.path, aConfig.path, alphaExtendedConfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + const aProject = configuredProjectAt(projectService, 0); + const bProject = configuredProjectAt(projectService, 1); + checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); + checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + const aOptions1 = aProject.getCompilerOptions(); + const bOptions1 = bProject.getCompilerOptions(); + assert.isUndefined(aOptions1.strict); + assert.isUndefined(bOptions1.strict); checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - host.writeFile(alphaExtendedConfig.path, alphaExtendedConfig.content); - host.checkTimeoutQueueLengthAndRun(3); - host.writeFile(bravoExtendedConfig.path, bravoExtendedConfig.content); + host.writeFile(alphaExtendedConfig.path, JSON.stringify({ + compilerOptions: { + strict: true + } + })); host.checkTimeoutQueueLengthAndRun(2); + const aOptions2 = aProject.getCompilerOptions(); + const bOptions2 = bProject.getCompilerOptions(); + assert.isTrue(aOptions2.strict); + assert.isTrue(bOptions2.strict); + + host.writeFile(bravoExtendedConfig.path, JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + strict: false + } + })); + host.checkTimeoutQueueLengthAndRun(1); + const aOptions3 = aProject.getCompilerOptions(); + const bOptions3 = bProject.getCompilerOptions(); + assert.isTrue(aOptions3.strict); + assert.isFalse(bOptions3.strict); }); }); diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index d7d10c64961a9..7f2d429e3d33b 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -87,9 +87,9 @@ namespace ts.projectSystem { verifyEventWithOpenTs(bTs, configB.path, 1); host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); + host.checkTimeoutQueueLengthAndRun(1); const project = service.configuredProjects.get(configB.path)!; - verifyEvent(project, `Change in extended config file detected`); + verifyEvent(project, `Change in extended config file ${configA.path} detected`); }); describe("when opening original location project", () => { From 1a527d6bf9964bf96ae30dbba0ae6c7f8f2b999c Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Sat, 14 Nov 2020 01:08:36 -0500 Subject: [PATCH 05/10] Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. --- src/compiler/watchPublic.ts | 2 +- src/compiler/watchUtilities.ts | 67 ++++++++++++++++++++++++++++++++++ src/server/editorServices.ts | 17 ++++++++- src/server/project.ts | 35 ------------------ 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index a403132aac2b3..23d7f06d9c86e 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -790,7 +790,7 @@ namespace ts { } function watchExtendedConfigFiles() { - const configFile = builderProgram.getCompilerOptions().configFile; + const { configFile } = builderProgram.getCompilerOptions(); if (configFile) { updateExtendedConfigFilesWatch( configFile, diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 852a15f5bf741..f5c2e9acccad8 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -282,6 +282,73 @@ namespace ts { ); } + export interface SharedExtendedConfigFileWatcher

{ + watcher: FileWatcher; + callbacks: ESMap; + } + + export function updateSharedExtendedConfigFilesWatch

( + project: P, + projectCallback: FileWatcherCallback, + configFile: TsConfigSourceFile, + sharedExtendedConfigFilesMap: ESMap>, + watchFactory: WatchFactory, + watchOptions: WatchOptions | undefined + ) { + const extendedSourceFiles = configFile.extendedSourceFiles || emptyArray; + const newSharedExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); + + sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { + if (newSharedExtendedConfigFilesMap.has(key)) { + existingWatcher.callbacks.set(project, projectCallback); + } + else { + existingWatcher.callbacks.delete(project); + if (existingWatcher.callbacks.size === 0) { + sharedExtendedConfigFilesMap.delete(key); + closeFileWatcherOf(existingWatcher); + } + } + }); + + newSharedExtendedConfigFilesMap.forEach((_true, extendedConfigPath) => { + if (!sharedExtendedConfigFilesMap.has(extendedConfigPath)) { + const newWatcher = createSharedExtendedConfigFileWatcher(extendedConfigPath); + sharedExtendedConfigFilesMap.set(extendedConfigPath, newWatcher); + } + }); + + function createSharedExtendedConfigFileWatcher(extendedConfigPath: string) { + const callbacks = new Map(); + callbacks.set(project, projectCallback); + const watcher = watchFactory.watchFile( + extendedConfigPath, + invokeProjectCallbacks, + PollingInterval.High, + watchOptions, + WatchType.ExtendedConfigFile + ); + return { watcher, callbacks }; + + function invokeProjectCallbacks(fileName: string, eventKind: FileWatcherEventKind) { + return callbacks.forEach((callback) => callback(fileName, eventKind)); + } + } + } + + export function removeProjectFromSharedExtendedConfigFilesWatch

( + project: P, + sharedExtendedConfigFilesMap: ESMap> + ) { + sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { + existingWatcher.callbacks.delete(project); + if (existingWatcher.callbacks.size === 0) { + sharedExtendedConfigFilesMap.delete(key); + closeFileWatcherOf(existingWatcher); + } + }); + } + /** * 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 9ef64dcdd4a32..2488716077b0c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -756,6 +756,9 @@ namespace ts.server { /*@internal*/ readonly watchFactory: WatchFactory; + /*@internal*/ + private sharedExtendedConfigFilesMap = new Map>(); + /*@internal*/ readonly packageJsonCache: PackageJsonCache; /*@internal*/ @@ -1384,6 +1387,7 @@ namespace ts.server { project.print(/*writeProjectFileNames*/ true); project.close(); + removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); if (Debug.shouldAssert(AssertionLevel.Normal)) { this.filenameToScriptInfo.forEach(info => Debug.assert( !info.isAttached(project), @@ -2155,15 +2159,24 @@ namespace ts.server { const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader); if (lastFileExceededProgramSize) { project.disableLanguageService(lastFileExceededProgramSize); - project.stopWatchingExtendedConfigFiles(); project.stopWatchingWildCards(); + removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); } else { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); - project.watchExtendedConfigFiles(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 + if (compilerOptions.configFile) { + updateSharedExtendedConfigFilesWatch( + project, + (fileName) => this.onExtendedConfigChangedForConfiguredProject(project, fileName), + compilerOptions.configFile, + this.sharedExtendedConfigFilesMap, + this.watchFactory, + this.hostConfiguration.watchOptions, + ); + } } 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 1be47089fff38..3f41ef9de5050 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2057,8 +2057,6 @@ namespace ts.server { export class ConfiguredProject extends Project { /* @internal */ configFileWatcher: FileWatcher | undefined; - /* @internal */ - private extendedConfigFileWatchers: ESMap | undefined; private directoriesWatchedForWildcards: ESMap | undefined; readonly canonicalConfigFilePath: NormalizedPath; @@ -2269,38 +2267,6 @@ namespace ts.server { this.projectErrors = projectErrors; } - /* @internal */ - createExtendedConfigFileWatcher(extendedConfigFile: string): FileWatcher { - return this.projectService.watchFactory.watchFile( - extendedConfigFile, - (fileName) => this.projectService.onExtendedConfigChangedForConfiguredProject(this, fileName), - PollingInterval.High, - this.projectService.getWatchOptions(this), - WatchType.ExtendedConfigFile, - this - ); - } - - /* @internal */ - watchExtendedConfigFiles() { - const configFile = this.getCompilerOptions().configFile; - if (configFile) { - updateExtendedConfigFilesWatch( - configFile, - this.extendedConfigFileWatchers || (this.extendedConfigFileWatchers = new Map()), - (extendedConfigFile) => this.createExtendedConfigFileWatcher(extendedConfigFile), - ); - } - } - - /* @internal */ - stopWatchingExtendedConfigFiles() { - if (this.extendedConfigFileWatchers) { - clearMap(this.extendedConfigFileWatchers, closeFileWatcher); - this.extendedConfigFileWatchers = undefined; - } - } - /*@internal*/ watchWildcards(wildcardDirectories: ESMap) { updateWatchingWildcardDirectories( @@ -2325,7 +2291,6 @@ namespace ts.server { this.configFileWatcher = undefined; } - this.stopWatchingExtendedConfigFiles(); this.stopWatchingWildCards(); this.projectErrors = undefined; this.openFileWatchTriggered.clear(); From 963ccf9ef9a4c4b49f7a999622e437884ead67f1 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Thu, 19 Nov 2020 17:09:10 -0500 Subject: [PATCH 06/10] Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. --- src/compiler/watchPublic.ts | 2 +- src/compiler/watchUtilities.ts | 72 +---------------------------- src/server/editorServices.ts | 83 +++++++++++++++++++++------------- 3 files changed, 54 insertions(+), 103 deletions(-) diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 23d7f06d9c86e..22793ef163faf 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -792,7 +792,7 @@ namespace ts { function watchExtendedConfigFiles() { const { configFile } = builderProgram.getCompilerOptions(); if (configFile) { - updateExtendedConfigFilesWatch( + updateExtendedConfigFilesMap( configFile, extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), watchExtendedConfigFile diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index f5c2e9acccad8..089b2078d5905 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -258,10 +258,9 @@ namespace ts { } /** - * Updates the extended config file watches with the new set of extended config files from the config file - * after new program is created because the config file was reloaded or program was created first time from the config file + * Updates the map of extended config file watches with a new set of extended config files from a base config file */ - export function updateExtendedConfigFilesWatch( + export function updateExtendedConfigFilesMap( configFile: TsConfigSourceFile, extendedConfigFilesMap: ESMap, createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, @@ -282,73 +281,6 @@ namespace ts { ); } - export interface SharedExtendedConfigFileWatcher

{ - watcher: FileWatcher; - callbacks: ESMap; - } - - export function updateSharedExtendedConfigFilesWatch

( - project: P, - projectCallback: FileWatcherCallback, - configFile: TsConfigSourceFile, - sharedExtendedConfigFilesMap: ESMap>, - watchFactory: WatchFactory, - watchOptions: WatchOptions | undefined - ) { - const extendedSourceFiles = configFile.extendedSourceFiles || emptyArray; - const newSharedExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); - - sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { - if (newSharedExtendedConfigFilesMap.has(key)) { - existingWatcher.callbacks.set(project, projectCallback); - } - else { - existingWatcher.callbacks.delete(project); - if (existingWatcher.callbacks.size === 0) { - sharedExtendedConfigFilesMap.delete(key); - closeFileWatcherOf(existingWatcher); - } - } - }); - - newSharedExtendedConfigFilesMap.forEach((_true, extendedConfigPath) => { - if (!sharedExtendedConfigFilesMap.has(extendedConfigPath)) { - const newWatcher = createSharedExtendedConfigFileWatcher(extendedConfigPath); - sharedExtendedConfigFilesMap.set(extendedConfigPath, newWatcher); - } - }); - - function createSharedExtendedConfigFileWatcher(extendedConfigPath: string) { - const callbacks = new Map(); - callbacks.set(project, projectCallback); - const watcher = watchFactory.watchFile( - extendedConfigPath, - invokeProjectCallbacks, - PollingInterval.High, - watchOptions, - WatchType.ExtendedConfigFile - ); - return { watcher, callbacks }; - - function invokeProjectCallbacks(fileName: string, eventKind: FileWatcherEventKind) { - return callbacks.forEach((callback) => callback(fileName, eventKind)); - } - } - } - - export function removeProjectFromSharedExtendedConfigFilesWatch

( - project: P, - sharedExtendedConfigFilesMap: ESMap> - ) { - sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { - existingWatcher.callbacks.delete(project); - if (existingWatcher.callbacks.size === 0) { - sharedExtendedConfigFilesMap.delete(key); - closeFileWatcherOf(existingWatcher); - } - }); - } - /** * 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 2488716077b0c..9201403f1fdcc 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -757,7 +757,9 @@ namespace ts.server { readonly watchFactory: WatchFactory; /*@internal*/ - private sharedExtendedConfigFilesMap = new Map>(); + private sharedExtendedConfigFileMap = createMultiMap(); + /*@internal*/ + private sharedExtendedConfigFileWatchers = new Map(); /*@internal*/ readonly packageJsonCache: PackageJsonCache; @@ -1354,14 +1356,52 @@ namespace ts.server { } /*@internal*/ - onExtendedConfigChangedForConfiguredProject(project: ConfiguredProject, extendedConfigFile: string) { - this.logExtendedConfigFileWatchUpdate(asNormalizedPath(extendedConfigFile), project.canonicalConfigFilePath, ConfigFileWatcherStatus.ReloadingFiles); + private updateSharedExtendedConfigFileMap(project: ConfiguredProject) { + const extendedSourceFiles = project.getCompilerOptions().configFile?.extendedSourceFiles || emptyArray; + extendedSourceFiles.forEach((extendedSourceFile: string) => { + const extendedConfigPath = this.toPath(extendedSourceFile); + if (!this.sharedExtendedConfigFileMap.has(extendedConfigPath)) { + const watcher = this.watchFactory.watchFile( + extendedConfigPath, + () => this.onSharedExtendedConfigChanged(extendedConfigPath), + PollingInterval.High, + this.hostConfiguration.watchOptions, + WatchType.ExtendedConfigFile + ); + this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, watcher); + } + const otherProjects = this.sharedExtendedConfigFileMap.get(extendedConfigPath); + if (!otherProjects || !otherProjects.includes(project)) { + this.sharedExtendedConfigFileMap.add(extendedConfigPath, project); + } + }); + } - // Skip refresh if project is not yet loaded - if (project.isInitialLoadPending()) return; - project.pendingReload = ConfigFileProgramReloadLevel.Full; - project.pendingReloadReason = `Change in extended config file ${extendedConfigFile} detected`; - this.delayUpdateProjectGraph(project); + /*@internal*/ + private removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) { + for (const key of arrayFrom(this.sharedExtendedConfigFileMap.keys())) { + this.sharedExtendedConfigFileMap.remove(key, project); + const otherProjects = this.sharedExtendedConfigFileMap.get(key) || emptyArray; + if (otherProjects.length === 0) { + const watcher = this.sharedExtendedConfigFileWatchers.get(key); + if (watcher) { + watcher.close(); + this.sharedExtendedConfigFileWatchers.delete(key); + } + } + } + } + + /*@internal*/ + private onSharedExtendedConfigChanged(extendedConfigPath: Path) { + const projects = this.sharedExtendedConfigFileMap.get(extendedConfigPath) || emptyArray; + projects.forEach((project: ConfiguredProject) => { + // Skip refresh if project is not yet loaded + if (project.isInitialLoadPending()) return; + project.pendingReload = ConfigFileProgramReloadLevel.Full; + project.pendingReloadReason = `Change in extended config file ${extendedConfigPath} detected`; + this.delayUpdateProjectGraph(project); + }); } /** @@ -1387,7 +1427,6 @@ namespace ts.server { project.print(/*writeProjectFileNames*/ true); project.close(); - removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); if (Debug.shouldAssert(AssertionLevel.Normal)) { this.filenameToScriptInfo.forEach(info => Debug.assert( !info.isAttached(project), @@ -1420,6 +1459,7 @@ namespace ts.server { this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); this.setConfigFileExistenceInfoByClosedConfiguredProject(project); + this.removeProjectFromSharedExtendedConfigFileMap(project as ConfiguredProject); break; case ProjectKind.Inferred: unorderedRemoveItem(this.inferredProjects, project); @@ -1688,18 +1728,6 @@ namespace ts.server { this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } - /*@internal*/ - private logExtendedConfigFileWatchUpdate(extendedConfigFile: NormalizedPath, canonicalConfigFilePath: string, status: ConfigFileWatcherStatus) { - if (!this.logger.hasLevel(LogLevel.verbose)) { - return; - } - const watches: WatchType[] = []; - if (this.configuredProjects.has(canonicalConfigFilePath)) { - watches.push(WatchType.ExtendedConfigFile); - } - this.logger.info(`ExtendedConfigFilePresence:: Current Watches: ${watches}:: File: ${extendedConfigFile} Status: ${status}`); - } - /** * Create the watcher for the configFileExistenceInfo */ @@ -2160,23 +2188,14 @@ namespace ts.server { if (lastFileExceededProgramSize) { project.disableLanguageService(lastFileExceededProgramSize); project.stopWatchingWildCards(); - removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); + this.removeProjectFromSharedExtendedConfigFileMap(project); } else { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 - if (compilerOptions.configFile) { - updateSharedExtendedConfigFilesWatch( - project, - (fileName) => this.onExtendedConfigChangedForConfiguredProject(project, fileName), - compilerOptions.configFile, - this.sharedExtendedConfigFilesMap, - this.watchFactory, - this.hostConfiguration.watchOptions, - ); - } + this.updateSharedExtendedConfigFileMap(project); } project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides); const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles()); From c34125bda9d0dfd85fcdf6d9b24c69ea462bc2a2 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Wed, 9 Dec 2020 16:38:50 -0500 Subject: [PATCH 07/10] Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. --- src/compiler/tsbuildPublic.ts | 66 ++++++ src/compiler/watchPublic.ts | 12 +- src/compiler/watchUtilities.ts | 6 +- src/server/editorServices.ts | 79 ++++--- .../unittests/tscWatch/programUpdates.ts | 77 ++++++ .../unittests/tsserver/configuredProjects.ts | 67 +++++- .../demo/updates-with-bad-reference.js | 4 + .../demo/updates-with-circular-reference.js | 4 + ...project-with-extended-config-is-removed.js | 220 ++++++++++++++++++ 9 files changed, 490 insertions(+), 45 deletions(-) create mode 100644 tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index f624ed9098f68..876a21cf178a7 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -210,6 +210,10 @@ namespace ts { originalGetSourceFile: CompilerHost["getSourceFile"]; } + interface SharedExtendedConfigFileWatcher extends FileWatcher { + projects: Set; + } + interface SolutionBuilderState extends WatchFactory { readonly host: SolutionBuilderHost; readonly hostWithWatch: SolutionBuilderWithWatchHost; @@ -254,6 +258,7 @@ namespace ts { readonly allWatchedWildcardDirectories: ESMap>; readonly allWatchedInputFiles: ESMap>; readonly allWatchedConfigFiles: ESMap; + readonly allWatchedExtendedConfigFiles: ESMap; timerToBuildInvalidatedProject: any; reportFileChangeDetected: boolean; @@ -325,6 +330,7 @@ namespace ts { allWatchedWildcardDirectories: new Map(), allWatchedInputFiles: new Map(), allWatchedConfigFiles: new Map(), + allWatchedExtendedConfigFiles: new Map(), timerToBuildInvalidatedProject: undefined, reportFileChangeDetected: false, @@ -462,6 +468,18 @@ namespace ts { { onDeleteValue: closeFileWatcher } ); + state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => { + watcher.projects.forEach((project) => { + if (!currentProjects.has(project)) { + watcher.projects.delete(project); + } + }); + if (watcher.projects.size === 0) { + watcher.close(); + state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); + } + }); + mutateMapSkippingNewValues( state.allWatchedWildcardDirectories, currentProjects, @@ -1165,6 +1183,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 +1808,51 @@ namespace ts { )); } + function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { + const extendedSourceFiles = parsed?.options.configFile?.extendedSourceFiles || emptyArray; + const extendedConfigs = new Map(extendedSourceFiles.map((extendedSourceFile) => { + const extendedConfigFileName = extendedSourceFile as ResolvedConfigFileName; + const extendedConfigFilePath = toResolvedConfigFilePath(state, extendedConfigFileName); + return [extendedConfigFilePath, extendedConfigFileName] as const; + })); + extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { + const watcher = state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath); + if (watcher) { + watcher.projects.add(resolvedPath); + } + else { + // start watching previously unseen extended config + const projects = new Set([resolvedPath]); + const fileWatcher = state.watchFile( + extendedConfigFileName, + () => { + projects.forEach((projectConfigFilePath) => { + invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full); + }); + }, + PollingInterval.High, + parsed?.watchOptions, + WatchType.ExtendedConfigFile, + extendedConfigFileName + ); + state.allWatchedExtendedConfigFiles.set(extendedConfigFilePath, { + close: () => fileWatcher.close(), + projects, + }); + } + }); + // remove project from all unrelated watchers + state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => { + if (!extendedConfigs.has(extendedConfigFilePath)) { + watcher.projects.delete(resolvedPath); + if (watcher.projects.size === 0) { + watcher.close(); + state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); + } + } + }); + } + function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { if (!state.watch) return; updateWatchingWildcardDirectories( @@ -1846,6 +1910,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 +1923,7 @@ namespace ts { function stopWatching(state: SolutionBuilderState) { clearMap(state.allWatchedConfigFiles, closeFileWatcher); + clearMap(state.allWatchedExtendedConfigFiles, closeFileWatcher); clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf)); clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher)); } diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 22793ef163faf..e456fcd677f49 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -791,13 +791,11 @@ namespace ts { function watchExtendedConfigFiles() { const { configFile } = builderProgram.getCompilerOptions(); - if (configFile) { - updateExtendedConfigFilesMap( - configFile, - extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), - watchExtendedConfigFile - ); - } + updateExtendedConfigFilesMap( + configFile, + extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), + watchExtendedConfigFile + ); } function watchExtendedConfigFile(extendedConfigFile: string) { diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 089b2078d5905..687cc2640561a 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -261,11 +261,11 @@ namespace ts { * Updates the map of extended config file watches with a new set of extended config files from a base config file */ export function updateExtendedConfigFilesMap( - configFile: TsConfigSourceFile, - extendedConfigFilesMap: ESMap, + configFile: TsConfigSourceFile | undefined, + extendedConfigFilesMap: ESMap, createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, ) { - const extendedSourceFiles = configFile.extendedSourceFiles || emptyArray; + const extendedSourceFiles = configFile?.extendedSourceFiles ?? emptyArray; // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); // Update the extended config files watcher diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9201403f1fdcc..66d4bd423f473 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -641,6 +641,10 @@ namespace ts.server { errors: Diagnostic[] | undefined; } + interface SharedExtendedConfigFileWatcher extends FileWatcher { + projects: Set; + } + export class ProjectService { /*@internal*/ @@ -757,9 +761,7 @@ namespace ts.server { readonly watchFactory: WatchFactory; /*@internal*/ - private sharedExtendedConfigFileMap = createMultiMap(); - /*@internal*/ - private sharedExtendedConfigFileWatchers = new Map(); + private readonly sharedExtendedConfigFileWatchers = new Map(); /*@internal*/ readonly packageJsonCache: PackageJsonCache; @@ -1357,53 +1359,62 @@ namespace ts.server { /*@internal*/ private updateSharedExtendedConfigFileMap(project: ConfiguredProject) { - const extendedSourceFiles = project.getCompilerOptions().configFile?.extendedSourceFiles || emptyArray; - extendedSourceFiles.forEach((extendedSourceFile: string) => { - const extendedConfigPath = this.toPath(extendedSourceFile); - if (!this.sharedExtendedConfigFileMap.has(extendedConfigPath)) { - const watcher = this.watchFactory.watchFile( + const extendedConfigPaths: readonly Path[] = project.getCompilerOptions().configFile?.extendedSourceFiles + ?.map((file) => this.toPath(file)) ?? emptyArray; + for (const extendedConfigPath of extendedConfigPaths) { + const watcher = this.sharedExtendedConfigFileWatchers.get(extendedConfigPath); + if (watcher) { + watcher.projects.add(project); + } + else { + // start watching previously unseen extended config + const projects = new Set([project]); + const fileWatcherCallback = () => { + const reason = `Change in extended config file ${extendedConfigPath} detected`; + projects.forEach((project: ConfiguredProject) => { + // Skip refresh if project is not yet loaded + if (project.isInitialLoadPending()) return; + project.pendingReload = ConfigFileProgramReloadLevel.Full; + project.pendingReloadReason = reason; + this.delayUpdateProjectGraph(project); + }); + }; + const fileWatcher = this.watchFactory.watchFile( extendedConfigPath, - () => this.onSharedExtendedConfigChanged(extendedConfigPath), + fileWatcherCallback, PollingInterval.High, this.hostConfiguration.watchOptions, WatchType.ExtendedConfigFile ); - this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, watcher); + this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, { + close: () => fileWatcher.close(), + projects, + }); } - const otherProjects = this.sharedExtendedConfigFileMap.get(extendedConfigPath); - if (!otherProjects || !otherProjects.includes(project)) { - this.sharedExtendedConfigFileMap.add(extendedConfigPath, project); + } + // remove project from all unrelated watchers + this.sharedExtendedConfigFileWatchers.forEach((watcher, extendedConfigPath) => { + if (!extendedConfigPaths.includes(extendedConfigPath)) { + watcher.projects.delete(project); + if (watcher.projects.size === 0) { + watcher.close(); + this.sharedExtendedConfigFileWatchers.delete(extendedConfigPath); + } } }); } /*@internal*/ private removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) { - for (const key of arrayFrom(this.sharedExtendedConfigFileMap.keys())) { - this.sharedExtendedConfigFileMap.remove(key, project); - const otherProjects = this.sharedExtendedConfigFileMap.get(key) || emptyArray; - if (otherProjects.length === 0) { - const watcher = this.sharedExtendedConfigFileWatchers.get(key); - if (watcher) { - watcher.close(); - this.sharedExtendedConfigFileWatchers.delete(key); - } + for (const [sharedConfigPath, watcher] of arrayFrom(this.sharedExtendedConfigFileWatchers.entries())) { + watcher.projects.delete(project); + if (watcher.projects.size === 0) { + watcher.close(); + this.sharedExtendedConfigFileWatchers.delete(sharedConfigPath); } } } - /*@internal*/ - private onSharedExtendedConfigChanged(extendedConfigPath: Path) { - const projects = this.sharedExtendedConfigFileMap.get(extendedConfigPath) || emptyArray; - projects.forEach((project: ConfiguredProject) => { - // Skip refresh if project is not yet loaded - if (project.isInitialLoadPending()) return; - project.pendingReload = ConfigFileProgramReloadLevel.Full; - project.pendingReloadReason = `Change in extended config file ${extendedConfigPath} detected`; - this.delayUpdateProjectGraph(project); - }); - } - /** * 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 diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index cf5ec38ea089e..0aac2e76a9df5 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1766,5 +1766,82 @@ import { x } from "../b";`), } ] }); + + verifyTscWatch({ + scenario, + subScenario: "works correctly when project with extended config is removed", + commandLineArgs: ["-b", "-w", configFilePath], + 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: configFilePath, + content: JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + { + path: "./project2.tsconfig.json", + }, + ], + files: [], + }) + }; + return createWatchedSystem([ + libFile, configFile, + alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ]); + }, + changes: [ + { + caption: "Remove project2 from base config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + ], + files: [], + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); }); } diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index 2a27cf9f7ca6c..85437496f6277 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1136,7 +1136,7 @@ foo();` projectService.openClientFile(aFile.path); projectService.openClientFile(bFile.path); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); + checkNumberOfConfiguredProjects(projectService, 2); const aProject = configuredProjectAt(projectService, 0); const bProject = configuredProjectAt(projectService, 1); checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); @@ -1171,6 +1171,71 @@ foo();` assert.isTrue(aOptions3.strict); assert.isFalse(bOptions3.strict); }); + + it("should stop watching the extended configs of closed projects", () => { + 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 + ]); + const projectService = createProjectService(host); + + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + checkNumberOfConfiguredProjects(projectService, 2); + const aProject = configuredProjectAt(projectService, 0); + const bProject = configuredProjectAt(projectService, 1); + checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); + checkProjectActualFiles(bProject, [bFile.path, bConfig.path, alphaExtendedConfig.path, bravoExtendedConfig.path]); + + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + projectService.closeClientFile(bFile.path); + host.deleteFile(bConfig.path); + + assert.isTrue(bProject.isClosed()); + checkNumberOfConfiguredProjects(projectService, 1); + checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, bFile.path]); + + projectService.closeClientFile(aFile.path); + host.deleteFile(aConfig.path); + + assert.isTrue(aProject.isClosed()); + checkNumberOfConfiguredProjects(projectService, 0); + checkWatchedFiles(host, [aFile.path, bFile.path]); + }); }); describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { 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 fee6acfb28316..755664b4174d7 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 e17c8d56528ea..58d286de31824 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 0000000000000..5efada1805b32 --- /dev/null +++ b/tests/baselines/reference/tsbuild/watchMode/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js @@ -0,0 +1,220 @@ +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 /a/b/tsconfig.json +Output:: +>> Screen clear +[12:00:27 AM] Starting compilation in watch mode... + +[12:00:44 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:47 AM] File change detected. Starting incremental compilation... + +[12:00:48 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 + From e7ff4b8bd1e8131494cec230c8fe95a0c2970ce0 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Thu, 10 Dec 2020 16:19:36 -0500 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Sheetal Nandi --- src/compiler/tsbuildPublic.ts | 31 +++++++++++++++---------------- src/compiler/watchPublic.ts | 3 +-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 876a21cf178a7..be449a4eb840c 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -474,10 +474,7 @@ namespace ts { watcher.projects.delete(project); } }); - if (watcher.projects.size === 0) { - watcher.close(); - state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); - } + watcher.close(); }); mutateMapSkippingNewValues( @@ -1809,12 +1806,10 @@ namespace ts { } function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { - const extendedSourceFiles = parsed?.options.configFile?.extendedSourceFiles || emptyArray; - const extendedConfigs = new Map(extendedSourceFiles.map((extendedSourceFile) => { - const extendedConfigFileName = extendedSourceFile as ResolvedConfigFileName; - const extendedConfigFilePath = toResolvedConfigFilePath(state, extendedConfigFileName); - return [extendedConfigFilePath, extendedConfigFileName] as const; - })); + const extendedConfigs = arrayToMap( + parsed?.options.configFile?.extendedSourceFiles || emptyArray as readonly ResolvedConfigFileName[], + extendedSourceFile => toResolvedConfigFilePath(state, extendedConfigFileName) + ); extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { const watcher = state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath); if (watcher) { @@ -1823,7 +1818,7 @@ namespace ts { else { // start watching previously unseen extended config const projects = new Set([resolvedPath]); - const fileWatcher = state.watchFile( + let fileWatcher = state.watchFile( extendedConfigFileName, () => { projects.forEach((projectConfigFilePath) => { @@ -1836,7 +1831,14 @@ namespace ts { extendedConfigFileName ); state.allWatchedExtendedConfigFiles.set(extendedConfigFilePath, { - close: () => fileWatcher.close(), + close: () => { + if (projects.size === 0) { + Debug.assert(fileWatcher !== undefined); + fileWatcher.close(); + fileWatcher = undefined!; + state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); + } + }, projects, }); } @@ -1845,10 +1847,7 @@ namespace ts { state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => { if (!extendedConfigs.has(extendedConfigFilePath)) { watcher.projects.delete(resolvedPath); - if (watcher.projects.size === 0) { - watcher.close(); - state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); - } + watcher.close(); } }); } diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index e456fcd677f49..4d514307826e3 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -790,9 +790,8 @@ namespace ts { } function watchExtendedConfigFiles() { - const { configFile } = builderProgram.getCompilerOptions(); updateExtendedConfigFilesMap( - configFile, + compilerOptions.configFile, extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), watchExtendedConfigFile ); From 45f19cc6fec08f2b20c3ff7e142a7657fa03cbf5 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Thu, 10 Dec 2020 16:33:08 -0500 Subject: [PATCH 09/10] Map extended config files by path --- src/compiler/tsbuildPublic.ts | 6 +++--- src/compiler/watchPublic.ts | 3 ++- src/compiler/watchUtilities.ts | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index be449a4eb840c..e1a8b1b4c3f98 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -468,7 +468,7 @@ namespace ts { { onDeleteValue: closeFileWatcher } ); - state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => { + state.allWatchedExtendedConfigFiles.forEach((watcher) => { watcher.projects.forEach((project) => { if (!currentProjects.has(project)) { watcher.projects.delete(project); @@ -1807,8 +1807,8 @@ namespace ts { function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { const extendedConfigs = arrayToMap( - parsed?.options.configFile?.extendedSourceFiles || emptyArray as readonly ResolvedConfigFileName[], - extendedSourceFile => toResolvedConfigFilePath(state, extendedConfigFileName) + (parsed?.options.configFile?.extendedSourceFiles || emptyArray) as ResolvedConfigFileName[], + extendedSourceFile => toResolvedConfigFilePath(state, extendedSourceFile) ); extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { const watcher = state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath); diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 4d514307826e3..8b84bdbdbb55f 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -793,11 +793,12 @@ namespace ts { updateExtendedConfigFilesMap( compilerOptions.configFile, extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), + toPath, watchExtendedConfigFile ); } - function watchExtendedConfigFile(extendedConfigFile: string) { + 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 687cc2640561a..e26d0d2fc7254 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -263,11 +263,12 @@ namespace ts { export function updateExtendedConfigFilesMap( configFile: TsConfigSourceFile | undefined, extendedConfigFilesMap: ESMap, - createExtendedConfigFileWatch: (extendedConfigPath: string) => FileWatcher, + toPath: (file: string) => Path, + createExtendedConfigFileWatch: (extendedConfigPath: Path) => FileWatcher, ) { const extendedSourceFiles = configFile?.extendedSourceFiles ?? emptyArray; // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` - const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); + const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, toPath, returnTrue); // Update the extended config files watcher mutateMap( extendedConfigFilesMap, From 7d25e430f42b8e3ccf63eff35e768c90332ca812 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 10 Dec 2020 14:09:18 -0800 Subject: [PATCH 10/10] Move shared watcher into utilities and add more tests --- src/compiler/tsbuildPublic.ts | 72 +-- src/compiler/watchPublic.ts | 15 +- src/compiler/watchUtilities.ts | 56 +- src/server/editorServices.ts | 84 +-- src/server/project.ts | 1 + src/testRunner/unittests/tsbuild/watchMode.ts | 163 +++++ .../unittests/tscWatch/programUpdates.ts | 161 +---- .../unittests/tsserver/configuredProjects.ts | 264 ++++----- .../tsserver/events/projectLoading.ts | 2 +- ...project-with-extended-config-is-removed.js | 21 +- .../works-with-extended-source-files.js | 557 ++++++++++++++++++ ...s => extended-source-files-are-watched.js} | 116 +++- ...ile-is-changed-to-extend-another-config.js | 129 ---- ...nged-to-no-longer-extend-another-config.js | 131 ---- 14 files changed, 1074 insertions(+), 698 deletions(-) create mode 100644 tests/baselines/reference/tsbuild/watchMode/programUpdates/works-with-extended-source-files.js rename tests/baselines/reference/tscWatch/programUpdates/{works-correctly-when-extended-config-files-are-changed.js => extended-source-files-are-watched.js} (64%) delete mode 100644 tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js delete mode 100644 tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index e1a8b1b4c3f98..0f019b3f9c6d3 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -210,10 +210,6 @@ namespace ts { originalGetSourceFile: CompilerHost["getSourceFile"]; } - interface SharedExtendedConfigFileWatcher extends FileWatcher { - projects: Set; - } - interface SolutionBuilderState extends WatchFactory { readonly host: SolutionBuilderHost; readonly hostWithWatch: SolutionBuilderWithWatchHost; @@ -258,7 +254,7 @@ namespace ts { readonly allWatchedWildcardDirectories: ESMap>; readonly allWatchedInputFiles: ESMap>; readonly allWatchedConfigFiles: ESMap; - readonly allWatchedExtendedConfigFiles: ESMap; + readonly allWatchedExtendedConfigFiles: ESMap>; timerToBuildInvalidatedProject: any; reportFileChangeDetected: boolean; @@ -468,8 +464,8 @@ namespace ts { { onDeleteValue: closeFileWatcher } ); - state.allWatchedExtendedConfigFiles.forEach((watcher) => { - watcher.projects.forEach((project) => { + state.allWatchedExtendedConfigFiles.forEach(watcher => { + watcher.projects.forEach(project => { if (!currentProjects.has(project)) { watcher.projects.delete(project); } @@ -1806,50 +1802,21 @@ namespace ts { } function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { - const extendedConfigs = arrayToMap( - (parsed?.options.configFile?.extendedSourceFiles || emptyArray) as ResolvedConfigFileName[], - extendedSourceFile => toResolvedConfigFilePath(state, extendedSourceFile) + 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), ); - extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { - const watcher = state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath); - if (watcher) { - watcher.projects.add(resolvedPath); - } - else { - // start watching previously unseen extended config - const projects = new Set([resolvedPath]); - let fileWatcher = state.watchFile( - extendedConfigFileName, - () => { - projects.forEach((projectConfigFilePath) => { - invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full); - }); - }, - PollingInterval.High, - parsed?.watchOptions, - WatchType.ExtendedConfigFile, - extendedConfigFileName - ); - state.allWatchedExtendedConfigFiles.set(extendedConfigFilePath, { - close: () => { - if (projects.size === 0) { - Debug.assert(fileWatcher !== undefined); - fileWatcher.close(); - fileWatcher = undefined!; - state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath); - } - }, - projects, - }); - } - }); - // remove project from all unrelated watchers - state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => { - if (!extendedConfigs.has(extendedConfigFilePath)) { - watcher.projects.delete(resolvedPath); - watcher.close(); - } - }); } function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { @@ -1922,7 +1889,10 @@ namespace ts { function stopWatching(state: SolutionBuilderState) { clearMap(state.allWatchedConfigFiles, closeFileWatcher); - clearMap(state.allWatchedExtendedConfigFiles, 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/watchPublic.ts b/src/compiler/watchPublic.ts index 8b84bdbdbb55f..25bb26332d48e 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -790,11 +790,16 @@ namespace ts { } function watchExtendedConfigFiles() { - updateExtendedConfigFilesMap( - compilerOptions.configFile, - extendedConfigFilesMap || (extendedConfigFilesMap = new Map()), - toPath, - watchExtendedConfigFile + // 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 + } ); } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index e26d0d2fc7254..80fd5fe7a8f98 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -257,29 +257,49 @@ namespace ts { Full } + export interface SharedExtendedConfigFileWatcher extends FileWatcher { + fileWatcher: FileWatcher; + projects: Set; + } + /** - * Updates the map of extended config file watches with a new set of extended config files from a base config file + * 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 updateExtendedConfigFilesMap( - configFile: TsConfigSourceFile | undefined, - extendedConfigFilesMap: ESMap, - toPath: (file: string) => Path, - createExtendedConfigFileWatch: (extendedConfigPath: Path) => FileWatcher, + export function updateSharedExtendedConfigFileWatcher( + projectPath: T, + parsed: ParsedCommandLine | undefined, + extendedConfigFilesMap: ESMap>, + createExtendedConfigFileWatch: (extendedConfigPath: string, extendedConfigFilePath: Path) => FileWatcher, + toPath: (fileName: string) => Path, ) { - const extendedSourceFiles = configFile?.extendedSourceFiles ?? emptyArray; - // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` - const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, toPath, returnTrue); + 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 - mutateMap( - extendedConfigFilesMap, - newExtendedConfigFilesMap, - { - // Watch the extended config files - createNewValue: createExtendedConfigFileWatch, - // Config files that are no longer extended should no longer be watched. - onDeleteValue: closeFileWatcher + 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); + }, + }); + } + }); } /** diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 66d4bd423f473..5787a5f01a678 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -641,10 +641,6 @@ namespace ts.server { errors: Diagnostic[] | undefined; } - interface SharedExtendedConfigFileWatcher extends FileWatcher { - projects: Set; - } - export class ProjectService { /*@internal*/ @@ -761,7 +757,7 @@ namespace ts.server { readonly watchFactory: WatchFactory; /*@internal*/ - private readonly sharedExtendedConfigFileWatchers = new Map(); + private readonly sharedExtendedConfigFileWatchers = new Map>(); /*@internal*/ readonly packageJsonCache: PackageJsonCache; @@ -1358,61 +1354,40 @@ namespace ts.server { } /*@internal*/ - private updateSharedExtendedConfigFileMap(project: ConfiguredProject) { - const extendedConfigPaths: readonly Path[] = project.getCompilerOptions().configFile?.extendedSourceFiles - ?.map((file) => this.toPath(file)) ?? emptyArray; - for (const extendedConfigPath of extendedConfigPaths) { - const watcher = this.sharedExtendedConfigFileWatchers.get(extendedConfigPath); - if (watcher) { - watcher.projects.add(project); - } - else { - // start watching previously unseen extended config - const projects = new Set([project]); - const fileWatcherCallback = () => { - const reason = `Change in extended config file ${extendedConfigPath} detected`; - projects.forEach((project: ConfiguredProject) => { + 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.isInitialLoadPending()) return; + if (!project || project.isInitialLoadPending()) return; project.pendingReload = ConfigFileProgramReloadLevel.Full; - project.pendingReloadReason = reason; + project.pendingReloadReason = `Change in extended config file ${extendedConfigFileName} detected`; this.delayUpdateProjectGraph(project); + ensureProjectsForOpenFiles = true; }); - }; - const fileWatcher = this.watchFactory.watchFile( - extendedConfigPath, - fileWatcherCallback, - PollingInterval.High, - this.hostConfiguration.watchOptions, - WatchType.ExtendedConfigFile - ); - this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, { - close: () => fileWatcher.close(), - projects, - }); - } - } - // remove project from all unrelated watchers - this.sharedExtendedConfigFileWatchers.forEach((watcher, extendedConfigPath) => { - if (!extendedConfigPaths.includes(extendedConfigPath)) { - watcher.projects.delete(project); - if (watcher.projects.size === 0) { - watcher.close(); - this.sharedExtendedConfigFileWatchers.delete(extendedConfigPath); - } - } - }); + if (ensureProjectsForOpenFiles) this.delayEnsureProjectForOpenFiles(); + }, + PollingInterval.High, + this.hostConfiguration.watchOptions, + WatchType.ExtendedConfigFile + ), + fileName => this.toPath(fileName), + ); } /*@internal*/ - private removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) { - for (const [sharedConfigPath, watcher] of arrayFrom(this.sharedExtendedConfigFileWatchers.entries())) { - watcher.projects.delete(project); - if (watcher.projects.size === 0) { - watcher.close(); - this.sharedExtendedConfigFileWatchers.delete(sharedConfigPath); - } - } + removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) { + this.sharedExtendedConfigFileWatchers.forEach(watcher => { + watcher.projects.delete(project.canonicalConfigFilePath); + watcher.close(); + }); } /** @@ -1470,7 +1445,6 @@ namespace ts.server { this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); this.setConfigFileExistenceInfoByClosedConfiguredProject(project); - this.removeProjectFromSharedExtendedConfigFileMap(project as ConfiguredProject); break; case ProjectKind.Inferred: unorderedRemoveItem(this.inferredProjects, project); @@ -2206,7 +2180,7 @@ namespace ts.server { project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 - this.updateSharedExtendedConfigFileMap(project); + 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 3f41ef9de5050..6323e458227cb 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 a0442229ba817..a4167b844974f 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 0aac2e76a9df5..80a4d60cf700a 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1629,7 +1629,7 @@ import { x } from "../b";`), verifyTscWatch({ scenario, - subScenario: "works correctly when config file is changed to extend another config", + subScenario: "extended source files are watched", commandLineArgs: ["-w", "-p", configFilePath], sys: () => { const firstExtendedConfigFile: File = { @@ -1666,85 +1666,7 @@ import { x } from "../b";`), files: [commonFile1.path, commonFile2.path] })), timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "works correctly when config file is changed to no longer extend another config", - 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({ - extends: "./second.tsconfig.json", - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - }) - }; - return createWatchedSystem([ - libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile - ]); - }, - changes: [ - { - caption: "Change config to stop extending another config", - change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "works correctly when extended config files are changed", - 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({ - extends: "./second.tsconfig.json", - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - }) - }; - return createWatchedSystem([ - libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile - ]); - }, - changes: [ + }, { caption: "Change first extended config", change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ @@ -1763,84 +1685,15 @@ import { x } from "../b";`), } })), timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "works correctly when project with extended config is removed", - commandLineArgs: ["-b", "-w", configFilePath], - 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: configFilePath, - content: JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - { - path: "./project2.tsconfig.json", - }, - ], - files: [], - }) - }; - return createWatchedSystem([ - libFile, configFile, - alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile - ]); - }, - changes: [ + }, { - caption: "Remove project2 from base config", + caption: "Change config to stop extending another config", change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - ], - files: [], + 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 85437496f6277..eac227348c930 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1094,147 +1094,135 @@ foo();` assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); }); - it("should watch the extended configs of multiple projects", () => { - 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 - ]); - const projectService = createProjectService(host); - - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - checkNumberOfConfiguredProjects(projectService, 2); - const aProject = configuredProjectAt(projectService, 0); - const bProject = configuredProjectAt(projectService, 1); - checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); - checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - const aOptions1 = aProject.getCompilerOptions(); - const bOptions1 = bProject.getCompilerOptions(); - assert.isUndefined(aOptions1.strict); - assert.isUndefined(bOptions1.strict); - - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - - host.writeFile(alphaExtendedConfig.path, JSON.stringify({ - compilerOptions: { - strict: true - } - })); - host.checkTimeoutQueueLengthAndRun(2); - const aOptions2 = aProject.getCompilerOptions(); - const bOptions2 = bProject.getCompilerOptions(); - assert.isTrue(aOptions2.strict); - assert.isTrue(bOptions2.strict); - - host.writeFile(bravoExtendedConfig.path, JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - strict: false - } - })); - host.checkTimeoutQueueLengthAndRun(1); - const aOptions3 = aProject.getCompilerOptions(); - const bOptions3 = bProject.getCompilerOptions(); - assert.isTrue(aOptions3.strict); - assert.isFalse(bOptions3.strict); - }); + 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 stop watching the extended configs of closed projects", () => { - 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({ + 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", - 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 - ]); - const projectService = createProjectService(host); - - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - checkNumberOfConfiguredProjects(projectService, 2); - const aProject = configuredProjectAt(projectService, 0); - const bProject = configuredProjectAt(projectService, 1); - checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); - checkProjectActualFiles(bProject, [bFile.path, bConfig.path, alphaExtendedConfig.path, bravoExtendedConfig.path]); - - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - - projectService.closeClientFile(bFile.path); - host.deleteFile(bConfig.path); - - assert.isTrue(bProject.isClosed()); - checkNumberOfConfiguredProjects(projectService, 1); - checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, bFile.path]); - - projectService.closeClientFile(aFile.path); - host.deleteFile(aConfig.path); + })); + 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]); + }); - assert.isTrue(aProject.isClosed()); - checkNumberOfConfiguredProjects(projectService, 0); - checkWatchedFiles(host, [aFile.path, bFile.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]); + }); }); }); diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 7f2d429e3d33b..76015c3fec503 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -87,7 +87,7 @@ namespace ts.projectSystem { verifyEventWithOpenTs(bTs, configB.path, 1); host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(1); + host.checkTimeoutQueueLengthAndRun(2); const project = service.configuredProjects.get(configB.path)!; verifyEvent(project, `Change in extended config file ${configA.path} detected`); }); 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 index 5efada1805b32..eafc6e973674c 100644 --- 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 @@ -37,12 +37,25 @@ let y = 1 let z = 0; -/a/lib/tsc.js -b -w /a/b/tsconfig.json +/a/lib/tsc.js -b -w -v Output:: >> Screen clear [12:00:27 AM] Starting compilation in watch mode... -[12:00:44 AM] Found 0 errors. Watching for file changes. +[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. @@ -194,9 +207,9 @@ Input:: Output:: >> Screen clear -[12:00:47 AM] File change detected. Starting incremental compilation... +[12:00:52 AM] File change detected. Starting incremental compilation... -[12:00:48 AM] Found 0 errors. Watching for file changes. +[12:00:53 AM] Found 0 errors. Watching for file changes. 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 0000000000000..0c4c2339200ec --- /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/works-correctly-when-extended-config-files-are-changed.js b/tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js similarity index 64% rename from tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js rename to tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js index 76a19d085cdea..892ea32f15b67 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-extended-config-files-are-changed.js +++ b/tests/baselines/reference/tscWatch/programUpdates/extended-source-files-are-watched.js @@ -19,7 +19,7 @@ let x = 1 let y = 1 //// [/a/b/tsconfig.json] -{"extends":"./second.tsconfig.json","compilerOptions":{},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} +{"compilerOptions":{},"files":["/a/b/commonFile1.ts","/a/b/commonFile2.ts"]} //// [/a/b/first.tsconfig.json] {"compilerOptions":{"strict":true}} @@ -38,7 +38,7 @@ Output:: 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 options: {"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts @@ -59,10 +59,6 @@ WatchedFiles:: {"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:: @@ -73,16 +69,65 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined //// [/a/b/commonFile1.js] -"use strict"; var x = 1; //// [/a/b/commonFile2.js] -"use strict"; 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:: @@ -92,9 +137,9 @@ Input:: Output:: >> Screen clear -[12:00:29 AM] File change detected. Starting incremental compilation... +[12:00:33 AM] File change detected. Starting incremental compilation... -[12:00:30 AM] Found 0 errors. Watching for file changes. +[12:00:34 AM] Found 0 errors. Watching for file changes. @@ -143,9 +188,9 @@ Input:: Output:: >> Screen clear -[12:00:33 AM] File change detected. Starting incremental compilation... +[12:00:37 AM] File change detected. Starting incremental compilation... -[12:00:34 AM] Found 0 errors. Watching for file changes. +[12:00:38 AM] Found 0 errors. Watching for file changes. @@ -184,3 +229,50 @@ FsWatchesRecursive:: 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 + diff --git a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js deleted file mode 100644 index 579194112fb44..0000000000000 --- a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-extend-another-config.js +++ /dev/null @@ -1,129 +0,0 @@ -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 - diff --git a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js b/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js deleted file mode 100644 index f3efefeec59d7..0000000000000 --- a/tests/baselines/reference/tscWatch/programUpdates/works-correctly-when-config-file-is-changed-to-no-longer-extend-another-config.js +++ /dev/null @@ -1,131 +0,0 @@ -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] -{"extends":"./second.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: {"strict":true,"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} -/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 - -//// [/a/b/commonFile1.js] -"use strict"; -var x = 1; - - -//// [/a/b/commonFile2.js] -"use strict"; -var y = 1; - - - -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: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: {"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 -