22namespace ts . refactor {
33 const refactorName = "Convert import" ;
44
5- const namespaceToNamedAction = {
6- name : "Convert namespace import to named imports" ,
7- description : Diagnostics . Convert_namespace_import_to_named_imports . message ,
8- kind : "refactor.rewrite.import.named" ,
9- } ;
10- const namedToNamespaceAction = {
11- name : "Convert named imports to namespace import" ,
12- description : Diagnostics . Convert_named_imports_to_namespace_import . message ,
13- kind : "refactor.rewrite.import.namespace" ,
5+ const actions = {
6+ [ ImportKind . Named ] : {
7+ name : "Convert namespace import to named imports" ,
8+ description : Diagnostics . Convert_namespace_import_to_named_imports . message ,
9+ kind : "refactor.rewrite.import.named" ,
10+ } ,
11+ [ ImportKind . Namespace ] : {
12+ name : "Convert named imports to namespace import" ,
13+ description : Diagnostics . Convert_named_imports_to_namespace_import . message ,
14+ kind : "refactor.rewrite.import.namespace" ,
15+ } ,
16+ [ ImportKind . Default ] : {
17+ name : "Convert named imports to default import" ,
18+ description : Diagnostics . Convert_named_imports_to_default_import . message ,
19+ kind : "refactor.rewrite.import.default" ,
20+ } ,
1421 } ;
1522
1623 registerRefactor ( refactorName , {
17- kinds : [
18- namespaceToNamedAction . kind ,
19- namedToNamespaceAction . kind
20- ] ,
24+ kinds : getOwnValues ( actions ) . map ( a => a . kind ) ,
2125 getAvailableActions : function getRefactorActionsToConvertBetweenNamedAndNamespacedImports ( context ) : readonly ApplicableRefactorInfo [ ] {
22- const info = getImportToConvert ( context , context . triggerReason === "invoked" ) ;
26+ const info = getImportConversionInfo ( context , context . triggerReason === "invoked" ) ;
2327 if ( ! info ) return emptyArray ;
2428
2529 if ( ! isRefactorErrorInfo ( info ) ) {
26- const namespaceImport = info . kind === SyntaxKind . NamespaceImport ;
27- const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction ;
30+ const action = actions [ info . convertTo ] ;
2831 return [ { name : refactorName , description : action . description , actions : [ action ] } ] ;
2932 }
3033
3134 if ( context . preferences . provideRefactorNotApplicableReason ) {
32- return [
33- { name : refactorName , description : namespaceToNamedAction . description ,
34- actions : [ { ...namespaceToNamedAction , notApplicableReason : info . error } ] } ,
35- { name : refactorName , description : namedToNamespaceAction . description ,
36- actions : [ { ...namedToNamespaceAction , notApplicableReason : info . error } ] }
37- ] ;
35+ return getOwnValues ( actions ) . map ( action => ( {
36+ name : refactorName ,
37+ description : action . description ,
38+ actions : [ { ...action , notApplicableReason : info . error } ]
39+ } ) ) ;
3840 }
3941
4042 return emptyArray ;
4143 } ,
4244 getEditsForAction : function getRefactorEditsToConvertBetweenNamedAndNamespacedImports ( context , actionName ) : RefactorEditInfo {
43- Debug . assert ( actionName === namespaceToNamedAction . name || actionName === namedToNamespaceAction . name , "Unexpected action name" ) ;
44- const info = getImportToConvert ( context ) ;
45+ Debug . assert ( some ( getOwnValues ( actions ) , action => action . name === actionName ) , "Unexpected action name" ) ;
46+ const info = getImportConversionInfo ( context ) ;
4547 Debug . assert ( info && ! isRefactorErrorInfo ( info ) , "Expected applicable refactor info" ) ;
4648 const edits = textChanges . ChangeTracker . with ( context , t => doChange ( context . file , context . program , t , info ) ) ;
4749 return { edits, renameFilename : undefined , renameLocation : undefined } ;
4850 }
4951 } ) ;
5052
5153 // Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`.
52- function getImportToConvert ( context : RefactorContext , considerPartialSpans = true ) : NamedImportBindings | RefactorErrorInfo | undefined {
54+ type ImportConversionInfo =
55+ | { convertTo : ImportKind . Default , import : NamedImports }
56+ | { convertTo : ImportKind . Namespace , import : NamedImports }
57+ | { convertTo : ImportKind . Named , import : NamespaceImport } ;
58+
59+ function getImportConversionInfo ( context : RefactorContext , considerPartialSpans = true ) : ImportConversionInfo | RefactorErrorInfo | undefined {
5360 const { file } = context ;
5461 const span = getRefactorContextSpan ( context ) ;
5562 const token = getTokenAtPosition ( file , span . start ) ;
@@ -69,16 +76,25 @@ namespace ts.refactor {
6976 return { error : getLocaleSpecificMessage ( Diagnostics . Could_not_find_namespace_import_or_named_imports ) } ;
7077 }
7178
72- return importClause . namedBindings ;
79+ if ( importClause . namedBindings . kind === SyntaxKind . NamespaceImport ) {
80+ return { convertTo : ImportKind . Named , import : importClause . namedBindings } ;
81+ }
82+ const compilerOptions = context . program . getCompilerOptions ( ) ;
83+ const shouldUseDefault = getAllowSyntheticDefaultImports ( compilerOptions )
84+ && isExportEqualsModule ( importClause . parent . moduleSpecifier , context . program . getTypeChecker ( ) ) ;
85+
86+ return shouldUseDefault
87+ ? { convertTo : ImportKind . Default , import : importClause . namedBindings }
88+ : { convertTo : ImportKind . Namespace , import : importClause . namedBindings } ;
7389 }
7490
75- function doChange ( sourceFile : SourceFile , program : Program , changes : textChanges . ChangeTracker , toConvert : NamedImportBindings ) : void {
91+ function doChange ( sourceFile : SourceFile , program : Program , changes : textChanges . ChangeTracker , info : ImportConversionInfo ) : void {
7692 const checker = program . getTypeChecker ( ) ;
77- if ( toConvert . kind === SyntaxKind . NamespaceImport ) {
78- doChangeNamespaceToNamed ( sourceFile , checker , changes , toConvert , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
93+ if ( info . convertTo === ImportKind . Named ) {
94+ doChangeNamespaceToNamed ( sourceFile , checker , changes , info . import , getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) ;
7995 }
8096 else {
81- doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert ) ;
97+ doChangeNamedToNamespaceOrDefault ( sourceFile , checker , changes , info . import , info . convertTo === ImportKind . Default ) ;
8298 }
8399 }
84100
@@ -137,7 +153,7 @@ namespace ts.refactor {
137153 return isPropertyAccessExpression ( propertyAccessOrQualifiedName ) ? propertyAccessOrQualifiedName . expression : propertyAccessOrQualifiedName . left ;
138154 }
139155
140- function doChangeNamedToNamespace ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamedImports ) : void {
156+ function doChangeNamedToNamespaceOrDefault ( sourceFile : SourceFile , checker : TypeChecker , changes : textChanges . ChangeTracker , toConvert : NamedImports , shouldUseDefault : boolean ) {
141157 const importDecl = toConvert . parent . parent ;
142158 const { moduleSpecifier } = importDecl ;
143159
@@ -188,14 +204,23 @@ namespace ts.refactor {
188204 } ) ;
189205 }
190206
191- changes . replaceNode ( sourceFile , toConvert , factory . createNamespaceImport ( factory . createIdentifier ( namespaceImportName ) ) ) ;
207+ changes . replaceNode ( sourceFile , toConvert , shouldUseDefault
208+ ? factory . createIdentifier ( namespaceImportName )
209+ : factory . createNamespaceImport ( factory . createIdentifier ( namespaceImportName ) ) ) ;
192210 if ( neededNamedImports . size ) {
193211 const newNamedImports : ImportSpecifier [ ] = arrayFrom ( neededNamedImports . values ( ) ) . map ( element =>
194212 factory . createImportSpecifier ( element . isTypeOnly , element . propertyName && factory . createIdentifier ( element . propertyName . text ) , factory . createIdentifier ( element . name . text ) ) ) ;
195213 changes . insertNodeAfter ( sourceFile , toConvert . parent . parent , updateImport ( importDecl , /*defaultImportName*/ undefined , newNamedImports ) ) ;
196214 }
197215 }
198216
217+ function isExportEqualsModule ( moduleSpecifier : Expression , checker : TypeChecker ) {
218+ const externalModule = checker . resolveExternalModuleName ( moduleSpecifier ) ;
219+ if ( ! externalModule ) return false ;
220+ const exportEquals = checker . resolveExternalModuleSymbol ( externalModule ) ;
221+ return externalModule !== exportEquals ;
222+ }
223+
199224 function updateImport ( old : ImportDeclaration , defaultImportName : Identifier | undefined , elements : readonly ImportSpecifier [ ] | undefined ) : ImportDeclaration {
200225 return factory . createImportDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined ,
201226 factory . createImportClause ( /*isTypeOnly*/ false , defaultImportName , elements && elements . length ? factory . createNamedImports ( elements ) : undefined ) , old . moduleSpecifier , /*assertClause*/ undefined ) ;
0 commit comments