@@ -136,7 +136,7 @@ namespace ts {
136136 onSourceFileRemoved ( path : Path ) : void ;
137137 }
138138
139- export interface BuilderState {
139+ export interface BuilderStateOld {
140140 /**
141141 * Updates the program in the builder to represent new state
142142 */
@@ -156,7 +156,65 @@ namespace ts {
156156 getAllDependencies ( programOfThisState : Program , sourceFile : SourceFile ) : ReadonlyArray < string > ;
157157 }
158158
159- export function createBuilderState ( host : BuilderStateHost ) : BuilderState {
159+ export interface BuilderState {
160+ /**
161+ * Information of the file eg. its version, signature etc
162+ */
163+ fileInfos : Map < FileInfo > ;
164+ /**
165+ * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
166+ * Otherwise undefined
167+ * Thus non undefined value indicates, module emit
168+ */
169+ readonly referencedMap : ReadonlyMap < ReferencedSet > | undefined ;
170+ /**
171+ * Map of files that have already called update signature.
172+ * That means hence forth these files are assumed to have
173+ * no change in their signature for this version of the program
174+ */
175+ hasCalledUpdateShapeSignature : Map < true > ;
176+ /**
177+ * Cache of all files excluding default library file for the current program
178+ */
179+ allFilesExcludingDefaultLibraryFile : ReadonlyArray < SourceFile > | undefined ;
180+ /**
181+ * Cache of all the file names
182+ */
183+ allFileNames : ReadonlyArray < string > | undefined ;
184+ }
185+
186+ export function createBuilderState ( newProgram : Program , getCanonicalFileName : GetCanonicalFileName , oldState ?: BuilderState ) : BuilderState {
187+ const fileInfos = createMap < FileInfo > ( ) ;
188+ const referencedMap = newProgram . getCompilerOptions ( ) . module !== ModuleKind . None ? createMap < ReferencedSet > ( ) : undefined ;
189+ const hasCalledUpdateShapeSignature = createMap < true > ( ) ;
190+ const useOldState = oldState && ! ! oldState . referencedMap !== ! ! referencedMap ;
191+
192+ // Create the reference map, and set the file infos
193+ for ( const sourceFile of newProgram . getSourceFiles ( ) ) {
194+ const version = sourceFile . version ;
195+ const oldInfo = useOldState && oldState . fileInfos . get ( sourceFile . path ) ;
196+ if ( referencedMap ) {
197+ const newReferences = getReferencedFiles ( newProgram , sourceFile , getCanonicalFileName ) ;
198+ if ( newReferences ) {
199+ referencedMap . set ( sourceFile . path , newReferences ) ;
200+ }
201+ }
202+ fileInfos . set ( sourceFile . path , { version, signature : oldInfo && oldInfo . signature } ) ;
203+ }
204+
205+ oldState = undefined ;
206+ newProgram = undefined ;
207+
208+ return {
209+ fileInfos,
210+ referencedMap,
211+ hasCalledUpdateShapeSignature,
212+ allFilesExcludingDefaultLibraryFile : undefined ,
213+ allFileNames : undefined
214+ } ;
215+ }
216+
217+ export function createBuilderStateOld ( host : BuilderStateHost ) : BuilderStateOld {
160218 /**
161219 * Create the canonical file name for identity
162220 */
@@ -171,11 +229,6 @@ namespace ts {
171229 */
172230 const fileInfos = createMap < FileInfo > ( ) ;
173231
174- /**
175- * true if module emit is enabled
176- */
177- let isModuleEmit : boolean ;
178-
179232 /**
180233 * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
181234 * Otherwise undefined
@@ -218,7 +271,7 @@ namespace ts {
218271 function updateProgram ( newProgram : Program ) {
219272 const newProgramHasModuleEmit = newProgram . getCompilerOptions ( ) . module !== ModuleKind . None ;
220273 const oldReferencedMap = referencedMap ;
221- const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit ;
274+ const isModuleEmitChanged = ! ! referencedMap !== newProgramHasModuleEmit ;
222275 if ( isModuleEmitChanged ) {
223276 // Changes in the module emit, clear out everything and initialize as if first time
224277
@@ -229,8 +282,7 @@ namespace ts {
229282 referencedMap = newProgramHasModuleEmit ? createMap < ReferencedSet > ( ) : undefined ;
230283
231284 // Update the module emit
232- isModuleEmit = newProgramHasModuleEmit ;
233- getEmitDependentFilesAffectedBy = isModuleEmit ?
285+ getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ?
234286 getFilesAffectedByUpdatedShapeWhenModuleEmit :
235287 getFilesAffectedByUpdatedShapeWhenNonModuleEmit ;
236288 }
@@ -326,12 +378,11 @@ namespace ts {
326378 }
327379
328380 // If this is non module emit, or its a global file, it depends on all the source files
329- if ( ! isModuleEmit || ( ! isExternalModule ( sourceFile ) && ! containsOnlyAmbientModules ( sourceFile ) ) ) {
381+ if ( ! referencedMap || ( ! isExternalModule ( sourceFile ) && ! containsOnlyAmbientModules ( sourceFile ) ) ) {
330382 return getAllFileNames ( programOfThisState ) ;
331383 }
332384
333385 // Get the references, traversing deep from the referenceMap
334- Debug . assert ( ! ! referencedMap ) ;
335386 const seenMap = createMap < true > ( ) ;
336387 const queue = [ sourceFile . path ] ;
337388 while ( queue . length ) {
@@ -497,3 +548,177 @@ namespace ts {
497548 }
498549 }
499550}
551+
552+ /*@internal */
553+ namespace ts . BuilderState {
554+ type ComputeHash = ( data : string ) => string ;
555+
556+ /**
557+ * Gets the files affected by the path from the program
558+ */
559+ export function getFilesAffectedBy ( state : BuilderState , programOfThisState : Program , path : Path , cancellationToken : CancellationToken | undefined , computeHash ?: ComputeHash , cacheToUpdateSignature ?: Map < string > ) : ReadonlyArray < SourceFile > {
560+ // Since the operation could be cancelled, the signatures are always stored in the cache
561+ // They will be commited once it is safe to use them
562+ // eg when calling this api from tsserver, if there is no cancellation of the operation
563+ // In the other cases the affected files signatures are commited only after the iteration through the result is complete
564+ const signatureCache = cacheToUpdateSignature || createMap ( ) ;
565+ const sourceFile = programOfThisState . getSourceFileByPath ( path ) ;
566+ if ( ! sourceFile ) {
567+ return emptyArray ;
568+ }
569+
570+ if ( ! updateShapeSignature ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash ) ) {
571+ return [ sourceFile ] ;
572+ }
573+
574+ const result = ( state . referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit ) ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash ) ;
575+ if ( ! cacheToUpdateSignature ) {
576+ // Commit all the signatures in the signature cache
577+ updateSignaturesFromCache ( state , signatureCache ) ;
578+ }
579+ return result ;
580+ }
581+
582+ /**
583+ * Updates the signatures from the cache into state's fileinfo signatures
584+ * This should be called whenever it is safe to commit the state of the builder
585+ */
586+ export function updateSignaturesFromCache ( state : BuilderState , signatureCache : Map < string > ) {
587+ signatureCache . forEach ( ( signature , path ) => {
588+ state . fileInfos . get ( path ) . signature = signature ;
589+ state . hasCalledUpdateShapeSignature . set ( path , true ) ;
590+ } ) ;
591+ }
592+
593+ /**
594+ * Returns if the shape of the signature has changed since last emit
595+ */
596+ function updateShapeSignature ( state : Readonly < BuilderState > , programOfThisState : Program , sourceFile : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash | undefined ) {
597+ Debug . assert ( ! ! sourceFile ) ;
598+
599+ // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
600+ if ( state . hasCalledUpdateShapeSignature . has ( sourceFile . path ) || cacheToUpdateSignature . has ( sourceFile . path ) ) {
601+ return false ;
602+ }
603+
604+ const info = state . fileInfos . get ( sourceFile . path ) ;
605+ Debug . assert ( ! ! info ) ;
606+
607+ const prevSignature = info . signature ;
608+ let latestSignature : string ;
609+ if ( sourceFile . isDeclarationFile ) {
610+ latestSignature = sourceFile . version ;
611+ }
612+ else {
613+ const emitOutput = getFileEmitOutput ( programOfThisState , sourceFile , /*emitOnlyDtsFiles*/ true , cancellationToken ) ;
614+ if ( emitOutput . outputFiles && emitOutput . outputFiles . length > 0 ) {
615+ latestSignature = ( computeHash || identity ) ( emitOutput . outputFiles [ 0 ] . text ) ;
616+ }
617+ else {
618+ latestSignature = prevSignature ;
619+ }
620+ }
621+ cacheToUpdateSignature . set ( sourceFile . path , latestSignature ) ;
622+
623+ return ! prevSignature || latestSignature !== prevSignature ;
624+ }
625+
626+ /**
627+ * Gets the files referenced by the the file path
628+ */
629+ function getReferencedByPaths ( state : Readonly < BuilderState > , referencedFilePath : Path ) {
630+ return mapDefinedIter ( state . referencedMap . entries ( ) , ( [ filePath , referencesInFile ] ) =>
631+ referencesInFile . has ( referencedFilePath ) ? filePath as Path : undefined
632+ ) ;
633+ }
634+
635+ /**
636+ * For script files that contains only ambient external modules, although they are not actually external module files,
637+ * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
638+ * there are no point to rebuild all script files if these special files have changed. However, if any statement
639+ * in the file is not ambient external module, we treat it as a regular script file.
640+ */
641+ function containsOnlyAmbientModules ( sourceFile : SourceFile ) {
642+ for ( const statement of sourceFile . statements ) {
643+ if ( ! isModuleWithStringLiteralName ( statement ) ) {
644+ return false ;
645+ }
646+ }
647+ return true ;
648+ }
649+
650+ /**
651+ * Gets all files of the program excluding the default library file
652+ */
653+ function getAllFilesExcludingDefaultLibraryFile ( state : BuilderState , programOfThisState : Program , firstSourceFile : SourceFile ) : ReadonlyArray < SourceFile > {
654+ // Use cached result
655+ if ( state . allFilesExcludingDefaultLibraryFile ) {
656+ return state . allFilesExcludingDefaultLibraryFile ;
657+ }
658+
659+ let result : SourceFile [ ] ;
660+ addSourceFile ( firstSourceFile ) ;
661+ for ( const sourceFile of programOfThisState . getSourceFiles ( ) ) {
662+ if ( sourceFile !== firstSourceFile ) {
663+ addSourceFile ( sourceFile ) ;
664+ }
665+ }
666+ state . allFilesExcludingDefaultLibraryFile = result || emptyArray ;
667+ return state . allFilesExcludingDefaultLibraryFile ;
668+
669+ function addSourceFile ( sourceFile : SourceFile ) {
670+ if ( ! programOfThisState . isSourceFileDefaultLibrary ( sourceFile ) ) {
671+ ( result || ( result = [ ] ) ) . push ( sourceFile ) ;
672+ }
673+ }
674+ }
675+
676+ /**
677+ * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed
678+ */
679+ function getFilesAffectedByUpdatedShapeWhenNonModuleEmit ( state : BuilderState , programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile ) {
680+ const compilerOptions = programOfThisState . getCompilerOptions ( ) ;
681+ // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
682+ // so returning the file itself is good enough.
683+ if ( compilerOptions && ( compilerOptions . out || compilerOptions . outFile ) ) {
684+ return [ sourceFileWithUpdatedShape ] ;
685+ }
686+ return getAllFilesExcludingDefaultLibraryFile ( state , programOfThisState , sourceFileWithUpdatedShape ) ;
687+ }
688+
689+ /**
690+ * When program emits modular code, gets the files affected by the sourceFile whose shape has changed
691+ */
692+ function getFilesAffectedByUpdatedShapeWhenModuleEmit ( state : BuilderState , programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash | undefined ) {
693+ if ( ! isExternalModule ( sourceFileWithUpdatedShape ) && ! containsOnlyAmbientModules ( sourceFileWithUpdatedShape ) ) {
694+ return getAllFilesExcludingDefaultLibraryFile ( state , programOfThisState , sourceFileWithUpdatedShape ) ;
695+ }
696+
697+ const compilerOptions = programOfThisState . getCompilerOptions ( ) ;
698+ if ( compilerOptions && ( compilerOptions . isolatedModules || compilerOptions . out || compilerOptions . outFile ) ) {
699+ return [ sourceFileWithUpdatedShape ] ;
700+ }
701+
702+ // Now we need to if each file in the referencedBy list has a shape change as well.
703+ // Because if so, its own referencedBy files need to be saved as well to make the
704+ // emitting result consistent with files on disk.
705+ const seenFileNamesMap = createMap < SourceFile > ( ) ;
706+
707+ // Start with the paths this file was referenced by
708+ seenFileNamesMap . set ( sourceFileWithUpdatedShape . path , sourceFileWithUpdatedShape ) ;
709+ const queue = getReferencedByPaths ( state , sourceFileWithUpdatedShape . path ) ;
710+ while ( queue . length > 0 ) {
711+ const currentPath = queue . pop ( ) ;
712+ if ( ! seenFileNamesMap . has ( currentPath ) ) {
713+ const currentSourceFile = programOfThisState . getSourceFileByPath ( currentPath ) ;
714+ seenFileNamesMap . set ( currentPath , currentSourceFile ) ;
715+ if ( currentSourceFile && updateShapeSignature ( state , programOfThisState , currentSourceFile , cacheToUpdateSignature , cancellationToken , computeHash ) ) {
716+ queue . push ( ...getReferencedByPaths ( state , currentPath ) ) ;
717+ }
718+ }
719+ }
720+
721+ // Return array of values that needs emit
722+ return flatMapIter ( seenFileNamesMap . values ( ) , value => value ) ;
723+ }
724+ }
0 commit comments