@@ -2,6 +2,7 @@ namespace ts {
2
2
export interface EmitOutput {
3
3
outputFiles : OutputFile [ ] ;
4
4
emitSkipped : boolean ;
5
+ /* @internal */ exportedModulesFromDeclarationEmit ?: ExportedModulesFromDeclarationEmit ;
5
6
}
6
7
7
8
export interface OutputFile {
@@ -17,7 +18,7 @@ namespace ts {
17
18
cancellationToken ?: CancellationToken , customTransformers ?: CustomTransformers ) : EmitOutput {
18
19
const outputFiles : OutputFile [ ] = [ ] ;
19
20
const emitResult = program . emit ( sourceFile , writeFile , cancellationToken , emitOnlyDtsFiles , customTransformers ) ;
20
- return { outputFiles, emitSkipped : emitResult . emitSkipped } ;
21
+ return { outputFiles, emitSkipped : emitResult . emitSkipped , exportedModulesFromDeclarationEmit : emitResult . exportedModulesFromDeclarationEmit } ;
21
22
22
23
function writeFile ( fileName : string , text : string , writeByteOrderMark : boolean ) {
23
24
outputFiles . push ( { name : fileName , writeByteOrderMark, text } ) ;
@@ -35,6 +36,11 @@ namespace ts {
35
36
* Thus non undefined value indicates, module emit
36
37
*/
37
38
readonly referencedMap : ReadonlyMap < BuilderState . ReferencedSet > | undefined ;
39
+ /**
40
+ * Contains the map of exported modules ReferencedSet=exorted module files from the file if module emit is enabled
41
+ * Otherwise undefined
42
+ */
43
+ readonly exportedModulesMap : Map < BuilderState . ReferencedSet > | undefined ;
38
44
/**
39
45
* Map of files that have already called update signature.
40
46
* That means hence forth these files are assumed to have
@@ -70,6 +76,30 @@ namespace ts.BuilderState {
70
76
*/
71
77
export type ComputeHash = ( data : string ) => string ;
72
78
79
+ /**
80
+ * Exported modules to from declaration emit being computed.
81
+ * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file
82
+ */
83
+ export type ComputingExportedModulesMap = Map < ReferencedSet | false > ;
84
+
85
+ /**
86
+ * Get the referencedFile from the imported module symbol
87
+ */
88
+ function getReferencedFileFromImportedModuleSymbol ( symbol : Symbol ) {
89
+ if ( symbol . declarations && symbol . declarations [ 0 ] ) {
90
+ const declarationSourceFile = getSourceFileOfNode ( symbol . declarations [ 0 ] ) ;
91
+ return declarationSourceFile && declarationSourceFile . path ;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get the referencedFile from the import name node from file
97
+ */
98
+ function getReferencedFileFromImportLiteral ( checker : TypeChecker , importName : StringLiteralLike ) {
99
+ const symbol = checker . getSymbolAtLocation ( importName ) ;
100
+ return symbol && getReferencedFileFromImportedModuleSymbol ( symbol ) ;
101
+ }
102
+
73
103
/**
74
104
* Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
75
105
*/
@@ -82,12 +112,9 @@ namespace ts.BuilderState {
82
112
if ( sourceFile . imports && sourceFile . imports . length > 0 ) {
83
113
const checker : TypeChecker = program . getTypeChecker ( ) ;
84
114
for ( const importName of sourceFile . imports ) {
85
- const symbol = checker . getSymbolAtLocation ( importName ) ;
86
- if ( symbol && symbol . declarations && symbol . declarations [ 0 ] ) {
87
- const declarationSourceFile = getSourceFileOfNode ( symbol . declarations [ 0 ] ) ;
88
- if ( declarationSourceFile ) {
89
- addReferencedFile ( declarationSourceFile . path ) ;
90
- }
115
+ const declarationSourceFilePath = getReferencedFileFromImportLiteral ( checker , importName ) ;
116
+ if ( declarationSourceFilePath ) {
117
+ addReferencedFile ( declarationSourceFilePath ) ;
91
118
}
92
119
}
93
120
}
@@ -137,6 +164,7 @@ namespace ts.BuilderState {
137
164
export function create ( newProgram : Program , getCanonicalFileName : GetCanonicalFileName , oldState ?: Readonly < BuilderState > ) : BuilderState {
138
165
const fileInfos = createMap < FileInfo > ( ) ;
139
166
const referencedMap = newProgram . getCompilerOptions ( ) . module !== ModuleKind . None ? createMap < ReferencedSet > ( ) : undefined ;
167
+ const exportedModulesMap = referencedMap ? createMap < ReferencedSet > ( ) : undefined ;
140
168
const hasCalledUpdateShapeSignature = createMap < true > ( ) ;
141
169
const useOldState = canReuseOldState ( referencedMap , oldState ) ;
142
170
@@ -149,13 +177,21 @@ namespace ts.BuilderState {
149
177
if ( newReferences ) {
150
178
referencedMap . set ( sourceFile . path , newReferences ) ;
151
179
}
180
+ // Copy old visible to outside files map
181
+ if ( useOldState ) {
182
+ const exportedModules = oldState ! . exportedModulesMap ! . get ( sourceFile . path ) ;
183
+ if ( exportedModules ) {
184
+ exportedModulesMap ! . set ( sourceFile . path , exportedModules ) ;
185
+ }
186
+ }
152
187
}
153
188
fileInfos . set ( sourceFile . path , { version, signature : oldInfo && oldInfo . signature } ) ;
154
189
}
155
190
156
191
return {
157
192
fileInfos,
158
193
referencedMap,
194
+ exportedModulesMap,
159
195
hasCalledUpdateShapeSignature,
160
196
allFilesExcludingDefaultLibraryFile : undefined ,
161
197
allFileNames : undefined
@@ -165,7 +201,7 @@ namespace ts.BuilderState {
165
201
/**
166
202
* Gets the files affected by the path from the program
167
203
*/
168
- export function getFilesAffectedBy ( state : BuilderState , programOfThisState : Program , path : Path , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash , cacheToUpdateSignature ?: Map < string > ) : ReadonlyArray < SourceFile > {
204
+ export function getFilesAffectedBy ( state : BuilderState , programOfThisState : Program , path : Path , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash , cacheToUpdateSignature ?: Map < string > , exportedModulesMapCache ?: ComputingExportedModulesMap ) : ReadonlyArray < SourceFile > {
169
205
// Since the operation could be cancelled, the signatures are always stored in the cache
170
206
// They will be commited once it is safe to use them
171
207
// eg when calling this api from tsserver, if there is no cancellation of the operation
@@ -176,11 +212,11 @@ namespace ts.BuilderState {
176
212
return emptyArray ;
177
213
}
178
214
179
- if ( ! updateShapeSignature ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash ) ) {
215
+ if ( ! updateShapeSignature ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash , exportedModulesMapCache ) ) {
180
216
return [ sourceFile ] ;
181
217
}
182
218
183
- const result = ( state . referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit ) ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash ) ;
219
+ const result = ( state . referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit ) ( state , programOfThisState , sourceFile , signatureCache , cancellationToken , computeHash , exportedModulesMapCache ) ;
184
220
if ( ! cacheToUpdateSignature ) {
185
221
// Commit all the signatures in the signature cache
186
222
updateSignaturesFromCache ( state , signatureCache ) ;
@@ -202,8 +238,9 @@ namespace ts.BuilderState {
202
238
/**
203
239
* Returns if the shape of the signature has changed since last emit
204
240
*/
205
- function updateShapeSignature ( state : Readonly < BuilderState > , programOfThisState : Program , sourceFile : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash ) {
241
+ function updateShapeSignature ( state : Readonly < BuilderState > , programOfThisState : Program , sourceFile : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash , exportedModulesMapCache ?: ComputingExportedModulesMap ) {
206
242
Debug . assert ( ! ! sourceFile ) ;
243
+ Debug . assert ( ! exportedModulesMapCache || ! ! state . exportedModulesMap , "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state" ) ;
207
244
208
245
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
209
246
if ( state . hasCalledUpdateShapeSignature . has ( sourceFile . path ) || cacheToUpdateSignature . has ( sourceFile . path ) ) {
@@ -222,16 +259,105 @@ namespace ts.BuilderState {
222
259
const emitOutput = getFileEmitOutput ( programOfThisState , sourceFile , /*emitOnlyDtsFiles*/ true , cancellationToken ) ;
223
260
if ( emitOutput . outputFiles && emitOutput . outputFiles . length > 0 ) {
224
261
latestSignature = computeHash ( emitOutput . outputFiles [ 0 ] . text ) ;
262
+ if ( exportedModulesMapCache && latestSignature !== prevSignature ) {
263
+ updateExportedModules ( programOfThisState , sourceFile , emitOutput . exportedModulesFromDeclarationEmit , exportedModulesMapCache ) ;
264
+ }
225
265
}
226
266
else {
227
267
latestSignature = prevSignature ! ; // TODO: GH#18217
228
268
}
269
+
229
270
}
230
271
cacheToUpdateSignature . set ( sourceFile . path , latestSignature ) ;
231
272
232
273
return ! prevSignature || latestSignature !== prevSignature ;
233
274
}
234
275
276
+ /**
277
+ * Coverts the declaration emit result into exported modules map
278
+ */
279
+ function updateExportedModules ( programOfThisState : Program , sourceFile : SourceFile , exportedModulesFromDeclarationEmit : ExportedModulesFromDeclarationEmit | undefined , exportedModulesMapCache : ComputingExportedModulesMap ) {
280
+ if ( ! exportedModulesFromDeclarationEmit ) {
281
+ exportedModulesMapCache . set ( sourceFile . path , false ) ;
282
+ return ;
283
+ }
284
+
285
+ const checker = programOfThisState . getTypeChecker ( ) ;
286
+ let exportedModules : Map < true > | undefined ;
287
+
288
+ exportedModulesFromDeclarationEmit . exportedModuleSpecifiers . forEach ( importName =>
289
+ addExportedModule ( getReferencedFileFromImportLiteral ( checker , importName ) ) ) ;
290
+ exportedModulesFromDeclarationEmit . exportedModuleSymbolsUsingImportTypeNodes . forEach ( symbol =>
291
+ addExportedModule ( getReferencedFileFromImportedModuleSymbol ( symbol ) ) ) ;
292
+
293
+ exportedModulesMapCache . set ( sourceFile . path , exportedModules || false ) ;
294
+
295
+ function addExportedModule ( exportedModulePath : Path | undefined ) {
296
+ if ( exportedModulePath ) {
297
+ if ( ! exportedModules ) {
298
+ exportedModules = createMap < true > ( ) ;
299
+ }
300
+ exportedModules . set ( exportedModulePath , true ) ;
301
+ }
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Updates the exported modules from cache into state's exported modules map
307
+ * This should be called whenever it is safe to commit the state of the builder
308
+ */
309
+ export function updateExportedFilesMapFromCache ( state : BuilderState , exportedModulesMapCache : ComputingExportedModulesMap | undefined ) {
310
+ if ( exportedModulesMapCache ) {
311
+ Debug . assert ( ! ! state . exportedModulesMap ) ;
312
+ exportedModulesMapCache . forEach ( ( exportedModules , path ) => {
313
+ if ( exportedModules ) {
314
+ state . exportedModulesMap ! . set ( path , exportedModules ) ;
315
+ }
316
+ else {
317
+ state . exportedModulesMap ! . delete ( path ) ;
318
+ }
319
+ } ) ;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Gets the files affected by exported module
325
+ */
326
+ export function getFilesAffectedByExportedModule ( state : BuilderState , path : Path , exportedModulesMapCache ?: ComputingExportedModulesMap ) : ReadonlyArray < Path > {
327
+ if ( ! state . exportedModulesMap ) {
328
+ return emptyArray ;
329
+ }
330
+
331
+ Debug . assert ( ! ! exportedModulesMapCache ) ;
332
+ let affectedFiles : Map < true > | undefined ;
333
+ // Go through exported modules from cache first
334
+ exportedModulesMapCache ! . forEach ( ( exportedModules , exportedFromPath ) => {
335
+ // If exported modules has path, all files referencing file exported from are affected
336
+ if ( exportedModules && exportedModules . has ( path ) ) {
337
+ addFilesReferencing ( exportedFromPath as Path ) ;
338
+ }
339
+ } ) ;
340
+ state . exportedModulesMap . forEach ( ( exportedModules , exportedFromPath ) => {
341
+ // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
342
+ if ( ! exportedModulesMapCache ! . has ( exportedFromPath ) && exportedModules . has ( path ) ) {
343
+ addFilesReferencing ( exportedFromPath as Path ) ;
344
+ }
345
+ } ) ;
346
+
347
+ return affectedFiles ? arrayFrom ( affectedFiles . keys ( ) ) as Path [ ] : emptyArray ;
348
+
349
+ function addFilesReferencing ( referencingFilePath : Path ) {
350
+ state . referencedMap ! . forEach ( ( referencesInFile , filePath ) => {
351
+ if ( referencesInFile . has ( referencingFilePath ) ) {
352
+ if ( ! affectedFiles ) {
353
+ affectedFiles = createMap < true > ( ) ;
354
+ }
355
+ affectedFiles . set ( filePath , true ) ;
356
+ }
357
+ } ) ;
358
+ }
359
+ }
360
+
235
361
/**
236
362
* Get all the dependencies of the sourceFile
237
363
*/
@@ -347,7 +473,7 @@ namespace ts.BuilderState {
347
473
/**
348
474
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
349
475
*/
350
- function getFilesAffectedByUpdatedShapeWhenModuleEmit ( state : BuilderState , programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash | undefined ) {
476
+ function getFilesAffectedByUpdatedShapeWhenModuleEmit ( state : BuilderState , programOfThisState : Program , sourceFileWithUpdatedShape : SourceFile , cacheToUpdateSignature : Map < string > , cancellationToken : CancellationToken | undefined , computeHash : ComputeHash | undefined , exportedModulesMapCache : ComputingExportedModulesMap | undefined ) {
351
477
if ( ! isExternalModule ( sourceFileWithUpdatedShape ) && ! containsOnlyAmbientModules ( sourceFileWithUpdatedShape ) ) {
352
478
return getAllFilesExcludingDefaultLibraryFile ( state , programOfThisState , sourceFileWithUpdatedShape ) ;
353
479
}
@@ -370,7 +496,7 @@ namespace ts.BuilderState {
370
496
if ( ! seenFileNamesMap . has ( currentPath ) ) {
371
497
const currentSourceFile = programOfThisState . getSourceFileByPath ( currentPath ) ! ;
372
498
seenFileNamesMap . set ( currentPath , currentSourceFile ) ;
373
- if ( currentSourceFile && updateShapeSignature ( state , programOfThisState , currentSourceFile , cacheToUpdateSignature , cancellationToken , computeHash ! ) ) { // TODO: GH#18217
499
+ if ( currentSourceFile && updateShapeSignature ( state , programOfThisState , currentSourceFile , cacheToUpdateSignature , cancellationToken , computeHash ! , exportedModulesMapCache ) ) { // TODO: GH#18217
374
500
queue . push ( ...getReferencedByPaths ( state , currentPath ) ) ;
375
501
}
376
502
}
0 commit comments