diff --git a/apps/oxfmt/src/format.rs b/apps/oxfmt/src/format.rs index a11f4244d42ac..2075f42196113 100644 --- a/apps/oxfmt/src/format.rs +++ b/apps/oxfmt/src/format.rs @@ -14,7 +14,7 @@ use crate::{ command::{FormatCommand, OutputOptions}, reporter::DefaultReporter, result::CliRunResult, - service::FormatService, + service::{FormatService, SuccessResult}, walk::Walk, }; @@ -97,10 +97,8 @@ impl FormatRunner { // Get the receiver for streaming entries let rx_entry = walker.stream_entries(); - // Count all files for stats - let (tx_count, rx_count) = mpsc::channel::<()>(); - // Collect file paths that were updated - let (tx_path, rx_path) = mpsc::channel(); + // Collect format results (changed paths or unchanged count) + let (tx_success, rx_success) = mpsc::channel(); // Diagnostic from formatting service let (mut diagnostic_service, tx_error) = DiagnosticService::new(Box::new(DefaultReporter::default())); @@ -125,11 +123,20 @@ impl FormatRunner { #[cfg(feature = "napi")] let format_service = format_service.with_external_formatter(external_formatter_clone); - format_service.run_streaming(rx_entry, &tx_error, &tx_path, tx_count); + format_service.run_streaming(rx_entry, &tx_error, &tx_success); }); - // First, collect and print sorted file paths to stdout - let mut changed_paths: Vec = rx_path.iter().collect(); + // Collect results and separate changed paths from unchanged count + let mut changed_paths: Vec = vec![]; + let mut unchanged_count: usize = 0; + for result in rx_success { + match result { + SuccessResult::Changed(path) => changed_paths.push(path), + SuccessResult::Unchanged => unchanged_count += 1, + } + } + + // Print sorted changed file paths to stdout if !changed_paths.is_empty() { changed_paths.sort_unstable(); print_and_flush(stdout, &changed_paths.join("\n")); @@ -138,9 +145,11 @@ impl FormatRunner { // Then, output diagnostics errors to stderr // NOTE: This is blocking and print errors let diagnostics = diagnostic_service.run(stderr); + // NOTE: We are not using `DiagnosticService` for warnings + let error_count = diagnostics.errors_count(); // Count the processed files - let total_target_files_count = rx_count.iter().count(); + let total_target_files_count = changed_paths.len() + unchanged_count + error_count; let print_stats = |stdout| { let elapsed_ms = start_time.elapsed().as_millis(); print_and_flush( @@ -163,7 +172,7 @@ impl FormatRunner { return CliRunResult::NoFilesFound; } - if 0 < diagnostics.errors_count() { + if 0 < error_count { // Each error is already printed in reporter print_and_flush( stderr, diff --git a/apps/oxfmt/src/service.rs b/apps/oxfmt/src/service.rs index f23c4b0e448e5..3396498c4c488 100644 --- a/apps/oxfmt/src/service.rs +++ b/apps/oxfmt/src/service.rs @@ -13,7 +13,10 @@ use oxc_span::SourceType; use crate::{command::OutputOptions, walk::WalkEntry}; -type PathSender = mpsc::Sender; +pub enum SuccessResult { + Changed(String), + Unchanged, +} pub struct FormatService { allocator_pool: AllocatorPool, @@ -55,67 +58,56 @@ impl FormatService { } /// Process entries as they are received from the channel - #[expect(clippy::needless_pass_by_value)] pub fn run_streaming( &self, rx_entry: mpsc::Receiver, tx_error: &DiagnosticSender, - tx_path: &PathSender, - // Take ownership to close the channel when done - tx_count: mpsc::Sender<()>, + tx_success: &mpsc::Sender, ) { rx_entry.into_iter().par_bridge().for_each(|entry| { - self.process_entry(&entry, tx_error, tx_path); - // Signal that we processed one file (ignore send errors if receiver dropped) - let _ = tx_count.send(()); - }); - } - - /// Process a single entry - fn process_entry(&self, entry: &WalkEntry, tx_error: &DiagnosticSender, tx_path: &PathSender) { - let start_time = Instant::now(); + let start_time = Instant::now(); - let path = &entry.path; - let (code, is_changed) = match self.format_file(entry) { - Ok(res) => res, - Err(diagnostics) => { - tx_error.send(diagnostics).unwrap(); - return; - } - }; + let path = &entry.path; + let (code, is_changed) = match self.format_file(&entry) { + Ok(res) => res, + Err(diagnostics) => { + tx_error.send(diagnostics).unwrap(); + return; + } + }; - let elapsed = start_time.elapsed(); + let elapsed = start_time.elapsed(); - // Write back if needed - if matches!(self.output_options, OutputOptions::Write) && is_changed { - fs::write(path, code) - .map_err(|_| format!("Failed to write to '{}'", path.to_string_lossy())) - .unwrap(); - } + // Write back if needed + if matches!(self.output_options, OutputOptions::Write) && is_changed { + fs::write(path, code) + .map_err(|_| format!("Failed to write to '{}'", path.to_string_lossy())) + .unwrap(); + } - // Notify if needed - if let Some(output) = match (&self.output_options, is_changed) { - (OutputOptions::Check | OutputOptions::ListDifferent, true) => { - let display_path = path - // Show path relative to `cwd` for cleaner output - .strip_prefix(&self.cwd) - .unwrap_or(path) - .to_string_lossy() - // Normalize path separators for consistent output across platforms - .cow_replace('\\', "/") - .to_string(); - let elapsed = elapsed.as_millis(); + // Report result + let result = match (&self.output_options, is_changed) { + (OutputOptions::Check | OutputOptions::ListDifferent, true) => { + let display_path = path + // Show path relative to `cwd` for cleaner output + .strip_prefix(&self.cwd) + .unwrap_or(path) + .to_string_lossy() + // Normalize path separators for consistent output across platforms + .cow_replace('\\', "/") + .to_string(); + let elapsed = elapsed.as_millis(); - if matches!(self.output_options, OutputOptions::Check) { - Some(format!("{display_path} ({elapsed}ms)")) - } else { - Some(display_path) + if matches!(self.output_options, OutputOptions::Check) { + SuccessResult::Changed(format!("{display_path} ({elapsed}ms)")) + } else { + SuccessResult::Changed(display_path) + } } - } - _ => None, - } { - tx_path.send(output).unwrap(); - } + _ => SuccessResult::Unchanged, + }; + tx_success.send(result).unwrap(); + }); } fn format_file(&self, entry: &WalkEntry) -> Result<(String, bool), Vec> {