Skip to content

Commit bba146a

Browse files
committed
refactor(oxfmt): Prettify external_formatter errors (#19565)
Fixes #19527 ### Before ``` Failed to format file with external formatter: /u/p/c/oxc/apps/oxfmt/a.yml JS formatFile promise rejected: GenericFailure, [object Object] ``` ### After ``` SyntaxError: Nested mappings are not allowed in compact mappings (1:6) [a.yml] > 1 | key: value: nested | ^^^^^^^^^^^^^ > 2 | | ^ ```
1 parent 87cb667 commit bba146a

File tree

3 files changed

+42
-13
lines changed

3 files changed

+42
-13
lines changed

apps/oxfmt/src-js/cli/worker-proxy.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,25 @@ export async function formatFile(
4444
options: FormatFileParam["options"],
4545
code: string,
4646
): Promise<string> {
47-
return pool!.run({ options, code } satisfies FormatFileParam, {
48-
name: "formatFile",
49-
});
47+
return (
48+
pool!
49+
.run({ options, code } satisfies FormatFileParam, { name: "formatFile" })
50+
// `tinypool` with `runtime: "child_process"` serializes Error as plain objects via IPC.
51+
// (e.g. `{ name, message, stack, ... }`)
52+
// And napi-rs converts unknown JS values to Rust Error by calling `String()` on them,
53+
// which yields `"[object Object]"` for plain objects...
54+
// So, this function reconstructs a proper `Error` instance so napi-rs can extract the message.
55+
.catch((err) => {
56+
if (err instanceof Error) throw err;
57+
if (err !== null && typeof err === "object") {
58+
const obj = err as { name: string; message: string };
59+
const newErr = new Error(obj.message);
60+
newErr.name = obj.name;
61+
throw newErr;
62+
}
63+
throw new Error(String(err));
64+
})
65+
);
5066
}
5167

5268
export async function sortTailwindClasses(

apps/oxfmt/src/core/external_formatter.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,9 @@ fn wrap_init_external_formatter(
327327
match status {
328328
Ok(promise) => match promise.await {
329329
Ok(languages) => Ok(languages),
330-
Err(err) => Err(format!("JS initExternalFormatter promise rejected: {err}")),
330+
Err(err) => Err(err.reason.clone()),
331331
},
332-
Err(err) => Err(format!("Failed to call JS initExternalFormatter callback: {err}")),
332+
Err(err) => Err(err.reason.clone()),
333333
}
334334
});
335335
drop(guard);
@@ -352,9 +352,9 @@ fn wrap_format_embedded(
352352
match status {
353353
Ok(promise) => match promise.await {
354354
Ok(formatted_code) => Ok(formatted_code),
355-
Err(err) => Err(format!("JS formatEmbeddedCode promise rejected: {err}")),
355+
Err(err) => Err(err.reason.clone()),
356356
},
357-
Err(err) => Err(format!("Failed to call JS formatEmbeddedCode callback: {err}")),
357+
Err(err) => Err(err.reason.clone()),
358358
}
359359
});
360360
drop(guard);
@@ -377,9 +377,9 @@ fn wrap_format_file(
377377
match status {
378378
Ok(promise) => match promise.await {
379379
Ok(formatted_code) => Ok(formatted_code),
380-
Err(err) => Err(format!("JS formatFile promise rejected: {err}")),
380+
Err(err) => Err(err.reason.clone()),
381381
},
382-
Err(err) => Err(format!("Failed to call JS formatFile callback: {err}")),
382+
Err(err) => Err(err.reason.clone()),
383383
}
384384
});
385385
drop(guard);

apps/oxfmt/src/core/format.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,23 @@ impl SourceFormatter {
219219
}
220220

221221
external_formatter.format_file(external_options, source_text).map_err(|err| {
222-
OxcDiagnostic::error(format!(
223-
"Failed to format file with external formatter: {}\n{err}",
224-
path.display()
225-
))
222+
// NOTE: We are trying to make the error from oxc_formatter and external_formatter (Prettier) look similar.
223+
// Ideally, we would unify them into `OxcDiagnostic`,
224+
// which would eliminate the need for relative path conversion.
225+
// However, doing so would require:
226+
// - Parsing Prettier's error messages
227+
// - Converting span information from UTF-16 to UTF-8
228+
// This is a non-trivial amount of work, so for now, just leave this as a best effort.
229+
let relative = std::env::current_dir()
230+
.ok()
231+
.and_then(|cwd| path.strip_prefix(cwd).ok().map(Path::to_path_buf));
232+
let display_path = relative.as_deref().unwrap_or(path).to_string_lossy();
233+
let message = if let Some((first, rest)) = err.split_once('\n') {
234+
format!("{first}\n[{display_path}]\n{rest}")
235+
} else {
236+
format!("{err}\n[{display_path}]")
237+
};
238+
OxcDiagnostic::error(message)
226239
})
227240
}
228241

0 commit comments

Comments
 (0)