use super::{Builder, sorted_keys};
use crate::color;
use crate::tables;
use anyhow::Result;
use std::collections::HashSet;
fn is_fixable(processors: &crate::builder::ProcessorMap, name: &str) -> bool {
crate::registries::processor::can_fix(name) || processors[name].config_has_fix()
}
impl Builder {
pub fn fix(
&self,
ctx: &crate::build_context::BuildContext,
processor_filter: Option<&[String]>,
) -> Result<()> {
let processors = self.create_processors()?;
let mut graph = self.build_graph_with_processors(ctx, &processors)?;
let filter_set: Option<HashSet<&str>> =
processor_filter.map(|names| names.iter().map(std::string::String::as_str).collect());
let fixable: Vec<&str> = processors
.keys()
.filter(|name| {
if !is_fixable(&processors, name.as_str()) {
return false;
}
if let Some(ref filter) = filter_set {
return filter.contains(name.as_str());
}
true
})
.map(std::string::String::as_str)
.collect();
if fixable.is_empty() {
if processor_filter.is_some() {
crate::output::info(&color::yellow(
"No matching processors with fix capability.",
));
} else {
crate::output::info(&color::yellow("No processors with fix capability found."));
}
return Ok(());
}
let fixable_set: HashSet<&str> = fixable.iter().copied().collect();
graph.retain_products(|p| fixable_set.contains(p.processor.as_str()));
let products: Vec<_> = graph.products().to_vec();
if products.is_empty() {
crate::output::info(&color::yellow("No files to fix."));
return Ok(());
}
crate::output::info(&color::bold(&format!(
"Fixing {} file(s) using: {}",
products.len(),
fixable.join(", "),
)));
let mut fixed_count = 0usize;
let mut error_count = 0usize;
let mut by_processor: std::collections::BTreeMap<&str, Vec<&crate::graph::Product>> =
std::collections::BTreeMap::new();
for product in &products {
by_processor
.entry(&product.processor)
.or_default()
.push(product);
}
for (proc_name, proc_products) in &by_processor {
let Some(processor) = processors.get(*proc_name) else {
continue;
};
if processor.supports_fix_batch() && proc_products.len() > 1 {
let refs: Vec<&crate::graph::Product> = proc_products.clone();
let results = processor.fix_batch(ctx, &refs);
for result in results {
match result {
Ok(()) => fixed_count += 1,
Err(e) => {
eprintln!("{}: {}", color::red(&format!("[{proc_name}] fix error")), e);
error_count += 1;
}
}
}
} else {
for product in proc_products {
match processor.fix(ctx, product) {
Ok(()) => fixed_count += 1,
Err(e) => {
eprintln!("{}: {}", color::red(&format!("[{proc_name}] fix error")), e);
error_count += 1;
}
}
}
}
}
if error_count > 0 {
crate::output::error(&format!(
"Fix completed: {fixed_count} fixed, {error_count} errors",
));
anyhow::bail!("Fix failed with {error_count} error(s)");
}
crate::output::info(&color::green(&format!(
"Fix completed: {fixed_count} file(s) processed",
)));
Ok(())
}
pub fn fix_list(&self) -> Result<()> {
let processors = self.create_processors()?;
let proc_names = sorted_keys(&processors);
let fixers: Vec<&String> = proc_names
.iter()
.filter(|name| is_fixable(&processors, name.as_str()))
.copied()
.collect();
if fixers.is_empty() {
if crate::json_output::is_json_mode() {
println!("[]");
} else {
crate::output::info(&color::yellow(
"No fix-capable processors declared in this project.",
));
}
return Ok(());
}
if crate::json_output::is_json_mode() {
let entries: Vec<serde_json::Value> = fixers.iter().map(|name| {
serde_json::json!({
"name": name,
"type": crate::registries::processor::processor_type_of(name.as_str()).as_str(),
"description": crate::registries::processor::description_of(name.as_str()),
})
}).collect();
println!("{}", serde_json::to_string_pretty(&entries)?);
return Ok(());
}
let rows: Vec<Vec<String>> = fixers
.iter()
.map(|name| {
vec![
(*name).clone(),
crate::registries::processor::processor_type_of(name.as_str())
.as_str()
.to_string(),
crate::registries::processor::description_of(name.as_str()).to_string(),
]
})
.collect();
tables::print_table(&["Name", "Type", "Description"], &rows);
Ok(())
}
}