Files
oxc-project__oxc/apps/oxfmt/src-js/index.ts
leaysgur 8213e72f37 refactor(formatter,oxfmt): flatten embedded dispatch contract (#25391)
Due to historical reasons, the IR was passed as `[Doc, (Doc, ...Doc)]` only when processing `gql-in-js`, whilst for other cases such as `css-in-js` and `html-in-js`, it was always `[Doc]`.

Now, every result is single `Doc`.
2026-08-09 14:32:11 +00:00

122 lines
4.8 KiB
TypeScript

// napi-JS `oxfmt` API entry point
import {
formatFile,
formatEmbeddedCode,
formatEmbeddedDoc,
sortTailwindClasses,
} from "./libs/apis";
import { toFormatFileResult, toNullable } from "./libs/napi-callbacks";
// Types are auto-generated from the JSON Schema.
import type {
Oxfmtrc,
FormatConfig,
SortImportsConfig,
SortPackageJsonConfig,
SortTailwindcssConfig,
} from "./config.generated";
// --- Type exports ---
// Re-export all generated config types.
// So that downstream libraries can reference them in declaration emit without TS4058/TS4082 errors.
export type * from "./config.generated";
// The same naming convention as `oxlint` for consistency.
// Using `interface extends` so that TypeScript displays `OxfmtConfig` in errors
// and hovers instead of resolving to the generated `Oxfmtrc` name.
export interface OxfmtConfig extends Oxfmtrc {}
// Backward-compatible type aliases using `Options` suffix.
/**
* Configuration options for the `format()` API.
*
* Based on `FormatConfig` generated from the JSON Schema,
* with additional deprecated aliases for backward compatibility.
* @deprecated Use `FormatConfig` instead.
*/
export type FormatOptions = FormatConfig & {
/** @deprecated Use `sortImports` instead. */
experimentalSortImports?: SortImportsConfig;
/** @deprecated Use `sortPackageJson` instead. */
experimentalSortPackageJson?: boolean | SortPackageJsonConfig;
/** @deprecated Use `sortTailwindcss` instead. */
experimentalTailwindcss?: SortTailwindcssConfig;
};
/** @deprecated Use `FormatConfig["sortImports"]` instead. */
export type SortImportsOptions = SortImportsConfig;
/** @deprecated Use `FormatConfig["sortPackageJson"]` instead. */
export type SortPackageJsonOptions = SortPackageJsonConfig;
/** @deprecated Use `FormatConfig["sortTailwindcss"]` instead. */
export type SortTailwindcssOptions = SortTailwindcssConfig;
/** @deprecated Use `FormatConfig["sortTailwindcss"]` instead. */
export type TailwindcssOptions = SortTailwindcssConfig;
// --- Function exports ---
/**
* Define an oxfmt configuration with type inference.
*/
export function defineConfig<T extends OxfmtConfig>(config: T & OxfmtConfig): T {
return config;
}
// NOTE: Native bindings are loaded lazily on first `format()`/`jsTextToDoc()` call,
// instead of via a static `import "./bindings"`.
//
// A static import would run `requireNative()` which `dlopen`s the native `.node` addon.
// That is wasteful (and sometimes harmful),
// because the two paths that import this entry don't always need the binding:
// 1. Config loading: config files do `import { defineConfig } from "oxfmt"`,
// where `defineConfig` is a plain identity function needing no native code.
// Normally the binding is already cached there (the CLI's own copy),
// so an eager load is just a harmless cache hit.
// But when a nested config resolves "oxfmt" to a separate install (its own `node_modules`),
// it triggers a fresh re-entrant `dlopen` on the main thread, which hangs (observed on WSL2).
// See https://github.com/oxc-project/oxc/issues/23125
// 2. `jsTextToDoc` (via prettier-plugin-oxfmt, runs in the worker process where the binding is NOT preloaded):
// deferring the load until it's actually called avoids paying the `dlopen` cost on runs that have no embedded code to format.
let BINDINGS_CACHE = null as typeof import("./bindings") | null;
/**
* Format the given source text according to the specified options.
*/
export async function format(fileName: string, sourceText: string, options?: FormatConfig) {
if (typeof fileName !== "string") throw new TypeError("`fileName` must be a string");
if (typeof sourceText !== "string") throw new TypeError("`sourceText` must be a string");
BINDINGS_CACHE ??= await import("./bindings");
return BINDINGS_CACHE.format(
fileName,
sourceText,
options ?? {},
(options, code) => toFormatFileResult(formatFile({ options, code })),
(options, code) => toNullable(formatEmbeddedCode({ options, code })),
(options, code) => toNullable(formatEmbeddedDoc({ options, code })),
(options, classes) => toNullable(sortTailwindClasses({ options, classes })),
);
}
/**
* Format a JS/TS snippet for Prettier `textToDoc()` plugin flow.
*/
export async function jsTextToDoc(
sourceExt: string,
sourceText: string,
oxfmtPluginOptionsJson: string,
parentContext: string,
) {
BINDINGS_CACHE ??= await import("./bindings");
return BINDINGS_CACHE.jsTextToDoc(
sourceExt,
sourceText,
oxfmtPluginOptionsJson,
parentContext,
() => toFormatFileResult(Promise.reject("formatFile is unavailable for jsTextToDoc")),
(options, code) => toNullable(formatEmbeddedCode({ options, code })),
(options, code) => toNullable(formatEmbeddedDoc({ options, code })),
(options, classes) => toNullable(sortTailwindClasses({ options, classes })),
);
}