1
1
/// <reference path="core.ts"/>
2
2
3
3
namespace ts {
4
+ export type FileWatcherCallback = ( path : string , removed ?: boolean ) => void ;
5
+ export type DirectoryWatcherCallback = ( path : string ) => void ;
6
+
4
7
export interface System {
5
8
args : string [ ] ;
6
9
newLine : string ;
7
10
useCaseSensitiveFileNames : boolean ;
8
11
write ( s : string ) : void ;
9
12
readFile ( path : string , encoding ?: string ) : string ;
10
13
writeFile ( path : string , data : string , writeByteOrderMark ?: boolean ) : void ;
11
- watchFile ?( path : string , callback : ( path : string , removed ?: boolean ) => void ) : FileWatcher ;
12
- watchDirectory ?( path : string , callback : ( path : string ) => void , recursive ?: boolean ) : FileWatcher ;
14
+ watchFile ?( path : Path , callback : FileWatcherCallback ) : FileWatcher ;
15
+ watchDirectory ?( path : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) : FileWatcher ;
13
16
resolvePath ( path : string ) : string ;
14
17
fileExists ( path : string ) : boolean ;
15
18
directoryExists ( path : string ) : boolean ;
@@ -22,15 +25,20 @@ namespace ts {
22
25
}
23
26
24
27
interface WatchedFile {
25
- fileName : string ;
26
- callback : ( fileName : string , removed ?: boolean ) => void ;
27
- mtime : Date ;
28
+ filePath : Path ;
29
+ callback : FileWatcherCallback ;
30
+ mtime ? : Date ;
28
31
}
29
32
30
33
export interface FileWatcher {
31
34
close ( ) : void ;
32
35
}
33
36
37
+ export interface DirectoryWatcher extends FileWatcher {
38
+ directoryPath : Path ;
39
+ referenceCount : number ;
40
+ }
41
+
34
42
declare var require : any ;
35
43
declare var module : any ;
36
44
declare var process : any ;
@@ -62,8 +70,8 @@ namespace ts {
62
70
readFile ( path : string ) : string ;
63
71
writeFile ( path : string , contents : string ) : void ;
64
72
readDirectory ( path : string , extension ?: string , exclude ?: string [ ] ) : string [ ] ;
65
- watchFile ?( path : string , callback : ( path : string , removed ?: boolean ) => void ) : FileWatcher ;
66
- watchDirectory ?( path : string , callback : ( path : string ) => void , recursive ?: boolean ) : FileWatcher ;
73
+ watchFile ?( path : string , callback : FileWatcherCallback ) : FileWatcher ;
74
+ watchDirectory ?( path : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) : FileWatcher ;
67
75
} ;
68
76
69
77
export var sys : System = ( function ( ) {
@@ -221,7 +229,7 @@ namespace ts {
221
229
222
230
// average async stat takes about 30 microseconds
223
231
// set chunk size to do 30 files in < 1 millisecond
224
- function createWatchedFileSet ( interval = 2500 , chunkSize = 30 ) {
232
+ function createPollingWatchedFileSet ( interval = 2500 , chunkSize = 30 ) {
225
233
let watchedFiles : WatchedFile [ ] = [ ] ;
226
234
let nextFileToCheck = 0 ;
227
235
let watchTimer : any ;
@@ -236,13 +244,13 @@ namespace ts {
236
244
return ;
237
245
}
238
246
239
- _fs . stat ( watchedFile . fileName , ( err : any , stats : any ) => {
247
+ _fs . stat ( watchedFile . filePath , ( err : any , stats : any ) => {
240
248
if ( err ) {
241
- watchedFile . callback ( watchedFile . fileName ) ;
249
+ watchedFile . callback ( watchedFile . filePath ) ;
242
250
}
243
251
else if ( watchedFile . mtime . getTime ( ) !== stats . mtime . getTime ( ) ) {
244
- watchedFile . mtime = getModifiedTime ( watchedFile . fileName ) ;
245
- watchedFile . callback ( watchedFile . fileName , watchedFile . mtime . getTime ( ) === 0 ) ;
252
+ watchedFile . mtime = getModifiedTime ( watchedFile . filePath ) ;
253
+ watchedFile . callback ( watchedFile . filePath , watchedFile . mtime . getTime ( ) === 0 ) ;
246
254
}
247
255
} ) ;
248
256
}
@@ -270,11 +278,11 @@ namespace ts {
270
278
} , interval ) ;
271
279
}
272
280
273
- function addFile ( fileName : string , callback : ( fileName : string , removed ?: boolean ) => void ) : WatchedFile {
281
+ function addFile ( filePath : Path , callback : FileWatcherCallback ) : WatchedFile {
274
282
const file : WatchedFile = {
275
- fileName ,
283
+ filePath ,
276
284
callback,
277
- mtime : getModifiedTime ( fileName )
285
+ mtime : getModifiedTime ( filePath )
278
286
} ;
279
287
280
288
watchedFiles . push ( file ) ;
@@ -297,6 +305,88 @@ namespace ts {
297
305
} ;
298
306
}
299
307
308
+ function createWatchedFileSet ( ) {
309
+ const dirWatchers = createFileMap < DirectoryWatcher > ( ) ;
310
+ // One file can have multiple watchers
311
+ const fileWatcherCallbacks = createFileMap < FileWatcherCallback [ ] > ( ) ;
312
+ return { addFile, removeFile } ;
313
+
314
+ function reduceDirWatcherRefCountForFile ( filePath : Path ) {
315
+ const dirPath = getDirectoryPath ( filePath ) ;
316
+ if ( dirWatchers . contains ( dirPath ) ) {
317
+ const watcher = dirWatchers . get ( dirPath ) ;
318
+ watcher . referenceCount -= 1 ;
319
+ if ( watcher . referenceCount <= 0 ) {
320
+ watcher . close ( ) ;
321
+ dirWatchers . remove ( dirPath ) ;
322
+ }
323
+ }
324
+ }
325
+
326
+ function addDirWatcher ( dirPath : Path ) : void {
327
+ if ( dirWatchers . contains ( dirPath ) ) {
328
+ const watcher = dirWatchers . get ( dirPath ) ;
329
+ watcher . referenceCount += 1 ;
330
+ return ;
331
+ }
332
+
333
+ const watcher : DirectoryWatcher = _fs . watch (
334
+ dirPath ,
335
+ { persistent : true } ,
336
+ ( eventName : string , relativeFileName : string ) => fileEventHandler ( eventName , relativeFileName , dirPath )
337
+ ) ;
338
+ watcher . referenceCount = 1 ;
339
+ dirWatchers . set ( dirPath , watcher ) ;
340
+ return ;
341
+ }
342
+
343
+ function addFileWatcherCallback ( filePath : Path , callback : FileWatcherCallback ) : void {
344
+ if ( fileWatcherCallbacks . contains ( filePath ) ) {
345
+ fileWatcherCallbacks . get ( filePath ) . push ( callback ) ;
346
+ }
347
+ else {
348
+ fileWatcherCallbacks . set ( filePath , [ callback ] ) ;
349
+ }
350
+ }
351
+
352
+ function addFile ( filePath : Path , callback : FileWatcherCallback ) : WatchedFile {
353
+ addFileWatcherCallback ( filePath , callback ) ;
354
+ addDirWatcher ( getDirectoryPath ( filePath ) ) ;
355
+
356
+ return { filePath, callback } ;
357
+ }
358
+
359
+ function removeFile ( watchedFile : WatchedFile ) {
360
+ removeFileWatcherCallback ( watchedFile . filePath , watchedFile . callback ) ;
361
+ reduceDirWatcherRefCountForFile ( watchedFile . filePath ) ;
362
+ }
363
+
364
+ function removeFileWatcherCallback ( filePath : Path , callback : FileWatcherCallback ) {
365
+ if ( fileWatcherCallbacks . contains ( filePath ) ) {
366
+ const newCallbacks = copyListRemovingItem ( callback , fileWatcherCallbacks . get ( filePath ) ) ;
367
+ if ( newCallbacks . length === 0 ) {
368
+ fileWatcherCallbacks . remove ( filePath ) ;
369
+ }
370
+ else {
371
+ fileWatcherCallbacks . set ( filePath , newCallbacks ) ;
372
+ }
373
+ }
374
+ }
375
+
376
+ /**
377
+ * @param watcherPath is the path from which the watcher is triggered.
378
+ */
379
+ function fileEventHandler ( eventName : string , relativefileName : string , baseDirPath : Path ) {
380
+ // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
381
+ const filePath = relativefileName === undefined ? undefined : toPath ( relativefileName , baseDirPath , getCanonicalPath ) ;
382
+ if ( eventName === "change" && fileWatcherCallbacks . contains ( filePath ) ) {
383
+ for ( const fileCallback of fileWatcherCallbacks . get ( filePath ) ) {
384
+ fileCallback ( filePath ) ;
385
+ }
386
+ }
387
+ }
388
+ }
389
+
300
390
// REVIEW: for now this implementation uses polling.
301
391
// The advantage of polling is that it works reliably
302
392
// on all os and with network mounted files.
@@ -310,8 +400,13 @@ namespace ts {
310
400
// changes for large reference sets? If so, do we want
311
401
// to increase the chunk size or decrease the interval
312
402
// time dynamically to match the large reference set?
403
+ const pollingWatchedFileSet = createPollingWatchedFileSet ( ) ;
313
404
const watchedFileSet = createWatchedFileSet ( ) ;
314
405
406
+ function isNode4OrLater ( ) : boolean {
407
+ return parseInt ( process . version . charAt ( 1 ) ) >= 4 ;
408
+ }
409
+
315
410
const platform : string = _os . platform ( ) ;
316
411
// win32\win64 are case insensitive platforms, MacOS (darwin) by default is also case insensitive
317
412
const useCaseSensitiveFileNames = platform !== "win32" && platform !== "win64" && platform !== "darwin" ;
@@ -405,29 +500,38 @@ namespace ts {
405
500
} ,
406
501
readFile,
407
502
writeFile,
408
- watchFile : ( fileName , callback ) => {
503
+ watchFile : ( filePath , callback ) => {
409
504
// Node 4.0 stablized the `fs.watch` function on Windows which avoids polling
410
505
// and is more efficient than `fs.watchFile` (ref: https://github.com/nodejs/node/pull/2649
411
506
// and https://github.com/Microsoft/TypeScript/issues/4643), therefore
412
507
// if the current node.js version is newer than 4, use `fs.watch` instead.
413
- const watchedFile = watchedFileSet . addFile ( fileName , callback ) ;
508
+ const watchSet = isNode4OrLater ( ) ? watchedFileSet : pollingWatchedFileSet ;
509
+ const watchedFile = watchSet . addFile ( filePath , callback ) ;
414
510
return {
415
- close : ( ) => watchedFileSet . removeFile ( watchedFile )
511
+ close : ( ) => watchSet . removeFile ( watchedFile )
416
512
} ;
417
513
} ,
418
514
watchDirectory : ( path , callback , recursive ) => {
419
515
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
420
516
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
517
+ let options : any ;
518
+ if ( isNode4OrLater ( ) && ( process . platform === "win32" || process . platform === "darwin" ) ) {
519
+ options = { persistent : true , recursive : ! ! recursive } ;
520
+ }
521
+ else {
522
+ options = { persistent : true } ;
523
+ }
524
+
421
525
return _fs . watch (
422
526
path ,
423
- { persistent : true , recursive : ! ! recursive } ,
527
+ options ,
424
528
( eventName : string , relativeFileName : string ) => {
425
529
// In watchDirectory we only care about adding and removing files (when event name is
426
530
// "rename"); changes made within files are handled by corresponding fileWatchers (when
427
531
// event name is "change")
428
532
if ( eventName === "rename" ) {
429
533
// When deleting a file, the passed baseFileName is null
430
- callback ( ! relativeFileName ? relativeFileName : normalizePath ( ts . combinePaths ( path , relativeFileName ) ) ) ;
534
+ callback ( ! relativeFileName ? relativeFileName : normalizePath ( combinePaths ( path , relativeFileName ) ) ) ;
431
535
} ;
432
536
}
433
537
) ;
@@ -511,5 +615,3 @@ namespace ts {
511
615
}
512
616
} ) ( ) ;
513
617
}
514
-
515
-
0 commit comments