@@ -174,115 +174,134 @@ namespace ts.OrganizeImports {
174174 return importGroup ;
175175 }
176176
177- const { importWithoutClause, defaultImports , namespaceImports , namedImports } = getCategorizedImports ( importGroup ) ;
177+ const { importWithoutClause, typeOnlyImports , regularImports } = getCategorizedImports ( importGroup ) ;
178178
179179 const coalescedImports : ImportDeclaration [ ] = [ ] ;
180180
181181 if ( importWithoutClause ) {
182182 coalescedImports . push ( importWithoutClause ) ;
183183 }
184184
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
192195
193- return coalescedImports ;
194- }
196+ continue ;
197+ }
195198
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
198201
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+ }
204207
205- if ( defaultImports . length === 0 && namedImports . length === 0 ) {
206- return coalescedImports ;
207- }
208+ if ( defaultImports . length === 0 && namedImports . length === 0 ) {
209+ continue ;
210+ }
208211
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+ }
218222 }
219- }
220223
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
222225
223- const sortedImportSpecifiers = sortSpecifiers ( newImportSpecifiers ) ;
226+ const sortedImportSpecifiers = sortSpecifiers ( newImportSpecifiers ) ;
224227
225- const importDecl = defaultImports . length > 0
226- ? defaultImports [ 0 ]
227- : namedImports [ 0 ] ;
228+ const importDecl = defaultImports . length > 0
229+ ? defaultImports [ 0 ]
230+ : namedImports [ 0 ] ;
228231
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
236239
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+ }
239252
240253 return coalescedImports ;
241254
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+ }
262256
263- const { name, namedBindings } = importDeclaration . importClause ;
257+ interface ImportGroup {
258+ defaultImports : ImportDeclaration [ ] ;
259+ namespaceImports : ImportDeclaration [ ] ;
260+ namedImports : ImportDeclaration [ ] ;
261+ }
264262
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+ }
268282
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 ) ;
277288 }
278289
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+ }
285298 }
299+
300+ return {
301+ importWithoutClause,
302+ typeOnlyImports,
303+ regularImports,
304+ } ;
286305 }
287306
288307 // Internal for testing
@@ -294,37 +313,38 @@ namespace ts.OrganizeImports {
294313 return exportGroup ;
295314 }
296315
297- const { exportWithoutClause, namedExports } = getCategorizedExports ( exportGroup ) ;
316+ const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports ( exportGroup ) ;
298317
299318 const coalescedExports : ExportDeclaration [ ] = [ ] ;
300319
301320 if ( exportWithoutClause ) {
302321 coalescedExports . push ( exportWithoutClause ) ;
303322 }
304323
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 ) ) ;
307346 }
308347
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-
328348 return coalescedExports ;
329349
330350 /*
@@ -335,13 +355,17 @@ namespace ts.OrganizeImports {
335355 function getCategorizedExports ( exportGroup : readonly ExportDeclaration [ ] ) {
336356 let exportWithoutClause : ExportDeclaration | undefined ;
337357 const namedExports : ExportDeclaration [ ] = [ ] ;
358+ const typeOnlyExports : ExportDeclaration [ ] = [ ] ;
338359
339360 for ( const exportDeclaration of exportGroup ) {
340361 if ( exportDeclaration . exportClause === undefined ) {
341362 // Only the first such export is interesting - the others are redundant.
342363 // Note: Unfortunately, we will lose trivia that was on this node.
343364 exportWithoutClause = exportWithoutClause || exportDeclaration ;
344365 }
366+ else if ( exportDeclaration . isTypeOnly ) {
367+ typeOnlyExports . push ( exportDeclaration ) ;
368+ }
345369 else {
346370 namedExports . push ( exportDeclaration ) ;
347371 }
@@ -350,6 +374,7 @@ namespace ts.OrganizeImports {
350374 return {
351375 exportWithoutClause,
352376 namedExports,
377+ typeOnlyExports,
353378 } ;
354379 }
355380 }
0 commit comments