@@ -174,115 +174,134 @@ namespace ts.OrganizeImports {
174
174
return importGroup ;
175
175
}
176
176
177
- const { importWithoutClause, defaultImports , namespaceImports , namedImports } = getCategorizedImports ( importGroup ) ;
177
+ const { importWithoutClause, typeOnlyImports , regularImports } = getCategorizedImports ( importGroup ) ;
178
178
179
179
const coalescedImports : ImportDeclaration [ ] = [ ] ;
180
180
181
181
if ( importWithoutClause ) {
182
182
coalescedImports . push ( importWithoutClause ) ;
183
183
}
184
184
185
- // Normally, we don't combine default and namespace imports, but it would be silly to
186
- // produce two import declarations in this special case.
187
- if ( defaultImports . length === 1 && namespaceImports . length === 1 && namedImports . length === 0 ) {
188
- // Add the namespace import to the existing default ImportDeclaration.
189
- const defaultImport = defaultImports [ 0 ] ;
190
- coalescedImports . push (
191
- updateImportDeclarationAndClause ( defaultImport , defaultImport . importClause ! . name , namespaceImports [ 0 ] . importClause ! . namedBindings ) ) ; // TODO: GH#18217
185
+ for ( const group of [ regularImports , typeOnlyImports ] ) {
186
+ const isTypeOnly = group === typeOnlyImports ;
187
+ const { defaultImports, namespaceImports, namedImports } = group ;
188
+ // Normally, we don't combine default and namespace imports, but it would be silly to
189
+ // produce two import declarations in this special case.
190
+ if ( ! isTypeOnly && defaultImports . length === 1 && namespaceImports . length === 1 && namedImports . length === 0 ) {
191
+ // Add the namespace import to the existing default ImportDeclaration.
192
+ const defaultImport = defaultImports [ 0 ] ;
193
+ coalescedImports . push (
194
+ updateImportDeclarationAndClause ( defaultImport , defaultImport . importClause ! . name , namespaceImports [ 0 ] . importClause ! . namedBindings ) ) ; // TODO: GH#18217
192
195
193
- return coalescedImports ;
194
- }
196
+ continue ;
197
+ }
195
198
196
- const sortedNamespaceImports = stableSort ( namespaceImports , ( i1 , i2 ) =>
197
- compareIdentifiers ( ( i1 . importClause ! . namedBindings as NamespaceImport ) . name , ( i2 . importClause ! . namedBindings as NamespaceImport ) . name ) ) ; // TODO: GH#18217
199
+ const sortedNamespaceImports = stableSort ( namespaceImports , ( i1 , i2 ) =>
200
+ compareIdentifiers ( ( i1 . importClause ! . namedBindings as NamespaceImport ) . name , ( i2 . importClause ! . namedBindings as NamespaceImport ) . name ) ) ; // TODO: GH#18217
198
201
199
- for ( const namespaceImport of sortedNamespaceImports ) {
200
- // Drop the name, if any
201
- coalescedImports . push (
202
- updateImportDeclarationAndClause ( namespaceImport , /*name*/ undefined , namespaceImport . importClause ! . namedBindings ) ) ; // TODO: GH#18217
203
- }
202
+ for ( const namespaceImport of sortedNamespaceImports ) {
203
+ // Drop the name, if any
204
+ coalescedImports . push (
205
+ updateImportDeclarationAndClause ( namespaceImport , /*name*/ undefined , namespaceImport . importClause ! . namedBindings ) ) ; // TODO: GH#18217
206
+ }
204
207
205
- if ( defaultImports . length === 0 && namedImports . length === 0 ) {
206
- return coalescedImports ;
207
- }
208
+ if ( defaultImports . length === 0 && namedImports . length === 0 ) {
209
+ continue ;
210
+ }
208
211
209
- let newDefaultImport : Identifier | undefined ;
210
- const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
211
- if ( defaultImports . length === 1 ) {
212
- newDefaultImport = defaultImports [ 0 ] . importClause ! . name ;
213
- }
214
- else {
215
- for ( const defaultImport of defaultImports ) {
216
- newImportSpecifiers . push (
217
- createImportSpecifier ( createIdentifier ( "default" ) , defaultImport . importClause ! . name ! ) ) ; // TODO: GH#18217
212
+ let newDefaultImport : Identifier | undefined ;
213
+ const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
214
+ if ( defaultImports . length === 1 ) {
215
+ newDefaultImport = defaultImports [ 0 ] . importClause ! . name ;
216
+ }
217
+ else {
218
+ for ( const defaultImport of defaultImports ) {
219
+ newImportSpecifiers . push (
220
+ createImportSpecifier ( createIdentifier ( "default" ) , defaultImport . importClause ! . name ! ) ) ; // TODO: GH#18217
221
+ }
218
222
}
219
- }
220
223
221
- newImportSpecifiers . push ( ...flatMap ( namedImports , i => ( i . importClause ! . namedBindings as NamedImports ) . elements ) ) ; // TODO: GH#18217
224
+ newImportSpecifiers . push ( ...flatMap ( namedImports , i => ( i . importClause ! . namedBindings as NamedImports ) . elements ) ) ; // TODO: GH#18217
222
225
223
- const sortedImportSpecifiers = sortSpecifiers ( newImportSpecifiers ) ;
226
+ const sortedImportSpecifiers = sortSpecifiers ( newImportSpecifiers ) ;
224
227
225
- const importDecl = defaultImports . length > 0
226
- ? defaultImports [ 0 ]
227
- : namedImports [ 0 ] ;
228
+ const importDecl = defaultImports . length > 0
229
+ ? defaultImports [ 0 ]
230
+ : namedImports [ 0 ] ;
228
231
229
- const newNamedImports = sortedImportSpecifiers . length === 0
230
- ? newDefaultImport
231
- ? undefined
232
- : createNamedImports ( emptyArray )
233
- : namedImports . length === 0
234
- ? createNamedImports ( sortedImportSpecifiers )
235
- : updateNamedImports ( namedImports [ 0 ] . importClause ! . namedBindings as NamedImports , sortedImportSpecifiers ) ; // TODO: GH#18217
232
+ const newNamedImports = sortedImportSpecifiers . length === 0
233
+ ? newDefaultImport
234
+ ? undefined
235
+ : createNamedImports ( emptyArray )
236
+ : namedImports . length === 0
237
+ ? createNamedImports ( sortedImportSpecifiers )
238
+ : updateNamedImports ( namedImports [ 0 ] . importClause ! . namedBindings as NamedImports , sortedImportSpecifiers ) ; // TODO: GH#18217
236
239
237
- coalescedImports . push (
238
- updateImportDeclarationAndClause ( importDecl , newDefaultImport , newNamedImports ) ) ;
240
+ // Type-only imports are not allowed to combine
241
+ if ( isTypeOnly && newDefaultImport && newNamedImports ) {
242
+ coalescedImports . push (
243
+ updateImportDeclarationAndClause ( importDecl , newDefaultImport , /*namedBindings*/ undefined ) ) ;
244
+ coalescedImports . push (
245
+ updateImportDeclarationAndClause ( namedImports [ 0 ] ?? importDecl , /*name*/ undefined , newNamedImports ) ) ;
246
+ }
247
+ else {
248
+ coalescedImports . push (
249
+ updateImportDeclarationAndClause ( importDecl , newDefaultImport , newNamedImports ) ) ;
250
+ }
251
+ }
239
252
240
253
return coalescedImports ;
241
254
242
- /*
243
- * Returns entire import declarations because they may already have been rewritten and
244
- * may lack parent pointers. The desired parts can easily be recovered based on the
245
- * categorization.
246
- *
247
- * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`.
248
- */
249
- function getCategorizedImports ( importGroup : readonly ImportDeclaration [ ] ) {
250
- let importWithoutClause : ImportDeclaration | undefined ;
251
- const defaultImports : ImportDeclaration [ ] = [ ] ;
252
- const namespaceImports : ImportDeclaration [ ] = [ ] ;
253
- const namedImports : ImportDeclaration [ ] = [ ] ;
254
-
255
- for ( const importDeclaration of importGroup ) {
256
- if ( importDeclaration . importClause === undefined ) {
257
- // Only the first such import is interesting - the others are redundant.
258
- // Note: Unfortunately, we will lose trivia that was on this node.
259
- importWithoutClause = importWithoutClause || importDeclaration ;
260
- continue ;
261
- }
255
+ }
262
256
263
- const { name, namedBindings } = importDeclaration . importClause ;
257
+ interface ImportGroup {
258
+ defaultImports : ImportDeclaration [ ] ;
259
+ namespaceImports : ImportDeclaration [ ] ;
260
+ namedImports : ImportDeclaration [ ] ;
261
+ }
264
262
265
- if ( name ) {
266
- defaultImports . push ( importDeclaration ) ;
267
- }
263
+ /*
264
+ * Returns entire import declarations because they may already have been rewritten and
265
+ * may lack parent pointers. The desired parts can easily be recovered based on the
266
+ * categorization.
267
+ *
268
+ * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`.
269
+ */
270
+ function getCategorizedImports ( importGroup : readonly ImportDeclaration [ ] ) {
271
+ let importWithoutClause : ImportDeclaration | undefined ;
272
+ const typeOnlyImports : ImportGroup = { defaultImports : [ ] , namespaceImports : [ ] , namedImports : [ ] } ;
273
+ const regularImports : ImportGroup = { defaultImports : [ ] , namespaceImports : [ ] , namedImports : [ ] } ;
274
+
275
+ for ( const importDeclaration of importGroup ) {
276
+ if ( importDeclaration . importClause === undefined ) {
277
+ // Only the first such import is interesting - the others are redundant.
278
+ // Note: Unfortunately, we will lose trivia that was on this node.
279
+ importWithoutClause = importWithoutClause || importDeclaration ;
280
+ continue ;
281
+ }
268
282
269
- if ( namedBindings ) {
270
- if ( isNamespaceImport ( namedBindings ) ) {
271
- namespaceImports . push ( importDeclaration ) ;
272
- }
273
- else {
274
- namedImports . push ( importDeclaration ) ;
275
- }
276
- }
283
+ const group = importDeclaration . importClause . isTypeOnly ? typeOnlyImports : regularImports ;
284
+ const { name, namedBindings } = importDeclaration . importClause ;
285
+
286
+ if ( name ) {
287
+ group . defaultImports . push ( importDeclaration ) ;
277
288
}
278
289
279
- return {
280
- importWithoutClause,
281
- defaultImports,
282
- namespaceImports,
283
- namedImports,
284
- } ;
290
+ if ( namedBindings ) {
291
+ if ( isNamespaceImport ( namedBindings ) ) {
292
+ group . namespaceImports . push ( importDeclaration ) ;
293
+ }
294
+ else {
295
+ group . namedImports . push ( importDeclaration ) ;
296
+ }
297
+ }
285
298
}
299
+
300
+ return {
301
+ importWithoutClause,
302
+ typeOnlyImports,
303
+ regularImports,
304
+ } ;
286
305
}
287
306
288
307
// Internal for testing
@@ -294,37 +313,38 @@ namespace ts.OrganizeImports {
294
313
return exportGroup ;
295
314
}
296
315
297
- const { exportWithoutClause, namedExports } = getCategorizedExports ( exportGroup ) ;
316
+ const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports ( exportGroup ) ;
298
317
299
318
const coalescedExports : ExportDeclaration [ ] = [ ] ;
300
319
301
320
if ( exportWithoutClause ) {
302
321
coalescedExports . push ( exportWithoutClause ) ;
303
322
}
304
323
305
- if ( namedExports . length === 0 ) {
306
- return coalescedExports ;
324
+ for ( const exportGroup of [ namedExports , typeOnlyExports ] ) {
325
+ if ( exportGroup . length === 0 ) {
326
+ continue ;
327
+ }
328
+ const newExportSpecifiers : ExportSpecifier [ ] = [ ] ;
329
+ newExportSpecifiers . push ( ...flatMap ( exportGroup , i => i . exportClause && isNamedExports ( i . exportClause ) ? i . exportClause . elements : emptyArray ) ) ;
330
+
331
+ const sortedExportSpecifiers = sortSpecifiers ( newExportSpecifiers ) ;
332
+
333
+ const exportDecl = exportGroup [ 0 ] ;
334
+ coalescedExports . push (
335
+ updateExportDeclaration (
336
+ exportDecl ,
337
+ exportDecl . decorators ,
338
+ exportDecl . modifiers ,
339
+ exportDecl . exportClause && (
340
+ isNamedExports ( exportDecl . exportClause ) ?
341
+ updateNamedExports ( exportDecl . exportClause , sortedExportSpecifiers ) :
342
+ updateNamespaceExport ( exportDecl . exportClause , exportDecl . exportClause . name )
343
+ ) ,
344
+ exportDecl . moduleSpecifier ,
345
+ exportDecl . isTypeOnly ) ) ;
307
346
}
308
347
309
- const newExportSpecifiers : ExportSpecifier [ ] = [ ] ;
310
- newExportSpecifiers . push ( ...flatMap ( namedExports , i => i . exportClause && isNamedExports ( i . exportClause ) ? i . exportClause . elements : emptyArray ) ) ;
311
-
312
- const sortedExportSpecifiers = sortSpecifiers ( newExportSpecifiers ) ;
313
-
314
- const exportDecl = namedExports [ 0 ] ;
315
- coalescedExports . push (
316
- updateExportDeclaration (
317
- exportDecl ,
318
- exportDecl . decorators ,
319
- exportDecl . modifiers ,
320
- exportDecl . exportClause && (
321
- isNamedExports ( exportDecl . exportClause ) ?
322
- updateNamedExports ( exportDecl . exportClause , sortedExportSpecifiers ) :
323
- updateNamespaceExport ( exportDecl . exportClause , exportDecl . exportClause . name )
324
- ) ,
325
- exportDecl . moduleSpecifier ,
326
- exportDecl . isTypeOnly ) ) ;
327
-
328
348
return coalescedExports ;
329
349
330
350
/*
@@ -335,13 +355,17 @@ namespace ts.OrganizeImports {
335
355
function getCategorizedExports ( exportGroup : readonly ExportDeclaration [ ] ) {
336
356
let exportWithoutClause : ExportDeclaration | undefined ;
337
357
const namedExports : ExportDeclaration [ ] = [ ] ;
358
+ const typeOnlyExports : ExportDeclaration [ ] = [ ] ;
338
359
339
360
for ( const exportDeclaration of exportGroup ) {
340
361
if ( exportDeclaration . exportClause === undefined ) {
341
362
// Only the first such export is interesting - the others are redundant.
342
363
// Note: Unfortunately, we will lose trivia that was on this node.
343
364
exportWithoutClause = exportWithoutClause || exportDeclaration ;
344
365
}
366
+ else if ( exportDeclaration . isTypeOnly ) {
367
+ typeOnlyExports . push ( exportDeclaration ) ;
368
+ }
345
369
else {
346
370
namedExports . push ( exportDeclaration ) ;
347
371
}
@@ -350,6 +374,7 @@ namespace ts.OrganizeImports {
350
374
return {
351
375
exportWithoutClause,
352
376
namedExports,
377
+ typeOnlyExports,
353
378
} ;
354
379
}
355
380
}
0 commit comments