diff --git a/apps/oxfmt/src-js/bindings.d.ts b/apps/oxfmt/src-js/bindings.d.ts index 3ae4818b9c3f5..bfcf298d45f17 100644 --- a/apps/oxfmt/src-js/bindings.d.ts +++ b/apps/oxfmt/src-js/bindings.d.ts @@ -31,7 +31,7 @@ export declare const enum Severity { * * Since it internally uses `await prettier.format()` in JS side, `formatSync()` cannot be provided. */ -export declare function format(filename: string, sourceText: string, options: any | undefined | null, initExternalFormatterCb: (numThreads: number) => Promise, formatEmbeddedCb: (options: Record, code: string) => Promise, formatFileCb: (options: Record, code: string) => Promise, sortTailwindClassesCb: (filepath: string, options: Record, classes: string[]) => Promise): Promise +export declare function format(filename: string, sourceText: string, options: any | undefined | null, initExternalFormatterCb: (numThreads: number) => Promise, formatEmbeddedCb: (options: Record, code: string) => Promise, formatFileCb: (options: Record, code: string) => Promise, sortTailwindClassesCb: (options: Record, classes: string[]) => Promise): Promise export interface FormatResult { /** The formatted code. */ @@ -55,4 +55,4 @@ export interface FormatResult { * - `mode`: If main logic will run in JS side, use this to indicate which mode * - `exitCode`: If main logic already ran in Rust side, return the exit code */ -export declare function runCli(args: Array, initExternalFormatterCb: (numThreads: number) => Promise, formatEmbeddedCb: (options: Record, code: string) => Promise, formatFileCb: (options: Record, code: string) => Promise, sortTailwindcssClassesCb: (filepath: string, options: Record, classes: string[]) => Promise): Promise<[string, number | undefined | null]> +export declare function runCli(args: Array, initExternalFormatterCb: (numThreads: number) => Promise, formatEmbeddedCb: (options: Record, code: string) => Promise, formatFileCb: (options: Record, code: string) => Promise, sortTailwindcssClassesCb: (options: Record, classes: string[]) => Promise): Promise<[string, number | undefined | null]> diff --git a/apps/oxfmt/src-js/cli/worker-proxy.ts b/apps/oxfmt/src-js/cli/worker-proxy.ts index 3783b2fcb1183..87b7c06011a8e 100644 --- a/apps/oxfmt/src-js/cli/worker-proxy.ts +++ b/apps/oxfmt/src-js/cli/worker-proxy.ts @@ -5,7 +5,6 @@ import type { FormatFileParam, SortTailwindClassesArgs, } from "../libs/apis"; -import type { Options } from "prettier"; // Worker pool for parallel Prettier formatting let pool: Tinypool | null = null; @@ -29,24 +28,29 @@ export async function disposeExternalFormatter(): Promise { pool = null; } -export async function formatEmbeddedCode(options: Options, code: string): Promise { +export async function formatEmbeddedCode( + options: FormatEmbeddedCodeParam["options"], + code: string, +): Promise { return pool!.run({ options, code } satisfies FormatEmbeddedCodeParam, { name: "formatEmbeddedCode", }); } -export async function formatFile(options: Options, code: string): Promise { +export async function formatFile( + options: FormatFileParam["options"], + code: string, +): Promise { return pool!.run({ options, code } satisfies FormatFileParam, { name: "formatFile", }); } export async function sortTailwindClasses( - filepath: string, - options: Options, + options: SortTailwindClassesArgs["options"], classes: string[], ): Promise { - return pool!.run({ filepath, options, classes } satisfies SortTailwindClassesArgs, { + return pool!.run({ classes, options } satisfies SortTailwindClassesArgs, { name: "sortTailwindClasses", }); } diff --git a/apps/oxfmt/src-js/index.ts b/apps/oxfmt/src-js/index.ts index 2c11851462e0c..c64b00cc9a2a8 100644 --- a/apps/oxfmt/src-js/index.ts +++ b/apps/oxfmt/src-js/index.ts @@ -19,7 +19,7 @@ export async function format(fileName: string, sourceText: string, options?: For resolvePlugins, (options, code) => formatEmbeddedCode({ options, code }), (options, code) => formatFile({ options, code }), - (filepath, options, classes) => sortTailwindClasses({ filepath, classes, options }), + (options, classes) => sortTailwindClasses({ options, classes }), ); } diff --git a/apps/oxfmt/src-js/libs/apis.ts b/apps/oxfmt/src-js/libs/apis.ts index 8c4034aee9633..d25be74a117e6 100644 --- a/apps/oxfmt/src-js/libs/apis.ts +++ b/apps/oxfmt/src-js/libs/apis.ts @@ -132,30 +132,29 @@ async function setupTailwindPlugin(options: Options): Promise { // --- export interface SortTailwindClassesArgs { - filepath: string; classes: string[]; - options?: { + options: { + filepath?: string; tailwindStylesheet?: string; tailwindConfig?: string; tailwindPreserveWhitespace?: boolean; tailwindPreserveDuplicates?: boolean; - } & Options; + }; } /** * Process Tailwind CSS classes found in JS/TS files in batch. - * @param args - Object containing filepath, classes, and options + * @param args - Object containing classes and options (filepath is in options.filepath) * @returns Array of sorted class strings (same order/length as input) */ export async function sortTailwindClasses({ - filepath, classes, - options = {}, + options, }: SortTailwindClassesArgs): Promise { const { createSorter } = await import("prettier-plugin-tailwindcss/sorter"); const sorter = await createSorter({ - filepath, + filepath: options.filepath, stylesheetPath: options.tailwindStylesheet, configPath: options.tailwindConfig, preserveWhitespace: options.tailwindPreserveWhitespace, diff --git a/apps/oxfmt/src/core/external_formatter.rs b/apps/oxfmt/src/core/external_formatter.rs index 32f8f3b1fa7be..7429bd420b118 100644 --- a/apps/oxfmt/src/core/external_formatter.rs +++ b/apps/oxfmt/src/core/external_formatter.rs @@ -1,7 +1,4 @@ -use std::{ - path::Path, - sync::{Arc, RwLock}, -}; +use std::sync::{Arc, RwLock}; use napi::{ Status, @@ -63,11 +60,12 @@ pub type JsFormatFileCb = ThreadsafeFunction< >; /// Type alias for Tailwind class processing callback. -/// Takes (filepath, options, classes) and returns sorted array. +/// Takes (options, classes) and returns sorted array. +/// The `filepath` is included in `options`. pub type JsSortTailwindClassesCb = ThreadsafeFunction< - FnArgs<(String, Value, Vec)>, // Input: (filepath, options, classes) - Promise>, // Return: promise of sorted array - FnArgs<(String, Value, Vec)>, + FnArgs<(Value, Vec)>, // Input: (options, classes) + Promise>, // Return: promise of sorted array + FnArgs<(Value, Vec)>, Status, false, >; @@ -114,9 +112,9 @@ type InitExternalFormatterCallback = Arc Result, String> + Send + Sync>; /// Internal callback type for Tailwind processing with config. -/// Takes (filepath, options, classes) and returns sorted classes. -type TailwindWithConfigCallback = - Arc) -> Vec + Send + Sync>; +/// Takes (options, classes) and returns sorted classes. +/// The `filepath` is included in `options`. +type TailwindWithConfigCallback = Arc) -> Vec + Send + Sync>; /// External formatter that wraps a JS callback. #[derive(Clone)] @@ -195,10 +193,9 @@ impl ExternalFormatter { } /// Convert this external formatter to the oxc_formatter::ExternalCallbacks type. - /// The filepath and options are captured in the closures and passed to JS on each call. + /// The options (including `filepath`) are captured in the closures and passed to JS on each call. pub fn to_external_callbacks( &self, - filepath: &Path, format_options: &FormatOptions, options: Value, ) -> ExternalCallbacks { @@ -245,11 +242,10 @@ impl ExternalFormatter { let needs_tailwind = format_options.experimental_tailwindcss.is_some(); let tailwind_callback: Option = if needs_tailwind { - let file_path = filepath.to_string_lossy().to_string(); let sort_tailwindcss_classes = Arc::clone(&self.sort_tailwindcss_classes); Some(Arc::new(move |classes: Vec| { debug_span!("oxfmt::external::sort_tailwind", classes_count = classes.len()) - .in_scope(|| (sort_tailwindcss_classes)(&file_path, &options, classes)) + .in_scope(|| (sort_tailwindcss_classes)(&options, classes)) })) } else { None @@ -280,7 +276,7 @@ impl ExternalFormatter { init: Arc::new(|_| Err("Dummy init called".to_string())), format_embedded: Arc::new(|_, _| Err("Dummy format_embedded called".to_string())), format_file: Arc::new(|_, _| Err("Dummy format_file called".to_string())), - sort_tailwindcss_classes: Arc::new(|_, _, _| vec![]), + sort_tailwindcss_classes: Arc::new(|_, _| vec![]), } } } @@ -395,14 +391,14 @@ fn wrap_format_file( fn wrap_sort_tailwind_classes( cb_handle: Arc>>, ) -> TailwindWithConfigCallback { - Arc::new(move |filepath: &str, options: &Value, classes: Vec| { + Arc::new(move |options: &Value, classes: Vec| { let guard = cb_handle.read().unwrap(); let Some(cb) = guard.as_ref() else { // Return original classes if callback unavailable return classes; }; let result = block_on(async { - let args = FnArgs::from((filepath.to_string(), options.clone(), classes.clone())); + let args = FnArgs::from((options.clone(), classes.clone())); match cb.call_async(args).await { Ok(promise) => match promise.await { Ok(sorted) => sorted, diff --git a/apps/oxfmt/src/core/format.rs b/apps/oxfmt/src/core/format.rs index 6a8b1678da11d..761bab9296c05 100644 --- a/apps/oxfmt/src/core/format.rs +++ b/apps/oxfmt/src/core/format.rs @@ -127,7 +127,7 @@ impl SourceFormatter { path: &Path, source_type: SourceType, format_options: FormatOptions, - external_options: Value, + mut external_options: Value, ) -> Result { let source_type = enable_jsx_source_type(source_type); let allocator = self.allocator_pool.get(); @@ -147,7 +147,16 @@ impl SourceFormatter { .as_ref() .expect("`external_formatter` must exist when `napi` feature is enabled"); - Some(external_formatter.to_external_callbacks(path, &format_options, external_options)) + // Set `filepath` on options for Prettier plugins that depend on it, + // and for the Tailwind sorter to resolve config. + if let Value::Object(ref mut map) = external_options { + map.insert( + "filepath".to_string(), + Value::String(path.to_string_lossy().to_string()), + ); + } + + Some(external_formatter.to_external_callbacks(&format_options, external_options)) }; #[cfg(not(feature = "napi"))] @@ -200,14 +209,12 @@ impl SourceFormatter { .as_ref() .expect("`external_formatter` must exist when `napi` feature is enabled"); - let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or(""); - // Set `parser` and `filepath` on options for Prettier. // We specify `parser` to skip parser inference for perf, // and `filepath` because some plugins depend on it. if let Value::Object(ref mut map) = external_options { map.insert("parser".to_string(), Value::String(parser_name.to_string())); - map.insert("filepath".to_string(), Value::String(file_name.to_string())); + map.insert("filepath".to_string(), Value::String(path.to_string_lossy().to_string())); } external_formatter.format_file(external_options, source_text).map_err(|err| { diff --git a/apps/oxfmt/src/main_napi.rs b/apps/oxfmt/src/main_napi.rs index c79840fa71172..a372ba9c20385 100644 --- a/apps/oxfmt/src/main_napi.rs +++ b/apps/oxfmt/src/main_napi.rs @@ -41,9 +41,7 @@ pub async fn run_cli( format_embedded_cb: JsFormatEmbeddedCb, #[napi(ts_arg_type = "(options: Record, code: string) => Promise")] format_file_cb: JsFormatFileCb, - #[napi( - ts_arg_type = "(filepath: string, options: Record, classes: string[]) => Promise" - )] + #[napi(ts_arg_type = "(options: Record, classes: string[]) => Promise")] sort_tailwindcss_classes_cb: JsSortTailwindClassesCb, ) -> (String, Option) { // Convert `String` args to `OsString` for compatibility with `bpaf` @@ -144,9 +142,7 @@ pub async fn format( format_embedded_cb: JsFormatEmbeddedCb, #[napi(ts_arg_type = "(options: Record, code: string) => Promise")] format_file_cb: JsFormatFileCb, - #[napi( - ts_arg_type = "(filepath: string, options: Record, classes: string[]) => Promise" - )] + #[napi(ts_arg_type = "(options: Record, classes: string[]) => Promise")] sort_tailwind_classes_cb: JsSortTailwindClassesCb, ) -> FormatResult { let num_of_threads = 1;