@@ -22,7 +22,7 @@ namespace ts {
2222 * This api is only for internal use
2323 */
2424 /*@internal */
25- getFilesAffectedBy ( programOfThisState : Program , path : Path ) : ReadonlyArray < SourceFile > ;
25+ getFilesAffectedBy ( programOfThisState : Program , path : Path , cancellationToken : CancellationToken ) : ReadonlyArray < SourceFile > ;
2626 }
2727
2828 /**
@@ -86,7 +86,7 @@ namespace ts {
8686 * Get the files affected by the source file.
8787 * This is dependent on whether its a module emit or not and hence function expression
8888 */
89- let getEmitDependentFilesAffectedBy : ( programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > | undefined ) => ReadonlyArray < SourceFile > ;
89+ let getEmitDependentFilesAffectedBy : ( programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined ) => ReadonlyArray < SourceFile > ;
9090
9191 /**
9292 * Cache of semantic diagnostics for files with their Path being the key
@@ -272,38 +272,65 @@ namespace ts {
272272 /**
273273 * Gets the files affected by the path from the program
274274 */
275- function getFilesAffectedBy ( programOfThisState : Program , path : Path , cacheToUpdateSignature ?: Map < string > ) : ReadonlyArray < SourceFile > {
275+ function getFilesAffectedBy ( programOfThisState : Program , path : Path , cancellationToken : CancellationToken | undefined , cacheToUpdateSignature ?: Map < string > ) : ReadonlyArray < SourceFile > {
276+ // Since the operation could be cancelled, the signatures are always stored in the cache
277+ // They will be commited once it is safe to use them
278+ // eg when calling this api from tsserver, if there is no cancellation of the operation
279+ // In the other cases the affected files signatures are commited only after the iteration through the result is complete
280+ const signatureCache = cacheToUpdateSignature || createMap ( ) ;
276281 const sourceFile = programOfThisState . getSourceFileByPath ( path ) ;
277282 if ( ! sourceFile ) {
278283 return emptyArray ;
279284 }
280285
281- if ( ! updateShapeSignature ( programOfThisState , sourceFile , cacheToUpdateSignature ) ) {
286+ if ( ! updateShapeSignature ( programOfThisState , sourceFile , signatureCache , cancellationToken ) ) {
282287 return [ sourceFile ] ;
283288 }
284289
285- return getEmitDependentFilesAffectedBy ( programOfThisState , sourceFile , cacheToUpdateSignature ) ;
290+ const result = getEmitDependentFilesAffectedBy ( programOfThisState , sourceFile , signatureCache , cancellationToken ) ;
291+ if ( ! cacheToUpdateSignature ) {
292+ // Commit all the signatures in the signature cache
293+ updateSignaturesFromCache ( signatureCache ) ;
294+ }
295+ return result ;
296+ }
297+
298+ /**
299+ * Updates the signatures from the cache
300+ * This should be called whenever it is safe to commit the state of the builder
301+ */
302+ function updateSignaturesFromCache ( signatureCache : Map < string > ) {
303+ signatureCache . forEach ( ( signature , path ) => {
304+ fileInfos . get ( path ) . signature = signature ;
305+ hasCalledUpdateShapeSignature . set ( path , true ) ;
306+ } ) ;
286307 }
287308
288- function getNextAffectedFile ( programOfThisState : Program ) : SourceFile | Program | undefined {
309+ /**
310+ * This function returns the next affected file to be processed.
311+ * Note that until doneAffected is called it would keep reporting same result
312+ * This is to allow the callers to be able to actually remove affected file only when the operation is complete
313+ * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained
314+ */
315+ function getNextAffectedFile ( programOfThisState : Program , cancellationToken : CancellationToken | undefined ) : SourceFile | Program | undefined {
289316 while ( true ) {
290317 if ( affectedFiles ) {
291318 while ( affectedFilesIndex < affectedFiles . length ) {
292319 const affectedFile = affectedFiles [ affectedFilesIndex ] ;
293- affectedFilesIndex ++ ;
294320 if ( ! seenAffectedFiles . has ( affectedFile . path ) ) {
295321 // Set the next affected file as seen and remove the cached semantic diagnostics
296- seenAffectedFiles . set ( affectedFile . path , true ) ;
297322 semanticDiagnosticsPerFile . delete ( affectedFile . path ) ;
298323 return affectedFile ;
299324 }
325+ seenAffectedFiles . set ( affectedFile . path , true ) ;
326+ affectedFilesIndex ++ ;
300327 }
301328
302329 // Remove the changed file from the change set
303330 changedFilesSet . delete ( currentChangedFilePath ) ;
304331 currentChangedFilePath = undefined ;
305332 // Commit the changes in file signature
306- currentAffectedFilesSignatures . forEach ( ( signature , path ) => fileInfos . get ( path ) . signature = signature ) ;
333+ updateSignaturesFromCache ( currentAffectedFilesSignatures ) ;
307334 currentAffectedFilesSignatures . clear ( ) ;
308335 affectedFiles = undefined ;
309336 }
@@ -320,21 +347,37 @@ namespace ts {
320347 // so operations are performed directly on program, return program
321348 if ( compilerOptions . outFile || compilerOptions . out ) {
322349 Debug . assert ( semanticDiagnosticsPerFile . size === 0 ) ;
323- changedFilesSet . clear ( ) ;
324350 return programOfThisState ;
325351 }
326352
327353 // Get next batch of affected files
354+ currentAffectedFilesSignatures . clear ( ) ;
355+ affectedFiles = getFilesAffectedBy ( programOfThisState , nextKey . value as Path , cancellationToken , currentAffectedFilesSignatures ) ;
328356 currentChangedFilePath = nextKey . value as Path ;
357+ semanticDiagnosticsPerFile . delete ( currentChangedFilePath ) ;
329358 affectedFilesIndex = 0 ;
330- affectedFiles = getFilesAffectedBy ( programOfThisState , nextKey . value as Path , currentAffectedFilesSignatures ) ;
359+ }
360+ }
361+
362+ /**
363+ * This is called after completing operation on the next affected file.
364+ * The operations here are postponed to ensure that cancellation during the iteration is handled correctly
365+ */
366+ function doneWithAffectedFile ( programOfThisState : Program , affected : SourceFile | Program ) {
367+ if ( affected === programOfThisState ) {
368+ changedFilesSet . clear ( ) ;
369+ }
370+ else {
371+ seenAffectedFiles . set ( ( < SourceFile > affected ) . path , true ) ;
372+ affectedFilesIndex ++ ;
331373 }
332374 }
333375
334376 /**
335377 * Returns the result with affected file
336378 */
337- function toAffectedFileResult < T > ( result : T , affected : SourceFile | Program ) : AffectedFileResult < T > {
379+ function toAffectedFileResult < T > ( programOfThisState : Program , result : T , affected : SourceFile | Program ) : AffectedFileResult < T > {
380+ doneWithAffectedFile ( programOfThisState , affected ) ;
338381 return { result, affected } ;
339382 }
340383
@@ -343,14 +386,15 @@ namespace ts {
343386 * Returns undefined when iteration is complete
344387 */
345388 function emitNextAffectedFile ( programOfThisState : Program , writeFileCallback : WriteFileCallback , cancellationToken ?: CancellationToken , customTransformers ?: CustomTransformers ) : AffectedFileResult < EmitResult > {
346- const affectedFile = getNextAffectedFile ( programOfThisState ) ;
389+ const affectedFile = getNextAffectedFile ( programOfThisState , cancellationToken ) ;
347390 if ( ! affectedFile ) {
348391 // Done
349392 return undefined ;
350393 }
351394 else if ( affectedFile === programOfThisState ) {
352395 // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
353396 return toAffectedFileResult (
397+ programOfThisState ,
354398 programOfThisState . emit ( /*targetSourceFile*/ undefined , writeFileCallback , cancellationToken , /*emitOnlyDtsFiles*/ false , customTransformers ) ,
355399 programOfThisState
356400 ) ;
@@ -359,6 +403,7 @@ namespace ts {
359403 // Emit the affected file
360404 const targetSourceFile = affectedFile as SourceFile ;
361405 return toAffectedFileResult (
406+ programOfThisState ,
362407 programOfThisState . emit ( targetSourceFile , writeFileCallback , cancellationToken , /*emitOnlyDtsFiles*/ false , customTransformers ) ,
363408 targetSourceFile
364409 ) ;
@@ -370,14 +415,15 @@ namespace ts {
370415 */
371416 function getSemanticDiagnosticsOfNextAffectedFile ( programOfThisState : Program , cancellationToken ?: CancellationToken , ignoreSourceFile ?: ( sourceFile : SourceFile ) => boolean ) : AffectedFileResult < ReadonlyArray < Diagnostic > > {
372417 while ( true ) {
373- const affectedFile = getNextAffectedFile ( programOfThisState ) ;
418+ const affectedFile = getNextAffectedFile ( programOfThisState , cancellationToken ) ;
374419 if ( ! affectedFile ) {
375420 // Done
376421 return undefined ;
377422 }
378423 else if ( affectedFile === programOfThisState ) {
379424 // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified)
380425 return toAffectedFileResult (
426+ programOfThisState ,
381427 programOfThisState . getSemanticDiagnostics ( /*targetSourceFile*/ undefined , cancellationToken ) ,
382428 programOfThisState
383429 ) ;
@@ -387,10 +433,12 @@ namespace ts {
387433 const targetSourceFile = affectedFile as SourceFile ;
388434 if ( ignoreSourceFile && ignoreSourceFile ( targetSourceFile ) ) {
389435 // Get next affected file
436+ doneWithAffectedFile ( programOfThisState , targetSourceFile ) ;
390437 continue ;
391438 }
392439
393440 return toAffectedFileResult (
441+ programOfThisState ,
394442 getSemanticDiagnosticsOfFile ( programOfThisState , targetSourceFile , cancellationToken ) ,
395443 targetSourceFile
396444 ) ;
@@ -505,46 +553,34 @@ namespace ts {
505553 * Returns if the shape of the signature has changed since last emit
506554 * Note that it also updates the current signature as the latest signature for the file
507555 */
508- function updateShapeSignature ( program : Program , sourceFile : SourceFile , cacheToUpdateSignature : Map < string > | undefined ) {
556+ function updateShapeSignature ( program : Program , sourceFile : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined ) {
509557 Debug . assert ( ! ! sourceFile ) ;
510558
511559 // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
512- if ( hasCalledUpdateShapeSignature . has ( sourceFile . path ) ) {
560+ if ( hasCalledUpdateShapeSignature . has ( sourceFile . path ) || cacheToUpdateSignature . has ( sourceFile . path ) ) {
513561 return false ;
514562 }
515563
516- Debug . assert ( ! cacheToUpdateSignature || ! cacheToUpdateSignature . has ( sourceFile . path ) ) ;
517- hasCalledUpdateShapeSignature . set ( sourceFile . path , true ) ;
518564 const info = fileInfos . get ( sourceFile . path ) ;
519565 Debug . assert ( ! ! info ) ;
520566
521567 const prevSignature = info . signature ;
522568 let latestSignature : string ;
523569 if ( sourceFile . isDeclarationFile ) {
524570 latestSignature = sourceFile . version ;
525- setLatestSigature ( ) ;
526571 }
527572 else {
528- const emitOutput = getFileEmitOutput ( program , sourceFile , /*emitOnlyDtsFiles*/ true ) ;
573+ const emitOutput = getFileEmitOutput ( program , sourceFile , /*emitOnlyDtsFiles*/ true , cancellationToken ) ;
529574 if ( emitOutput . outputFiles && emitOutput . outputFiles . length > 0 ) {
530575 latestSignature = options . computeHash ( emitOutput . outputFiles [ 0 ] . text ) ;
531- setLatestSigature ( ) ;
532576 }
533577 else {
534578 latestSignature = prevSignature ;
535579 }
536580 }
581+ cacheToUpdateSignature . set ( sourceFile . path , latestSignature ) ;
537582
538583 return ! prevSignature || latestSignature !== prevSignature ;
539-
540- function setLatestSigature ( ) {
541- if ( cacheToUpdateSignature ) {
542- cacheToUpdateSignature . set ( sourceFile . path , latestSignature ) ;
543- }
544- else {
545- info . signature = latestSignature ;
546- }
547- }
548584 }
549585
550586 /**
@@ -652,7 +688,7 @@ namespace ts {
652688 /**
653689 * When program emits modular code, gets the files affected by the sourceFile whose shape has changed
654690 */
655- function getFilesAffectedByUpdatedShapeWhenModuleEmit ( programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > | undefined ) {
691+ function getFilesAffectedByUpdatedShapeWhenModuleEmit ( programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined ) {
656692 if ( ! isExternalModule ( sourceFileWithUpdatedShape ) && ! containsOnlyAmbientModules ( sourceFileWithUpdatedShape ) ) {
657693 return getAllFilesExcludingDefaultLibraryFile ( programOfThisState , sourceFileWithUpdatedShape ) ;
658694 }
@@ -675,7 +711,7 @@ namespace ts {
675711 if ( ! seenFileNamesMap . has ( currentPath ) ) {
676712 const currentSourceFile = programOfThisState . getSourceFileByPath ( currentPath ) ;
677713 seenFileNamesMap . set ( currentPath , currentSourceFile ) ;
678- if ( currentSourceFile && updateShapeSignature ( programOfThisState , currentSourceFile , cacheToUpdateSignature ) ) {
714+ if ( currentSourceFile && updateShapeSignature ( programOfThisState , currentSourceFile , cacheToUpdateSignature , cancellationToken ) ) {
679715 queue . push ( ...getReferencedByPaths ( currentPath ) ) ;
680716 }
681717 }
0 commit comments