Skip to content

Commit e40730f

Browse files
authored
Handle fsWatch event with accesstime change on mac os (#56403)
1 parent d1d14e6 commit e40730f

File tree

5 files changed

+398
-3
lines changed

5 files changed

+398
-3
lines changed

src/compiler/sys.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,7 @@ export interface CreateSystemWatchFunctions {
914914
useNonPollingWatchers?: boolean;
915915
tscWatchDirectory: string | undefined;
916916
inodeWatching: boolean;
917+
fsWatchWithTimestamp: boolean | undefined;
917918
sysLog: (s: string) => void;
918919
}
919920

@@ -934,6 +935,7 @@ export function createSystemWatchFunctions({
934935
useNonPollingWatchers,
935936
tscWatchDirectory,
936937
inodeWatching,
938+
fsWatchWithTimestamp,
937939
sysLog,
938940
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
939941
const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>();
@@ -1189,7 +1191,7 @@ export function createSystemWatchFunctions({
11891191
return watchPresentFileSystemEntryWithFsWatchFile();
11901192
}
11911193
try {
1192-
const presentWatcher = fsWatchWorker(
1194+
const presentWatcher = (!fsWatchWithTimestamp ? fsWatchWorker : fsWatchWorkerHandlingTimestamp)(
11931195
fileOrDirectory,
11941196
recursive,
11951197
inodeWatching ?
@@ -1286,6 +1288,18 @@ export function createSystemWatchFunctions({
12861288
);
12871289
}
12881290
}
1291+
1292+
function fsWatchWorkerHandlingTimestamp(fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback): FsWatchWorkerWatcher {
1293+
let modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
1294+
return fsWatchWorker(fileOrDirectory, recursive, (eventName, relativeFileName, currentModifiedTime) => {
1295+
if (eventName === "change") {
1296+
currentModifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
1297+
if (currentModifiedTime.getTime() === modifiedTime.getTime()) return;
1298+
}
1299+
modifiedTime = currentModifiedTime || getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
1300+
callback(eventName, relativeFileName, modifiedTime);
1301+
});
1302+
}
12891303
}
12901304

12911305
/**
@@ -1482,7 +1496,8 @@ export let sys: System = (() => {
14821496
from?(input: string, encoding?: string): any;
14831497
} = require("buffer").Buffer;
14841498

1485-
const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin";
1499+
const isMacOs = process.platform === "darwin";
1500+
const isLinuxOrMacOs = process.platform === "linux" || isMacOs;
14861501

14871502
const platform: string = _os.platform();
14881503
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
@@ -1495,7 +1510,7 @@ export let sys: System = (() => {
14951510
// Note that if we ever emit as files like cjs/mjs, this check will be wrong.
14961511
const executingFilePath = __filename.endsWith("sys.js") ? _path.join(_path.dirname(__dirname), "__fake__.js") : __filename;
14971512

1498-
const fsSupportsRecursiveFsWatch = process.platform === "win32" || process.platform === "darwin";
1513+
const fsSupportsRecursiveFsWatch = process.platform === "win32" || isMacOs;
14991514
const getCurrentDirectory = memoize(() => process.cwd());
15001515
const { watchFile, watchDirectory } = createSystemWatchFunctions({
15011516
pollingWatchFileWorker: fsWatchFileWorker,
@@ -1515,6 +1530,7 @@ export let sys: System = (() => {
15151530
useNonPollingWatchers: !!process.env.TSC_NONPOLLING_WATCHER,
15161531
tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
15171532
inodeWatching: isLinuxOrMacOs,
1533+
fsWatchWithTimestamp: isMacOs,
15181534
sysLog,
15191535
});
15201536
const nodeSystem: System = {

src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export interface TestServerHostCreationParameters {
7979
runWithoutRecursiveWatches?: boolean;
8080
runWithFallbackPolling?: boolean;
8181
inodeWatching?: boolean;
82+
fsWatchWithTimestamp?: boolean;
8283
}
8384

8485
export function createWatchedSystem(fileOrFolderList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
@@ -377,6 +378,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
377378
runWithoutRecursiveWatches,
378379
runWithFallbackPolling,
379380
inodeWatching,
381+
fsWatchWithTimestamp,
380382
}: TestServerHostCreationParameters = {},
381383
) {
382384
this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames;
@@ -414,6 +416,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
414416
tscWatchFile,
415417
tscWatchDirectory,
416418
inodeWatching: !!this.inodeWatching,
419+
fsWatchWithTimestamp,
417420
sysLog: s => this.write(s + this.newLine),
418421
});
419422
this.watchFile = watchFile;

src/testRunner/unittests/tscWatch/watchEnvironment.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,42 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
689689
});
690690
});
691691

692+
describe("with fsWatch with fsWatchWithTimestamp", () => {
693+
function verify(fsWatchWithTimestamp: boolean) {
694+
verifyTscWatch({
695+
scenario,
696+
subScenario: `fsWatch/fsWatchWithTimestamp ${fsWatchWithTimestamp}`,
697+
commandLineArgs: ["-w", "--extendedDiagnostics"],
698+
sys: () =>
699+
createWatchedSystem(
700+
{
701+
[libFile.path]: libFile.content,
702+
"/user/username/projects/myproject/main.ts": `export const x = 10;`,
703+
"/user/username/projects/myproject/tsconfig.json": jsonToReadableText({ files: ["main.ts"] }),
704+
},
705+
{
706+
currentDirectory: "/user/username/projects/myproject",
707+
fsWatchWithTimestamp,
708+
},
709+
),
710+
edits: [
711+
{
712+
caption: "emulate access",
713+
edit: sys => sys.invokeFsWatches("/user/username/projects/myproject/main.ts", "change", /*modifiedTime*/ undefined, /*useTildeSuffix*/ undefined),
714+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
715+
},
716+
{
717+
caption: "modify file contents",
718+
edit: sys => sys.appendFile("/user/username/projects/myproject/main.ts", "export const y = 10;"),
719+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
720+
},
721+
],
722+
});
723+
}
724+
verify(/*fsWatchWithTimestamp*/ true);
725+
verify(/*fsWatchWithTimestamp*/ false);
726+
});
727+
692728
verifyTscWatch({
693729
scenario,
694730
subScenario: "fsEvent for change is repeated",
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
currentDirectory:: /user/username/projects/myproject useCaseSensitiveFileNames: false
2+
Input::
3+
//// [/a/lib/lib.d.ts]
4+
/// <reference no-default-lib="true"/>
5+
interface Boolean {}
6+
interface Function {}
7+
interface CallableFunction {}
8+
interface NewableFunction {}
9+
interface IArguments {}
10+
interface Number { toExponential: any; }
11+
interface Object {}
12+
interface RegExp {}
13+
interface String { charAt: any; }
14+
interface Array<T> { length: number; [n: number]: T; }
15+
16+
//// [/user/username/projects/myproject/main.ts]
17+
export const x = 10;
18+
19+
//// [/user/username/projects/myproject/tsconfig.json]
20+
{
21+
"files": [
22+
"main.ts"
23+
]
24+
}
25+
26+
27+
/a/lib/tsc.js -w --extendedDiagnostics
28+
Output::
29+
[12:00:21 AM] Starting compilation in watch mode...
30+
31+
Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false
32+
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 undefined Config file
33+
Synchronizing program
34+
CreatingProgramWith::
35+
roots: ["/user/username/projects/myproject/main.ts"]
36+
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
37+
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
38+
FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 undefined Source file
39+
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Type roots
40+
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Type roots
41+
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Type roots
42+
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Type roots
43+
[12:00:24 AM] Found 0 errors. Watching for file changes.
44+
45+
46+
47+
//// [/user/username/projects/myproject/main.js]
48+
"use strict";
49+
Object.defineProperty(exports, "__esModule", { value: true });
50+
exports.x = void 0;
51+
exports.x = 10;
52+
53+
54+
55+
PolledWatches::
56+
/user/username/projects/myproject/node_modules/@types: *new*
57+
{"pollingInterval":500}
58+
/user/username/projects/node_modules/@types: *new*
59+
{"pollingInterval":500}
60+
61+
FsWatches::
62+
/a/lib/lib.d.ts: *new*
63+
{}
64+
/user/username/projects/myproject/main.ts: *new*
65+
{}
66+
/user/username/projects/myproject/tsconfig.json: *new*
67+
{}
68+
69+
Program root files: [
70+
"/user/username/projects/myproject/main.ts"
71+
]
72+
Program options: {
73+
"watch": true,
74+
"extendedDiagnostics": true,
75+
"configFilePath": "/user/username/projects/myproject/tsconfig.json"
76+
}
77+
Program structureReused: Not
78+
Program files::
79+
/a/lib/lib.d.ts
80+
/user/username/projects/myproject/main.ts
81+
82+
Semantic diagnostics in builder refreshed for::
83+
/a/lib/lib.d.ts
84+
/user/username/projects/myproject/main.ts
85+
86+
Shape signatures in builder refreshed for::
87+
/a/lib/lib.d.ts (used version)
88+
/user/username/projects/myproject/main.ts (used version)
89+
90+
exitCode:: ExitStatus.undefined
91+
92+
Change:: emulate access
93+
94+
Input::
95+
96+
Output::
97+
FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
98+
Scheduling update
99+
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
100+
101+
102+
Timeout callback:: count: 1
103+
1: timerToUpdateProgram *new*
104+
105+
Before running Timeout callback:: count: 1
106+
1: timerToUpdateProgram
107+
108+
After running Timeout callback:: count: 0
109+
Output::
110+
Synchronizing program
111+
112+
113+
114+
115+
exitCode:: ExitStatus.undefined
116+
117+
Change:: modify file contents
118+
119+
Input::
120+
//// [/user/username/projects/myproject/main.ts]
121+
export const x = 10;export const y = 10;
122+
123+
124+
Output::
125+
FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
126+
Scheduling update
127+
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
128+
129+
130+
Timeout callback:: count: 1
131+
2: timerToUpdateProgram *new*
132+
133+
Before running Timeout callback:: count: 1
134+
2: timerToUpdateProgram
135+
136+
After running Timeout callback:: count: 0
137+
Output::
138+
Synchronizing program
139+
[12:00:27 AM] File change detected. Starting incremental compilation...
140+
141+
CreatingProgramWith::
142+
roots: ["/user/username/projects/myproject/main.ts"]
143+
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
144+
[12:00:31 AM] Found 0 errors. Watching for file changes.
145+
146+
147+
148+
//// [/user/username/projects/myproject/main.js]
149+
"use strict";
150+
Object.defineProperty(exports, "__esModule", { value: true });
151+
exports.y = exports.x = void 0;
152+
exports.x = 10;
153+
exports.y = 10;
154+
155+
156+
157+
158+
Program root files: [
159+
"/user/username/projects/myproject/main.ts"
160+
]
161+
Program options: {
162+
"watch": true,
163+
"extendedDiagnostics": true,
164+
"configFilePath": "/user/username/projects/myproject/tsconfig.json"
165+
}
166+
Program structureReused: Completely
167+
Program files::
168+
/a/lib/lib.d.ts
169+
/user/username/projects/myproject/main.ts
170+
171+
Semantic diagnostics in builder refreshed for::
172+
/user/username/projects/myproject/main.ts
173+
174+
Shape signatures in builder refreshed for::
175+
/user/username/projects/myproject/main.ts (computed .d.ts)
176+
177+
exitCode:: ExitStatus.undefined

0 commit comments

Comments
 (0)