@@ -40,6 +40,9 @@ namespace ts.codefix {
40
40
} ,
41
41
} ) ;
42
42
43
+ /**
44
+ * Computes multiple import additions to a file and writes them to a ChangeTracker.
45
+ */
43
46
export interface ImportAdder {
44
47
hasFixes ( ) : boolean ;
45
48
addImportFromDiagnostic : ( diagnostic : DiagnosticWithLocation , context : CodeFixContextBase ) => void ;
@@ -235,6 +238,47 @@ namespace ts.codefix {
235
238
}
236
239
}
237
240
241
+ /**
242
+ * Computes module specifiers for multiple import additions to a file.
243
+ */
244
+ export interface ImportSpecifierResolver {
245
+ getModuleSpecifierForBestExportInfo (
246
+ exportInfo : readonly SymbolExportInfo [ ] ,
247
+ symbolName : string ,
248
+ position : number ,
249
+ isValidTypeOnlyUseSite : boolean ,
250
+ fromCacheOnly ?: boolean
251
+ ) : { exportInfo ?: SymbolExportInfo , moduleSpecifier : string , computedWithoutCacheCount : number } | undefined ;
252
+ }
253
+
254
+ export function createImportSpecifierResolver ( importingFile : SourceFile , program : Program , host : LanguageServiceHost , preferences : UserPreferences ) : ImportSpecifierResolver {
255
+ const packageJsonImportFilter = createPackageJsonImportFilter ( importingFile , preferences , host ) ;
256
+ const importMap = createExistingImportMap ( program . getTypeChecker ( ) , importingFile , program . getCompilerOptions ( ) ) ;
257
+ return { getModuleSpecifierForBestExportInfo } ;
258
+
259
+ function getModuleSpecifierForBestExportInfo (
260
+ exportInfo : readonly SymbolExportInfo [ ] ,
261
+ symbolName : string ,
262
+ position : number ,
263
+ isValidTypeOnlyUseSite : boolean ,
264
+ fromCacheOnly ?: boolean ,
265
+ ) : { exportInfo ?: SymbolExportInfo , moduleSpecifier : string , computedWithoutCacheCount : number } | undefined {
266
+ const { fixes, computedWithoutCacheCount } = getImportFixes (
267
+ exportInfo ,
268
+ { symbolName, position } ,
269
+ isValidTypeOnlyUseSite ,
270
+ /*useRequire*/ false ,
271
+ program ,
272
+ importingFile ,
273
+ host ,
274
+ preferences ,
275
+ importMap ,
276
+ fromCacheOnly ) ;
277
+ const result = getBestFix ( fixes , importingFile , program , packageJsonImportFilter , host ) ;
278
+ return result && { ...result , computedWithoutCacheCount } ;
279
+ }
280
+ }
281
+
238
282
// Sorted with the preferred fix coming first.
239
283
const enum ImportFixKind { UseNamespace , JsdocTypeImport , AddToExisting , AddNew , PromoteTypeOnly }
240
284
// These should not be combined as bitflags, but are given powers of 2 values to
@@ -394,32 +438,6 @@ namespace ts.codefix {
394
438
}
395
439
}
396
440
397
- export function getModuleSpecifierForBestExportInfo (
398
- exportInfo : readonly SymbolExportInfo [ ] ,
399
- symbolName : string ,
400
- position : number ,
401
- isValidTypeOnlyUseSite : boolean ,
402
- importingFile : SourceFile ,
403
- program : Program ,
404
- host : LanguageServiceHost ,
405
- preferences : UserPreferences ,
406
- packageJsonImportFilter ?: PackageJsonImportFilter ,
407
- fromCacheOnly ?: boolean ,
408
- ) : { exportInfo ?: SymbolExportInfo , moduleSpecifier : string , computedWithoutCacheCount : number } | undefined {
409
- const { fixes, computedWithoutCacheCount } = getImportFixes (
410
- exportInfo ,
411
- { symbolName, position } ,
412
- isValidTypeOnlyUseSite ,
413
- /*useRequire*/ false ,
414
- program ,
415
- importingFile ,
416
- host ,
417
- preferences ,
418
- fromCacheOnly ) ;
419
- const result = getBestFix ( fixes , importingFile , program , packageJsonImportFilter || createPackageJsonImportFilter ( importingFile , preferences , host ) , host ) ;
420
- return result && { ...result , computedWithoutCacheCount } ;
421
- }
422
-
423
441
function getImportFixes (
424
442
exportInfos : readonly SymbolExportInfo [ ] ,
425
443
useNamespaceInfo : {
@@ -433,10 +451,11 @@ namespace ts.codefix {
433
451
sourceFile : SourceFile ,
434
452
host : LanguageServiceHost ,
435
453
preferences : UserPreferences ,
454
+ importMap = createExistingImportMap ( program . getTypeChecker ( ) , sourceFile , program . getCompilerOptions ( ) ) ,
436
455
fromCacheOnly ?: boolean ,
437
456
) : { computedWithoutCacheCount : number , fixes : readonly ImportFixWithModuleSpecifier [ ] } {
438
457
const checker = program . getTypeChecker ( ) ;
439
- const existingImports = flatMap ( exportInfos , info => getExistingImportDeclarations ( info , checker , sourceFile , program . getCompilerOptions ( ) ) ) ;
458
+ const existingImports = flatMap ( exportInfos , importMap . getImportsForExportInfo ) ;
440
459
const useNamespace = useNamespaceInfo && tryUseExistingNamespaceImport ( existingImports , useNamespaceInfo . symbolName , useNamespaceInfo . position , checker ) ;
441
460
const addToExisting = tryAddToExistingImport ( existingImports , isValidTypeOnlyUseSite , checker , program . getCompilerOptions ( ) ) ;
442
461
if ( addToExisting ) {
@@ -587,19 +606,34 @@ namespace ts.codefix {
587
606
} ) ;
588
607
}
589
608
590
- function getExistingImportDeclarations ( { moduleSymbol, exportKind, targetFlags, symbol } : SymbolExportInfo , checker : TypeChecker , importingFile : SourceFile , compilerOptions : CompilerOptions ) : readonly FixAddToExistingImportInfo [ ] {
591
- // Can't use an es6 import for a type in JS.
592
- if ( ! ( targetFlags & SymbolFlags . Value ) && isSourceFileJS ( importingFile ) ) return emptyArray ;
593
- const importKind = getImportKind ( importingFile , exportKind , compilerOptions ) ;
594
- return mapDefined ( importingFile . imports , ( moduleSpecifier ) : FixAddToExistingImportInfo | undefined => {
609
+ function createExistingImportMap ( checker : TypeChecker , importingFile : SourceFile , compilerOptions : CompilerOptions ) {
610
+ let importMap : MultiMap < SymbolId , AnyImportOrRequire > | undefined ;
611
+ for ( const moduleSpecifier of importingFile . imports ) {
595
612
const i = importFromModuleSpecifier ( moduleSpecifier ) ;
596
613
if ( isVariableDeclarationInitializedToRequire ( i . parent ) ) {
597
- return checker . resolveExternalModuleName ( moduleSpecifier ) === moduleSymbol ? { declaration : i . parent , importKind, symbol, targetFlags } : undefined ;
614
+ const moduleSymbol = checker . resolveExternalModuleName ( moduleSpecifier ) ;
615
+ if ( moduleSymbol ) {
616
+ ( importMap ||= createMultiMap ( ) ) . add ( getSymbolId ( moduleSymbol ) , i . parent ) ;
617
+ }
598
618
}
599
- if ( i . kind === SyntaxKind . ImportDeclaration || i . kind === SyntaxKind . ImportEqualsDeclaration ) {
600
- return checker . getSymbolAtLocation ( moduleSpecifier ) === moduleSymbol ? { declaration : i , importKind, symbol, targetFlags } : undefined ;
619
+ else if ( i . kind === SyntaxKind . ImportDeclaration || i . kind === SyntaxKind . ImportEqualsDeclaration ) {
620
+ const moduleSymbol = checker . getSymbolAtLocation ( moduleSpecifier ) ;
621
+ if ( moduleSymbol ) {
622
+ ( importMap ||= createMultiMap ( ) ) . add ( getSymbolId ( moduleSymbol ) , i ) ;
623
+ }
601
624
}
602
- } ) ;
625
+ }
626
+
627
+ return {
628
+ getImportsForExportInfo : ( { moduleSymbol, exportKind, targetFlags, symbol } : SymbolExportInfo ) : readonly FixAddToExistingImportInfo [ ] => {
629
+ // Can't use an es6 import for a type in JS.
630
+ if ( ! ( targetFlags & SymbolFlags . Value ) && isSourceFileJS ( importingFile ) ) return emptyArray ;
631
+ const matchingDeclarations = importMap ?. get ( getSymbolId ( moduleSymbol ) ) ;
632
+ if ( ! matchingDeclarations ) return emptyArray ;
633
+ const importKind = getImportKind ( importingFile , exportKind , compilerOptions ) ;
634
+ return matchingDeclarations . map ( declaration => ( { declaration, importKind, symbol, targetFlags } ) ) ;
635
+ }
636
+ } ;
603
637
}
604
638
605
639
function shouldUseRequire ( sourceFile : SourceFile , program : Program ) : boolean {
0 commit comments