diff --git a/apps/oxfmt/src-js/bindings.d.ts b/apps/oxfmt/src-js/bindings.d.ts index b2ad4929130bd..7b2758eba34c9 100644 --- a/apps/oxfmt/src-js/bindings.d.ts +++ b/apps/oxfmt/src-js/bindings.d.ts @@ -6,7 +6,8 @@ * JS side passes in: * 1. `args`: Command line arguments (process.argv.slice(2)) * 2. `format_embedded_cb`: Callback to format embedded code in templates + * 3. `format_file_cb`: Callback to format files * * Returns `true` if formatting succeeded without errors, `false` otherwise. */ -export declare function format(args: Array, formatEmbeddedCb: (tagName: string, code: string) => Promise): Promise +export declare function format(args: Array, formatEmbeddedCb: (tagName: string, code: string) => Promise, formatFileCb: (fileName: string, code: string) => Promise): Promise diff --git a/apps/oxfmt/src-js/cli.ts b/apps/oxfmt/src-js/cli.ts index 4fbee168de644..1585a6ccdfe51 100644 --- a/apps/oxfmt/src-js/cli.ts +++ b/apps/oxfmt/src-js/cli.ts @@ -1,10 +1,10 @@ import { format } from "./bindings.js"; -import { formatEmbeddedCode } from "./embedded.js"; +import { formatEmbeddedCode, formatFile } from "./prettier-proxy.js"; const args = process.argv.slice(2); // Call the Rust formatter with our JS callback -const success = await format(args, formatEmbeddedCode); +const success = await format(args, formatEmbeddedCode, formatFile); // NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`. // `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies. diff --git a/apps/oxfmt/src-js/index.ts b/apps/oxfmt/src-js/index.ts index aca8eda9ee404..8d1081d099923 100644 --- a/apps/oxfmt/src-js/index.ts +++ b/apps/oxfmt/src-js/index.ts @@ -1,2 +1,2 @@ export * from "./bindings.js"; -export { formatEmbeddedCode } from "./embedded.js"; +export { formatEmbeddedCode, formatFile } from "./prettier-proxy.js"; diff --git a/apps/oxfmt/src-js/embedded.ts b/apps/oxfmt/src-js/prettier-proxy.ts similarity index 62% rename from apps/oxfmt/src-js/embedded.ts rename to apps/oxfmt/src-js/prettier-proxy.ts index 99eb405613be5..cee4db10edd13 100644 --- a/apps/oxfmt/src-js/embedded.ts +++ b/apps/oxfmt/src-js/prettier-proxy.ts @@ -1,5 +1,5 @@ // Import Prettier lazily. -// This helps to reduce initial load time if embedded formatting is not needed. +// This helps to reduce initial load time if not needed. // // Also, this solves unknown issue described below... // @@ -11,6 +11,8 @@ // But actually, this makes `oxfmt --lsp` immediately stop with `Parse error` JSON-RPC error let prettierCache: typeof import("prettier"); +// --- + // Map template tag names to Prettier parsers const TAG_TO_PARSER: Record = { // CSS @@ -30,8 +32,8 @@ const TAG_TO_PARSER: Record = { }; /** - * Format embedded code using Prettier (synchronous). - * Note: Called from Rust via NAPI ThreadsafeFunction with FnArgs + * Format embedded code using Prettier. + * NOTE: Called from Rust via NAPI ThreadsafeFunction with FnArgs * @param tagName - The template tag name (e.g., "css", "gql", "html") * @param code - The code to format * @returns Formatted code @@ -51,6 +53,7 @@ export async function formatEmbeddedCode(tagName: string, code: string): Promise return prettierCache .format(code, { parser, + // TODO: Read config printWidth: 80, tabWidth: 2, semi: true, @@ -59,3 +62,24 @@ export async function formatEmbeddedCode(tagName: string, code: string): Promise .then((formatted) => formatted.trimEnd()) .catch(() => code); } + +/** + * Format whole file content using Prettier. + * NOTE: Called from Rust via NAPI ThreadsafeFunction with FnArgs + * @param fileName - The file name (used to infer parser) + * @param code - The code to format + * @returns Formatted code + */ +export async function formatFile(fileName: string, code: string): Promise { + if (!prettierCache) { + prettierCache = await import("prettier"); + } + + // TODO: Tweak parser for `tsconfig.json` with `jsonc` parser? + + return prettierCache.format(code, { + // Let Prettier infer the parser + filepath: fileName, + // TODO: Read config + }); +} diff --git a/apps/oxfmt/src/format.rs b/apps/oxfmt/src/format.rs index a11f4244d42ac..476d4f14d7e91 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, }; @@ -69,7 +69,11 @@ impl FormatRunner { } }; + // Extract options before move let ignore_patterns = config.ignore_patterns.clone().unwrap_or_default(); + #[cfg(feature = "napi")] + let handle_external_files = config.experimental_external_formatter.is_some_and(|b| b); + let format_options = match config.into_format_options() { Ok(options) => options, Err(err) => { @@ -97,10 +101,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())); @@ -123,13 +125,23 @@ impl FormatRunner { let format_service = FormatService::new(allocator_pool, cwd, output_options_clone, format_options); #[cfg(feature = "napi")] - let format_service = format_service.with_external_formatter(external_formatter_clone); + let format_service = format_service + .with_external_formatter(external_formatter_clone, handle_external_files); - 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,21 +150,24 @@ 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(); + // Count the handled files + // NOTE: Files that processed but not handled due to `external_files` option are not counted + let total_handled_files_count = changed_paths.len() + unchanged_count + error_count; let print_stats = |stdout| { let elapsed_ms = start_time.elapsed().as_millis(); print_and_flush( stdout, &format!( - "Finished in {elapsed_ms}ms on {total_target_files_count} files using {num_of_threads} threads.\n", + "Finished in {elapsed_ms}ms on {total_handled_files_count} files using {num_of_threads} threads.\n", ), ); }; // Check if no files were found - if total_target_files_count == 0 { + if total_handled_files_count == 0 { if misc_options.no_error_on_unmatched_pattern { print_and_flush(stderr, "No files found matching the given patterns.\n"); print_stats(stdout); @@ -163,7 +178,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/main_napi.rs b/apps/oxfmt/src/main_napi.rs index 7373cc2a4f891..d58e6b30af503 100644 --- a/apps/oxfmt/src/main_napi.rs +++ b/apps/oxfmt/src/main_napi.rs @@ -11,7 +11,7 @@ use crate::{ format::FormatRunner, init::{init_miette, init_tracing}, lsp::run_lsp, - prettier_plugins::{JsFormatEmbeddedCb, create_external_formatter}, + prettier_plugins::{ExternalFormatter, JsFormatEmbeddedCb, JsFormatFileCb}, result::CliRunResult, }; @@ -23,6 +23,7 @@ use crate::{ /// JS side passes in: /// 1. `args`: Command line arguments (process.argv.slice(2)) /// 2. `format_embedded_cb`: Callback to format embedded code in templates +/// 3. `format_file_cb`: Callback to format files /// /// Returns `true` if formatting succeeded without errors, `false` otherwise. #[expect(clippy::allow_attributes)] @@ -32,12 +33,18 @@ pub async fn format( args: Vec, #[napi(ts_arg_type = "(tagName: string, code: string) => Promise")] format_embedded_cb: JsFormatEmbeddedCb, + #[napi(ts_arg_type = "(fileName: string, code: string) => Promise")] + format_file_cb: JsFormatFileCb, ) -> bool { - format_impl(args, format_embedded_cb).await.report() == ExitCode::SUCCESS + format_impl(args, format_embedded_cb, format_file_cb).await.report() == ExitCode::SUCCESS } /// Run the formatter. -async fn format_impl(args: Vec, format_embedded_cb: JsFormatEmbeddedCb) -> CliRunResult { +async fn format_impl( + args: Vec, + format_embedded_cb: JsFormatEmbeddedCb, + format_file_cb: JsFormatFileCb, +) -> CliRunResult { // Convert String args to OsString for compatibility with bpaf let args: Vec = args.into_iter().map(OsString::from).collect(); @@ -67,7 +74,7 @@ async fn format_impl(args: Vec, format_embedded_cb: JsFormatEmbeddedCb) command.handle_threads(); // Create external formatter from JS callback - let external_formatter = create_external_formatter(format_embedded_cb); + let external_formatter = ExternalFormatter::new(format_embedded_cb, format_file_cb); // stdio is blocked by LineWriter, use a BufWriter to reduce syscalls. // See `https://github.com/rust-lang/rust/issues/60673`. diff --git a/apps/oxfmt/src/prettier_plugins/external_formatter.rs b/apps/oxfmt/src/prettier_plugins/external_formatter.rs index 5e54d8d52e6a9..e02626467f717 100644 --- a/apps/oxfmt/src/prettier_plugins/external_formatter.rs +++ b/apps/oxfmt/src/prettier_plugins/external_formatter.rs @@ -22,21 +22,47 @@ pub type JsFormatEmbeddedCb = ThreadsafeFunction< false, >; +/// Type alias for the callback function signature. +/// Takes (tag_name, code) as separate arguments and returns formatted code. +pub type JsFormatFileCb = ThreadsafeFunction< + // Input arguments + FnArgs<(String, String)>, // (file_name, code) as separate arguments + // Return type (what JS function returns) + Promise, + // Arguments (repeated) + FnArgs<(String, String)>, + // Error status + Status, + // CalleeHandled + false, +>; + +/// Callback function type for formatting files. +/// Takes (file_name, code) and returns formatted code or an error. +type FileFormatterCallback = Arc Result + Send + Sync>; + /// External formatter that wraps a JS callback. #[derive(Clone)] pub struct ExternalFormatter { pub format_embedded: EmbeddedFormatterCallback, + pub format_file: FileFormatterCallback, } impl std::fmt::Debug for ExternalFormatter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ExternalFormatter").field("format_embedded", &"").finish() + f.debug_struct("ExternalFormatter") + .field("format_embedded", &"") + .field("format_file", &"") + .finish() } } impl ExternalFormatter { - pub fn new(format_embedded: EmbeddedFormatterCallback) -> Self { - Self { format_embedded } + /// Create an [`ExternalFormatter`] from JS callbacks. + pub fn new(format_embedded_cb: JsFormatEmbeddedCb, format_file_cb: JsFormatFileCb) -> Self { + let rust_format_embedded = wrap_format_embedded(format_embedded_cb); + let rust_format_file = wrap_format_file(format_file_cb); + Self { format_embedded: rust_format_embedded, format_file: rust_format_file } } /// Convert this external formatter to the oxc_formatter::EmbeddedFormatter type @@ -45,12 +71,15 @@ impl ExternalFormatter { // The callback already expects &str, so just use it directly oxc_formatter::EmbeddedFormatter::new(callback) } + + /// Format non-js file using the JS callback. + pub fn format_file(&self, file_name: &str, code: &str) -> Result { + (self.format_file)(file_name, code) + } } /// Wrap JS `formatEmbeddedCode` callback as a normal Rust function. -/// -/// Uses a channel to capture the result from the JS callback. -pub fn wrap_format_embedded(cb: JsFormatEmbeddedCb) -> EmbeddedFormatterCallback { +fn wrap_format_embedded(cb: JsFormatEmbeddedCb) -> EmbeddedFormatterCallback { Arc::new(move |tag_name: &str, code: &str| { block_on(async { let status = @@ -70,8 +99,23 @@ pub fn wrap_format_embedded(cb: JsFormatEmbeddedCb) -> EmbeddedFormatterCallback }) } -/// Create an [`ExternalFormatter`] from JS callbacks. -pub fn create_external_formatter(format_embedded_cb: JsFormatEmbeddedCb) -> ExternalFormatter { - let rust_format_embedded = wrap_format_embedded(format_embedded_cb); - ExternalFormatter::new(rust_format_embedded) +/// Wrap JS `formatFile` callback as a normal Rust function. +fn wrap_format_file(cb: JsFormatFileCb) -> EmbeddedFormatterCallback { + Arc::new(move |file_name: &str, code: &str| { + block_on(async { + let status = + cb.call_async(FnArgs::from((file_name.to_string(), code.to_string()))).await; + match status { + Ok(promise) => match promise.await { + Ok(formatted_code) => Ok(formatted_code), + Err(err) => { + Err(format!("JS formatter promise rejected for file '{file_name}': {err}")) + } + }, + Err(err) => Err(format!( + "Failed to call JS formatting callback for file '{file_name}': {err}" + )), + } + }) + }) } diff --git a/apps/oxfmt/src/prettier_plugins/mod.rs b/apps/oxfmt/src/prettier_plugins/mod.rs index 4790a9d11138f..9cc701aaa9151 100644 --- a/apps/oxfmt/src/prettier_plugins/mod.rs +++ b/apps/oxfmt/src/prettier_plugins/mod.rs @@ -1,3 +1,3 @@ mod external_formatter; -pub use external_formatter::{ExternalFormatter, JsFormatEmbeddedCb, create_external_formatter}; +pub use external_formatter::{ExternalFormatter, JsFormatEmbeddedCb, JsFormatFileCb}; diff --git a/apps/oxfmt/src/service.rs b/apps/oxfmt/src/service.rs index df9df79e9b383..3d0f1541f230d 100644 --- a/apps/oxfmt/src/service.rs +++ b/apps/oxfmt/src/service.rs @@ -4,13 +4,25 @@ use cow_utils::CowUtils; use rayon::prelude::*; use oxc_allocator::AllocatorPool; -use oxc_diagnostics::{DiagnosticSender, DiagnosticService, OxcDiagnostic}; -use oxc_formatter::{FormatOptions, Formatter, enable_jsx_source_type, get_parse_options}; +use oxc_diagnostics::{DiagnosticSender, DiagnosticService, Error, OxcDiagnostic}; +use oxc_formatter::{ + FormatOptions, Formatter, enable_jsx_source_type, get_parse_options, get_supported_source_type, +}; use oxc_parser::Parser; +use oxc_span::SourceType; use crate::{command::OutputOptions, walk::WalkEntry}; -type PathSender = mpsc::Sender; +pub enum SuccessResult { + Changed(String), + Unchanged, +} + +enum FormatFileResult { + Handled { is_changed: bool, code: String }, + NotHandled, + Error(Vec), +} pub struct FormatService { allocator_pool: AllocatorPool, @@ -18,6 +30,8 @@ pub struct FormatService { output_options: OutputOptions, format_options: FormatOptions, #[cfg(feature = "napi")] + handle_external_files: bool, + #[cfg(feature = "napi")] external_formatter: Option, } @@ -37,6 +51,8 @@ impl FormatService { output_options, format_options, #[cfg(feature = "napi")] + handle_external_files: false, + #[cfg(feature = "napi")] external_formatter: None, } } @@ -46,36 +62,80 @@ impl FormatService { pub fn with_external_formatter( mut self, external_formatter: Option, + handle_external_files: bool, ) -> Self { self.external_formatter = external_formatter; + self.handle_external_files = handle_external_files; self } /// 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(()); + let start_time = Instant::now(); + + let path = &entry.path; + let (code, is_changed) = match self.format_file(&entry) { + FormatFileResult::Handled { is_changed, code } => (code, is_changed), + FormatFileResult::NotHandled => return, + FormatFileResult::Error(diagnostics) => { + tx_error.send(diagnostics).unwrap(); + return; + } + }; + + 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(); + } + + // 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) { + SuccessResult::Changed(format!("{display_path} ({elapsed}ms)")) + } else { + SuccessResult::Changed(display_path) + } + } + _ => SuccessResult::Unchanged, + }; + tx_success.send(result).unwrap(); }); } - /// Process a single entry - fn process_entry(&self, entry: &WalkEntry, tx_error: &DiagnosticSender, tx_path: &PathSender) { - let start_time = Instant::now(); - + fn format_file(&self, entry: &WalkEntry) -> FormatFileResult { let path = &entry.path; - let source_type = enable_jsx_source_type(entry.source_type); + let source_type = get_supported_source_type(path.as_path()); + + #[cfg(feature = "napi")] + let handle_external_files = self.handle_external_files; + #[cfg(not(feature = "napi"))] + let handle_external_files = false; + + if source_type.is_none() && !handle_external_files { + return FormatFileResult::NotHandled; + } - let allocator = self.allocator_pool.get(); 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( @@ -87,22 +147,47 @@ impl FormatService { .with_help("This may be due to the file being a binary or inaccessible."), ], ); - tx_error.send(diagnostics).unwrap(); - return; + return FormatFileResult::Error(diagnostics); + }; + + let code = match match source_type { + Some(source_type) => self.format_by_oxc_formatter(entry, &source_text, source_type), + #[cfg(feature = "napi")] + None => self.format_by_external_formatter(entry, &source_text), + #[cfg(not(feature = "napi"))] + None => unreachable!( + "If `napi` feature is disabled, non-supported entry should not be passed: {}", + path.display() + ), + } { + Ok(code) => code, + Err(diagnostics) => return FormatFileResult::Error(diagnostics), }; - let ret = Parser::new(&allocator, &source_text, source_type) + FormatFileResult::Handled { is_changed: source_text != code, code } + } + + fn format_by_oxc_formatter( + &self, + entry: &WalkEntry, + source_text: &str, + source_type: SourceType, + ) -> Result> { + let path = &entry.path; + let source_type = enable_jsx_source_type(source_type); + + let allocator = self.allocator_pool.get(); + 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, + source_text, ret.errors, ); - tx_error.send(diagnostics).unwrap(); - return; + return Err(diagnostics); } let base_formatter = Formatter::new(&allocator, self.format_options.clone()); @@ -129,58 +214,59 @@ impl FormatService { let diagnostics = DiagnosticService::wrap_diagnostics( self.cwd.clone(), path, - &source_text, + source_text, vec![OxcDiagnostic::error(format!( "Failed to print formatted code: {}\n{err}", path.display() ))], ); - tx_error.send(diagnostics).unwrap(); - return; + return Err(diagnostics); } }; #[cfg(feature = "detect_code_removal")] { - if let Some(diff) = oxc_formatter::detect_code_removal(&source_text, &code, source_type) + 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; + Ok(code) + } - // 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(); - } + #[cfg(feature = "napi")] + fn format_by_external_formatter( + &self, + entry: &WalkEntry, + source_text: &str, + ) -> Result> { + let path = &entry.path; + let file_name = path.file_name().expect("Path must have a file name").to_string_lossy(); - // 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(); - - if matches!(self.output_options, OutputOptions::Check) { - Some(format!("{display_path} ({elapsed}ms)")) - } else { - Some(display_path) - } + let code = match self + .external_formatter + .as_ref() + .expect("`external_formatter` must exist when `napi` feature is enabled") + .format_file(&file_name, source_text) + { + Ok(code) => code, + Err(err) => { + // TODO: Need to handle `UndefinedParserError` or not + let diagnostics = DiagnosticService::wrap_diagnostics( + self.cwd.clone(), + path, + source_text, + vec![OxcDiagnostic::error(format!( + "Failed to format file with external formatter: {}\n{err}", + path.display() + ))], + ); + return Err(diagnostics); } - _ => None, - } { - tx_path.send(output).unwrap(); - } + }; + + Ok(code) } } diff --git a/apps/oxfmt/src/walk.rs b/apps/oxfmt/src/walk.rs index 65aa5489a1e10..da8e21422f634 100644 --- a/apps/oxfmt/src/walk.rs +++ b/apps/oxfmt/src/walk.rs @@ -5,9 +5,6 @@ use std::{ use ignore::{gitignore::GitignoreBuilder, overrides::OverrideBuilder}; -use oxc_formatter::get_supported_source_type; -use oxc_span::SourceType; - pub struct Walk { inner: ignore::WalkParallel, } @@ -202,7 +199,6 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Vec { pub struct WalkEntry { pub path: PathBuf, - pub source_type: SourceType, } struct WalkBuilder { @@ -229,10 +225,8 @@ 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 walk_entry = WalkEntry { path: entry.path().to_path_buf() }; // 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() { diff --git a/crates/oxc_formatter/src/service/oxfmtrc.rs b/crates/oxc_formatter/src/service/oxfmtrc.rs index 345ed7fbccc25..5310a3243a144 100644 --- a/crates/oxc_formatter/src/service/oxfmtrc.rs +++ b/crates/oxc_formatter/src/service/oxfmtrc.rs @@ -69,7 +69,7 @@ pub struct Oxfmtrc { #[schemars(skip)] pub experimental_ternaries: Option, - /// Control whether formats quoted code embedded in the file. (Default: "auto") + /// Control whether formats quoted code embedded in the file. (Default: "off") #[serde(skip_serializing_if = "Option::is_none")] pub embedded_language_formatting: Option, @@ -77,6 +77,10 @@ pub struct Oxfmtrc { #[serde(skip_serializing_if = "Option::is_none")] pub experimental_sort_imports: Option, + /// Experimental: Use external formatter (Prettier) for non-JS/TS files. (Default: false) + #[serde(skip_serializing_if = "Option::is_none")] + pub experimental_external_formatter: Option, + /// Ignore files matching these glob patterns. Current working directory is used as the root. #[serde(skip_serializing_if = "Option::is_none")] pub ignore_patterns: Option>, diff --git a/crates/oxc_formatter/tests/snapshots/schema_json.snap b/crates/oxc_formatter/tests/snapshots/schema_json.snap index 08a1e582179d4..ba2e0cfc60b47 100644 --- a/crates/oxc_formatter/tests/snapshots/schema_json.snap +++ b/crates/oxc_formatter/tests/snapshots/schema_json.snap @@ -34,7 +34,7 @@ expression: json ] }, "embeddedLanguageFormatting": { - "description": "Control whether formats quoted code embedded in the file. (Default: \"auto\")", + "description": "Control whether formats quoted code embedded in the file. (Default: \"off\")", "anyOf": [ { "$ref": "#/definitions/EmbeddedLanguageFormattingConfig" @@ -55,6 +55,13 @@ expression: json } ] }, + "experimentalExternalFormatter": { + "description": "Experimental: Use external formatter (Prettier) for non-JS/TS files. (Default: false)", + "type": [ + "boolean", + "null" + ] + }, "experimentalSortImports": { "description": "Experimental: Sort import statements. Disabled by default.", "anyOf": [ diff --git a/npm/oxfmt/configuration_schema.json b/npm/oxfmt/configuration_schema.json index ca5f5668d90dd..fc2d9f55cb0e9 100644 --- a/npm/oxfmt/configuration_schema.json +++ b/npm/oxfmt/configuration_schema.json @@ -30,7 +30,7 @@ ] }, "embeddedLanguageFormatting": { - "description": "Control whether formats quoted code embedded in the file. (Default: \"auto\")", + "description": "Control whether formats quoted code embedded in the file. (Default: \"off\")", "anyOf": [ { "$ref": "#/definitions/EmbeddedLanguageFormattingConfig" @@ -51,6 +51,13 @@ } ] }, + "experimentalExternalFormatter": { + "description": "Experimental: Use external formatter (Prettier) for non-JS/TS files. (Default: false)", + "type": [ + "boolean", + "null" + ] + }, "experimentalSortImports": { "description": "Experimental: Sort import statements. Disabled by default.", "anyOf": [