Skip to content

Commit 98bcd9d

Browse files
committed
timestamp watching for fsWatch is only for files.
Directories generate change event in multiple scenarios and should not filter events based on modified time Fixes #57792
1 parent 7781e29 commit 98bcd9d

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

src/compiler/sys.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -378,16 +378,24 @@ function createDynamicPriorityPollingWatchFile(host: {
378378
}
379379
}
380380

381-
function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile {
381+
function createUseFsEventsOnParentDirectoryWatchFile(
382+
fsWatch: FsWatch,
383+
useCaseSensitiveFileNames: boolean,
384+
getModifiedTime: NonNullable<System["getModifiedTime"]>,
385+
fsWatchWithTimestamp: boolean | undefined,
386+
): HostWatchFile {
382387
// One file can have multiple watchers
383388
const fileWatcherCallbacks = createMultiMap<string, FileWatcherCallback>();
389+
const fileTimestamps = fsWatchWithTimestamp ? new Map<string, Date>() : undefined;
384390
const dirWatchers = new Map<string, DirectoryWatcher>();
385391
const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames);
386392
return nonPollingWatchFile;
387393

388394
function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher {
389395
const filePath = toCanonicalName(fileName);
390-
fileWatcherCallbacks.add(filePath, callback);
396+
if (fileWatcherCallbacks.add(filePath, callback).length === 1 && fileTimestamps) {
397+
fileTimestamps.set(filePath, getModifiedTime(fileName) || missingFileModifiedTime);
398+
}
391399
const dirPath = getDirectoryPath(filePath) || ".";
392400
const watcher = dirWatchers.get(dirPath) ||
393401
createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions);
@@ -410,15 +418,29 @@ function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSe
410418
const watcher = fsWatch(
411419
dirName,
412420
FileSystemEntryKind.Directory,
413-
(_eventName: string, relativeFileName, modifiedTime) => {
421+
(eventName: string, relativeFileName) => {
414422
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
415423
if (!isString(relativeFileName)) return;
416424
const fileName = getNormalizedAbsolutePath(relativeFileName, dirName);
425+
const filePath = toCanonicalName(fileName);
417426
// Some applications save a working file via rename operations
418-
const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName));
427+
const callbacks = fileName && fileWatcherCallbacks.get(filePath);
419428
if (callbacks) {
429+
let currentModifiedTime;
430+
let eventKind = FileWatcherEventKind.Changed;
431+
if (fileTimestamps) {
432+
const existingTime = fileTimestamps.get(filePath)!;
433+
if (eventName === "change") {
434+
currentModifiedTime = getModifiedTime(fileName) || missingFileModifiedTime;
435+
if (currentModifiedTime.getTime() === existingTime.getTime()) return;
436+
}
437+
currentModifiedTime ||= getModifiedTime(fileName) || missingFileModifiedTime;
438+
fileTimestamps.set(filePath, currentModifiedTime);
439+
if (existingTime === missingFileModifiedTime) eventKind = FileWatcherEventKind.Created;
440+
else if (currentModifiedTime === missingFileModifiedTime) eventKind = FileWatcherEventKind.Deleted;
441+
}
420442
for (const fileCallback of callbacks) {
421-
fileCallback(fileName, FileWatcherEventKind.Changed, modifiedTime);
443+
fileCallback(fileName, eventKind, currentModifiedTime);
422444
}
423445
}
424446
},
@@ -974,7 +996,7 @@ export function createSystemWatchFunctions({
974996
);
975997
case WatchFileKind.UseFsEventsOnParentDirectory:
976998
if (!nonPollingWatchFile) {
977-
nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames);
999+
nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames, getModifiedTime, fsWatchWithTimestamp);
9781000
}
9791001
return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options));
9801002
default:
@@ -1191,7 +1213,7 @@ export function createSystemWatchFunctions({
11911213
return watchPresentFileSystemEntryWithFsWatchFile();
11921214
}
11931215
try {
1194-
const presentWatcher = (!fsWatchWithTimestamp ? fsWatchWorker : fsWatchWorkerHandlingTimestamp)(
1216+
const presentWatcher = (entryKind === FileSystemEntryKind.Directory || !fsWatchWithTimestamp ? fsWatchWorker : fsWatchWorkerHandlingTimestamp)(
11951217
fileOrDirectory,
11961218
recursive,
11971219
inodeWatching ?

tests/baselines/reference/tscWatch/watchEnvironment/fsWatch/fsWatchWithTimestamp-true-useFsEventsOnParentDirectory.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,57 @@ Input::
105105
export const x = 10;export const y = 10;
106106

107107

108-
Before running Timeout callback:: count: 0
108+
Output::
109+
FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 {"watchFile":5} Source file
110+
Scheduling update
111+
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 {"watchFile":5} Source file
112+
113+
114+
Timeout callback:: count: 1
115+
1: timerToUpdateProgram *new*
116+
117+
Before running Timeout callback:: count: 1
118+
1: timerToUpdateProgram
109119

110120
After running Timeout callback:: count: 0
121+
Output::
122+
Synchronizing program
123+
[HH:MM:SS AM] File change detected. Starting incremental compilation...
124+
125+
CreatingProgramWith::
126+
roots: ["/user/username/projects/myproject/main.ts"]
127+
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
128+
[HH:MM:SS AM] Found 0 errors. Watching for file changes.
129+
130+
131+
132+
//// [/user/username/projects/myproject/main.js]
133+
"use strict";
134+
Object.defineProperty(exports, "__esModule", { value: true });
135+
exports.y = exports.x = void 0;
136+
exports.x = 10;
137+
exports.y = 10;
111138

112139

140+
141+
142+
Program root files: [
143+
"/user/username/projects/myproject/main.ts"
144+
]
145+
Program options: {
146+
"watch": true,
147+
"extendedDiagnostics": true,
148+
"configFilePath": "/user/username/projects/myproject/tsconfig.json"
149+
}
150+
Program structureReused: Completely
151+
Program files::
152+
/a/lib/lib.d.ts
153+
/user/username/projects/myproject/main.ts
154+
155+
Semantic diagnostics in builder refreshed for::
156+
/user/username/projects/myproject/main.ts
157+
158+
Shape signatures in builder refreshed for::
159+
/user/username/projects/myproject/main.ts (computed .d.ts)
160+
113161
exitCode:: ExitStatus.undefined

0 commit comments

Comments
 (0)