use clap::Args;
use ignore::Walk;
use rayon::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Instant;
use vize_patina::{format_results, format_summary, Linter, OutputFormat};
#[derive(Args)]
pub struct LintArgs {
#[arg(default_value = "./**/*.vue")]
pub patterns: Vec<String>,
#[arg(long)]
pub fix: bool,
#[arg(short, long)]
pub config: Option<PathBuf>,
#[arg(short, long, default_value = "text")]
pub format: String,
#[arg(long)]
pub max_warnings: Option<usize>,
#[arg(short, long)]
pub quiet: bool,
}
pub fn run(args: LintArgs) {
let start = Instant::now();
let files: Vec<PathBuf> = args
.patterns
.iter()
.flat_map(|pattern| {
Walk::new(pattern)
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "vue")
.unwrap_or(false)
})
.map(|e| e.path().to_path_buf())
})
.collect();
if files.is_empty() {
eprintln!("No .vue files found matching patterns: {:?}", args.patterns);
return;
}
let linter = Linter::new();
let error_count = AtomicUsize::new(0);
let warning_count = AtomicUsize::new(0);
let results: Vec<_> = files
.par_iter()
.filter_map(|path| {
let source = match fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to read {}: {}", path.display(), e);
return None;
}
};
let filename = path.to_string_lossy().to_string();
let result = linter.lint_sfc(&source, &filename);
error_count.fetch_add(result.error_count, Ordering::Relaxed);
warning_count.fetch_add(result.warning_count, Ordering::Relaxed);
Some((filename, source, result))
})
.collect();
let total_errors = error_count.load(Ordering::Relaxed);
let total_warnings = warning_count.load(Ordering::Relaxed);
let format = match args.format.as_str() {
"json" => OutputFormat::Json,
_ => OutputFormat::Text,
};
if !args.quiet || total_errors > 0 || total_warnings > 0 {
let lint_results: Vec<_> = results.iter().map(|(_, _, r)| r).cloned().collect();
let sources: Vec<_> = results
.iter()
.map(|(f, s, _)| (f.clone(), s.clone()))
.collect();
let output = format_results(&lint_results, &sources, format);
if !output.trim().is_empty() {
print!("{}", output);
}
}
let elapsed = start.elapsed();
if format == OutputFormat::Text {
println!(
"\n{}",
format_summary(total_errors, total_warnings, files.len())
);
println!("Linted {} files in {:.4?}", files.len(), elapsed);
}
if args.fix {
eprintln!("\nNote: --fix is not yet implemented");
}
if total_errors > 0 {
std::process::exit(1);
}
if let Some(max) = args.max_warnings {
if total_warnings > max {
eprintln!("\nToo many warnings ({} > max {})", total_warnings, max);
std::process::exit(1);
}
}
}