@@ -81,9 +81,9 @@ namespace ts.codefix {
81
81
const symbolName = getNameForExportedSymbol ( exportedSymbol , getEmitScriptTarget ( compilerOptions ) ) ;
82
82
const checker = program . getTypeChecker ( ) ;
83
83
const symbol = checker . getMergedSymbol ( skipAlias ( exportedSymbol , checker ) ) ;
84
- const exportInfos = getAllReExportingModules ( sourceFile , symbol , moduleSymbol , symbolName , /*isJsxTagName*/ false , host , program , preferences , useAutoImportProvider ) ;
84
+ const exportInfo = getAllReExportingModules ( sourceFile , symbol , moduleSymbol , symbolName , /*isJsxTagName*/ false , host , program , preferences , useAutoImportProvider ) ;
85
85
const useRequire = shouldUseRequire ( sourceFile , program ) ;
86
- const fix = getImportFixForSymbol ( sourceFile , exportInfos , moduleSymbol , symbolName , program , /*position*/ undefined , ! ! isValidTypeOnlyUseSite , useRequire , host , preferences ) ;
86
+ const fix = getImportFixForSymbol ( sourceFile , exportInfo , moduleSymbol , symbolName , program , /*position*/ undefined , ! ! isValidTypeOnlyUseSite , useRequire , host , preferences ) ;
87
87
if ( fix ) {
88
88
addImport ( { fixes : [ fix ] , symbolName, errorIdentifierText : undefined } ) ;
89
89
}
@@ -248,32 +248,35 @@ namespace ts.codefix {
248
248
}
249
249
type ImportFix = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport | FixPromoteTypeOnlyImport ;
250
250
type ImportFixWithModuleSpecifier = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport ;
251
- interface FixUseNamespaceImport {
251
+
252
+ // Properties are be undefined if fix is derived from an existing import
253
+ interface ImportFixBase {
254
+ readonly isReExport ?: boolean ;
255
+ readonly exportInfo ?: SymbolExportInfo ;
256
+ readonly moduleSpecifier : string ;
257
+ }
258
+ interface FixUseNamespaceImport extends ImportFixBase {
252
259
readonly kind : ImportFixKind . UseNamespace ;
253
260
readonly namespacePrefix : string ;
254
261
readonly position : number ;
255
- readonly moduleSpecifier : string ;
256
262
}
257
- interface FixAddJsdocTypeImport {
263
+ interface FixAddJsdocTypeImport extends ImportFixBase {
258
264
readonly kind : ImportFixKind . JsdocTypeImport ;
259
- readonly moduleSpecifier : string ;
260
265
readonly position : number ;
266
+ readonly isReExport : boolean ;
261
267
readonly exportInfo : SymbolExportInfo ;
262
268
}
263
- interface FixAddToExistingImport {
269
+ interface FixAddToExistingImport extends ImportFixBase {
264
270
readonly kind : ImportFixKind . AddToExisting ;
265
271
readonly importClauseOrBindingPattern : ImportClause | ObjectBindingPattern ;
266
- readonly moduleSpecifier : string ;
267
272
readonly importKind : ImportKind . Default | ImportKind . Named ;
268
273
readonly addAsTypeOnly : AddAsTypeOnly ;
269
274
}
270
- interface FixAddNewImport {
275
+ interface FixAddNewImport extends ImportFixBase {
271
276
readonly kind : ImportFixKind . AddNew ;
272
- readonly moduleSpecifier : string ;
273
277
readonly importKind : ImportKind ;
274
278
readonly addAsTypeOnly : AddAsTypeOnly ;
275
279
readonly useRequire : boolean ;
276
- readonly exportInfo ?: SymbolExportInfo ;
277
280
}
278
281
interface FixPromoteTypeOnlyImport {
279
282
readonly kind : ImportFixKind . PromoteTypeOnly ;
@@ -331,7 +334,7 @@ namespace ts.codefix {
331
334
function getImportFixForSymbol ( sourceFile : SourceFile , exportInfos : readonly SymbolExportInfo [ ] , moduleSymbol : Symbol , symbolName : string , program : Program , position : number | undefined , isValidTypeOnlyUseSite : boolean , useRequire : boolean , host : LanguageServiceHost , preferences : UserPreferences ) {
332
335
Debug . assert ( exportInfos . some ( info => info . moduleSymbol === moduleSymbol || info . symbol . parent === moduleSymbol ) , "Some exportInfo should match the specified moduleSymbol" ) ;
333
336
const packageJsonImportFilter = createPackageJsonImportFilter ( sourceFile , preferences , host ) ;
334
- return getBestFix ( getImportFixes ( exportInfos , symbolName , position , isValidTypeOnlyUseSite , useRequire , program , sourceFile , host , preferences ) , sourceFile , program , packageJsonImportFilter ) ;
337
+ return getBestFix ( getImportFixes ( exportInfos , symbolName , position , isValidTypeOnlyUseSite , useRequire , program , sourceFile , host , preferences ) , sourceFile , program , packageJsonImportFilter , host ) ;
335
338
}
336
339
337
340
function codeFixActionToCodeAction ( { description, changes, commands } : CodeFixAction ) : CodeAction {
@@ -410,7 +413,7 @@ namespace ts.codefix {
410
413
host ,
411
414
preferences ,
412
415
fromCacheOnly ) ;
413
- const result = getBestFix ( fixes , importingFile , program , packageJsonImportFilter || createPackageJsonImportFilter ( importingFile , preferences , host ) ) ;
416
+ const result = getBestFix ( fixes , importingFile , program , packageJsonImportFilter || createPackageJsonImportFilter ( importingFile , preferences , host ) , host ) ;
414
417
return result && { ...result , computedWithoutCacheCount } ;
415
418
}
416
419
@@ -606,7 +609,7 @@ namespace ts.codefix {
606
609
position : number | undefined ,
607
610
isValidTypeOnlyUseSite : boolean ,
608
611
useRequire : boolean ,
609
- moduleSymbols : readonly SymbolExportInfo [ ] ,
612
+ exportInfo : readonly SymbolExportInfo [ ] ,
610
613
host : LanguageServiceHost ,
611
614
preferences : UserPreferences ,
612
615
fromCacheOnly ?: boolean ,
@@ -620,7 +623,7 @@ namespace ts.codefix {
620
623
: ( moduleSymbol : Symbol , checker : TypeChecker ) => moduleSpecifiers . getModuleSpecifiersWithCacheInfo ( moduleSymbol , checker , compilerOptions , sourceFile , moduleSpecifierResolutionHost , preferences ) ;
621
624
622
625
let computedWithoutCacheCount = 0 ;
623
- const fixes = flatMap ( moduleSymbols , exportInfo => {
626
+ const fixes = flatMap ( exportInfo , ( exportInfo , i ) => {
624
627
const checker = getChecker ( exportInfo . isFromPackageJson ) ;
625
628
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers ( exportInfo . moduleSymbol , checker ) ;
626
629
const importedSymbolHasValueMeaning = ! ! ( exportInfo . targetFlags & SymbolFlags . Value ) ;
@@ -629,14 +632,15 @@ namespace ts.codefix {
629
632
return moduleSpecifiers ?. map ( ( moduleSpecifier ) : FixAddNewImport | FixAddJsdocTypeImport =>
630
633
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
631
634
! importedSymbolHasValueMeaning && isJs && position !== undefined
632
- ? { kind : ImportFixKind . JsdocTypeImport , moduleSpecifier, position, exportInfo }
635
+ ? { kind : ImportFixKind . JsdocTypeImport , moduleSpecifier, position, exportInfo, isReExport : i > 0 }
633
636
: {
634
637
kind : ImportFixKind . AddNew ,
635
638
moduleSpecifier,
636
639
importKind : getImportKind ( sourceFile , exportInfo . exportKind , compilerOptions ) ,
637
640
useRequire,
638
641
addAsTypeOnly,
639
642
exportInfo,
643
+ isReExport : i > 0 ,
640
644
}
641
645
) ;
642
646
} ) ;
@@ -675,7 +679,7 @@ namespace ts.codefix {
675
679
}
676
680
}
677
681
678
- interface FixesInfo { readonly fixes : readonly ImportFix [ ] ; readonly symbolName : string ; readonly errorIdentifierText : string | undefined ; }
682
+ interface FixesInfo { readonly fixes : readonly ImportFix [ ] , readonly symbolName : string , readonly errorIdentifierText : string | undefined }
679
683
function getFixesInfo ( context : CodeFixContextBase , errorCode : number , pos : number , useAutoImportProvider : boolean ) : FixesInfo | undefined {
680
684
const symbolToken = getTokenAtPosition ( context . sourceFile , pos ) ;
681
685
let info ;
@@ -695,39 +699,73 @@ namespace ts.codefix {
695
699
}
696
700
697
701
const packageJsonImportFilter = createPackageJsonImportFilter ( context . sourceFile , context . preferences , context . host ) ;
698
- return info && { ...info , fixes : sortFixes ( info . fixes , context . sourceFile , context . program , packageJsonImportFilter ) } ;
702
+ return info && { ...info , fixes : sortFixes ( info . fixes , context . sourceFile , context . program , packageJsonImportFilter , context . host ) } ;
699
703
}
700
704
701
- function sortFixes ( fixes : readonly ImportFixWithModuleSpecifier [ ] , sourceFile : SourceFile , program : Program , packageJsonImportFilter : PackageJsonImportFilter ) : readonly ImportFixWithModuleSpecifier [ ] {
702
- return sort ( fixes , ( a , b ) => compareValues ( a . kind , b . kind ) || compareModuleSpecifiers ( a , b , sourceFile , program , packageJsonImportFilter . allowsImportingSpecifier ) ) ;
705
+ function sortFixes ( fixes : readonly ImportFixWithModuleSpecifier [ ] , sourceFile : SourceFile , program : Program , packageJsonImportFilter : PackageJsonImportFilter , host : LanguageServiceHost ) : readonly ImportFixWithModuleSpecifier [ ] {
706
+ const _toPath = ( fileName : string ) => toPath ( fileName , host . getCurrentDirectory ( ) , hostGetCanonicalFileName ( host ) ) ;
707
+ return sort ( fixes , ( a , b ) => compareValues ( a . kind , b . kind ) || compareModuleSpecifiers ( a , b , sourceFile , program , packageJsonImportFilter . allowsImportingSpecifier , _toPath ) ) ;
703
708
}
704
709
705
- function getBestFix < T extends ImportFixWithModuleSpecifier > ( fixes : readonly T [ ] , sourceFile : SourceFile , program : Program , packageJsonImportFilter : PackageJsonImportFilter ) : T | undefined {
710
+ function getBestFix ( fixes : readonly ImportFixWithModuleSpecifier [ ] , sourceFile : SourceFile , program : Program , packageJsonImportFilter : PackageJsonImportFilter , host : LanguageServiceHost ) : ImportFixWithModuleSpecifier | undefined {
706
711
if ( ! some ( fixes ) ) return ;
707
712
// These will always be placed first if available, and are better than other kinds
708
713
if ( fixes [ 0 ] . kind === ImportFixKind . UseNamespace || fixes [ 0 ] . kind === ImportFixKind . AddToExisting ) {
709
714
return fixes [ 0 ] ;
710
715
}
716
+
711
717
return fixes . reduce ( ( best , fix ) =>
712
718
// Takes true branch of conditional if `fix` is better than `best`
713
- compareModuleSpecifiers ( fix , best , sourceFile , program , packageJsonImportFilter . allowsImportingSpecifier ) === Comparison . LessThan ? fix : best
719
+ compareModuleSpecifiers (
720
+ fix ,
721
+ best ,
722
+ sourceFile ,
723
+ program ,
724
+ packageJsonImportFilter . allowsImportingSpecifier ,
725
+ fileName => toPath ( fileName , host . getCurrentDirectory ( ) , hostGetCanonicalFileName ( host ) ) ,
726
+ ) === Comparison . LessThan ? fix : best
714
727
) ;
715
728
}
716
729
717
730
/** @returns `Comparison.LessThan` if `a` is better than `b`. */
718
- function compareModuleSpecifiers ( a : ImportFixWithModuleSpecifier , b : ImportFixWithModuleSpecifier , importingFile : SourceFile , program : Program , allowsImportingSpecifier : ( specifier : string ) => boolean ) : Comparison {
731
+ function compareModuleSpecifiers (
732
+ a : ImportFixWithModuleSpecifier ,
733
+ b : ImportFixWithModuleSpecifier ,
734
+ importingFile : SourceFile ,
735
+ program : Program ,
736
+ allowsImportingSpecifier : ( specifier : string ) => boolean ,
737
+ toPath : ( fileName : string ) => Path ,
738
+ ) : Comparison {
719
739
if ( a . kind !== ImportFixKind . UseNamespace && b . kind !== ImportFixKind . UseNamespace ) {
720
740
return compareBooleans ( allowsImportingSpecifier ( b . moduleSpecifier ) , allowsImportingSpecifier ( a . moduleSpecifier ) )
721
741
|| compareNodeCoreModuleSpecifiers ( a . moduleSpecifier , b . moduleSpecifier , importingFile , program )
722
- || compareBooleans ( isOnlyDotsAndSlashes ( a . moduleSpecifier ) , isOnlyDotsAndSlashes ( b . moduleSpecifier ) )
742
+ || compareBooleans (
743
+ isFixPossiblyReExportingImportingFile ( a , importingFile , program . getCompilerOptions ( ) , toPath ) ,
744
+ isFixPossiblyReExportingImportingFile ( b , importingFile , program . getCompilerOptions ( ) , toPath ) )
723
745
|| compareNumberOfDirectorySeparators ( a . moduleSpecifier , b . moduleSpecifier ) ;
724
746
}
725
747
return Comparison . EqualTo ;
726
748
}
727
749
728
- const notDotOrSlashPattern = / [ ^ . \/ ] / ;
729
- function isOnlyDotsAndSlashes ( path : string ) {
730
- return ! notDotOrSlashPattern . test ( path ) ;
750
+ // This is a simple heuristic to try to avoid creating an import cycle with a barrel re-export.
751
+ // E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`.
752
+ // This can produce false positives or negatives if re-exports cross into sibling directories
753
+ // (e.g. `export * from "../whatever"`) or are not named "index" (we don't even try to consider
754
+ // this if we're in a resolution mode where you can't drop trailing "/index" from paths).
755
+ function isFixPossiblyReExportingImportingFile ( fix : ImportFixWithModuleSpecifier , importingFile : SourceFile , compilerOptions : CompilerOptions , toPath : ( fileName : string ) => Path ) : boolean {
756
+ if ( fix . isReExport &&
757
+ fix . exportInfo ?. moduleFileName &&
758
+ getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeJs &&
759
+ isIndexFileName ( fix . exportInfo . moduleFileName )
760
+ ) {
761
+ const reExportDir = toPath ( getDirectoryPath ( fix . exportInfo . moduleFileName ) ) ;
762
+ return startsWith ( ( importingFile . path ) , reExportDir ) ;
763
+ }
764
+ return false ;
765
+ }
766
+
767
+ function isIndexFileName ( fileName : string ) {
768
+ return getBaseFileName ( fileName , [ ".js" , ".jsx" , ".d.ts" , ".ts" , ".tsx" ] , /*ignoreCase*/ true ) === "index" ;
731
769
}
732
770
733
771
function compareNodeCoreModuleSpecifiers ( a : string , b : string , importingFile : SourceFile , program : Program ) : Comparison {
@@ -742,9 +780,9 @@ namespace ts.codefix {
742
780
if ( ! umdSymbol ) return undefined ;
743
781
const symbol = checker . getAliasedSymbol ( umdSymbol ) ;
744
782
const symbolName = umdSymbol . name ;
745
- const exportInfos : readonly SymbolExportInfo [ ] = [ { symbol : umdSymbol , moduleSymbol : symbol , moduleFileName : undefined , exportKind : ExportKind . UMD , targetFlags : symbol . flags , isFromPackageJson : false } ] ;
783
+ const exportInfo : readonly SymbolExportInfo [ ] = [ { symbol : umdSymbol , moduleSymbol : symbol , moduleFileName : undefined , exportKind : ExportKind . UMD , targetFlags : symbol . flags , isFromPackageJson : false } ] ;
746
784
const useRequire = shouldUseRequire ( sourceFile , program ) ;
747
- const fixes = getImportFixes ( exportInfos , symbolName , isIdentifier ( token ) ? token . getStart ( sourceFile ) : undefined , /*isValidTypeOnlyUseSite*/ false , useRequire , program , sourceFile , host , preferences ) ;
785
+ const fixes = getImportFixes ( exportInfo , symbolName , isIdentifier ( token ) ? token . getStart ( sourceFile ) : undefined , /*isValidTypeOnlyUseSite*/ false , useRequire , program , sourceFile , host , preferences ) ;
748
786
return { fixes, symbolName, errorIdentifierText : tryCast ( token , isIdentifier ) ?. text } ;
749
787
}
750
788
function getUmdSymbol ( token : Node , checker : TypeChecker ) : Symbol | undefined {
@@ -814,8 +852,8 @@ namespace ts.codefix {
814
852
815
853
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite ( symbolToken ) ;
816
854
const useRequire = shouldUseRequire ( sourceFile , program ) ;
817
- const exportInfos = getExportInfos ( symbolName , isJSXTagName ( symbolToken ) , getMeaningFromLocation ( symbolToken ) , cancellationToken , sourceFile , program , useAutoImportProvider , host , preferences ) ;
818
- const fixes = arrayFrom ( flatMapIterator ( exportInfos . entries ( ) , ( [ _ , exportInfos ] ) =>
855
+ const exportInfo = getExportInfos ( symbolName , isJSXTagName ( symbolToken ) , getMeaningFromLocation ( symbolToken ) , cancellationToken , sourceFile , program , useAutoImportProvider , host , preferences ) ;
856
+ const fixes = arrayFrom ( flatMapIterator ( exportInfo . entries ( ) , ( [ _ , exportInfos ] ) =>
819
857
getImportFixes ( exportInfos , symbolName , symbolToken . getStart ( sourceFile ) , isValidTypeOnlyUseSite , useRequire , program , sourceFile , host , preferences ) ) ) ;
820
858
return { fixes, symbolName, errorIdentifierText : symbolToken . text } ;
821
859
}
0 commit comments