@@ -3,56 +3,44 @@ namespace ts.OrganizeImports {
3
3
export function organizeImports (
4
4
sourceFile : SourceFile ,
5
5
formatContext : formatting . FormatContext ,
6
- host : LanguageServiceHost ,
7
- cancellationToken : CancellationToken ) {
6
+ host : LanguageServiceHost ) {
8
7
9
- // All of the (old) ImportDeclarations in the file, in syntactic order.
10
- const oldImportDecls : ImportDeclaration [ ] = [ ] ;
8
+ // TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
11
9
12
- forEachChild ( sourceFile , node => {
13
- cancellationToken . throwIfCancellationRequested ( ) ;
14
- if ( isImportDeclaration ( node ) ) {
15
- oldImportDecls . push ( node ) ;
16
- }
17
- // TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
18
- } ) ;
10
+ // All of the old ImportDeclarations in the file, in syntactic order.
11
+ const oldImportDecls = sourceFile . statements . filter ( isImportDeclaration ) ;
19
12
20
13
if ( oldImportDecls . length === 0 ) {
21
14
return [ ] ;
22
15
}
23
16
24
- const usedImportDecls = removeUnusedImports ( oldImportDecls ) ;
25
- cancellationToken . throwIfCancellationRequested ( ) ;
26
- const sortedImportDecls = sortImports ( usedImportDecls ) ;
27
- cancellationToken . throwIfCancellationRequested ( ) ;
28
- const coalescedImportDecls = coalesceImports ( sortedImportDecls ) ;
29
- cancellationToken . throwIfCancellationRequested ( ) ;
17
+ const oldValidImportDecls = oldImportDecls . filter ( importDecl => getExternalModuleName ( importDecl . moduleSpecifier ) ) ;
18
+ const oldInvalidImportDecls = oldImportDecls . filter ( importDecl => ! getExternalModuleName ( importDecl . moduleSpecifier ) ) ;
30
19
31
- // All of the ( new) ImportDeclarations in the file, in sorted order.
32
- const newImportDecls = coalescedImportDecls ;
20
+ // All of the new ImportDeclarations in the file, in sorted order.
21
+ const newImportDecls = coalesceImports ( sortImports ( removeUnusedImports ( oldValidImportDecls ) ) ) . concat ( oldInvalidImportDecls ) ;
33
22
34
23
const changeTracker = textChanges . ChangeTracker . fromContext ( { host, formatContext } ) ;
35
24
36
- // NB: Stopping before i === 0
37
- for ( let i = oldImportDecls . length - 1 ; i > 0 ; i -- ) {
38
- changeTracker . deleteNode ( sourceFile , oldImportDecls [ i ] ) ;
39
- }
40
-
25
+ // Delete or replace the first import.
41
26
if ( newImportDecls . length === 0 ) {
42
27
changeTracker . deleteNode ( sourceFile , oldImportDecls [ 0 ] ) ;
43
28
}
44
29
else {
45
- // Delete the surrounding trivia because it will have been retained in newImportDecls.
46
- const replaceOptions = {
30
+ // Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
31
+ changeTracker . replaceNodeWithNodes ( sourceFile , oldImportDecls [ 0 ] , newImportDecls , {
47
32
useNonAdjustedStartPosition : false ,
48
33
useNonAdjustedEndPosition : false ,
49
34
suffix : getNewLineOrDefaultFromHost ( host , formatContext . options ) ,
50
- } ;
51
- changeTracker . replaceNodeWithNodes ( sourceFile , oldImportDecls [ 0 ] , newImportDecls , replaceOptions ) ;
35
+ } ) ;
52
36
}
53
37
54
- const changes = changeTracker . getChanges ( ) ;
55
- return changes ;
38
+ // Delete any subsequent imports.
39
+ for ( let i = 1 ; i < oldImportDecls . length ; i ++ ) {
40
+ changeTracker . deleteNode ( sourceFile , oldImportDecls [ i ] ) ;
41
+ }
42
+
43
+ return changeTracker . getChanges ( ) ;
56
44
}
57
45
58
46
function removeUnusedImports ( oldImports : ReadonlyArray < ImportDeclaration > ) {
@@ -61,51 +49,14 @@ namespace ts.OrganizeImports {
61
49
62
50
/* @internal */ // Internal for testing
63
51
export function sortImports ( oldImports : ReadonlyArray < ImportDeclaration > ) {
64
- if ( oldImports . length < 2 ) {
65
- return oldImports ;
66
- }
67
-
68
- // NB: declaration order determines sort order
69
- const enum ModuleNameKind {
70
- NonRelative ,
71
- Relative ,
72
- Invalid ,
73
- }
74
-
75
- const importRecords = oldImports . map ( createImportRecord ) ;
76
-
77
- const sortedRecords = stableSort ( importRecords , ( import1 , import2 ) => {
78
- const { name : name1 , kind : kind1 } = import1 ;
79
- const { name : name2 , kind : kind2 } = import2 ;
80
-
81
- if ( kind1 !== kind2 ) {
82
- return kind1 < kind2
83
- ? Comparison . LessThan
84
- : Comparison . GreaterThan ;
85
- }
86
-
87
- // Note that we're using simple equality, retaining case-sensitivity.
88
- if ( name1 !== name2 ) {
89
- return name1 < name2
90
- ? Comparison . LessThan
91
- : Comparison . GreaterThan ;
92
- }
93
-
94
- return Comparison . EqualTo ;
52
+ return stableSort ( oldImports , ( import1 , import2 ) => {
53
+ const name1 = getExternalModuleName ( import1 . moduleSpecifier ) ;
54
+ const name2 = getExternalModuleName ( import2 . moduleSpecifier ) ;
55
+ Debug . assert ( name1 !== undefined ) ;
56
+ Debug . assert ( name2 !== undefined ) ;
57
+ return compareBooleans ( isExternalModuleNameRelative ( name1 ) , isExternalModuleNameRelative ( name2 ) ) ||
58
+ compareStringsCaseSensitive ( name1 , name2 ) ;
95
59
} ) ;
96
-
97
- return sortedRecords . map ( r => r . importDeclaration ) ;
98
-
99
- function createImportRecord ( importDeclaration : ImportDeclaration ) {
100
- const specifier = importDeclaration . moduleSpecifier ;
101
- const name = getExternalModuleName ( specifier ) ;
102
- if ( name ) {
103
- const isRelative = isExternalModuleNameRelative ( name ) ;
104
- return { importDeclaration, name, kind : isRelative ? ModuleNameKind . Relative : ModuleNameKind . NonRelative } ;
105
- }
106
-
107
- return { importDeclaration, name : specifier . getText ( ) , kind : ModuleNameKind . Invalid } ;
108
- }
109
60
}
110
61
111
62
function getExternalModuleName ( specifier : Expression ) {
@@ -123,11 +74,13 @@ namespace ts.OrganizeImports {
123
74
const groups : ImportDeclaration [ ] [ ] = [ ] ;
124
75
125
76
let groupName : string | undefined = getExternalModuleName ( sortedImports [ 0 ] . moduleSpecifier ) ;
77
+ Debug . assert ( groupName !== undefined ) ;
126
78
let group : ImportDeclaration [ ] = [ ] ;
127
79
128
80
for ( const importDeclaration of sortedImports ) {
129
81
const moduleName = getExternalModuleName ( importDeclaration . moduleSpecifier ) ;
130
- if ( moduleName && moduleName === groupName ) {
82
+ Debug . assert ( moduleName !== undefined ) ;
83
+ if ( moduleName === groupName ) {
131
84
group . push ( importDeclaration ) ;
132
85
}
133
86
else if ( group . length ) {
@@ -159,38 +112,10 @@ namespace ts.OrganizeImports {
159
112
const groupedImports = groupSortedImports ( sortedImports ) ;
160
113
for ( const importGroup of groupedImports ) {
161
114
162
- let seenImportWithoutClause = false ;
115
+ const { importWithoutClause , defaultImports , namespaceImports , namedImports } = getImportParts ( importGroup ) ;
163
116
164
- const defaultImports : Identifier [ ] = [ ] ;
165
- const namespaceImports : NamespaceImport [ ] = [ ] ;
166
- const namedImports : NamedImports [ ] = [ ] ;
167
-
168
- for ( const importDeclaration of importGroup ) {
169
- if ( importDeclaration . importClause === undefined ) {
170
- // Only the first such import is interesting - the others are redundant.
171
- // Note: Unfortunately, we will lose trivia that was on this node.
172
- if ( ! seenImportWithoutClause ) {
173
- coalescedImports . push ( importDeclaration ) ;
174
- }
175
-
176
- seenImportWithoutClause = true ;
177
- continue ;
178
- }
179
-
180
- const { name, namedBindings } = importDeclaration . importClause ;
181
-
182
- if ( name ) {
183
- defaultImports . push ( name ) ;
184
- }
185
-
186
- if ( namedBindings ) {
187
- if ( isNamespaceImport ( namedBindings ) ) {
188
- namespaceImports . push ( namedBindings ) ;
189
- }
190
- else {
191
- namedImports . push ( namedBindings ) ;
192
- }
193
- }
117
+ if ( importWithoutClause ) {
118
+ coalescedImports . push ( importWithoutClause ) ;
194
119
}
195
120
196
121
// Normally, we don't combine default and namespace imports, but it would be silly to
@@ -204,8 +129,6 @@ namespace ts.OrganizeImports {
204
129
continue ;
205
130
}
206
131
207
- // For convenience, we cheat and do a little sorting during coalescing.
208
- // Seems reasonable since we're restructuring so much anyway.
209
132
const sortedNamespaceImports = stableSort ( namespaceImports , ( n1 , n2 ) => compareIdentifiers ( n1 . name , n2 . name ) ) ;
210
133
211
134
for ( const namespaceImport of sortedNamespaceImports ) {
@@ -218,7 +141,7 @@ namespace ts.OrganizeImports {
218
141
continue ;
219
142
}
220
143
221
- let newDefaultImport : Identifier = undefined ;
144
+ let newDefaultImport : Identifier | undefined ;
222
145
const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
223
146
if ( defaultImports . length === 1 ) {
224
147
newDefaultImport = defaultImports [ 0 ] ;
@@ -230,18 +153,11 @@ namespace ts.OrganizeImports {
230
153
}
231
154
}
232
155
233
- for ( const namedImport of namedImports ) {
234
- for ( const specifier of namedImport . elements ) {
235
- newImportSpecifiers . push ( specifier ) ;
236
- }
237
- }
156
+ newImportSpecifiers . push ( ...flatMap ( namedImports , n => n . elements ) ) ;
238
157
239
- const sortedImportSpecifiers = stableSort ( newImportSpecifiers , ( s1 , s2 ) => {
240
- const nameComparison = compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) ;
241
- return nameComparison !== Comparison . EqualTo
242
- ? nameComparison
243
- : compareIdentifiers ( s1 . name , s2 . name ) ;
244
- } ) ;
158
+ const sortedImportSpecifiers = stableSort ( newImportSpecifiers , ( s1 , s2 ) =>
159
+ compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) ||
160
+ compareIdentifiers ( s1 . name , s2 . name ) ) ;
245
161
246
162
const importClause = defaultImports . length > 0
247
163
? defaultImports [ 0 ] . parent as ImportClause
@@ -259,19 +175,46 @@ namespace ts.OrganizeImports {
259
175
260
176
return coalescedImports ;
261
177
262
- // `undefined` is the min value.
263
- function compareIdentifiers ( s1 : Identifier | undefined , s2 : Identifier | undefined ) {
264
- return s1 === undefined
265
- ? s2 === undefined
266
- ? Comparison . EqualTo
267
- : Comparison . LessThan
268
- : s2 === undefined
269
- ? Comparison . GreaterThan
270
- : s1 . text < s2 . text
271
- ? Comparison . LessThan
272
- : s1 . text > s2 . text
273
- ? Comparison . GreaterThan
274
- : Comparison . EqualTo ;
178
+ function getImportParts ( importGroup : ReadonlyArray < ImportDeclaration > ) {
179
+ let importWithoutClause : ImportDeclaration | undefined ;
180
+ const defaultImports : Identifier [ ] = [ ] ;
181
+ const namespaceImports : NamespaceImport [ ] = [ ] ;
182
+ const namedImports : NamedImports [ ] = [ ] ;
183
+
184
+ for ( const importDeclaration of importGroup ) {
185
+ if ( importDeclaration . importClause === undefined ) {
186
+ // Only the first such import is interesting - the others are redundant.
187
+ // Note: Unfortunately, we will lose trivia that was on this node.
188
+ importWithoutClause = importWithoutClause || importDeclaration ;
189
+ continue ;
190
+ }
191
+
192
+ const { name, namedBindings } = importDeclaration . importClause ;
193
+
194
+ if ( name ) {
195
+ defaultImports . push ( name ) ;
196
+ }
197
+
198
+ if ( namedBindings ) {
199
+ if ( isNamespaceImport ( namedBindings ) ) {
200
+ namespaceImports . push ( namedBindings ) ;
201
+ }
202
+ else {
203
+ namedImports . push ( namedBindings ) ;
204
+ }
205
+ }
206
+ }
207
+
208
+ return {
209
+ importWithoutClause,
210
+ defaultImports,
211
+ namespaceImports,
212
+ namedImports,
213
+ } ;
214
+ }
215
+
216
+ function compareIdentifiers ( s1 : Identifier , s2 : Identifier ) {
217
+ return compareStringsCaseSensitive ( s1 . text , s2 . text ) ;
275
218
}
276
219
277
220
function updateImportDeclarationAndClause (
0 commit comments