use std::collections::{HashMap, HashSet};
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use crate::core::ast::semantic::SemanticModel;
pub fn cleanup_imports_exports(module: &mut Module) {
let semantic = SemanticModel::new(module);
let unused_imports: HashSet<String> = semantic.get_unused_imports().into_iter().collect();
let mut import_groups: HashMap<String, Vec<ImportDecl>> = HashMap::new();
let mut sources_order: Vec<String> = Vec::new();
let mut new_body = Vec::new();
for item in std::mem::take(&mut module.body) {
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = item {
let mut used_specifiers = Vec::new();
for spec in import_decl.specifiers {
let local_name = match &spec {
ImportSpecifier::Named(named) => named.local.sym.to_string(),
ImportSpecifier::Default(def) => def.local.sym.to_string(),
ImportSpecifier::Namespace(ns) => ns.local.sym.to_string(),
};
if !unused_imports.contains(&local_name) {
used_specifiers.push(spec);
}
}
if !used_specifiers.is_empty() {
let src = import_decl.src.value.to_string();
if !import_groups.contains_key(&src) {
sources_order.push(src.clone());
}
let decl = ImportDecl {
specifiers: used_specifiers,
..import_decl
};
import_groups.entry(src).or_default().push(decl);
}
} else {
new_body.push(item);
}
}
let mut merged_imports = Vec::new();
for src in sources_order {
let decls = import_groups.remove(&src).unwrap_or_default();
if decls.is_empty() {
continue;
}
let mut default_spec = None;
let mut ns_spec = None;
let mut named_specs = HashMap::new();
for decl in decls {
for spec in decl.specifiers {
match spec {
ImportSpecifier::Default(def) => {
if default_spec.is_none() {
default_spec = Some(def);
}
}
ImportSpecifier::Namespace(ns) => {
if ns_spec.is_none() {
ns_spec = Some(ns);
}
}
ImportSpecifier::Named(named) => {
named_specs.insert(named.local.sym.clone(), named);
}
}
}
}
let mut final_specifiers = Vec::new();
if let Some(def) = default_spec {
final_specifiers.push(ImportSpecifier::Default(def));
}
if let Some(ns) = ns_spec {
final_specifiers.push(ImportSpecifier::Namespace(ns));
}
if !named_specs.is_empty() {
let mut specs: Vec<_> = named_specs.into_values().collect();
specs.sort_by(|a, b| a.local.sym.cmp(&b.local.sym));
final_specifiers.extend(specs.into_iter().map(ImportSpecifier::Named));
}
merged_imports.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: final_specifiers,
src: Box::new(Str {
span: DUMMY_SP,
value: src.into(),
raw: None,
}),
type_only: false,
with: None,
phase: Default::default(),
})));
}
let mut final_body = merged_imports;
final_body.extend(new_body);
let mut named_exports: Vec<ExportSpecifier> = Vec::new();
let mut other_items = Vec::new();
for item in final_body {
if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(ref export)) = item {
if export.src.is_none() && export.with.is_none() && !export.type_only {
named_exports.extend(export.specifiers.clone());
continue;
}
}
other_items.push(item);
}
if !named_exports.is_empty() {
let mut unique_exports = Vec::new();
let mut seen = HashSet::new();
for spec in named_exports {
if let ExportSpecifier::Named(named) = &spec {
let name = match &named.orig {
ModuleExportName::Ident(id) => id.sym.to_string(),
ModuleExportName::Str(s) => s.value.to_string(),
};
if seen.insert(name) {
unique_exports.push(spec);
}
} else {
unique_exports.push(spec);
}
}
unique_exports.sort_by(|a, b| {
let a_name = match a {
ExportSpecifier::Named(n) => match &n.orig {
ModuleExportName::Ident(id) => id.sym.to_string(),
ModuleExportName::Str(s) => s.value.to_string(),
},
_ => "".to_string(),
};
let b_name = match b {
ExportSpecifier::Named(n) => match &n.orig {
ModuleExportName::Ident(id) => id.sym.to_string(),
ModuleExportName::Str(s) => s.value.to_string(),
},
_ => "".to_string(),
};
a_name.cmp(&b_name)
});
other_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
NamedExport {
span: DUMMY_SP,
specifiers: unique_exports,
src: None,
type_only: false,
with: None,
},
)));
}
module.body = other_items;
}
pub fn run_autofix(path: &std::path::Path) -> anyhow::Result<()> {
use crate::core::ast::parser::parse_file;
use crate::core::ast::printer::print_module;
use crate::core::format::{FormatOptions, FormatPipeline};
let original_content = std::fs::read_to_string(path)?;
let mut parsed = parse_file(path).map_err(|e| anyhow::anyhow!(e))?;
cleanup_imports_exports(&mut parsed.module);
let mut output = print_module(&parsed, &parsed.module).map_err(|e| anyhow::anyhow!(e))?;
let format_opts = FormatOptions {
enabled: true,
use_prettier: false,
preserve_indent: true,
preserve_quotes: true,
preserve_semicolons: true,
normalize_newlines: true,
};
let mut pipeline = FormatPipeline::new(format_opts);
output = pipeline.format(&output, Some(&original_content), path);
output = crate::core::format::normalize::insert_newline_after_imports(&output);
if output != original_content {
std::fs::write(path, output)?;
}
Ok(())
}