Skip to content

Commit fbceea2

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

File tree

3 files changed

+115
-11
lines changed

3 files changed

+115
-11
lines changed

src/compiler/sys.ts

Lines changed: 112 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[];
@@ -223,6 +228,92 @@ namespace ts {
223228
const _path = require("path");
224229
const _os = require("os");
225230
const _crypto = require("crypto");
231+
const _process = require("process");
232+
233+
const useNonPollingWatchers = _process.env["TSC_NONPOLLING_WATCHER"];
234+
235+
function createWatchedFileSet() {
236+
const dirWatchers: Map<DirectoryWatcher> = {};
237+
// One file can have multiple watchers
238+
const fileWatcherCallbacks: Map<FileWatcherCallback[]> = {};
239+
return { addFile, removeFile };
240+
241+
function reduceDirWatcherRefCountForFile(fileName: string) {
242+
const dirName = getDirectoryPath(fileName);
243+
if (hasProperty(dirWatchers, dirName)) {
244+
const watcher = dirWatchers[dirName];
245+
watcher.referenceCount -= 1;
246+
if (watcher.referenceCount <= 0) {
247+
watcher.close();
248+
delete dirWatchers[dirName];
249+
}
250+
}
251+
}
252+
253+
function addDirWatcher(dirPath: string): void {
254+
if (hasProperty(dirWatchers, dirPath)) {
255+
const watcher = dirWatchers[dirPath];
256+
watcher.referenceCount += 1;
257+
return;
258+
}
259+
260+
const watcher: DirectoryWatcher = _fs.watch(
261+
dirPath,
262+
{ persistent: true },
263+
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
264+
);
265+
watcher.referenceCount = 1;
266+
dirWatchers[dirPath] = watcher;
267+
return;
268+
}
269+
270+
function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void {
271+
if (hasProperty(fileWatcherCallbacks, filePath)) {
272+
fileWatcherCallbacks[filePath].push(callback);
273+
}
274+
else {
275+
fileWatcherCallbacks[filePath] = [callback];
276+
}
277+
}
278+
279+
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
280+
addFileWatcherCallback(fileName, callback);
281+
addDirWatcher(getDirectoryPath(fileName));
282+
283+
return { fileName, callback };
284+
}
285+
286+
function removeFile(watchedFile: WatchedFile) {
287+
removeFileWatcherCallback(watchedFile.fileName, watchedFile.callback);
288+
reduceDirWatcherRefCountForFile(watchedFile.fileName);
289+
}
290+
291+
function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) {
292+
if (hasProperty(fileWatcherCallbacks, filePath)) {
293+
const newCallbacks = copyListRemovingItem(callback, fileWatcherCallbacks[filePath]);
294+
if (newCallbacks.length === 0) {
295+
delete fileWatcherCallbacks[filePath];
296+
}
297+
else {
298+
fileWatcherCallbacks[filePath] = newCallbacks;
299+
}
300+
}
301+
}
302+
303+
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
304+
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
305+
const fileName = typeof relativeFileName !== "string"
306+
? undefined
307+
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
308+
// Some applications save a working file via rename operations
309+
if ((eventName === "change" || eventName === "rename") && hasProperty(fileWatcherCallbacks, fileName)) {
310+
for (const fileCallback of fileWatcherCallbacks[fileName]) {
311+
fileCallback(fileName);
312+
}
313+
}
314+
}
315+
}
316+
const watchedFileSet = createWatchedFileSet();
226317

227318
function isNode4OrLater(): boolean {
228319
return parseInt(process.version.charAt(1)) >= 4;
@@ -319,7 +410,7 @@ namespace ts {
319410
const files = _fs.readdirSync(path || ".").sort();
320411
const directories: string[] = [];
321412
for (const current of files) {
322-
// This is necessary because on some file system node fails to exclude
413+
// This is necessary because on some file system node fails to exclude
323414
// "." and "..". See https://github.com/nodejs/node/issues/4002
324415
if (current === "." || current === "..") {
325416
continue;
@@ -353,7 +444,26 @@ namespace ts {
353444
readFile,
354445
writeFile,
355446
watchFile: (fileName, callback) => {
356-
return _fs.watchFile(fileName, callback);
447+
if (useNonPollingWatchers) {
448+
const watchedFile = watchedFileSet.addFile(fileName, callback);
449+
return {
450+
close: () => watchedFileSet.removeFile(watchedFile)
451+
};
452+
}
453+
else {
454+
_fs.watchFile(fileName, { persistent: true, interval: 250 }, fileChanged);
455+
return {
456+
close: () => _fs.unwatchFile(fileName, fileChanged)
457+
};
458+
}
459+
460+
function fileChanged(curr: any, prev: any) {
461+
if (+curr.mtime <= +prev.mtime) {
462+
return;
463+
}
464+
465+
callback(fileName);
466+
}
357467
},
358468
watchDirectory: (directoryName, callback, recursive) => {
359469
// 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)