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: ` +`, + }, + { + name: "edge/vue-for-multiline-template-literal.vue", + content: ` +`, + }, + { + 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