11/// <reference path="core.ts"/>
22
33namespace ts {
4+ export type FileWatcherCallback = ( path : string , removed ?: boolean ) => void ;
5+ export type DirectoryWatcherCallback = ( path : string ) => void ;
6+
47 export interface System {
58 args : string [ ] ;
69 newLine : string ;
710 useCaseSensitiveFileNames : boolean ;
811 write ( s : string ) : void ;
912 readFile ( path : string , encoding ?: string ) : string ;
1013 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 ;
1316 resolvePath ( path : string ) : string ;
1417 fileExists ( path : string ) : boolean ;
1518 directoryExists ( path : string ) : boolean ;
@@ -22,15 +25,20 @@ namespace ts {
2225 }
2326
2427 interface WatchedFile {
25- fileName : string ;
26- callback : ( fileName : string , removed ?: boolean ) => void ;
27- mtime : Date ;
28+ filePath : Path ;
29+ callback : FileWatcherCallback ;
30+ mtime ? : Date ;
2831 }
2932
3033 export interface FileWatcher {
3134 close ( ) : void ;
3235 }
3336
37+ export interface DirectoryWatcher extends FileWatcher {
38+ directoryPath : Path ;
39+ referenceCount : number ;
40+ }
41+
3442 declare var require : any ;
3543 declare var module : any ;
3644 declare var process : any ;
@@ -62,8 +70,8 @@ namespace ts {
6270 readFile ( path : string ) : string ;
6371 writeFile ( path : string , contents : string ) : void ;
6472 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 ;
6775 } ;
6876
6977 export var sys : System = ( function ( ) {
@@ -221,7 +229,7 @@ namespace ts {
221229
222230 // average async stat takes about 30 microseconds
223231 // set chunk size to do 30 files in < 1 millisecond
224- function createWatchedFileSet ( interval = 2500 , chunkSize = 30 ) {
232+ function createPollingWatchedFileSet ( interval = 2500 , chunkSize = 30 ) {
225233 let watchedFiles : WatchedFile [ ] = [ ] ;
226234 let nextFileToCheck = 0 ;
227235 let watchTimer : any ;
@@ -236,13 +244,13 @@ namespace ts {
236244 return ;
237245 }
238246
239- _fs . stat ( watchedFile . fileName , ( err : any , stats : any ) => {
247+ _fs . stat ( watchedFile . filePath , ( err : any , stats : any ) => {
240248 if ( err ) {
241- watchedFile . callback ( watchedFile . fileName ) ;
249+ watchedFile . callback ( watchedFile . filePath ) ;
242250 }
243251 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 ) ;
246254 }
247255 } ) ;
248256 }
@@ -270,11 +278,11 @@ namespace ts {
270278 } , interval ) ;
271279 }
272280
273- function addFile ( fileName : string , callback : ( fileName : string , removed ?: boolean ) => void ) : WatchedFile {
281+ function addFile ( filePath : Path , callback : FileWatcherCallback ) : WatchedFile {
274282 const file : WatchedFile = {
275- fileName ,
283+ filePath ,
276284 callback,
277- mtime : getModifiedTime ( fileName )
285+ mtime : getModifiedTime ( filePath )
278286 } ;
279287
280288 watchedFiles . push ( file ) ;
@@ -297,6 +305,88 @@ namespace ts {
297305 } ;
298306 }
299307
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+
300390 // REVIEW: for now this implementation uses polling.
301391 // The advantage of polling is that it works reliably
302392 // on all os and with network mounted files.
@@ -310,8 +400,13 @@ namespace ts {
310400 // changes for large reference sets? If so, do we want
311401 // to increase the chunk size or decrease the interval
312402 // time dynamically to match the large reference set?
403+ const pollingWatchedFileSet = createPollingWatchedFileSet ( ) ;
313404 const watchedFileSet = createWatchedFileSet ( ) ;
314405
406+ function isNode4OrLater ( ) : boolean {
407+ return parseInt ( process . version . charAt ( 1 ) ) >= 4 ;
408+ }
409+
315410 const platform : string = _os . platform ( ) ;
316411 // win32\win64 are case insensitive platforms, MacOS (darwin) by default is also case insensitive
317412 const useCaseSensitiveFileNames = platform !== "win32" && platform !== "win64" && platform !== "darwin" ;
@@ -405,29 +500,38 @@ namespace ts {
405500 } ,
406501 readFile,
407502 writeFile,
408- watchFile : ( fileName , callback ) => {
503+ watchFile : ( filePath , callback ) => {
409504 // Node 4.0 stablized the `fs.watch` function on Windows which avoids polling
410505 // and is more efficient than `fs.watchFile` (ref: https://github.com/nodejs/node/pull/2649
411506 // and https://github.com/Microsoft/TypeScript/issues/4643), therefore
412507 // 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 ) ;
414510 return {
415- close : ( ) => watchedFileSet . removeFile ( watchedFile )
511+ close : ( ) => watchSet . removeFile ( watchedFile )
416512 } ;
417513 } ,
418514 watchDirectory : ( path , callback , recursive ) => {
419515 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
420516 // (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+
421525 return _fs . watch (
422526 path ,
423- { persistent : true , recursive : ! ! recursive } ,
527+ options ,
424528 ( eventName : string , relativeFileName : string ) => {
425529 // In watchDirectory we only care about adding and removing files (when event name is
426530 // "rename"); changes made within files are handled by corresponding fileWatchers (when
427531 // event name is "change")
428532 if ( eventName === "rename" ) {
429533 // 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 ) ) ) ;
431535 } ;
432536 }
433537 ) ;
@@ -511,5 +615,3 @@ namespace ts {
511615 }
512616 } ) ( ) ;
513617}
514-
515-
0 commit comments