1
1
import * as ts from 'typescript' ;
2
- import { dirname } from 'path' ;
2
+ import { dirname , join } from 'path' ;
3
3
import { createPassiveFileSystem } from '../file-system/PassiveFileSystem' ;
4
4
import forwardSlash from '../../utils/path/forwardSlash' ;
5
5
import { createRealFileSystem } from '../file-system/RealFileSystem' ;
6
6
7
7
interface ControlledTypeScriptSystem extends ts . System {
8
8
// control watcher
9
+ invokeFileCreated ( path : string ) : void ;
9
10
invokeFileChanged ( path : string ) : void ;
10
11
invokeFileDeleted ( path : string ) : void ;
12
+ pollAndInvokeCreatedOrDeleted ( ) : void ;
11
13
// control cache
12
14
clearCache ( ) : void ;
13
15
// mark these methods as defined - not optional
@@ -41,9 +43,10 @@ function createControlledTypeScriptSystem(
41
43
mode : FileSystemMode = 'readonly'
42
44
) : ControlledTypeScriptSystem {
43
45
// watchers
44
- const fileWatchersMap = new Map < string , ts . FileWatcherCallback [ ] > ( ) ;
45
- const directoryWatchersMap = new Map < string , ts . DirectoryWatcherCallback [ ] > ( ) ;
46
- const recursiveDirectoryWatchersMap = new Map < string , ts . DirectoryWatcherCallback [ ] > ( ) ;
46
+ const fileWatcherCallbacksMap = new Map < string , ts . FileWatcherCallback [ ] > ( ) ;
47
+ const directoryWatcherCallbacksMap = new Map < string , ts . DirectoryWatcherCallback [ ] > ( ) ;
48
+ const recursiveDirectoryWatcherCallbacksMap = new Map < string , ts . DirectoryWatcherCallback [ ] > ( ) ;
49
+ const directorySnapshots = new Map < string , string [ ] > ( ) ;
47
50
const deletedFiles = new Map < string , boolean > ( ) ;
48
51
// eslint-disable-next-line @typescript-eslint/no-explicit-any
49
52
const timeoutCallbacks = new Set < any > ( ) ;
@@ -82,10 +85,12 @@ function createControlledTypeScriptSystem(
82
85
function invokeFileWatchers ( path : string , event : ts . FileWatcherEventKind ) {
83
86
const normalizedPath = realFileSystem . normalizePath ( path ) ;
84
87
85
- const fileWatchers = fileWatchersMap . get ( normalizedPath ) ;
86
- if ( fileWatchers ) {
88
+ const fileWatcherCallbacks = fileWatcherCallbacksMap . get ( normalizedPath ) ;
89
+ if ( fileWatcherCallbacks ) {
87
90
// typescript expects normalized paths with posix forward slash
88
- fileWatchers . forEach ( ( fileWatcher ) => fileWatcher ( forwardSlash ( normalizedPath ) , event ) ) ;
91
+ fileWatcherCallbacks . forEach ( ( fileWatcherCallback ) =>
92
+ fileWatcherCallback ( forwardSlash ( normalizedPath ) , event )
93
+ ) ;
89
94
}
90
95
}
91
96
@@ -97,24 +102,42 @@ function createControlledTypeScriptSystem(
97
102
return ;
98
103
}
99
104
100
- const directoryWatchers = directoryWatchersMap . get ( directory ) ;
101
- if ( directoryWatchers ) {
102
- directoryWatchers . forEach ( ( directoryWatcher ) =>
103
- directoryWatcher ( forwardSlash ( normalizedPath ) )
105
+ const directoryWatcherCallbacks = directoryWatcherCallbacksMap . get ( directory ) ;
106
+ if ( directoryWatcherCallbacks ) {
107
+ directoryWatcherCallbacks . forEach ( ( directoryWatcherCallback ) =>
108
+ directoryWatcherCallback ( forwardSlash ( normalizedPath ) )
104
109
) ;
105
110
}
106
111
107
- recursiveDirectoryWatchersMap . forEach ( ( recursiveDirectoryWatchers , watchedDirectory ) => {
108
- if (
109
- watchedDirectory === directory ||
110
- ( directory . startsWith ( watchedDirectory ) &&
111
- forwardSlash ( directory ) [ watchedDirectory . length ] === '/' )
112
- ) {
113
- recursiveDirectoryWatchers . forEach ( ( recursiveDirectoryWatcher ) =>
114
- recursiveDirectoryWatcher ( forwardSlash ( normalizedPath ) )
115
- ) ;
112
+ recursiveDirectoryWatcherCallbacksMap . forEach (
113
+ ( recursiveDirectoryWatcherCallbacks , watchedDirectory ) => {
114
+ if (
115
+ watchedDirectory === directory ||
116
+ ( directory . startsWith ( watchedDirectory ) &&
117
+ forwardSlash ( directory ) [ watchedDirectory . length ] === '/' )
118
+ ) {
119
+ recursiveDirectoryWatcherCallbacks . forEach ( ( recursiveDirectoryWatcherCallback ) =>
120
+ recursiveDirectoryWatcherCallback ( forwardSlash ( normalizedPath ) )
121
+ ) ;
122
+ }
116
123
}
117
- } ) ;
124
+ ) ;
125
+ }
126
+
127
+ function updateDirectorySnapshot ( path : string , recursive = false ) {
128
+ const dirents = passiveFileSystem . readDir ( path ) ;
129
+
130
+ if ( ! directorySnapshots . has ( path ) ) {
131
+ directorySnapshots . set (
132
+ path ,
133
+ dirents . filter ( ( dirent ) => dirent . isFile ( ) ) . map ( ( dirent ) => join ( path , dirent . name ) )
134
+ ) ;
135
+ }
136
+ if ( recursive ) {
137
+ dirents
138
+ . filter ( ( dirent ) => dirent . isDirectory ( ) )
139
+ . forEach ( ( dirent ) => updateDirectorySnapshot ( join ( path , dirent . name ) ) ) ;
140
+ }
118
141
}
119
142
120
143
function getWriteFileSystem ( path : string ) {
@@ -180,15 +203,17 @@ function createControlledTypeScriptSystem(
180
203
invokeFileWatchers ( path , ts . FileWatcherEventKind . Changed ) ;
181
204
} ,
182
205
watchFile ( path : string , callback : ts . FileWatcherCallback ) : ts . FileWatcher {
183
- return createWatcher ( fileWatchersMap , path , callback ) ;
206
+ return createWatcher ( fileWatcherCallbacksMap , path , callback ) ;
184
207
} ,
185
208
watchDirectory (
186
209
path : string ,
187
210
callback : ts . DirectoryWatcherCallback ,
188
211
recursive = false
189
212
) : ts . FileWatcher {
213
+ updateDirectorySnapshot ( path , recursive ) ;
214
+
190
215
return createWatcher (
191
- recursive ? recursiveDirectoryWatchersMap : directoryWatchersMap ,
216
+ recursive ? recursiveDirectoryWatcherCallbacksMap : directoryWatcherCallbacksMap ,
192
217
path ,
193
218
callback
194
219
) ;
@@ -212,10 +237,18 @@ function createControlledTypeScriptSystem(
212
237
await new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
213
238
}
214
239
} ,
240
+ invokeFileCreated ( path : string ) {
241
+ const normalizedPath = realFileSystem . normalizePath ( path ) ;
242
+
243
+ invokeFileWatchers ( path , ts . FileWatcherEventKind . Created ) ;
244
+ invokeDirectoryWatchers ( normalizedPath ) ;
245
+
246
+ deletedFiles . set ( normalizedPath , false ) ;
247
+ } ,
215
248
invokeFileChanged ( path : string ) {
216
249
const normalizedPath = realFileSystem . normalizePath ( path ) ;
217
250
218
- if ( deletedFiles . get ( normalizedPath ) || ! fileWatchersMap . has ( normalizedPath ) ) {
251
+ if ( deletedFiles . get ( normalizedPath ) || ! fileWatcherCallbacksMap . has ( normalizedPath ) ) {
219
252
invokeFileWatchers ( path , ts . FileWatcherEventKind . Created ) ;
220
253
invokeDirectoryWatchers ( normalizedPath ) ;
221
254
@@ -234,6 +267,52 @@ function createControlledTypeScriptSystem(
234
267
deletedFiles . set ( normalizedPath , true ) ;
235
268
}
236
269
} ,
270
+ pollAndInvokeCreatedOrDeleted ( ) {
271
+ const prevDirectorySnapshots = new Map ( directorySnapshots ) ;
272
+
273
+ directorySnapshots . clear ( ) ;
274
+ directoryWatcherCallbacksMap . forEach ( ( directoryWatcherCallback , path ) => {
275
+ updateDirectorySnapshot ( path , false ) ;
276
+ } ) ;
277
+ recursiveDirectoryWatcherCallbacksMap . forEach ( ( recursiveDirectoryWatcherCallback , path ) => {
278
+ updateDirectorySnapshot ( path , true ) ;
279
+ } ) ;
280
+
281
+ const filesCreated = new Set < string > ( ) ;
282
+ const filesDeleted = new Set < string > ( ) ;
283
+
284
+ function diffDirectorySnapshots (
285
+ prevFiles : string [ ] | undefined ,
286
+ nextFiles : string [ ] | undefined
287
+ ) {
288
+ if ( prevFiles && nextFiles ) {
289
+ nextFiles
290
+ . filter ( ( nextFile ) => ! prevFiles . includes ( nextFile ) )
291
+ . forEach ( ( createdFile ) => {
292
+ filesCreated . add ( createdFile ) ;
293
+ } ) ;
294
+ prevFiles
295
+ . filter ( ( prevFile ) => ! nextFiles . includes ( prevFile ) )
296
+ . forEach ( ( deletedFile ) => {
297
+ filesDeleted . add ( deletedFile ) ;
298
+ } ) ;
299
+ }
300
+ }
301
+
302
+ prevDirectorySnapshots . forEach ( ( prevFiles , path ) =>
303
+ diffDirectorySnapshots ( prevFiles , directorySnapshots . get ( path ) )
304
+ ) ;
305
+ directorySnapshots . forEach ( ( nextFiles , path ) =>
306
+ diffDirectorySnapshots ( prevDirectorySnapshots . get ( path ) , nextFiles )
307
+ ) ;
308
+
309
+ filesCreated . forEach ( ( path ) => {
310
+ controlledSystem . invokeFileCreated ( path ) ;
311
+ } ) ;
312
+ filesDeleted . forEach ( ( path ) => {
313
+ controlledSystem . invokeFileDeleted ( path ) ;
314
+ } ) ;
315
+ } ,
237
316
clearCache ( ) {
238
317
passiveFileSystem . clearCache ( ) ;
239
318
realFileSystem . clearCache ( ) ;
0 commit comments