@@ -238,63 +238,6 @@ def _parse_imports(self) -> None:
238
238
if function .type == "import" or (function .type == "identifier" and function .text .decode ("utf-8" ) == "require" ):
239
239
TSImportStatement (import_node , self .node_id , self .G , self .code_block , 0 )
240
240
241
- @writer
242
- def remove_unused_exports (self ) -> None :
243
- """Removes unused exports from the file.
244
-
245
- Analyzes all exports in the file and removes any that are not used. An export is considered unused if it has no direct
246
- symbol usages and no re-exports that are used elsewhere in the codebase.
247
-
248
- When removing unused exports, the method also cleans up any related unused imports. For default exports, it removes
249
- the 'export default' keyword, and for named exports, it removes the 'export' keyword or the entire export statement.
250
-
251
- Args:
252
- None
253
-
254
- Returns:
255
- None
256
- """
257
- for export in self .exports :
258
- symbol_export_unused = True
259
- symbols_to_remove = []
260
-
261
- exported_symbol = export .resolved_symbol
262
- for export_usage in export .symbol_usages :
263
- if export_usage .node_type == NodeType .IMPORT or (export_usage .node_type == NodeType .EXPORT and export_usage .resolved_symbol != exported_symbol ):
264
- # If the import has no usages then we can add the import to the list of symbols to remove
265
- reexport_usages = export_usage .symbol_usages
266
- if len (reexport_usages ) == 0 :
267
- symbols_to_remove .append (export_usage )
268
- break
269
-
270
- # If any of the import's usages are valid symbol usages, export is used.
271
- if any (usage .node_type == NodeType .SYMBOL for usage in reexport_usages ):
272
- symbol_export_unused = False
273
- break
274
-
275
- symbols_to_remove .append (export_usage )
276
-
277
- elif export_usage .node_type == NodeType .SYMBOL :
278
- symbol_export_unused = False
279
- break
280
-
281
- # export is not used, remove it
282
- if symbol_export_unused :
283
- # remove the unused imports
284
- for imp in symbols_to_remove :
285
- imp .remove ()
286
-
287
- if exported_symbol == exported_symbol .export .declared_symbol :
288
- # change this to be more robust
289
- if exported_symbol .source .startswith ("export default " ):
290
- exported_symbol .replace ("export default " , "" )
291
- else :
292
- exported_symbol .replace ("export " , "" )
293
- else :
294
- exported_symbol .export .remove ()
295
- if exported_symbol .export != export :
296
- export .remove ()
297
-
298
241
@noapidoc
299
242
def _get_export_data (self , relative_path : str , export_type : str = "EXPORT" ) -> tuple [tuple [str , str ], dict [str , callable ]]:
300
243
quoted_paths = (f"'{ relative_path } '" , f'"{ relative_path } "' )
@@ -463,51 +406,99 @@ def get_namespace(self, name: str) -> TSNamespace | None:
463
406
return next ((x for x in self .symbols if isinstance (x , TSNamespace ) and x .name == name ), None )
464
407
465
408
@writer
466
- def remove_unused_imports (self , moved_symbol_names : set [ str ] | None = None ) -> None :
409
+ def remove_unused_imports (self ) -> None :
467
410
"""Removes unused imports from the file.
468
411
469
- Args:
470
- moved_symbol_names: Optional set of symbol names that were moved to another file
412
+ Handles different TypeScript import styles:
413
+ - Single imports (import x from 'y')
414
+ - Named imports (import { x } from 'y')
415
+ - Multi-imports (import { a, b as c } from 'y')
416
+ - Type imports (import type { X } from 'y')
417
+ - Side effect imports (import 'y')
418
+ - Wildcard imports (import * as x from 'y')
419
+
420
+ Preserves:
421
+ - Comments and whitespace where possible
422
+ - Side effect imports (e.g., CSS imports)
423
+ - Type imports used in type annotations
424
+ """
425
+ # Process each import statement
426
+ for import_stmt in self .imports :
427
+ # Always preserve side effect imports since we can't track their usage
428
+ if import_stmt .import_type == ImportType .SIDE_EFFECT :
429
+ continue
430
+
431
+ # Check if all imports in this statement are unused
432
+ import_stmt .remove_if_unused ()
433
+
434
+ self .G .commit_transactions ()
435
+
436
+ @writer
437
+ def remove_unused_exports (self ) -> None :
438
+ """Removes unused exports from the file.
439
+
440
+ Handles different TypeScript export styles:
441
+ - Default exports (export default x)
442
+ - Named exports (export function x, export const x)
443
+ - Re-exports (export { x } from 'y')
444
+ - Type exports (export type X, export interface X)
445
+
446
+ Preserves:
447
+ - Type exports (these may be used in type positions)
448
+ - Default exports (these are often used dynamically)
449
+ - Exports used by other files through imports
450
+ - Exports used within the same file
471
451
"""
472
- for import_statement in self .import_statements :
473
- # Track which symbols in this import statement are still used
474
- used_symbols = []
475
- removed_symbols = []
476
-
477
- for import_symbol in import_statement .imports :
478
- # Skip side effect imports
479
- if import_symbol .import_type == ImportType .SIDE_EFFECT :
480
- continue
481
-
482
- symbol_name = import_symbol .alias .source if import_symbol .alias else import_symbol .name
483
-
484
- # Check if this import is still used in the file
485
- is_used = False
486
- for usage in import_symbol .usages :
487
- # Skip usages from moved symbols if provided
488
- if moved_symbol_names and usage .usage_symbol and usage .usage_symbol .name in moved_symbol_names :
489
- continue
490
- is_used = True
452
+ for export in self .exports :
453
+ # Skip type exports and default exports
454
+ if export .is_type_export () or export .is_default_export ():
455
+ continue
456
+
457
+ symbol_export_unused = True
458
+ symbols_to_remove = []
459
+
460
+ exported_symbol = export .resolved_symbol
461
+ for export_usage in export .symbol_usages :
462
+ if export_usage .node_type == NodeType .IMPORT or (export_usage .node_type == NodeType .EXPORT and export_usage .resolved_symbol != exported_symbol ):
463
+ # If the import has no usages then we can add the import to the list of symbols to remove
464
+ reexport_usages = export_usage .symbol_usages
465
+ if len (reexport_usages ) == 0 :
466
+ symbols_to_remove .append (export_usage )
467
+ break
468
+
469
+ # If any of the import's usages are valid symbol usages, export is used.
470
+ if any (usage .node_type == NodeType .SYMBOL for usage in reexport_usages ):
471
+ symbol_export_unused = False
472
+ break
473
+
474
+ symbols_to_remove .append (export_usage )
475
+
476
+ elif export_usage .node_type == NodeType .SYMBOL :
477
+ symbol_export_unused = False
491
478
break
492
479
493
- if is_used :
494
- used_symbols .append (import_symbol )
495
- else :
496
- removed_symbols .append (import_symbol )
497
-
498
- if not used_symbols and removed_symbols :
499
- # If no symbols are used, remove the entire import statement
500
- import_statement .remove ()
501
- elif removed_symbols and used_symbols :
502
- # If some symbols are used but others aren't, update the import statement
503
- new_imports = []
504
- for symbol in used_symbols :
505
- if symbol .alias :
506
- new_imports .append (f"{ symbol .name } as { symbol .alias .source } " )
480
+ # export is not used, remove it
481
+ if symbol_export_unused :
482
+ # remove the unused imports
483
+ for imp in symbols_to_remove :
484
+ imp .remove ()
485
+
486
+ # Handle different export types
487
+ if hasattr (export , 'source' ) and export .source :
488
+ # Re-export case (export { x } from 'y')
489
+ export .remove ()
490
+ elif exported_symbol and hasattr (exported_symbol , 'export' ) and exported_symbol .export :
491
+ if exported_symbol .export .declared_symbol == exported_symbol :
492
+ # Direct export case (export function x)
493
+ if exported_symbol .source .startswith ("export default " ):
494
+ exported_symbol .replace ("export default " , "" )
495
+ else :
496
+ exported_symbol .replace ("export " , "" )
507
497
else :
508
- new_imports .append (symbol .name )
498
+ # Export statement case (export { x })
499
+ exported_symbol .export .remove ()
500
+ else :
501
+ # Fallback - just remove the export
502
+ export .remove ()
509
503
510
- module = import_statement .module .source
511
- type_prefix = "type " if import_statement .is_type_import else ""
512
- new_statement = f"import { type_prefix } {{ { ', ' .join (new_imports )} }} from { module } ;"
513
- import_statement .source = new_statement
504
+ self .G .commit_transactions ()
0 commit comments