@@ -18,15 +18,29 @@ namespace ts {
18
18
text : string ;
19
19
}
20
20
21
+ export interface ChangedProgramFiles {
22
+ /** Minimal set of list of files that require emit */
23
+ readonly filesToEmit : ReadonlyArray < string > ;
24
+ /** File paths of source files changed/added/removed or affected by changed files */
25
+ readonly changedFiles : ReadonlyArray < string > ;
26
+ }
27
+
21
28
export interface Builder {
22
29
/**
23
30
* This is the callback when file infos in the builder are updated
24
31
*/
25
32
onProgramUpdateGraph ( program : Program , hasInvalidatedResolution : HasInvalidatedResolution ) : void ;
26
33
getFilesAffectedBy ( program : Program , path : Path ) : string [ ] ;
27
34
emitFile ( program : Program , path : Path ) : EmitOutput ;
35
+
36
+ /** Emit the changed files and clear the cache of the changed files */
28
37
emitChangedFiles ( program : Program ) : EmitOutputDetailed [ ] ;
38
+ /** Get the changed files since last query and then clear the cache of changed files */
39
+ getChangedProgramFiles ( program : Program ) : ChangedProgramFiles ;
40
+ /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */
29
41
getSemanticDiagnostics ( program : Program , cancellationToken ?: CancellationToken ) : Diagnostic [ ] ;
42
+
43
+ /** Called to reset the status of the builder */
30
44
clear ( ) : void ;
31
45
}
32
46
@@ -73,16 +87,18 @@ namespace ts {
73
87
) : Builder {
74
88
let isModuleEmit : boolean | undefined ;
75
89
// Last checked shape signature for the file info
76
- type FileInfo = { version : string ; signature : string ; } ;
77
- let fileInfos : Map < FileInfo > ;
90
+ type FileInfo = { fileName : string ; version : string ; signature : string ; } ;
91
+ const fileInfos = createMap < FileInfo > ( ) ;
78
92
const semanticDiagnosticsPerFile = createMap < Diagnostic [ ] > ( ) ;
79
- let changedFilesSinceLastEmit : Map < true > ;
93
+ /** The map has key by source file's path that has been changed */
94
+ const changedFileNames = createMap < string > ( ) ;
80
95
let emitHandler : EmitHandler ;
81
96
return {
82
97
onProgramUpdateGraph,
83
98
getFilesAffectedBy,
84
99
emitFile,
85
100
emitChangedFiles,
101
+ getChangedProgramFiles,
86
102
getSemanticDiagnostics,
87
103
clear
88
104
} ;
@@ -92,13 +108,11 @@ namespace ts {
92
108
if ( isModuleEmit !== currentIsModuleEmit ) {
93
109
isModuleEmit = currentIsModuleEmit ;
94
110
emitHandler = isModuleEmit ? getModuleEmitHandler ( ) : getNonModuleEmitHandler ( ) ;
95
- fileInfos = undefined ;
111
+ fileInfos . clear ( ) ;
96
112
semanticDiagnosticsPerFile . clear ( ) ;
97
113
}
98
-
99
- changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap < true > ( ) ;
100
114
mutateMap (
101
- fileInfos || ( fileInfos = createMap ( ) ) ,
115
+ fileInfos ,
102
116
arrayToMap ( program . getSourceFiles ( ) , sourceFile => sourceFile . path ) ,
103
117
{
104
118
// Add new file info
@@ -111,27 +125,26 @@ namespace ts {
111
125
) ;
112
126
}
113
127
114
- function registerChangedFile ( path : Path ) {
115
- changedFilesSinceLastEmit . set ( path , true ) ;
128
+ function registerChangedFile ( path : Path , fileName : string ) {
129
+ changedFileNames . set ( path , fileName ) ;
116
130
// All changed files need to re-evaluate its semantic diagnostics
117
131
semanticDiagnosticsPerFile . delete ( path ) ;
118
132
}
119
133
120
134
function addNewFileInfo ( program : Program , sourceFile : SourceFile ) : FileInfo {
121
- registerChangedFile ( sourceFile . path ) ;
135
+ registerChangedFile ( sourceFile . path , sourceFile . fileName ) ;
122
136
emitHandler . addScriptInfo ( program , sourceFile ) ;
123
- return { version : sourceFile . version , signature : undefined } ;
137
+ return { fileName : sourceFile . fileName , version : sourceFile . version , signature : undefined } ;
124
138
}
125
139
126
- function removeExistingFileInfo ( path : Path , _existingFileInfo : FileInfo ) {
127
- registerChangedFile ( path ) ;
140
+ function removeExistingFileInfo ( path : Path , existingFileInfo : FileInfo ) {
141
+ registerChangedFile ( path , existingFileInfo . fileName ) ;
128
142
emitHandler . removeScriptInfo ( path ) ;
129
143
}
130
144
131
145
function updateExistingFileInfo ( program : Program , existingInfo : FileInfo , sourceFile : SourceFile , hasInvalidatedResolution : HasInvalidatedResolution ) {
132
146
if ( existingInfo . version !== sourceFile . version || hasInvalidatedResolution ( sourceFile . path ) ) {
133
- registerChangedFile ( sourceFile . path ) ;
134
- semanticDiagnosticsPerFile . delete ( sourceFile . path ) ;
147
+ registerChangedFile ( sourceFile . path , sourceFile . fileName ) ;
135
148
existingInfo . version = sourceFile . version ;
136
149
emitHandler . updateScriptInfo ( program , sourceFile ) ;
137
150
}
@@ -154,7 +167,7 @@ namespace ts {
154
167
155
168
const sourceFile = program . getSourceFile ( path ) ;
156
169
const singleFileResult = sourceFile && shouldEmitFile ( sourceFile ) ? [ sourceFile . fileName ] : [ ] ;
157
- const info = fileInfos && fileInfos . get ( path ) ;
170
+ const info = fileInfos . get ( path ) ;
158
171
if ( ! info || ! updateShapeSignature ( program , sourceFile , info ) ) {
159
172
return singleFileResult ;
160
173
}
@@ -165,62 +178,73 @@ namespace ts {
165
178
166
179
function emitFile ( program : Program , path : Path ) {
167
180
ensureProgramGraph ( program ) ;
168
- if ( ! fileInfos || ! fileInfos . has ( path ) ) {
181
+ if ( ! fileInfos . has ( path ) ) {
169
182
return { outputFiles : [ ] , emitSkipped : true } ;
170
183
}
171
184
172
185
return getEmitOutput ( program , program . getSourceFileByPath ( path ) , /*emitOnlyDtsFiles*/ false , /*isDetailed*/ false ) ;
173
186
}
174
187
175
- function emitChangedFiles ( program : Program ) : EmitOutputDetailed [ ] {
176
- ensureProgramGraph ( program ) ;
177
- const result : EmitOutputDetailed [ ] = [ ] ;
178
- if ( changedFilesSinceLastEmit ) {
179
- const seenFiles = createMap < SourceFile > ( ) ;
180
- changedFilesSinceLastEmit . forEach ( ( __value , path : Path ) => {
181
- const affectedFiles = getFilesAffectedBy ( program , path ) ;
182
- for ( const file of affectedFiles ) {
183
- if ( ! seenFiles . has ( file ) ) {
184
- const sourceFile = program . getSourceFile ( file ) ;
185
- seenFiles . set ( file , sourceFile ) ;
186
- if ( sourceFile ) {
187
- // Any affected file shouldnt have the cached diagnostics
188
- semanticDiagnosticsPerFile . delete ( sourceFile . path ) ;
189
-
190
- const emitOutput = getEmitOutput ( program , sourceFile , /*emitOnlyDtsFiles*/ false , /*isDetailed*/ true ) as EmitOutputDetailed ;
191
- result . push ( emitOutput ) ;
192
-
193
- // mark all the emitted source files as seen
194
- if ( emitOutput . emittedSourceFiles ) {
195
- for ( const file of emitOutput . emittedSourceFiles ) {
196
- seenFiles . set ( file . fileName , file ) ;
197
- }
198
- }
188
+ function enumerateChangedFilesSet (
189
+ program : Program ,
190
+ onChangedFile : ( fileName : string ) => void ,
191
+ onAffectedFile : ( fileName : string , sourceFile : SourceFile ) => void
192
+ ) {
193
+ changedFileNames . forEach ( ( fileName , path ) => {
194
+ onChangedFile ( fileName ) ;
195
+ const affectedFiles = getFilesAffectedBy ( program , path as Path ) ;
196
+ for ( const file of affectedFiles ) {
197
+ onAffectedFile ( file , program . getSourceFile ( file ) ) ;
198
+ }
199
+ } ) ;
200
+ }
201
+
202
+ function enumerateChangedFilesEmitOutput (
203
+ program : Program ,
204
+ emitOnlyDtsFiles : boolean ,
205
+ onChangedFile : ( fileName : string ) => void ,
206
+ onEmitOutput : ( emitOutput : EmitOutputDetailed , sourceFile : SourceFile ) => void
207
+ ) {
208
+ const seenFiles = createMap < SourceFile > ( ) ;
209
+ enumerateChangedFilesSet ( program , onChangedFile , ( fileName , sourceFile ) => {
210
+ if ( ! seenFiles . has ( fileName ) ) {
211
+ seenFiles . set ( fileName , sourceFile ) ;
212
+ if ( sourceFile ) {
213
+ // Any affected file shouldnt have the cached diagnostics
214
+ semanticDiagnosticsPerFile . delete ( sourceFile . path ) ;
215
+
216
+ const emitOutput = getEmitOutput ( program , sourceFile , emitOnlyDtsFiles , /*isDetailed*/ true ) as EmitOutputDetailed ;
217
+ onEmitOutput ( emitOutput , sourceFile ) ;
218
+
219
+ // mark all the emitted source files as seen
220
+ if ( emitOutput . emittedSourceFiles ) {
221
+ for ( const file of emitOutput . emittedSourceFiles ) {
222
+ seenFiles . set ( file . fileName , file ) ;
199
223
}
200
224
}
201
225
}
202
- } ) ;
226
+ }
227
+ } ) ;
228
+ }
203
229
204
- changedFilesSinceLastEmit = undefined ;
205
- }
230
+ function emitChangedFiles ( program : Program ) : EmitOutputDetailed [ ] {
231
+ ensureProgramGraph ( program ) ;
232
+ const result : EmitOutputDetailed [ ] = [ ] ;
233
+ enumerateChangedFilesEmitOutput ( program , /*emitOnlyDtsFiles*/ false ,
234
+ /*onChangedFile*/ noop , emitOutput => result . push ( emitOutput ) ) ;
235
+ changedFileNames . clear ( ) ;
206
236
return result ;
207
237
}
208
238
209
239
function getSemanticDiagnostics ( program : Program , cancellationToken ?: CancellationToken ) : Diagnostic [ ] {
210
240
ensureProgramGraph ( program ) ;
211
241
212
242
// Ensure that changed files have cleared their respective
213
- if ( changedFilesSinceLastEmit ) {
214
- changedFilesSinceLastEmit . forEach ( ( __value , path : Path ) => {
215
- const affectedFiles = getFilesAffectedBy ( program , path ) ;
216
- for ( const file of affectedFiles ) {
217
- const sourceFile = program . getSourceFile ( file ) ;
218
- if ( sourceFile ) {
219
- semanticDiagnosticsPerFile . delete ( sourceFile . path ) ;
220
- }
221
- }
222
- } ) ;
223
- }
243
+ enumerateChangedFilesSet ( program , /*onChangedFile*/ noop , ( _affectedFileName , sourceFile ) => {
244
+ if ( sourceFile ) {
245
+ semanticDiagnosticsPerFile . delete ( sourceFile . path ) ;
246
+ }
247
+ } ) ;
224
248
225
249
let diagnostics : Diagnostic [ ] ;
226
250
for ( const sourceFile of program . getSourceFiles ( ) ) {
@@ -240,11 +264,36 @@ namespace ts {
240
264
return diagnostics || emptyArray ;
241
265
}
242
266
267
+ function getChangedProgramFiles ( program : Program ) : ChangedProgramFiles {
268
+ ensureProgramGraph ( program ) ;
269
+
270
+ let filesToEmit : string [ ] ;
271
+ let changedFiles : string [ ] ;
272
+ enumerateChangedFilesEmitOutput ( program , /*emitOnlyDtsFiles*/ true ,
273
+ // All the changed files are required to get diagnostics
274
+ changedFileName => addFileForDiagnostics ( changedFileName ) ,
275
+ // Emitted file is for emit as well as diagnostic
276
+ ( _emitOutput , sourceFile ) => {
277
+ ( filesToEmit || ( filesToEmit = [ ] ) ) . push ( sourceFile . fileName ) ;
278
+ addFileForDiagnostics ( sourceFile . fileName ) ;
279
+ } ) ;
280
+ changedFileNames . clear ( ) ;
281
+ return {
282
+ filesToEmit : filesToEmit || emptyArray ,
283
+ changedFiles : changedFiles || emptyArray
284
+ } ;
285
+
286
+ function addFileForDiagnostics ( fileName : string ) {
287
+ ( changedFiles || ( changedFiles = [ ] ) ) . push ( fileName ) ;
288
+ }
289
+ }
290
+
243
291
function clear ( ) {
244
292
isModuleEmit = undefined ;
245
293
emitHandler = undefined ;
246
- fileInfos = undefined ;
294
+ fileInfos . clear ( ) ;
247
295
semanticDiagnosticsPerFile . clear ( ) ;
296
+ changedFileNames . clear ( ) ;
248
297
}
249
298
250
299
/**
@@ -287,9 +336,7 @@ namespace ts {
287
336
}
288
337
289
338
/**
290
- * Gets the referenced files for a file from the program
291
- * @param program
292
- * @param path
339
+ * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
293
340
*/
294
341
function getReferencedFiles ( program : Program , sourceFile : SourceFile ) : Map < true > {
295
342
const referencedFiles = createMap < true > ( ) ;
@@ -371,55 +418,36 @@ namespace ts {
371
418
372
419
function getModuleEmitHandler ( ) : EmitHandler {
373
420
const references = createMap < Map < true > > ( ) ;
374
- const referencedBy = createMultiMap < Path > ( ) ;
375
421
return {
376
- addScriptInfo : ( program , sourceFile ) => {
377
- const refs = createMap < true > ( ) ;
378
- references . set ( sourceFile . path , refs ) ;
379
- setReferences ( program , sourceFile , refs ) ;
380
- } ,
422
+ addScriptInfo : setReferences ,
381
423
removeScriptInfo,
382
- updateScriptInfo : ( program , sourceFile ) => setReferences ( program , sourceFile , references . get ( sourceFile . path ) ) ,
424
+ updateScriptInfo : setReferences ,
383
425
getFilesAffectedByUpdatedShape
384
426
} ;
385
427
386
- function setReferences ( program : Program , sourceFile : SourceFile , existingReferences : Map < true > ) {
387
- const path = sourceFile . path ;
388
- mutateMap (
389
- // Existing references
390
- existingReferences ,
391
- // Updated references
392
- getReferencedFiles ( program , sourceFile ) ,
393
- {
394
- // Creating new Reference: as sourceFile references file with path 'key'
395
- // in other words source file (path) is referenced by 'key'
396
- createNewValue : ( key ) : true => { referencedBy . add ( key , path ) ; return true ; } ,
397
- // Remove existing reference by entry: source file doesnt reference file 'key' any more
398
- // in other words source file (path) is not referenced by 'key'
399
- onDeleteValue : ( key , _existingValue ) => { referencedBy . remove ( key , path ) ; }
400
- }
401
- ) ;
428
+ function setReferences ( program : Program , sourceFile : SourceFile ) {
429
+ references . set ( sourceFile . path , getReferencedFiles ( program , sourceFile ) ) ;
402
430
}
403
431
404
- function removeScriptInfo ( path : Path ) {
432
+ function removeScriptInfo ( removedFilePath : Path ) {
405
433
// Remove existing references
406
- references . forEach ( ( _value , key ) => {
407
- referencedBy . remove ( key , path ) ;
408
- } ) ;
409
- references . delete ( path ) ;
410
-
411
- // Delete the entry and add files referencing this file, as chagned files too
412
- const referencedByPaths = referencedBy . get ( path ) ;
413
- if ( referencedByPaths ) {
414
- for ( const path of referencedByPaths ) {
415
- registerChangedFile ( path ) ;
434
+ references . forEach ( ( referencesInFile , filePath ) => {
435
+ if ( referencesInFile . has ( removedFilePath ) ) {
436
+ // add files referencing the removedFilePath, as changed files too
437
+ const referencedByInfo = fileInfos . get ( filePath ) ;
438
+ if ( referencedByInfo ) {
439
+ registerChangedFile ( filePath as Path , referencedByInfo . fileName ) ;
440
+ }
416
441
}
417
- referencedBy . delete ( path ) ;
418
- }
442
+ } ) ;
443
+ // Delete the entry for the removed file path
444
+ references . delete ( removedFilePath ) ;
419
445
}
420
446
421
- function getReferencedByPaths ( path : Path ) {
422
- return referencedBy . get ( path ) || [ ] ;
447
+ function getReferencedByPaths ( referencedFilePath : Path ) {
448
+ return mapDefinedIter ( references . entries ( ) , ( [ filePath , referencesInFile ] ) =>
449
+ referencesInFile . has ( referencedFilePath ) ? filePath as Path : undefined
450
+ ) ;
423
451
}
424
452
425
453
function getFilesAffectedByUpdatedShape ( program : Program , sourceFile : SourceFile , singleFileResult : string [ ] ) : string [ ] {
@@ -444,7 +472,7 @@ namespace ts {
444
472
// Start with the paths this file was referenced by
445
473
const path = sourceFile . path ;
446
474
setSeenFileName ( path , sourceFile ) ;
447
- const queue = getReferencedByPaths ( path ) . slice ( ) ;
475
+ const queue = getReferencedByPaths ( path ) ;
448
476
while ( queue . length > 0 ) {
449
477
const currentPath = queue . pop ( ) ;
450
478
if ( ! seenFileNamesMap . has ( currentPath ) ) {
0 commit comments