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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/oxfmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ doctest = false

[dependencies]
oxc_allocator = { workspace = true, features = ["pool"] }
oxc_ast = { workspace = true }
oxc_data_structures = { workspace = true, features = ["rope"] }
oxc_diagnostics = { workspace = true }
oxc_formatter = { workspace = true }
Expand Down
14 changes: 2 additions & 12 deletions apps/oxfmt/src-js/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,9 @@ export interface FormatResult {
* NAPI based `textToDoc` API entry point for `prettier-plugin-oxfmt`.
*
* This API is specialized for JS/TS snippets embedded in non-JS files.
* Unlike `format()`, it is called only for JS/TS-in-xxx `textToDoc` flow.
*
* # Panics
* Panics if the current working directory cannot be determined.
* Unlike `format()`, it is called only for js-in-xxx `textToDoc()` flow.
*/
export declare function jsTextToDoc(filename: string, sourceText: string, oxfmtPluginOptionsJson: string, parentContext: string, 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<TextToDocResult>
export declare function jsTextToDoc(sourceExt: string, sourceText: string, oxfmtPluginOptionsJson: string, parentContext: string, 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<string | null>

/**
* NAPI based JS CLI entry point.
Expand All @@ -70,10 +67,3 @@ export declare function jsTextToDoc(filename: string, sourceText: string, oxfmtP
* - `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: (options: Record<string, any>, classes: string[]) => Promise<string[]>): Promise<[string, number | undefined | null]>

export interface TextToDocResult {
/** The formatted code. */
doc: string
/** Parse and format errors. */
errors: Array<OxcError>
}
4 changes: 2 additions & 2 deletions apps/oxfmt/src-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export async function format(fileName: string, sourceText: string, options?: For
* Format a JS/TS snippet for Prettier `textToDoc()` plugin flow.
*/
export async function jsTextToDoc(
fileName: string,
sourceExt: string,
sourceText: string,
oxfmtPluginOptionsJson: string,
parentContext: string,
) {
return napiJsTextToDoc(
fileName,
sourceExt,
sourceText,
oxfmtPluginOptionsJson,
parentContext,
Expand Down
1 change: 1 addition & 0 deletions apps/oxfmt/src-js/libs/prettier-plugin-oxfmt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const oxfmtParser: Parser<Doc> = {
export const parsers: Record<string, Parser> = {
// Override default JS/TS parsers
babel: oxfmtParser,
"babel-ts": oxfmtParser,
typescript: oxfmtParser,
};

Expand Down
66 changes: 43 additions & 23 deletions apps/oxfmt/src-js/libs/prettier-plugin-oxfmt/text-to-doc.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
import { doc } from "prettier";
import { jsTextToDoc } from "../../index";
import type { Parser, Doc } from "prettier";

const { hardline, join } = doc.builders;
const LINE_BREAK_RE = /\r?\n/;

export const textToDoc: Parser<Doc>["parse"] = async (embeddedSourceText, textToDocOptions) => {
// NOTE: For (j|t)s-in-xxx, default `parser` is either `babel` or `typescript`
// In case of ts-in-md, `filepath` is overridden to distinguish TSX or TS
// We need to infer `SourceType::from_path(fileName)` for `oxc_formatter`.
// `_oxfmtPluginOptionsJson` is a JSON string bundled by Rust (`oxfmtrc::finalize_external_options`),
// containing format options + parent filepath for the Rust-side `oxc_formatter`.
const { parser, parentParser, filepath, _oxfmtPluginOptionsJson } = textToDocOptions;
const fileName =
parser === "typescript"
? filepath.endsWith(".tsx")
? "dummy.tsx" // tsx-in-md
: "dummy.ts" // ts-in-md / ts-in-xxx
: "dummy.jsx"; // Otherwise, always enable JSX for js-in-xxx, it's safe

const { doc: formattedText, errors } = await jsTextToDoc(
fileName,

// For (j|t)s-in-xxx, default `parser` is either `babel`, `babel-ts` or `typescript`
// We need to infer `SourceType::from_extension(ext)` for `oxc_formatter`.
// - JS: always enable JSX for js-in-xxx, it's safe
// - TS: `typescript` (ts-in-vue|markdown|mdx) or `babel-ts` (ts-in-vue(script generic="..."))
// - In case of ts-in-md, `filepath` is overridden as `dummy.tx(x)` to distinguish TSX or TS
// - NOTE: tsx-in-vue is not supported since there is no signal from Prettier to detect it
// - Prettier is using `maybeJSXRe.test(sourceText)` to detect, but it's slow!
const isTS = parser === "typescript" || parser === "babel-ts";
const embeddedSourceExt = isTS ? (filepath?.endsWith(".tsx") ? "tsx" : "ts") : "jsx";

// Detect context from Prettier's internal flags
const parentContext = detectParentContext(parentParser!, textToDocOptions);

const doc = await jsTextToDoc(
embeddedSourceExt,
embeddedSourceText,
_oxfmtPluginOptionsJson as string,
[parentParser].join(":"),
parentContext,
);

if (0 < errors.length) throw new Error(errors[0].message);
if (doc === null) {
throw new Error("`oxfmt::textToDoc()` failed. Use `OXC_LOG` env var to see Rust-side logs.");
}

// NOTE: This is required for the parent ((j|t)s-in-xxx) printer
// to handle line breaks correctly,
// not only for `options.vueIndentScriptAndStyle` but also for basic printing.
// TODO: Will be handled in Rust, convert our IR to Prettier's Doc directly.
return join(hardline, formattedText.split(LINE_BREAK_RE));
// SAFETY: Rust side returns Prettier's `Doc` JSON
return JSON.parse(doc) as Doc;
};

/**
* Detects Vue fragment mode from Prettier's internal flags.
*
* When Prettier formats Vue SFC templates, it calls textToDoc with special flags:
* - `__isVueForBindingLeft`: v-for left-hand side (e.g., `(item, index)` in `v-for="(item, index) in items"`)
* - `__isVueBindings`: v-slot bindings (e.g., `{ item }` in `#default="{ item }"`)
* - `__isEmbeddedTypescriptGenericParameters`: `<script generic="...">` type parameters
*/
function detectParentContext(parentParser: string, options: Record<string, unknown>): string {
if (parentParser === "vue") {
if ("__isVueForBindingLeft" in options) return "vue-for-binding-left";
if ("__isVueBindings" in options) return "vue-bindings";
if ("__isEmbeddedTypescriptGenericParameters" in options) return "vue-script-generic";
return "vue-script";
}

return parentParser;
}
2 changes: 1 addition & 1 deletion apps/oxfmt/src/api/format_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub fn run(

// Resolve format options directly from the provided options
let resolved_options =
match resolve_options_from_value(&cwd, options.unwrap_or_default(), &strategy) {
match resolve_options_from_value(options.unwrap_or_default(), &strategy, Some(&cwd)) {
Ok(options) => options,
Err(err) => {
external_formatter.cleanup();
Expand Down
Loading
Loading