@@ -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,15 @@ 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
- }
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 ) ;
67
59
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 ;
95
60
} ) ;
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
61
}
110
62
111
63
function getExternalModuleName ( specifier : Expression ) {
@@ -123,11 +75,13 @@ namespace ts.OrganizeImports {
123
75
const groups : ImportDeclaration [ ] [ ] = [ ] ;
124
76
125
77
let groupName : string | undefined = getExternalModuleName ( sortedImports [ 0 ] . moduleSpecifier ) ;
78
+ Debug . assert ( groupName !== undefined ) ;
126
79
let group : ImportDeclaration [ ] = [ ] ;
127
80
128
81
for ( const importDeclaration of sortedImports ) {
129
82
const moduleName = getExternalModuleName ( importDeclaration . moduleSpecifier ) ;
130
- if ( moduleName && moduleName === groupName ) {
83
+ Debug . assert ( moduleName !== undefined ) ;
84
+ if ( moduleName === groupName ) {
131
85
group . push ( importDeclaration ) ;
132
86
}
133
87
else if ( group . length ) {
@@ -159,38 +113,10 @@ namespace ts.OrganizeImports {
159
113
const groupedImports = groupSortedImports ( sortedImports ) ;
160
114
for ( const importGroup of groupedImports ) {
161
115
162
- let seenImportWithoutClause = false ;
163
-
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
- }
116
+ const { importWithoutClause, defaultImports, namespaceImports, namedImports } = getImportParts ( importGroup ) ;
185
117
186
- if ( namedBindings ) {
187
- if ( isNamespaceImport ( namedBindings ) ) {
188
- namespaceImports . push ( namedBindings ) ;
189
- }
190
- else {
191
- namedImports . push ( namedBindings ) ;
192
- }
193
- }
118
+ if ( importWithoutClause ) {
119
+ coalescedImports . push ( importWithoutClause ) ;
194
120
}
195
121
196
122
// Normally, we don't combine default and namespace imports, but it would be silly to
@@ -204,8 +130,6 @@ namespace ts.OrganizeImports {
204
130
continue ;
205
131
}
206
132
207
- // For convenience, we cheat and do a little sorting during coalescing.
208
- // Seems reasonable since we're restructuring so much anyway.
209
133
const sortedNamespaceImports = stableSort ( namespaceImports , ( n1 , n2 ) => compareIdentifiers ( n1 . name , n2 . name ) ) ;
210
134
211
135
for ( const namespaceImport of sortedNamespaceImports ) {
@@ -218,7 +142,7 @@ namespace ts.OrganizeImports {
218
142
continue ;
219
143
}
220
144
221
- let newDefaultImport : Identifier = undefined ;
145
+ let newDefaultImport : Identifier | undefined ;
222
146
const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
223
147
if ( defaultImports . length === 1 ) {
224
148
newDefaultImport = defaultImports [ 0 ] ;
@@ -230,18 +154,11 @@ namespace ts.OrganizeImports {
230
154
}
231
155
}
232
156
233
- for ( const namedImport of namedImports ) {
234
- for ( const specifier of namedImport . elements ) {
235
- newImportSpecifiers . push ( specifier ) ;
236
- }
237
- }
157
+ newImportSpecifiers . push ( ...flatMap ( namedImports , n => n . elements ) ) ;
238
158
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
- } ) ;
159
+ const sortedImportSpecifiers = stableSort ( newImportSpecifiers , ( s1 , s2 ) =>
160
+ compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) ||
161
+ compareIdentifiers ( s1 . name , s2 . name ) ) ;
245
162
246
163
const importClause = defaultImports . length > 0
247
164
? defaultImports [ 0 ] . parent as ImportClause
@@ -259,19 +176,46 @@ namespace ts.OrganizeImports {
259
176
260
177
return coalescedImports ;
261
178
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 ;
179
+ function getImportParts ( importGroup : ReadonlyArray < ImportDeclaration > ) {
180
+ let importWithoutClause : ImportDeclaration | undefined ;
181
+ const defaultImports : Identifier [ ] = [ ] ;
182
+ const namespaceImports : NamespaceImport [ ] = [ ] ;
183
+ const namedImports : NamedImports [ ] = [ ] ;
184
+
185
+ for ( const importDeclaration of importGroup ) {
186
+ if ( importDeclaration . importClause === undefined ) {
187
+ // Only the first such import is interesting - the others are redundant.
188
+ // Note: Unfortunately, we will lose trivia that was on this node.
189
+ importWithoutClause = importWithoutClause || importDeclaration ;
190
+ continue ;
191
+ }
192
+
193
+ const { name, namedBindings } = importDeclaration . importClause ;
194
+
195
+ if ( name ) {
196
+ defaultImports . push ( name ) ;
197
+ }
198
+
199
+ if ( namedBindings ) {
200
+ if ( isNamespaceImport ( namedBindings ) ) {
201
+ namespaceImports . push ( namedBindings ) ;
202
+ }
203
+ else {
204
+ namedImports . push ( namedBindings ) ;
205
+ }
206
+ }
207
+ }
208
+
209
+ return {
210
+ importWithoutClause,
211
+ defaultImports,
212
+ namespaceImports,
213
+ namedImports,
214
+ }
215
+ }
216
+
217
+ function compareIdentifiers ( s1 : Identifier , s2 : Identifier ) {
218
+ return compareStringsCaseSensitive ( s1 . text , s2 . text ) ;
275
219
}
276
220
277
221
function updateImportDeclarationAndClause (
0 commit comments