Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/oxfmt/src-js/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[]>, formatEmbeddedCb: (options: Record<string, any>, code: string) => Promise<string>, formatFileCb: (options: Record<string, any>, code: string) => Promise<string>, sortTailwindClassesCb: (filepath: string, options: Record<string, any>, classes: string[]) => Promise<string[]>): Promise<FormatResult>
export declare function format(filename: string, sourceText: string, options: any | undefined | null, initExternalFormatterCb: (numThreads: number) => Promise<string[]>, formatEmbeddedCb: (options: Record<string, any>, code: string) => Promise<string>, formatFileCb: (options: Record<string, any>, code: string) => Promise<string>, sortTailwindClassesCb: (options: Record<string, any>, classes: string[]) => Promise<string[]>): Promise<FormatResult>

export interface FormatResult {
/** The formatted code. */
Expand All @@ -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<string>, initExternalFormatterCb: (numThreads: number) => Promise<string[]>, formatEmbeddedCb: (options: Record<string, any>, code: string) => Promise<string>, formatFileCb: (options: Record<string, any>, code: string) => Promise<string>, sortTailwindcssClassesCb: (filepath: string, options: Record<string, any>, classes: string[]) => Promise<string[]>): Promise<[string, number | undefined | null]>
export declare function runCli(args: Array<string>, initExternalFormatterCb: (numThreads: number) => Promise<string[]>, formatEmbeddedCb: (options: Record<string, any>, code: string) => Promise<string>, formatFileCb: (options: Record<string, any>, code: string) => Promise<string>, sortTailwindcssClassesCb: (options: Record<string, any>, classes: string[]) => Promise<string[]>): Promise<[string, number | undefined | null]>
16 changes: 10 additions & 6 deletions apps/oxfmt/src-js/cli/worker-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,24 +28,29 @@ export async function disposeExternalFormatter(): Promise<void> {
pool = null;
}

export async function formatEmbeddedCode(options: Options, code: string): Promise<string> {
export async function formatEmbeddedCode(
options: FormatEmbeddedCodeParam["options"],
code: string,
): Promise<string> {
return pool!.run({ options, code } satisfies FormatEmbeddedCodeParam, {
name: "formatEmbeddedCode",
});
}

export async function formatFile(options: Options, code: string): Promise<string> {
export async function formatFile(
options: FormatFileParam["options"],
code: string,
): Promise<string> {
return pool!.run({ options, code } satisfies FormatFileParam, {
name: "formatFile",
});
}

export async function sortTailwindClasses(
filepath: string,
options: Options,
options: SortTailwindClassesArgs["options"],
classes: string[],
): Promise<string[]> {
return pool!.run({ filepath, options, classes } satisfies SortTailwindClassesArgs, {
return pool!.run({ classes, options } satisfies SortTailwindClassesArgs, {
name: "sortTailwindClasses",
});
}
2 changes: 1 addition & 1 deletion apps/oxfmt/src-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
);
}

Expand Down
13 changes: 6 additions & 7 deletions apps/oxfmt/src-js/libs/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,29 @@ async function setupTailwindPlugin(options: Options): Promise<void> {
// ---

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<string[]> {
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,
Expand Down
32 changes: 14 additions & 18 deletions apps/oxfmt/src/core/external_formatter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
path::Path,
sync::{Arc, RwLock},
};
use std::sync::{Arc, RwLock};

use napi::{
Status,
Expand Down Expand Up @@ -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<String>)>, // Input: (filepath, options, classes)
Promise<Vec<String>>, // Return: promise of sorted array
FnArgs<(String, Value, Vec<String>)>,
FnArgs<(Value, Vec<String>)>, // Input: (options, classes)
Promise<Vec<String>>, // Return: promise of sorted array
FnArgs<(Value, Vec<String>)>,
Status,
false,
>;
Expand Down Expand Up @@ -114,9 +112,9 @@ type InitExternalFormatterCallback =
Arc<dyn Fn(usize) -> Result<Vec<String>, String> + Send + Sync>;

/// Internal callback type for Tailwind processing with config.
/// Takes (filepath, options, classes) and returns sorted classes.
type TailwindWithConfigCallback =
Arc<dyn Fn(&str, &Value, Vec<String>) -> Vec<String> + Send + Sync>;
/// Takes (options, classes) and returns sorted classes.
/// The `filepath` is included in `options`.
type TailwindWithConfigCallback = Arc<dyn Fn(&Value, Vec<String>) -> Vec<String> + Send + Sync>;

/// External formatter that wraps a JS callback.
#[derive(Clone)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -245,11 +242,10 @@ impl ExternalFormatter {

let needs_tailwind = format_options.experimental_tailwindcss.is_some();
let tailwind_callback: Option<TailwindCallback> = 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<String>| {
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
Expand Down Expand Up @@ -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![]),
}
}
}
Expand Down Expand Up @@ -395,14 +391,14 @@ fn wrap_format_file(
fn wrap_sort_tailwind_classes(
cb_handle: Arc<RwLock<Option<JsSortTailwindClassesCb>>>,
) -> TailwindWithConfigCallback {
Arc::new(move |filepath: &str, options: &Value, classes: Vec<String>| {
Arc::new(move |options: &Value, classes: Vec<String>| {
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,
Expand Down
17 changes: 12 additions & 5 deletions apps/oxfmt/src/core/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl SourceFormatter {
path: &Path,
source_type: SourceType,
format_options: FormatOptions,
external_options: Value,
mut external_options: Value,
) -> Result<String, OxcDiagnostic> {
let source_type = enable_jsx_source_type(source_type);
let allocator = self.allocator_pool.get();
Expand All @@ -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"))]
Expand Down Expand Up @@ -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| {
Expand Down
8 changes: 2 additions & 6 deletions apps/oxfmt/src/main_napi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ pub async fn run_cli(
format_embedded_cb: JsFormatEmbeddedCb,
#[napi(ts_arg_type = "(options: Record<string, any>, code: string) => Promise<string>")]
format_file_cb: JsFormatFileCb,
#[napi(
ts_arg_type = "(filepath: string, options: Record<string, any>, classes: string[]) => Promise<string[]>"
)]
#[napi(ts_arg_type = "(options: Record<string, any>, classes: string[]) => Promise<string[]>")]
sort_tailwindcss_classes_cb: JsSortTailwindClassesCb,
) -> (String, Option<u8>) {
// Convert `String` args to `OsString` for compatibility with `bpaf`
Expand Down Expand Up @@ -144,9 +142,7 @@ pub async fn format(
format_embedded_cb: JsFormatEmbeddedCb,
#[napi(ts_arg_type = "(options: Record<string, any>, code: string) => Promise<string>")]
format_file_cb: JsFormatFileCb,
#[napi(
ts_arg_type = "(filepath: string, options: Record<string, any>, classes: string[]) => Promise<string[]>"
)]
#[napi(ts_arg_type = "(options: Record<string, any>, classes: string[]) => Promise<string[]>")]
sort_tailwind_classes_cb: JsSortTailwindClassesCb,
) -> FormatResult {
let num_of_threads = 1;
Expand Down
Loading