@@ -57,6 +57,8 @@ namespace ts {
5757
5858 /* @internal */
5959 export type HostWatchFile = ( fileName : string , callback : FileWatcherCallback , pollingInterval : PollingInterval ) => FileWatcher ;
60+ /* @internal */
61+ export type HostWatchDirectory = ( fileName : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) => FileWatcher ;
6062
6163 /* @internal */
6264 export const missingFileModifiedTime = new Date ( 0 ) ; // Any subsequent modification will occur after this time
@@ -286,6 +288,93 @@ namespace ts {
286288 return false ;
287289 }
288290
291+ /*@internal */
292+ export interface RecursiveDirectoryWatcherHost {
293+ watchDirectory : HostWatchDirectory ;
294+ getAccessileSortedChildDirectories ( path : string ) : ReadonlyArray < string > ;
295+ filePathComparer : Comparer < string > ;
296+ }
297+
298+ /**
299+ * Watch the directory recursively using host provided method to watch child directories
300+ * that means if this is recursive watcher, watch the children directories as well
301+ * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
302+ */
303+ /*@internal */
304+ export function createRecursiveDirectoryWatcher ( host : RecursiveDirectoryWatcherHost ) : ( directoryName : string , callback : DirectoryWatcherCallback ) => FileWatcher {
305+ type ChildWatches = ReadonlyArray < DirectoryWatcher > ;
306+ interface DirectoryWatcher extends FileWatcher {
307+ childWatches : ChildWatches ;
308+ dirName : string ;
309+ }
310+
311+ return createDirectoryWatcher ;
312+
313+ /**
314+ * Create the directory watcher for the dirPath.
315+ */
316+ function createDirectoryWatcher ( dirName : string , callback : DirectoryWatcherCallback ) : DirectoryWatcher {
317+ const watcher = host . watchDirectory ( dirName , fileName => {
318+ // Call the actual callback
319+ callback ( fileName ) ;
320+
321+ // Iterate through existing children and update the watches if needed
322+ updateChildWatches ( result , callback ) ;
323+ } ) ;
324+
325+ let result : DirectoryWatcher = {
326+ close : ( ) => {
327+ watcher . close ( ) ;
328+ result . childWatches . forEach ( closeFileWatcher ) ;
329+ result = undefined ;
330+ } ,
331+ dirName,
332+ childWatches : emptyArray
333+ } ;
334+ updateChildWatches ( result , callback ) ;
335+ return result ;
336+ }
337+
338+ function updateChildWatches ( watcher : DirectoryWatcher , callback : DirectoryWatcherCallback ) {
339+ // Iterate through existing children and update the watches if needed
340+ if ( watcher ) {
341+ watcher . childWatches = watchChildDirectories ( watcher . dirName , watcher . childWatches , callback ) ;
342+ }
343+ }
344+
345+ /**
346+ * Watch the directories in the parentDir
347+ */
348+ function watchChildDirectories ( parentDir : string , existingChildWatches : ChildWatches , callback : DirectoryWatcherCallback ) : ChildWatches {
349+ let newChildWatches : DirectoryWatcher [ ] | undefined ;
350+ enumerateInsertsAndDeletes < string , DirectoryWatcher > (
351+ host . getAccessileSortedChildDirectories ( parentDir ) ,
352+ existingChildWatches ,
353+ ( child , childWatcher ) => host . filePathComparer ( getNormalizedAbsolutePath ( child , parentDir ) , childWatcher . dirName ) ,
354+ createAndAddChildDirectoryWatcher ,
355+ closeFileWatcher ,
356+ addChildDirectoryWatcher
357+ ) ;
358+
359+ return newChildWatches || emptyArray ;
360+
361+ /**
362+ * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list
363+ */
364+ function createAndAddChildDirectoryWatcher ( childName : string ) {
365+ const result = createDirectoryWatcher ( getNormalizedAbsolutePath ( childName , parentDir ) , callback ) ;
366+ addChildDirectoryWatcher ( result ) ;
367+ }
368+
369+ /**
370+ * Add child directory watcher to the new ChildDirectoryWatcher list
371+ */
372+ function addChildDirectoryWatcher ( childWatcher : DirectoryWatcher ) {
373+ ( newChildWatches || ( newChildWatches = [ ] ) ) . push ( childWatcher ) ;
374+ }
375+ }
376+ }
377+
289378 /**
290379 * Partial interface of the System thats needed to support the caching of directory structure
291380 */
@@ -402,7 +491,8 @@ namespace ts {
402491 }
403492
404493 const useNonPollingWatchers = process . env . TSC_NONPOLLING_WATCHER ;
405- const tscWatchOption = process . env . TSC_WATCHOPTION ;
494+ const tscWatchFile = process . env . TSC_WATCHFILE ;
495+ const tscWatchDirectory = process . env . TSC_WATCHDIRECTORY ;
406496
407497 const nodeSystem : System = {
408498 args : process . argv . slice ( 2 ) ,
@@ -483,19 +573,7 @@ namespace ts {
483573 }
484574 } ;
485575 nodeSystem . watchFile = getWatchFile ( ) ;
486- nodeSystem . watchDirectory = ( directoryName , callback , recursive ) => {
487- // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
488- // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
489- return fsWatchDirectory ( directoryName , ( eventName , relativeFileName ) => {
490- // In watchDirectory we only care about adding and removing files (when event name is
491- // "rename"); changes made within files are handled by corresponding fileWatchers (when
492- // event name is "change")
493- if ( eventName === "rename" ) {
494- // When deleting a file, the passed baseFileName is null
495- callback ( ! relativeFileName ? relativeFileName : normalizePath ( combinePaths ( directoryName , relativeFileName ) ) ) ;
496- }
497- } , recursive ) ;
498- } ;
576+ nodeSystem . watchDirectory = getWatchDirectory ( ) ;
499577 return nodeSystem ;
500578
501579 function isFileSystemCaseSensitive ( ) : boolean {
@@ -516,7 +594,7 @@ namespace ts {
516594 }
517595
518596 function getWatchFile ( ) : HostWatchFile {
519- switch ( tscWatchOption ) {
597+ switch ( tscWatchFile ) {
520598 case "PriorityPollingInterval" :
521599 // Use polling interval based on priority when create watch using host.watchFile
522600 return fsWatchFile ;
@@ -536,6 +614,29 @@ namespace ts {
536614 ( fileName , callback ) => fsWatchFile ( fileName , callback ) ;
537615 }
538616
617+ function getWatchDirectory ( ) : HostWatchDirectory {
618+ // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
619+ // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
620+ const fsSupportsRecursive = isNode4OrLater && ( process . platform === "win32" || process . platform === "darwin" ) ;
621+ if ( fsSupportsRecursive ) {
622+ return watchDirectoryUsingFsWatch ;
623+ }
624+
625+ const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ? watchDirectoryUsingFsWatchFile : watchDirectoryUsingFsWatch ;
626+ const watchDirectoryRecursively = createRecursiveDirectoryWatcher ( {
627+ filePathComparer : useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive ,
628+ getAccessileSortedChildDirectories : path => getAccessibleFileSystemEntries ( path ) . directories ,
629+ watchDirectory
630+ } ) ;
631+
632+ return ( directoryName , callback , recursive ) => {
633+ if ( recursive ) {
634+ return watchDirectoryRecursively ( directoryName , callback ) ;
635+ }
636+ watchDirectory ( directoryName , callback ) ;
637+ } ;
638+ }
639+
539640 function createNonPollingWatchFile ( ) {
540641 // One file can have multiple watchers
541642 const fileWatcherCallbacks = createMultiMap < FileWatcherCallback > ( ) ;
@@ -616,11 +717,11 @@ namespace ts {
616717
617718 type FsWatchCallback = ( eventName : "rename" | "change" , relativeFileName : string ) => void ;
618719
619- function createFsWatchFileCallback ( callback : FsWatchCallback ) : FileWatcherCallback {
720+ function createFileWatcherCallback ( callback : FsWatchCallback ) : FileWatcherCallback {
620721 return ( _fileName , eventKind ) => callback ( eventKind === FileWatcherEventKind . Changed ? "change" : "rename" , "" ) ;
621722 }
622723
623- function createFsWatchCallback ( fileName : string , callback : FileWatcherCallback ) : FsWatchCallback {
724+ function createFsWatchCallbackForFileWatcherCallback ( fileName : string , callback : FileWatcherCallback ) : FsWatchCallback {
624725 return eventName => {
625726 if ( eventName === "rename" ) {
626727 callback ( fileName , fileExists ( fileName ) ? FileWatcherEventKind . Created : FileWatcherEventKind . Deleted ) ;
@@ -632,6 +733,18 @@ namespace ts {
632733 } ;
633734 }
634735
736+ function createFsWatchCallbackForDirectoryWatcherCallback ( directoryName : string , callback : DirectoryWatcherCallback ) : FsWatchCallback {
737+ return ( eventName , relativeFileName ) => {
738+ // In watchDirectory we only care about adding and removing files (when event name is
739+ // "rename"); changes made within files are handled by corresponding fileWatchers (when
740+ // event name is "change")
741+ if ( eventName === "rename" ) {
742+ // When deleting a file, the passed baseFileName is null
743+ callback ( ! relativeFileName ? directoryName : normalizePath ( combinePaths ( directoryName , relativeFileName ) ) ) ;
744+ }
745+ } ;
746+ }
747+
635748 function fsWatch ( fileOrDirectory : string , entryKind : FileSystemEntryKind . File | FileSystemEntryKind . Directory , callback : FsWatchCallback , recursive : boolean , fallbackPollingWatchFile : HostWatchFile , pollingInterval ?: number ) : FileWatcher {
636749 let options : any ;
637750 /** Watcher for the file system entry depending on whether it is missing or present */
@@ -700,7 +813,7 @@ namespace ts {
700813 * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point
701814 */
702815 function watchPresentFileSystemEntryWithFsWatchFile ( ) : FileWatcher {
703- return fallbackPollingWatchFile ( fileOrDirectory , createFsWatchFileCallback ( callback ) , pollingInterval ) ;
816+ return fallbackPollingWatchFile ( fileOrDirectory , createFileWatcherCallback ( callback ) , pollingInterval ) ;
704817 }
705818
706819 /**
@@ -720,18 +833,26 @@ namespace ts {
720833 }
721834
722835 function watchFileUsingFsWatch ( fileName : string , callback : FileWatcherCallback , pollingInterval ?: number ) {
723- return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallback ( fileName , callback ) , /*recursive*/ false , fsWatchFile , pollingInterval ) ;
836+ return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallbackForFileWatcherCallback ( fileName , callback ) , /*recursive*/ false , fsWatchFile , pollingInterval ) ;
724837 }
725838
726839 function watchFileUsingDynamicWatchFile ( fileName : string , callback : FileWatcherCallback , pollingInterval ?: number ) {
727840 const watchFile = createDynamicPriorityPollingWatchFile ( nodeSystem ) ;
728- return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallback ( fileName , callback ) , /*recursive*/ false , watchFile , pollingInterval ) ;
841+ return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallbackForFileWatcherCallback ( fileName , callback ) , /*recursive*/ false , watchFile , pollingInterval ) ;
729842 }
730843
731844 function fsWatchDirectory ( directoryName : string , callback : FsWatchCallback , recursive ?: boolean ) : FileWatcher {
732845 return fsWatch ( directoryName , FileSystemEntryKind . Directory , callback , ! ! recursive , fsWatchFile ) ;
733846 }
734847
848+ function watchDirectoryUsingFsWatch ( directoryName : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) {
849+ return fsWatchDirectory ( directoryName , createFsWatchCallbackForDirectoryWatcherCallback ( directoryName , callback ) , recursive ) ;
850+ }
851+
852+ function watchDirectoryUsingFsWatchFile ( directoryName : string , callback : DirectoryWatcherCallback ) {
853+ return fsWatchFile ( directoryName , ( ) => callback ( directoryName ) , PollingInterval . Medium ) ;
854+ }
855+
735856 function readFile ( fileName : string , _encoding ?: string ) : string | undefined {
736857 if ( ! fileExists ( fileName ) ) {
737858 return undefined ;
0 commit comments