diff --git a/apps/oxfmt/.gitignore b/apps/oxfmt/.gitignore
index 3db66a0047980..185140d44a85e 100644
--- a/apps/oxfmt/.gitignore
+++ b/apps/oxfmt/.gitignore
@@ -1,3 +1,4 @@
/node_modules/
/dist/
+/prettier-fixtures/
*.node
diff --git a/apps/oxfmt/package.json b/apps/oxfmt/package.json
index 042acd2f22061..b855b23853b0e 100644
--- a/apps/oxfmt/package.json
+++ b/apps/oxfmt/package.json
@@ -14,7 +14,8 @@
"build-napi-test": "pnpm run build-napi",
"build-napi-release": "pnpm run build-napi --release --features allocator",
"build-js": "node scripts/build.js",
- "test": "vitest --dir test run"
+ "test": "vitest --dir test run",
+ "download-prettier-fixtures": "node scripts/download-prettier-fixtures.js"
},
"dependencies": {
"prettier": "3.8.1",
@@ -25,6 +26,7 @@
"@arethetypeswrong/core": "catalog:",
"@napi-rs/cli": "catalog:",
"@types/node": "catalog:",
+ "degit": "^2.8.4",
"execa": "^9.6.0",
"tsdown": "catalog:",
"vitest": "catalog:",
diff --git a/apps/oxfmt/scripts/download-prettier-fixtures.js b/apps/oxfmt/scripts/download-prettier-fixtures.js
new file mode 100644
index 0000000000000..6e8e6a0afcb5e
--- /dev/null
+++ b/apps/oxfmt/scripts/download-prettier-fixtures.js
@@ -0,0 +1,20 @@
+// oxlint-disable no-console
+
+import { execSync } from "node:child_process";
+import { rmSync } from "node:fs";
+import { join } from "node:path";
+import pkg from "../package.json" with { type: "json" };
+
+const version = pkg.dependencies.prettier;
+
+const root = join(import.meta.dirname, "..");
+const dest = join(root, "prettier-fixtures");
+
+rmSync(dest, { recursive: true, force: true });
+
+console.log(`Downloading prettier@${version} tests/format fixtures...`);
+execSync(`pnpm exec degit prettier/prettier/tests/format#${version} "${dest}"`, {
+ stdio: "inherit",
+ cwd: root,
+});
+console.log("Done!");
diff --git a/apps/oxfmt/test/api/js-in-xxx-conformance.test.ts b/apps/oxfmt/test/api/js-in-xxx-conformance.test.ts
new file mode 100644
index 0000000000000..35e676dfe36f7
--- /dev/null
+++ b/apps/oxfmt/test/api/js-in-xxx-conformance.test.ts
@@ -0,0 +1,107 @@
+import { existsSync, readdirSync, readFileSync } from "node:fs";
+import { join, relative } from "node:path";
+import prettier from "prettier";
+import { describe, expect, it } from "vitest";
+import { format } from "../../dist/index.js";
+
+// NOTE: Fixtures can be downloaded by `pnpm download-prettier-fixtures`
+const FIXTURES_DIR = join(import.meta.dirname, "../../prettier-fixtures");
+
+describe("Prettier conformance for .vue files", () => {
+ const vueFixtures = collectFixtures(".vue", [
+ "vue/range/example.vue",
+ "vue/multiparser/lang-tsx.vue",
+ ]);
+ vueFixtures.push(
+ {
+ name: "edge/vue-bindings-multiline-template-literal.vue",
+ content: `
+ {{ a }}
+
+`,
+ },
+ {
+ name: "edge/vue-for-multiline-template-literal.vue",
+ content: `
+ {{ item }} - {{ index }}
+
+`,
+ },
+ {
+ name: "edge/vue-script-generic-template-literal-type.vue",
+ content: `
+`,
+ },
+ );
+
+ describe.concurrent.each(vueFixtures)("$name", ({ name, content }) => {
+ it.each([
+ { printWidth: 80 },
+ {
+ printWidth: 100,
+ vueIndentScriptAndStyle: true,
+ singleQuote: true,
+ },
+ ])("%j", async (options) => {
+ const [oxfmtRes, prettierRes] = await compareWithPrettier(name, content, "vue", options);
+ expect(oxfmtRes).toBe(prettierRes);
+ });
+ });
+});
+
+// ---
+
+type TestCase = { name: string; content: string };
+
+function collectFixtures(ext: string, excludes: string[] = []): TestCase[] {
+ const dir = FIXTURES_DIR;
+ // NOTE: In CI, the fixtures might not be present, just skip and only run edge cases.
+ if (!existsSync(dir)) return [];
+
+ const results: TestCase[] = [];
+ for (const entry of readdirSync(dir, { withFileTypes: true, recursive: true })) {
+ if (!entry.isFile() || !entry.name.endsWith(ext)) continue;
+
+ const fullPath = join(entry.parentPath, entry.name);
+ const relPath = relative(dir, fullPath);
+ if (excludes.some((s) => relPath.includes(s))) continue;
+
+ results.push({ name: relPath, content: readFileSync(fullPath, "utf8") });
+ }
+
+ return results.sort((a, b) => a.name.localeCompare(b.name));
+}
+
+async function compareWithPrettier(
+ fileName: string,
+ content: string,
+ parser: string,
+ options = {},
+) {
+ let prettierResult;
+ try {
+ prettierResult = await prettier.format(content, {
+ parser,
+ filepath: fileName,
+ ...options,
+ });
+ } catch {
+ prettierResult = "ERROR";
+ }
+
+ let oxfmtResult;
+ const res = await format(fileName, content, options);
+ if (res.errors.length !== 0) {
+ oxfmtResult = "ERROR";
+ } else {
+ oxfmtResult = res.code;
+ }
+
+ return [oxfmtResult, prettierResult];
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fc4f9110874f5..52ad4d48aa5e1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,6 +78,9 @@ importers:
'@types/node':
specifier: 'catalog:'
version: 24.1.0
+ degit:
+ specifier: ^2.8.4
+ version: 2.8.4
execa:
specifier: ^9.6.0
version: 9.6.1