Skip to content

Commit a09b001

Browse files
committed
Add env to allow switch to non-polling if really wants to
1 parent 576e5f8 commit a09b001

File tree

3 files changed

+114
-11
lines changed

3 files changed

+114
-11
lines changed

src/compiler/sys.ts

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
namespace ts {
44
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
55
export type DirectoryWatcherCallback = (directoryName: string) => void;
6+
export interface WatchedFile {
7+
fileName: string;
8+
callback: FileWatcherCallback;
9+
mtime?: Date;
10+
}
611

712
export interface System {
813
args: string[];
@@ -224,6 +229,91 @@ namespace ts {
224229
const _os = require("os");
225230
const _crypto = require("crypto");
226231

232+
const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"];
233+
234+
function createWatchedFileSet() {
235+
const dirWatchers: Map<DirectoryWatcher> = {};
236+
// One file can have multiple watchers
237+
const fileWatcherCallbacks: Map<FileWatcherCallback[]> = {};
238+
return { addFile, removeFile };
239+
240+
function reduceDirWatcherRefCountForFile(fileName: string) {
241+
const dirName = getDirectoryPath(fileName);
242+
if (hasProperty(dirWatchers, dirName)) {
243+
const watcher = dirWatchers[dirName];
244+
watcher.referenceCount -= 1;
245+
if (watcher.referenceCount <= 0) {
246+
watcher.close();
247+
delete dirWatchers[dirName];
248+
}
249+
}
250+
}
251+
252+
function addDirWatcher(dirPath: string): void {
253+
if (hasProperty(dirWatchers, dirPath)) {
254+
const watcher = dirWatchers[dirPath];
255+
watcher.referenceCount += 1;
256+
return;
257+
}
258+
259+
const watcher: DirectoryWatcher = _fs.watch(
260+
dirPath,
261+
{ persistent: true },
262+
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
263+
);
264+
watcher.referenceCount = 1;
265+
dirWatchers[dirPath] = watcher;
266+
return;
267+
}
268+
269+
function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void {
270+
if (hasProperty(fileWatcherCallbacks, filePath)) {
271+
fileWatcherCallbacks[filePath].push(callback);
272+
}
273+
else {
274+
fileWatcherCallbacks[filePath] = [callback];
275+
}
276+
}
277+
278+
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
279+
addFileWatcherCallback(fileName, callback);
280+
addDirWatcher(getDirectoryPath(fileName));
281+
282+
return { fileName, callback };
283+
}
284+
285+
function removeFile(watchedFile: WatchedFile) {
286+
removeFileWatcherCallback(watchedFile.fileName, watchedFile.callback);
287+
reduceDirWatcherRefCountForFile(watchedFile.fileName);
288+
}
289+
290+
function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) {
291+
if (hasProperty(fileWatcherCallbacks, filePath)) {
292+
const newCallbacks = copyListRemovingItem(callback, fileWatcherCallbacks[filePath]);
293+
if (newCallbacks.length === 0) {
294+
delete fileWatcherCallbacks[filePath];
295+
}
296+
else {
297+
fileWatcherCallbacks[filePath] = newCallbacks;
298+
}
299+
}
300+
}
301+
302+
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
303+
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
304+
const fileName = typeof relativeFileName !== "string"
305+
? undefined
306+
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
307+
// Some applications save a working file via rename operations
308+
if ((eventName === "change" || eventName === "rename") && hasProperty(fileWatcherCallbacks, fileName)) {
309+
for (const fileCallback of fileWatcherCallbacks[fileName]) {
310+
fileCallback(fileName);
311+
}
312+
}
313+
}
314+
}
315+
const watchedFileSet = createWatchedFileSet();
316+
227317
function isNode4OrLater(): boolean {
228318
return parseInt(process.version.charAt(1)) >= 4;
229319
}
@@ -319,7 +409,7 @@ namespace ts {
319409
const files = _fs.readdirSync(path || ".").sort();
320410
const directories: string[] = [];
321411
for (const current of files) {
322-
// This is necessary because on some file system node fails to exclude
412+
// This is necessary because on some file system node fails to exclude
323413
// "." and "..". See https://github.com/nodejs/node/issues/4002
324414
if (current === "." || current === "..") {
325415
continue;
@@ -353,7 +443,26 @@ namespace ts {
353443
readFile,
354444
writeFile,
355445
watchFile: (fileName, callback) => {
356-
return _fs.watchFile(fileName, callback);
446+
if (useNonPollingWatchers) {
447+
const watchedFile = watchedFileSet.addFile(fileName, callback);
448+
return {
449+
close: () => watchedFileSet.removeFile(watchedFile)
450+
};
451+
}
452+
else {
453+
_fs.watchFile(fileName, { persistent: true, interval: 250 }, fileChanged);
454+
return {
455+
close: () => _fs.unwatchFile(fileName, fileChanged)
456+
};
457+
}
458+
459+
function fileChanged(curr: any, prev: any) {
460+
if (+curr.mtime <= +prev.mtime) {
461+
return;
462+
}
463+
464+
callback(fileName);
465+
}
357466
},
358467
watchDirectory: (directoryName, callback, recursive) => {
359468
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows

src/compiler/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,7 @@ namespace ts {
21042104
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
21052105
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
21062106
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
2107-
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
2107+
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
21082108
}
21092109

21102110
export const enum TypeFlags {
@@ -2484,7 +2484,7 @@ namespace ts {
24842484
// When options come from a config file, its path is recorded here
24852485
configFilePath?: string;
24862486
/* @internal */
2487-
// Path used to used to compute primary search locations
2487+
// Path used to used to compute primary search locations
24882488
typesRoot?: string;
24892489
types?: string[];
24902490

@@ -2801,7 +2801,7 @@ namespace ts {
28012801
*/
28022802
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
28032803
/**
2804-
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
2804+
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
28052805
*/
28062806
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
28072807
}

src/server/server.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,6 @@ namespace ts.server {
111111
detailLevel?: string;
112112
}
113113

114-
interface WatchedFile {
115-
fileName: string;
116-
callback: FileWatcherCallback;
117-
mtime?: Date;
118-
}
119-
120114
function parseLoggingEnvironmentString(logEnvStr: string): LogOptions {
121115
const logEnv: LogOptions = {};
122116
const args = logEnvStr.split(" ");

0 commit comments

Comments
 (0)