@@ -521,7 +521,14 @@ namespace ts {
521521 // Use polling interval based on priority when create watch using host.watchFile
522522 return fsWatchFile ;
523523 case "DynamicPriorityPolling" :
524+ // Use polling interval but change the interval depending on file changes and their default polling interval
524525 return createDynamicPriorityPollingWatchFile ( nodeSystem ) ;
526+ case "UseFsEvents" :
527+ // Use notifications from FS to watch with falling back to fs.watchFile
528+ return watchFileUsingFsWatch ;
529+ case "UseFsEventsWithFallbackDynamicPolling" :
530+ // Use notifications from FS to watch with falling back to dynamic watch file
531+ return watchFileUsingDynamicWatchFile ;
525532 }
526533 return useNonPollingWatchers ?
527534 createNonPollingWatchFile ( ) :
@@ -607,24 +614,58 @@ namespace ts {
607614 }
608615 }
609616
610- function fsWatchDirectory ( directoryName : string , callback : ( eventName : string , relativeFileName : string ) => void , recursive ?: boolean ) : FileWatcher {
617+ type FsWatchCallback = ( eventName : "rename" | "change" , relativeFileName : string ) => void ;
618+
619+ function createFsWatchFileCallback ( callback : FsWatchCallback ) : FileWatcherCallback {
620+ return ( _fileName , eventKind ) => callback ( eventKind === FileWatcherEventKind . Changed ? "change" : "rename" , "" ) ;
621+ }
622+
623+ function createFsWatchCallback ( fileName : string , callback : FileWatcherCallback ) : FsWatchCallback {
624+ return eventName => {
625+ if ( eventName === "rename" ) {
626+ callback ( fileName , fileExists ( fileName ) ? FileWatcherEventKind . Created : FileWatcherEventKind . Deleted ) ;
627+ }
628+ else {
629+ // Change
630+ callback ( fileName , FileWatcherEventKind . Changed ) ;
631+ }
632+ } ;
633+ }
634+
635+ function fsWatch ( fileOrDirectory : string , entryKind : FileSystemEntryKind . File | FileSystemEntryKind . Directory , callback : FsWatchCallback , recursive : boolean , fallbackPollingWatchFile : HostWatchFile , pollingInterval ?: number ) : FileWatcher {
611636 let options : any ;
612- /** Watcher for the directory depending on whether it is missing or present */
613- let watcher = ! directoryExists ( directoryName ) ?
614- watchMissingDirectory ( ) :
615- watchPresentDirectory ( ) ;
637+ /** Watcher for the file system entry depending on whether it is missing or present */
638+ let watcher = ! fileSystemEntryExists ( fileOrDirectory , entryKind ) ?
639+ watchMissingFileSystemEntry ( ) :
640+ watchPresentFileSystemEntry ( ) ;
616641 return {
617642 close : ( ) => {
618- // Close the watcher (either existing directory watcher or missing directory watcher)
643+ // Close the watcher (either existing file system entry watcher or missing file system entry watcher)
619644 watcher . close ( ) ;
645+ watcher = undefined ;
620646 }
621647 } ;
622648
623649 /**
624- * Watch the directory that is currently present
625- * and when the watched directory is deleted, switch to missing directory watcher
650+ * Invoke the callback with rename and update the watcher if not closed
651+ * @param createWatcher
652+ */
653+ function invokeCallbackAndUpdateWatcher ( createWatcher : ( ) => FileWatcher ) {
654+ // Call the callback for current directory
655+ callback ( "rename" , "" ) ;
656+
657+ // If watcher is not closed, update it
658+ if ( watcher ) {
659+ watcher . close ( ) ;
660+ watcher = createWatcher ( ) ;
661+ }
662+ }
663+
664+ /**
665+ * Watch the file or directory that is currently present
666+ * and when the watched file or directory is deleted, switch to missing file system entry watcher
626667 */
627- function watchPresentDirectory ( ) : FileWatcher {
668+ function watchPresentFileSystemEntry ( ) : FileWatcher {
628669 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
629670 // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
630671 if ( options === undefined ) {
@@ -635,40 +676,62 @@ namespace ts {
635676 options = { persistent : true } ;
636677 }
637678 }
679+ try {
638680
639- const dirWatcher = _fs . watch (
640- directoryName ,
641- options ,
642- callback
643- ) ;
644- dirWatcher . on ( "error" , ( ) => {
645- // Watch the missing directory
646- watcher . close ( ) ;
647- watcher = watchMissingDirectory ( ) ;
648- // Call the callback for current directory
649- callback ( "rename" , "" ) ;
650- } ) ;
651- return dirWatcher ;
681+ const presentWatcher = _fs . watch (
682+ fileOrDirectory ,
683+ options ,
684+ callback
685+ ) ;
686+ // Watch the missing file or directory or error
687+ presentWatcher . on ( "error" , ( ) => invokeCallbackAndUpdateWatcher ( watchMissingFileSystemEntry ) ) ;
688+ return presentWatcher ;
689+ }
690+ catch ( e ) {
691+ // Catch the exception and use polling instead
692+ // 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
693+ // so instead of throwing error, use fs.watchFile
694+ return watchPresentFileSystemEntryWithFsWatchFile ( ) ;
695+ }
696+ }
697+
698+ /**
699+ * Watch the file or directory using fs.watchFile since fs.watch threw exception
700+ * 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
701+ */
702+ function watchPresentFileSystemEntryWithFsWatchFile ( ) : FileWatcher {
703+ return fallbackPollingWatchFile ( fileOrDirectory , createFsWatchFileCallback ( callback ) , pollingInterval ) ;
652704 }
653705
654706 /**
655- * Watch the directory that is missing
656- * and switch to existing directory when the directory is created
707+ * Watch the file or directory that is missing
708+ * and switch to existing file or directory when the missing filesystem entry is created
657709 */
658- function watchMissingDirectory ( ) : FileWatcher {
659- return fsWatchFile ( directoryName , ( _fileName , eventKind ) => {
660- if ( eventKind === FileWatcherEventKind . Created && directoryExists ( directoryName ) ) {
661- watcher . close ( ) ;
662- watcher = watchPresentDirectory ( ) ;
663- // Call the callback for current directory
710+ function watchMissingFileSystemEntry ( ) : FileWatcher {
711+ return fallbackPollingWatchFile ( fileOrDirectory , ( _fileName , eventKind ) => {
712+ if ( eventKind === FileWatcherEventKind . Created && fileSystemEntryExists ( fileOrDirectory , entryKind ) ) {
713+ // Call the callback for current file or directory
664714 // For now it could be callback for the inner directory creation,
665715 // but just return current directory, better than current no-op
666- callback ( "rename" , "" ) ;
716+ invokeCallbackAndUpdateWatcher ( watchPresentFileSystemEntry ) ;
667717 }
668- } ) ;
718+ } , pollingInterval ) ;
669719 }
670720 }
671721
722+ function watchFileUsingFsWatch ( fileName : string , callback : FileWatcherCallback , pollingInterval ?: number ) {
723+ return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallback ( fileName , callback ) , /*recursive*/ false , fsWatchFile , pollingInterval ) ;
724+ }
725+
726+ function watchFileUsingDynamicWatchFile ( fileName : string , callback : FileWatcherCallback , pollingInterval ?: number ) {
727+ const watchFile = createDynamicPriorityPollingWatchFile ( nodeSystem ) ;
728+ return fsWatch ( fileName , FileSystemEntryKind . File , createFsWatchCallback ( fileName , callback ) , /*recursive*/ false , watchFile , pollingInterval ) ;
729+ }
730+
731+ function fsWatchDirectory ( directoryName : string , callback : FsWatchCallback , recursive ?: boolean ) : FileWatcher {
732+ return fsWatch ( directoryName , FileSystemEntryKind . Directory , callback , ! ! recursive , fsWatchFile ) ;
733+ }
734+
672735 function readFile ( fileName : string , _encoding ?: string ) : string | undefined {
673736 if ( ! fileExists ( fileName ) ) {
674737 return undefined ;
0 commit comments