@@ -35,8 +35,7 @@ namespace ts {
3535
3636 export type FileWatcherCallback = ( fileName : string , eventKind : FileWatcherEventKind , modifiedTime ?: Date ) => void ;
3737 export type DirectoryWatcherCallback = ( fileName : string ) => void ;
38- /*@internal */
39- export interface WatchedFile {
38+ interface WatchedFile {
4039 readonly fileName : string ;
4140 readonly callback : FileWatcherCallback ;
4241 mtime : Date ;
@@ -81,8 +80,7 @@ namespace ts {
8180 /* @internal */
8281 export let unchangedPollThresholds = createPollingIntervalBasedLevels ( defaultChunkLevels ) ;
8382
84- /* @internal */
85- export function setCustomPollingValues ( system : System ) {
83+ function setCustomPollingValues ( system : System ) {
8684 if ( ! system . getEnvironmentVariable ) {
8785 return ;
8886 }
@@ -189,31 +187,28 @@ namespace ts {
189187 }
190188 }
191189
192- /* @internal */
193- export function createDynamicPriorityPollingWatchFile ( host : {
190+ interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed {
191+ unchangedPolls : number ;
192+ }
193+ function createDynamicPriorityPollingWatchFile ( host : {
194194 getModifiedTime : NonNullable < System [ "getModifiedTime" ] > ;
195195 setTimeout : NonNullable < System [ "setTimeout" ] > ;
196196 } ) : HostWatchFile {
197- interface WatchedFile extends ts . WatchedFile {
198- isClosed ?: boolean ;
199- unchangedPolls : number ;
200- }
201-
202- interface PollingIntervalQueue extends Array < WatchedFile > {
197+ interface PollingIntervalQueue extends Array < WatchedFileWithUnchangedPolls > {
203198 pollingInterval : PollingInterval ;
204199 pollIndex : number ;
205200 pollScheduled : boolean ;
206201 }
207202
208- const watchedFiles : WatchedFile [ ] = [ ] ;
209- const changedFilesInLastPoll : WatchedFile [ ] = [ ] ;
203+ const watchedFiles : WatchedFileWithUnchangedPolls [ ] = [ ] ;
204+ const changedFilesInLastPoll : WatchedFileWithUnchangedPolls [ ] = [ ] ;
210205 const lowPollingIntervalQueue = createPollingIntervalQueue ( PollingInterval . Low ) ;
211206 const mediumPollingIntervalQueue = createPollingIntervalQueue ( PollingInterval . Medium ) ;
212207 const highPollingIntervalQueue = createPollingIntervalQueue ( PollingInterval . High ) ;
213208 return watchFile ;
214209
215210 function watchFile ( fileName : string , callback : FileWatcherCallback , defaultPollingInterval : PollingInterval ) : FileWatcher {
216- const file : WatchedFile = {
211+ const file : WatchedFileWithUnchangedPolls = {
217212 fileName,
218213 callback,
219214 unchangedPolls : 0 ,
@@ -233,7 +228,7 @@ namespace ts {
233228 }
234229
235230 function createPollingIntervalQueue ( pollingInterval : PollingInterval ) : PollingIntervalQueue {
236- const queue = [ ] as WatchedFile [ ] as PollingIntervalQueue ;
231+ const queue = [ ] as WatchedFileWithUnchangedPolls [ ] as PollingIntervalQueue ;
237232 queue . pollingInterval = pollingInterval ;
238233 queue . pollIndex = 0 ;
239234 queue . pollScheduled = false ;
@@ -265,7 +260,7 @@ namespace ts {
265260 }
266261 }
267262
268- function pollQueue ( queue : ( WatchedFile | undefined ) [ ] , pollingInterval : PollingInterval , pollIndex : number , chunkSize : number ) {
263+ function pollQueue ( queue : ( WatchedFileWithUnchangedPolls | undefined ) [ ] , pollingInterval : PollingInterval , pollIndex : number , chunkSize : number ) {
269264 return pollWatchedFileQueue (
270265 host ,
271266 queue ,
@@ -274,7 +269,7 @@ namespace ts {
274269 onWatchFileStat
275270 ) ;
276271
277- function onWatchFileStat ( watchedFile : WatchedFile , pollIndex : number , fileChanged : boolean ) {
272+ function onWatchFileStat ( watchedFile : WatchedFileWithUnchangedPolls , pollIndex : number , fileChanged : boolean ) {
278273 if ( fileChanged ) {
279274 watchedFile . unchangedPolls = 0 ;
280275 // Changed files go to changedFilesInLastPoll queue
@@ -311,12 +306,12 @@ namespace ts {
311306 }
312307 }
313308
314- function addToPollingIntervalQueue ( file : WatchedFile , pollingInterval : PollingInterval ) {
309+ function addToPollingIntervalQueue ( file : WatchedFileWithUnchangedPolls , pollingInterval : PollingInterval ) {
315310 pollingIntervalQueue ( pollingInterval ) . push ( file ) ;
316311 scheduleNextPollIfNotAlreadyScheduled ( pollingInterval ) ;
317312 }
318313
319- function addChangedFileToLowPollingIntervalQueue ( file : WatchedFile ) {
314+ function addChangedFileToLowPollingIntervalQueue ( file : WatchedFileWithUnchangedPolls ) {
320315 changedFilesInLastPoll . push ( file ) ;
321316 scheduleNextPollIfNotAlreadyScheduled ( PollingInterval . Low ) ;
322317 }
@@ -423,59 +418,50 @@ namespace ts {
423418 }
424419 }
425420
426- /* @internal */
427- export function createSingleFileWatcherPerName (
428- watchFile : HostWatchFile ,
429- useCaseSensitiveFileNames : boolean
430- ) : HostWatchFile {
431- interface SingleFileWatcher {
432- watcher : FileWatcher ;
433- refCount : number ;
434- }
435- const cache = new Map < string , SingleFileWatcher > ( ) ;
436- const callbacksCache = createMultiMap < FileWatcherCallback > ( ) ;
421+ interface SingleFileWatcher < T extends FileWatcherCallback | FsWatchCallback > {
422+ watcher : FileWatcher ;
423+ callbacks : T [ ] ;
424+ }
425+ function createSingleWatcherPerName < T extends FileWatcherCallback | FsWatchCallback > (
426+ cache : Map < SingleFileWatcher < T > > ,
427+ useCaseSensitiveFileNames : boolean ,
428+ name : string ,
429+ callback : T ,
430+ createWatcher : ( callback : T ) => FileWatcher ,
431+ ) : FileWatcher {
437432 const toCanonicalFileName = createGetCanonicalFileName ( useCaseSensitiveFileNames ) ;
433+ const path = toCanonicalFileName ( name ) ;
434+ const existing = cache . get ( path ) ;
435+ if ( existing ) {
436+ existing . callbacks . push ( callback ) ;
437+ }
438+ else {
439+ cache . set ( path , {
440+ watcher : createWatcher ( (
441+ // Cant infer types correctly so lets satisfy checker
442+ ( param1 : any , param2 : never , param3 : any ) => cache . get ( path ) ?. callbacks . slice ( ) . forEach ( cb => cb ( param1 , param2 , param3 ) )
443+ ) as T ) ,
444+ callbacks : [ callback ]
445+ } ) ;
446+ }
438447
439- return ( fileName , callback , pollingInterval , options ) => {
440- const path = toCanonicalFileName ( fileName ) ;
441- const existing = cache . get ( path ) ;
442- if ( existing ) {
443- existing . refCount ++ ;
444- }
445- else {
446- cache . set ( path , {
447- watcher : watchFile (
448- fileName ,
449- ( fileName , eventKind , modifiedTime ) => forEach (
450- callbacksCache . get ( path ) ,
451- cb => cb ( fileName , eventKind , modifiedTime )
452- ) ,
453- pollingInterval ,
454- options
455- ) ,
456- refCount : 1
457- } ) ;
448+ return {
449+ close : ( ) => {
450+ const watcher = cache . get ( path ) ;
451+ // Watcher is not expected to be undefined, but if it is normally its because
452+ // exception was thrown somewhere else and watch state is not what it should be
453+ if ( ! watcher ) return ;
454+ if ( ! orderedRemoveItem ( watcher . callbacks , callback ) || watcher . callbacks . length ) return ;
455+ cache . delete ( path ) ;
456+ closeFileWatcherOf ( watcher ) ;
458457 }
459- callbacksCache . add ( path , callback ) ;
460-
461- return {
462- close : ( ) => {
463- const watcher = Debug . checkDefined ( cache . get ( path ) ) ;
464- callbacksCache . remove ( path , callback ) ;
465- watcher . refCount -- ;
466- if ( watcher . refCount ) return ;
467- cache . delete ( path ) ;
468- closeFileWatcherOf ( watcher ) ;
469- }
470- } ;
471458 } ;
472459 }
473460
474461 /**
475462 * Returns true if file status changed
476463 */
477- /*@internal */
478- export function onWatchedFileStat ( watchedFile : WatchedFile , modifiedTime : Date ) : boolean {
464+ function onWatchedFileStat ( watchedFile : WatchedFile , modifiedTime : Date ) : boolean {
479465 const oldTime = watchedFile . mtime . getTime ( ) ;
480466 const newTime = modifiedTime . getTime ( ) ;
481467 if ( oldTime !== newTime ) {
@@ -512,8 +498,7 @@ namespace ts {
512498 curSysLog = logger ;
513499 }
514500
515- /*@internal */
516- export interface RecursiveDirectoryWatcherHost {
501+ interface RecursiveDirectoryWatcherHost {
517502 watchDirectory : HostWatchDirectory ;
518503 useCaseSensitiveFileNames : boolean ;
519504 getCurrentDirectory : System [ "getCurrentDirectory" ] ;
@@ -529,8 +514,7 @@ namespace ts {
529514 * that means if this is recursive watcher, watch the children directories as well
530515 * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
531516 */
532- /*@internal */
533- export function createDirectoryWatcherSupportingRecursive ( {
517+ function createDirectoryWatcherSupportingRecursive ( {
534518 watchDirectory,
535519 useCaseSensitiveFileNames,
536520 getCurrentDirectory,
@@ -792,8 +776,7 @@ namespace ts {
792776 Directory ,
793777 }
794778
795- /*@internal */
796- export function createFileWatcherCallback ( callback : FsWatchCallback ) : FileWatcherCallback {
779+ function createFileWatcherCallback ( callback : FsWatchCallback ) : FileWatcherCallback {
797780 return ( _fileName , eventKind , modifiedTime ) => callback ( eventKind === FileWatcherEventKind . Changed ? "change" : "rename" , "" , modifiedTime ) ;
798781 }
799782
@@ -854,7 +837,7 @@ namespace ts {
854837 /*@internal */
855838 export interface CreateSystemWatchFunctions {
856839 // Polling watch file
857- pollingWatchFile : HostWatchFile ;
840+ pollingWatchFileWorker : HostWatchFile ;
858841 // For dynamic polling watch file
859842 getModifiedTime : NonNullable < System [ "getModifiedTime" ] > ;
860843 setTimeout : NonNullable < System [ "setTimeout" ] > ;
@@ -878,7 +861,7 @@ namespace ts {
878861
879862 /*@internal */
880863 export function createSystemWatchFunctions ( {
881- pollingWatchFile ,
864+ pollingWatchFileWorker ,
882865 getModifiedTime,
883866 setTimeout,
884867 clearTimeout,
@@ -896,6 +879,9 @@ namespace ts {
896879 inodeWatching,
897880 sysLog,
898881 } : CreateSystemWatchFunctions ) : { watchFile : HostWatchFile ; watchDirectory : HostWatchDirectory ; } {
882+ const pollingWatches = new Map < string , SingleFileWatcher < FileWatcherCallback > > ( ) ;
883+ const fsWatches = new Map < string , SingleFileWatcher < FsWatchCallback > > ( ) ;
884+ const fsWatchesRecursive = new Map < string , SingleFileWatcher < FsWatchCallback > > ( ) ;
899885 let dynamicPollingWatchFile : HostWatchFile | undefined ;
900886 let fixedChunkSizePollingWatchFile : HostWatchFile | undefined ;
901887 let nonPollingWatchFile : HostWatchFile | undefined ;
@@ -968,7 +954,7 @@ namespace ts {
968954 // Use notifications from FS to watch with falling back to fs.watchFile
969955 generateWatchFileOptions ( WatchFileKind . UseFsEventsOnParentDirectory , PollingWatchKind . PriorityInterval , options ) :
970956 // Default to do not use fixed polling interval
971- { watchFile : defaultWatchFileKind ?.( ) || WatchFileKind . FixedPollingInterval } ;
957+ { watchFile : defaultWatchFileKind ?.( ) || WatchFileKind . UseFsEvents } ;
972958 }
973959 }
974960
@@ -1073,13 +1059,39 @@ namespace ts {
10731059 }
10741060 }
10751061
1062+ function pollingWatchFile ( fileName : string , callback : FileWatcherCallback , pollingInterval : PollingInterval , options : WatchOptions | undefined ) {
1063+ return createSingleWatcherPerName (
1064+ pollingWatches ,
1065+ useCaseSensitiveFileNames ,
1066+ fileName ,
1067+ callback ,
1068+ cb => pollingWatchFileWorker ( fileName , cb , pollingInterval , options ) ,
1069+ ) ;
1070+ }
10761071 function fsWatch (
10771072 fileOrDirectory : string ,
10781073 entryKind : FileSystemEntryKind ,
10791074 callback : FsWatchCallback ,
10801075 recursive : boolean ,
10811076 fallbackPollingInterval : PollingInterval ,
10821077 fallbackOptions : WatchOptions | undefined
1078+ ) : FileWatcher {
1079+ return createSingleWatcherPerName (
1080+ recursive ? fsWatchesRecursive : fsWatches ,
1081+ useCaseSensitiveFileNames ,
1082+ fileOrDirectory ,
1083+ callback ,
1084+ cb => fsWatchHandlingExistenceOnHost ( fileOrDirectory , entryKind , cb , recursive , fallbackPollingInterval , fallbackOptions ) ,
1085+ ) ;
1086+ }
1087+
1088+ function fsWatchHandlingExistenceOnHost (
1089+ fileOrDirectory : string ,
1090+ entryKind : FileSystemEntryKind ,
1091+ callback : FsWatchCallback ,
1092+ recursive : boolean ,
1093+ fallbackPollingInterval : PollingInterval ,
1094+ fallbackOptions : WatchOptions | undefined
10831095 ) : FileWatcher {
10841096 let lastDirectoryPartWithDirectorySeparator : string | undefined ;
10851097 let lastDirectoryPart : string | undefined ;
@@ -1445,7 +1457,7 @@ namespace ts {
14451457 const fsSupportsRecursiveFsWatch = isNode4OrLater && ( process . platform === "win32" || process . platform === "darwin" ) ;
14461458 const getCurrentDirectory = memoize ( ( ) => process . cwd ( ) ) ;
14471459 const { watchFile, watchDirectory } = createSystemWatchFunctions ( {
1448- pollingWatchFile : createSingleFileWatcherPerName ( fsWatchFileWorker , useCaseSensitiveFileNames ) ,
1460+ pollingWatchFileWorker : fsWatchFileWorker ,
14491461 getModifiedTime,
14501462 setTimeout,
14511463 clearTimeout,
0 commit comments