11import {
2- addRange ,
32 arrayFrom ,
43 compareStringsCaseSensitive ,
54 contains ,
@@ -10,6 +9,7 @@ import {
109 GetCanonicalFileName ,
1110 MultiMap ,
1211 PollingInterval ,
12+ System ,
1313} from "./_namespaces/ts" ;
1414
1515export interface TestFileWatcher {
@@ -25,7 +25,7 @@ export interface TestFsWatcher<DirCallback> {
2525export interface Watches < Data > {
2626 add ( path : string , data : Data ) : void ;
2727 remove ( path : string , data : Data ) : void ;
28- forEach ( path : string , cb : ( data : Data ) => void ) : void ;
28+ forEach ( path : string , cb : ( data : Data , path : string ) => void ) : void ;
2929 serialize ( baseline : string [ ] ) : void ;
3030}
3131
@@ -44,6 +44,7 @@ export function createWatchUtils<PollingWatcherData, FsWatcherData>(
4444 pollingWatchesName : string ,
4545 fsWatchesName : string ,
4646 getCanonicalFileName : GetCanonicalFileName ,
47+ system : Required < Pick < System , "realpath" > > ,
4748) : WatchUtils < PollingWatcherData , FsWatcherData > {
4849 const pollingWatches = initializeWatches < PollingWatcherData > ( pollingWatchesName ) ;
4950 const fsWatches = initializeWatches < FsWatcherData > ( fsWatchesName ) ;
@@ -64,6 +65,8 @@ export function createWatchUtils<PollingWatcherData, FsWatcherData>(
6465 const actuals = createMultiMap < string , Data > ( ) ;
6566 let serialized : Map < string , Data [ ] > | undefined ;
6667 let canonicalPathsToStrings : Map < string , Set < string > > | undefined ;
68+ let realToLinked : MultiMap < string , string > | undefined ;
69+ let pathToReal : Map < string , string > | undefined ;
6770 return {
6871 add,
6972 remove,
@@ -73,40 +76,69 @@ export function createWatchUtils<PollingWatcherData, FsWatcherData>(
7376
7477 function add ( path : string , data : Data ) {
7578 actuals . add ( path , data ) ;
76- if ( actuals . get ( path ) ! . length === 1 ) {
77- const canonicalPath = getCanonicalFileName ( path ) ;
78- if ( canonicalPath !== path ) {
79- ( canonicalPathsToStrings ??= new Map ( ) ) . set (
80- canonicalPath ,
81- ( canonicalPathsToStrings ?. get ( canonicalPath ) ?? new Set ( ) ) . add ( path ) ,
82- ) ;
83- }
79+ if ( actuals . get ( path ) ! . length !== 1 ) return ;
80+ const canonicalPath = getCanonicalFileName ( path ) ;
81+ if ( canonicalPath !== path ) {
82+ ( canonicalPathsToStrings ??= new Map ( ) ) . set (
83+ canonicalPath ,
84+ ( canonicalPathsToStrings ?. get ( canonicalPath ) ?? new Set ( ) ) . add ( path ) ,
85+ ) ;
86+ }
87+ const real = system . realpath ( path ) ;
88+ ( pathToReal ??= new Map ( ) ) . set ( path , real ) ;
89+ if ( real === path ) return ;
90+ const canonicalReal = getCanonicalFileName ( real ) ;
91+ if ( getCanonicalFileName ( path ) !== canonicalReal ) {
92+ ( realToLinked ??= createMultiMap ( ) ) . add ( canonicalReal , path ) ;
8493 }
8594 }
8695
8796 function remove ( path : string , data : Data ) {
8897 actuals . remove ( path , data ) ;
89- if ( ! actuals . has ( path ) ) {
90- const canonicalPath = getCanonicalFileName ( path ) ;
91- if ( canonicalPath !== path ) {
92- const existing = canonicalPathsToStrings ! . get ( canonicalPath ) ;
93- if ( existing ! . size === 1 ) canonicalPathsToStrings ! . delete ( canonicalPath ) ;
94- else existing ! . delete ( path ) ;
95- }
98+ if ( actuals . has ( path ) ) return ;
99+ const canonicalPath = getCanonicalFileName ( path ) ;
100+ if ( canonicalPath !== path ) {
101+ const existing = canonicalPathsToStrings ! . get ( canonicalPath ) ;
102+ if ( existing ! . size === 1 ) canonicalPathsToStrings ! . delete ( canonicalPath ) ;
103+ else existing ! . delete ( path ) ;
104+ }
105+ const real = pathToReal ?. get ( path ) ! ;
106+ pathToReal ! . delete ( path ) ;
107+ if ( real === path ) return ;
108+ const canonicalReal = getCanonicalFileName ( real ) ;
109+ if ( getCanonicalFileName ( path ) !== canonicalReal ) {
110+ realToLinked ! . remove ( canonicalReal , path ) ;
96111 }
97112 }
98113
99- function forEach ( path : string , cb : ( data : Data ) => void ) {
100- let allData : Data [ ] | undefined ;
101- allData = addRange ( allData , actuals . get ( path ) ) ;
114+ function getAllData ( path : string ) {
115+ let allData : Map < string , Data [ ] > | undefined ;
116+ addData ( path ) ;
102117 const canonicalPath = getCanonicalFileName ( path ) ;
103- if ( canonicalPath !== path ) allData = addRange ( allData , actuals . get ( canonicalPath ) ) ;
118+ if ( canonicalPath !== path ) addData ( canonicalPath ) ;
104119 canonicalPathsToStrings ?. get ( canonicalPath ) ?. forEach ( canonicalSamePath => {
105120 if ( canonicalSamePath !== path && canonicalSamePath !== canonicalPath ) {
106- allData = addRange ( allData , actuals . get ( canonicalSamePath ) ) ;
121+ addData ( canonicalSamePath ) ;
107122 }
108123 } ) ;
109- allData ?. forEach ( cb ) ;
124+ return allData ;
125+ function addData ( path : string ) {
126+ const data = actuals . get ( path ) ;
127+ if ( data ) ( allData ??= new Map ( ) ) . set ( path , data ) ;
128+ }
129+ }
130+
131+ function forEach ( path : string , cb : ( data : Data , path : string ) => void ) {
132+ const real = system . realpath ( path ) ;
133+ const canonicalPath = getCanonicalFileName ( path ) ;
134+ const canonicalReal = getCanonicalFileName ( real ) ;
135+ let allData = canonicalPath === canonicalReal ? getAllData ( path ) : getAllData ( real ) ;
136+ realToLinked ?. get ( canonicalReal ) ?. forEach ( linked => {
137+ if ( allData ?. has ( linked ) ) return ;
138+ const data = actuals . get ( linked ) ;
139+ if ( data ) ( allData ??= new Map ( ) ) . set ( linked , data ) ;
140+ } ) ;
141+ allData ?. forEach ( ( data , path ) => data . forEach ( d => cb ( d , path ) ) ) ;
110142 }
111143
112144 function serialize ( baseline : string [ ] ) {
0 commit comments