2
2
namespace ts . refactor {
3
3
const refactorName = "Convert import" ;
4
4
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
+ } ,
14
21
} ;
15
22
16
23
registerRefactor ( refactorName , {
17
- kinds : [
18
- namespaceToNamedAction . kind ,
19
- namedToNamespaceAction . kind
20
- ] ,
24
+ kinds : getOwnValues ( actions ) . map ( a => a . kind ) ,
21
25
getAvailableActions : function getRefactorActionsToConvertBetweenNamedAndNamespacedImports ( context ) : readonly ApplicableRefactorInfo [ ] {
22
- const info = getImportToConvert ( context , context . triggerReason === "invoked" ) ;
26
+ const info = getImportConversionInfo ( context , context . triggerReason === "invoked" ) ;
23
27
if ( ! info ) return emptyArray ;
24
28
25
29
if ( ! isRefactorErrorInfo ( info ) ) {
26
- const namespaceImport = info . kind === SyntaxKind . NamespaceImport ;
27
- const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction ;
30
+ const action = actions [ info . convertTo ] ;
28
31
return [ { name : refactorName , description : action . description , actions : [ action ] } ] ;
29
32
}
30
33
31
34
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
+ } ) ) ;
38
40
}
39
41
40
42
return emptyArray ;
41
43
} ,
42
44
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 ) ;
45
47
Debug . assert ( info && ! isRefactorErrorInfo ( info ) , "Expected applicable refactor info" ) ;
46
48
const edits = textChanges . ChangeTracker . with ( context , t => doChange ( context . file , context . program , t , info ) ) ;
47
49
return { edits, renameFilename : undefined , renameLocation : undefined } ;
48
50
}
49
51
} ) ;
50
52
51
53
// 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 {
53
60
const { file } = context ;
54
61
const span = getRefactorContextSpan ( context ) ;
55
62
const token = getTokenAtPosition ( file , span . start ) ;
@@ -69,16 +76,25 @@ namespace ts.refactor {
69
76
return { error : getLocaleSpecificMessage ( Diagnostics . Could_not_find_namespace_import_or_named_imports ) } ;
70
77
}
71
78
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 } ;
73
89
}
74
90
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 {
76
92
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 ( ) ) ) ;
79
95
}
80
96
else {
81
- doChangeNamedToNamespace ( sourceFile , checker , changes , toConvert ) ;
97
+ doChangeNamedToNamespaceOrDefault ( sourceFile , checker , changes , info . import , info . convertTo === ImportKind . Default ) ;
82
98
}
83
99
}
84
100
@@ -137,7 +153,7 @@ namespace ts.refactor {
137
153
return isPropertyAccessExpression ( propertyAccessOrQualifiedName ) ? propertyAccessOrQualifiedName . expression : propertyAccessOrQualifiedName . left ;
138
154
}
139
155
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 ) {
141
157
const importDecl = toConvert . parent . parent ;
142
158
const { moduleSpecifier } = importDecl ;
143
159
@@ -188,14 +204,23 @@ namespace ts.refactor {
188
204
} ) ;
189
205
}
190
206
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 ) ) ) ;
192
210
if ( neededNamedImports . size ) {
193
211
const newNamedImports : ImportSpecifier [ ] = arrayFrom ( neededNamedImports . values ( ) ) . map ( element =>
194
212
factory . createImportSpecifier ( element . isTypeOnly , element . propertyName && factory . createIdentifier ( element . propertyName . text ) , factory . createIdentifier ( element . name . text ) ) ) ;
195
213
changes . insertNodeAfter ( sourceFile , toConvert . parent . parent , updateImport ( importDecl , /*defaultImportName*/ undefined , newNamedImports ) ) ;
196
214
}
197
215
}
198
216
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
+
199
224
function updateImport ( old : ImportDeclaration , defaultImportName : Identifier | undefined , elements : readonly ImportSpecifier [ ] | undefined ) : ImportDeclaration {
200
225
return factory . createImportDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined ,
201
226
factory . createImportClause ( /*isTypeOnly*/ false , defaultImportName , elements && elements . length ? factory . createNamedImports ( elements ) : undefined ) , old . moduleSpecifier , /*assertClause*/ undefined ) ;
0 commit comments