From aa300f325d94ae6814d01e14d14f31b7f4519319 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Wed, 26 Nov 2025 18:29:51 +0900 Subject: [PATCH] feat(oxfmt): Collect all files other than JS/TS --- apps/oxfmt/src/service.rs | 131 ++++++++++++++++++++++++-------------- apps/oxfmt/src/walk.rs | 18 ++++-- 2 files changed, 93 insertions(+), 56 deletions(-) diff --git a/apps/oxfmt/src/service.rs b/apps/oxfmt/src/service.rs index d4e8e20715b12..1f7c503021bb5 100644 --- a/apps/oxfmt/src/service.rs +++ b/apps/oxfmt/src/service.rs @@ -7,6 +7,7 @@ use oxc_allocator::AllocatorPool; use oxc_diagnostics::{DiagnosticSender, DiagnosticService, OxcDiagnostic}; use oxc_formatter::{FormatOptions, Formatter, enable_jsx_source_type, get_parse_options}; use oxc_parser::Parser; +use oxc_span::SourceType; use crate::{command::OutputOptions, walk::WalkEntry}; @@ -62,6 +63,12 @@ impl FormatService { tx_count: mpsc::Sender<()>, ) { rx_entry.into_iter().par_bridge().for_each(|entry| { + // TODO: For now, just ignore it + // #[not(feature = "napi")] + if matches!(entry, WalkEntry::Prettier { .. }) { + return; + } + self.process_entry(&entry, tx_error, tx_path); // Signal that we processed one file (ignore send errors if receiver dropped) let _ = tx_count.send(()); @@ -72,10 +79,9 @@ impl FormatService { fn process_entry(&self, entry: &WalkEntry, tx_error: &DiagnosticSender, tx_path: &PathSender) { let start_time = Instant::now(); - let path = &entry.path; - let source_type = enable_jsx_source_type(entry.source_type); - - let allocator = self.allocator_pool.get(); + let path = match entry { + WalkEntry::OxcFormatter { path, .. } | WalkEntry::Prettier { path } => path, + }; let Ok(source_text) = read_to_string(path) else { // This happens if `.ts` for MPEG-TS binary is attempted to be formatted let diagnostics = DiagnosticService::wrap_diagnostics( @@ -91,63 +97,26 @@ impl FormatService { return; }; - let ret = Parser::new(&allocator, &source_text, source_type) - .with_options(get_parse_options()) - .parse(); - if !ret.errors.is_empty() { - let diagnostics = DiagnosticService::wrap_diagnostics( - self.cwd.clone(), - path, - &source_text, - ret.errors, - ); - tx_error.send(diagnostics).unwrap(); - return; - } - - let base_formatter = Formatter::new(&allocator, self.format_options.clone()); - - #[cfg(feature = "napi")] - let formatted = { - if self.format_options.embedded_language_formatting.is_off() { - base_formatter.format(&ret.program) - } else { - let embedded_formatter = self - .external_formatter - .as_ref() - .expect("`external_formatter` must exist when `napi` feature is enabled") - .to_embedded_formatter(); - base_formatter.format_with_embedded(&ret.program, embedded_formatter) + let format_result = match entry { + WalkEntry::OxcFormatter { path, source_type } => { + self.format_by_oxc_formatter(path, &source_text, *source_type) } + WalkEntry::Prettier { path } => self.format_by_prettier(path, &source_text), }; - #[cfg(not(feature = "napi"))] - let formatted = base_formatter.format(&ret.program); - - let code = match formatted.print() { - Ok(printed) => printed.into_code(), - Err(err) => { + let code = match format_result { + Ok(code) => code, + Err(diagnostics) => { let diagnostics = DiagnosticService::wrap_diagnostics( self.cwd.clone(), path, &source_text, - vec![OxcDiagnostic::error(format!( - "Failed to print formatted code: {}\n{err}", - path.display() - ))], + diagnostics, ); tx_error.send(diagnostics).unwrap(); return; } }; - #[cfg(feature = "detect_code_removal")] - { - if let Some(diff) = oxc_formatter::detect_code_removal(&source_text, &code, source_type) - { - unreachable!("Code removal detected in `{}`:\n{diff}", path.to_string_lossy()); - } - } - let elapsed = start_time.elapsed(); let is_changed = source_text != code; @@ -182,6 +151,70 @@ impl FormatService { tx_path.send(output).unwrap(); } } + + fn format_by_oxc_formatter( + &self, + path: &Path, + source_text: &str, + source_type: SourceType, + ) -> Result> { + let allocator = self.allocator_pool.get(); + let source_type = enable_jsx_source_type(source_type); + + let ret = Parser::new(&allocator, source_text, source_type) + .with_options(get_parse_options()) + .parse(); + if !ret.errors.is_empty() { + return Err(ret.errors); + } + + let base_formatter = Formatter::new(&allocator, self.format_options.clone()); + + #[cfg(feature = "napi")] + let formatted = { + if self.format_options.embedded_language_formatting.is_off() { + base_formatter.format(&ret.program) + } else { + let embedded_formatter = self + .external_formatter + .as_ref() + .expect("`external_formatter` must exist when `napi` feature is enabled") + .to_embedded_formatter(); + base_formatter.format_with_embedded(&ret.program, embedded_formatter) + } + }; + #[cfg(not(feature = "napi"))] + let formatted = base_formatter.format(&ret.program); + + let code = match formatted.print() { + Ok(printed) => printed.into_code(), + Err(err) => { + return Err(vec![OxcDiagnostic::error(format!( + "Failed to print formatted code: {}\n{err}", + path.display() + ))]); + } + }; + + #[cfg(feature = "detect_code_removal")] + { + if let Some(diff) = oxc_formatter::detect_code_removal(source_text, &code, source_type) + { + unreachable!("Code removal detected in `{}`:\n{diff}", path.to_string_lossy()); + } + } + + Ok(code) + } + + #[expect(clippy::unused_self)] + fn format_by_prettier( + &self, + _path: &Path, + _source_text: &str, + ) -> Result> { + unreachable!("Prettier formatting is not implemented yet"); + } } fn read_to_string(path: &Path) -> io::Result { diff --git a/apps/oxfmt/src/walk.rs b/apps/oxfmt/src/walk.rs index 65aa5489a1e10..196a9a793c913 100644 --- a/apps/oxfmt/src/walk.rs +++ b/apps/oxfmt/src/walk.rs @@ -200,9 +200,9 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Vec { // --- -pub struct WalkEntry { - pub path: PathBuf, - pub source_type: SourceType, +pub enum WalkEntry { + OxcFormatter { path: PathBuf, source_type: SourceType }, + Prettier { path: PathBuf }, } struct WalkBuilder { @@ -229,10 +229,14 @@ impl ignore::ParallelVisitor for WalkVisitor { // Use `is_file()` to detect symlinks to the directory named `.js` #[expect(clippy::filetype_is_file)] - if file_type.is_file() - && let Some(source_type) = get_supported_source_type(entry.path()) - { - let walk_entry = WalkEntry { path: entry.path().to_path_buf(), source_type }; + if file_type.is_file() { + let path = entry.path().to_path_buf(); + let walk_entry = if let Some(source_type) = get_supported_source_type(&path) { + WalkEntry::OxcFormatter { path, source_type } + } else { + WalkEntry::Prettier { path } + }; + // Send each entry immediately through the channel // If send fails, the receiver has been dropped, so stop walking if self.sender.send(walk_entry).is_err() {