diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index d888a3d599bb3..be0d973032edf 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -914,6 +914,7 @@ export interface CreateSystemWatchFunctions { useNonPollingWatchers?: boolean; tscWatchDirectory: string | undefined; inodeWatching: boolean; + fsWatchWithTimestamp: boolean | undefined; sysLog: (s: string) => void; } @@ -934,6 +935,7 @@ export function createSystemWatchFunctions({ useNonPollingWatchers, tscWatchDirectory, inodeWatching, + fsWatchWithTimestamp, sysLog, }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { const pollingWatches = new Map>(); @@ -1189,7 +1191,7 @@ export function createSystemWatchFunctions({ return watchPresentFileSystemEntryWithFsWatchFile(); } try { - const presentWatcher = fsWatchWorker( + const presentWatcher = (!fsWatchWithTimestamp ? fsWatchWorker : fsWatchWorkerHandlingTimestamp)( fileOrDirectory, recursive, inodeWatching ? @@ -1286,6 +1288,18 @@ export function createSystemWatchFunctions({ ); } } + + function fsWatchWorkerHandlingTimestamp(fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback): FsWatchWorkerWatcher { + let modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + return fsWatchWorker(fileOrDirectory, recursive, (eventName, relativeFileName, currentModifiedTime) => { + if (eventName === "change") { + currentModifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + if (currentModifiedTime.getTime() === modifiedTime.getTime()) return; + } + modifiedTime = currentModifiedTime || getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + callback(eventName, relativeFileName, modifiedTime); + }); + } } /** @@ -1482,7 +1496,8 @@ export let sys: System = (() => { from?(input: string, encoding?: string): any; } = require("buffer").Buffer; - const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; + const isMacOs = process.platform === "darwin"; + const isLinuxOrMacOs = process.platform === "linux" || isMacOs; const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); @@ -1495,7 +1510,7 @@ export let sys: System = (() => { // Note that if we ever emit as files like cjs/mjs, this check will be wrong. const executingFilePath = __filename.endsWith("sys.js") ? _path.join(_path.dirname(__dirname), "__fake__.js") : __filename; - const fsSupportsRecursiveFsWatch = process.platform === "win32" || process.platform === "darwin"; + const fsSupportsRecursiveFsWatch = process.platform === "win32" || isMacOs; const getCurrentDirectory = memoize(() => process.cwd()); const { watchFile, watchDirectory } = createSystemWatchFunctions({ pollingWatchFileWorker: fsWatchFileWorker, @@ -1515,6 +1530,7 @@ export let sys: System = (() => { useNonPollingWatchers: !!process.env.TSC_NONPOLLING_WATCHER, tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, inodeWatching: isLinuxOrMacOs, + fsWatchWithTimestamp: isMacOs, sysLog, }); const nodeSystem: System = { diff --git a/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts b/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts index 493ec2cd2aff9..842a4ef3bfad1 100644 --- a/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts +++ b/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts @@ -79,6 +79,7 @@ export interface TestServerHostCreationParameters { runWithoutRecursiveWatches?: boolean; runWithFallbackPolling?: boolean; inodeWatching?: boolean; + fsWatchWithTimestamp?: boolean; } export function createWatchedSystem(fileOrFolderList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { @@ -377,6 +378,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, runWithoutRecursiveWatches, runWithFallbackPolling, inodeWatching, + fsWatchWithTimestamp, }: TestServerHostCreationParameters = {}, ) { this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames; @@ -414,6 +416,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, tscWatchFile, tscWatchDirectory, inodeWatching: !!this.inodeWatching, + fsWatchWithTimestamp, sysLog: s => this.write(s + this.newLine), }); this.watchFile = watchFile; diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index bb1e106d789d8..c8278c047b0f5 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -704,6 +704,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po }, { currentDirectory: "/user/username/projects/myproject", + fsWatchWithTimestamp, }, ), edits: [ diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/fsWatch/fsWatchWithTimestamp-true.js b/tests/baselines/reference/tscWatch/watchEnvironment/fsWatch/fsWatchWithTimestamp-true.js index e8cfa9431277a..0d41c11b03e81 100644 --- a/tests/baselines/reference/tscWatch/watchEnvironment/fsWatch/fsWatchWithTimestamp-true.js +++ b/tests/baselines/reference/tscWatch/watchEnvironment/fsWatch/fsWatchWithTimestamp-true.js @@ -93,23 +93,9 @@ Change:: emulate access Input:: -Output:: -FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file -Scheduling update -Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file - - -Timeout callback:: count: 1 -1: timerToUpdateProgram *new* - -Before running Timeout callback:: count: 1 -1: timerToUpdateProgram +Before running Timeout callback:: count: 0 After running Timeout callback:: count: 0 -Output:: -Synchronizing program - - exitCode:: ExitStatus.undefined @@ -128,10 +114,10 @@ Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/mai Timeout callback:: count: 1 -2: timerToUpdateProgram *new* +1: timerToUpdateProgram *new* Before running Timeout callback:: count: 1 -2: timerToUpdateProgram +1: timerToUpdateProgram After running Timeout callback:: count: 0 Output::