refactor(ci): self-provision prettier suite in oxc_formatter_tests (#25467)

Remove the Prettier related stuff from the conformance, which is purely aimed at JS/TS.

Instead, `oxc_formatter_tests` manage Prettier's test fixtures along with Oxfmt.
This commit is contained in:
leaysgur
2026-08-12 00:40:59 +00:00
parent 6e1b4ddceb
commit 6ef3984410
17 changed files with 178 additions and 60 deletions
@@ -21,12 +21,6 @@ inputs:
type: boolean
description: Whether to clone typescript submodule
prettier:
default: true
required: false
type: boolean
description: Whether to clone prettier submodule
estree-conformance:
default: true
required: false
@@ -49,7 +43,6 @@ runs:
TEST_262: ${{ inputs.test262 }}
BABEL: ${{ inputs.babel }}
TYPESCRIPT: ${{ inputs.typescript }}
PRETTIER: ${{ inputs.prettier }}
ESTREE_CONFORMANCE: ${{ inputs.estree-conformance }}
NODE_COMPAT_TABLE: ${{ inputs.node-compat-table }}
run: |
@@ -57,6 +50,5 @@ runs:
"${TEST_262}" \
"${BABEL}" \
"${TYPESCRIPT}" \
"${PRETTIER}" \
"${ESTREE_CONFORMANCE}" \
"${NODE_COMPAT_TABLE}"
+3 -13
View File
@@ -2,7 +2,7 @@
// oxlint-disable no-console
// Clone submodules in parallel for faster setup
// Usage: node clone-parallel.mjs [test262] [babel] [typescript] [prettier] [estree-conformance] [node-compat-table]
// Usage: node clone-parallel.mjs [test262] [babel] [typescript] [estree-conformance] [node-compat-table]
// Arguments: "true" or "false" for each submodule
import { spawn } from "node:child_process";
@@ -15,8 +15,6 @@ const BABEL_SHA = "1eac4481473df52fbbcb452c4dca8d79039dbb63";
const TYPESCRIPT_SHA = "b465fdbfe175304d9b977da137b2c178ae1091d3";
const ESTREE_CONFORMANCE_SHA = "8d4e1faac9f350f914af9b8a6ec02c2d31ca799c";
const NODE_COMPAT_TABLE_SHA = "499beb6f1daa36f10c26b85a7f3ec3b3448ded23";
// NOTE: Prettier version is now pinned to `v3.9.6` (not updated by workflow above), update manually as needed
const PRETTIER_SHA = "8f0c95057cc91d5836409466cd9d9af3bb901e84";
const repoRoot = join(import.meta.dirname, "..", "..");
@@ -25,9 +23,8 @@ const args = process.argv.slice(2);
const TEST262 = args[0] !== "false";
const BABEL = args[1] !== "false";
const TYPESCRIPT = args[2] !== "false";
const PRETTIER = args[3] !== "false";
const ESTREE_CONFORMANCE = args[4] !== "false";
const NODE_COMPAT_TABLE = args[5] !== "false";
const ESTREE_CONFORMANCE = args[3] !== "false";
const NODE_COMPAT_TABLE = args[4] !== "false";
/**
* Run a git command and return a promise
@@ -130,13 +127,6 @@ async function main() {
TYPESCRIPT_SHA,
"typescript",
),
cloneRepo(
PRETTIER,
"prettier/prettier",
"tasks/prettier_conformance/prettier",
PRETTIER_SHA,
"prettier",
),
cloneRepo(
ESTREE_CONFORMANCE,
"oxc-project/estree-conformance",
+5 -5
View File
@@ -215,7 +215,6 @@ jobs:
if: steps.filter.outputs.changed == 'true'
with:
babel: false
prettier: false
node-compat-table: false
- if: steps.filter.outputs.changed == 'true'
name: Run tests
@@ -320,8 +319,8 @@ jobs:
- uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1
if: steps.filter-conformance.outputs.changed == 'true'
with:
# Cache `~/.degit` to reuse degit's tarball cache across CI runs
path: /home/runner/.degit
# Warm fixtures skip download entirely (`.version` stamps in download-fixtures.js)
path: ./apps/oxfmt/conformance/fixtures/externals
- if: steps.filter-conformance.outputs.changed == 'true'
name: Download conformance fixtures
run: pnpm --filter "./apps/oxfmt" download-fixtures
@@ -367,7 +366,6 @@ jobs:
if: steps.filter.outputs.changed == 'true'
with:
babel: false
prettier: false
node-compat-table: false
- if: steps.filter.outputs.changed == 'true'
name: Run tests
@@ -547,7 +545,9 @@ jobs:
- uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1
if: steps.filter.outputs.changed == 'true'
with:
path: /home/runner/.rustup
path: |
/home/runner/.rustup
./crates/oxc_formatter_tests/prettier
cache: |
rust
pnpm
-19
View File
@@ -1,4 +1,3 @@
# NOTE: Prettier version is now pinned to v3.7.0 (not updated by this workflow)
name: Update Submodules
permissions: {}
@@ -128,14 +127,12 @@ jobs:
TEST262_CURRENT=$(grep 'const TEST262_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
BABEL_CURRENT=$(grep 'const BABEL_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
TYPESCRIPT_CURRENT=$(grep 'const TYPESCRIPT_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
# PRETTIER_CURRENT=$(grep 'const PRETTIER_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
ESTREE_CONFORMANCE_CURRENT=$(grep 'const ESTREE_CONFORMANCE_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
NODE_COMPAT_TABLE_CURRENT=$(grep 'const NODE_COMPAT_TABLE_SHA' .github/scripts/clone-parallel.mjs | cut -d'"' -f2)
echo "TEST262_CURRENT=$TEST262_CURRENT" >> $GITHUB_OUTPUT
echo "BABEL_CURRENT=$BABEL_CURRENT" >> $GITHUB_OUTPUT
echo "TYPESCRIPT_CURRENT=$TYPESCRIPT_CURRENT" >> $GITHUB_OUTPUT
# echo "PRETTIER_CURRENT=$PRETTIER_CURRENT" >> $GITHUB_OUTPUT
echo "ESTREE_CONFORMANCE_CURRENT=$ESTREE_CONFORMANCE_CURRENT" >> $GITHUB_OUTPUT
echo "NODE_COMPAT_TABLE_CURRENT=$NODE_COMPAT_TABLE_CURRENT" >> $GITHUB_OUTPUT
@@ -143,7 +140,6 @@ jobs:
echo " TEST262: $TEST262_CURRENT"
echo " BABEL: $BABEL_CURRENT"
echo " TYPESCRIPT: $TYPESCRIPT_CURRENT"
# echo " PRETTIER: $PRETTIER_CURRENT"
echo " ESTREE_CONFORMANCE: $ESTREE_CONFORMANCE_CURRENT"
echo " NODE_COMPAT_TABLE: $NODE_COMPAT_TABLE_CURRENT"
@@ -156,8 +152,6 @@ jobs:
BABEL_CURRENT: ${{ steps.current-shas.outputs.BABEL_CURRENT }}
TYPESCRIPT_LATEST: ${{ steps.get-shas.outputs.TYPESCRIPT_LATEST }}
TYPESCRIPT_CURRENT: ${{ steps.current-shas.outputs.TYPESCRIPT_CURRENT }}
# PRETTIER_LATEST: ${{ steps.get-shas.outputs.PRETTIER_LATEST }}
# PRETTIER_CURRENT: ${{ steps.current-shas.outputs.PRETTIER_CURRENT }}
ESTREE_CONFORMANCE_LATEST: ${{ steps.get-shas.outputs.ESTREE_CONFORMANCE_LATEST }}
ESTREE_CONFORMANCE_CURRENT: ${{ steps.current-shas.outputs.ESTREE_CONFORMANCE_CURRENT }}
NODE_COMPAT_TABLE_LATEST: ${{ steps.get-shas.outputs.NODE_COMPAT_TABLE_LATEST }}
@@ -185,12 +179,6 @@ jobs:
echo "TYPESCRIPT needs update: $TYPESCRIPT_CURRENT -> $TYPESCRIPT_LATEST"
fi
# if [ "$PRETTIER_LATEST" != "$PRETTIER_CURRENT" ]; then
# updates_needed=true
# update_summary="${update_summary}- prettier: \`${PRETTIER_CURRENT:0:7}\` → \`${PRETTIER_LATEST:0:7}\`\n"
# echo "PRETTIER needs update: $PRETTIER_CURRENT -> $PRETTIER_LATEST"
# fi
if [ "$ESTREE_CONFORMANCE_LATEST" != "$ESTREE_CONFORMANCE_CURRENT" ]; then
updates_needed=true
update_summary="${update_summary}- estree-conformance: \`${ESTREE_CONFORMANCE_CURRENT:0:7}\` → \`${ESTREE_CONFORMANCE_LATEST:0:7}\`\n"
@@ -220,13 +208,11 @@ jobs:
TEST262_LATEST: ${{ steps.get-shas.outputs.TEST262_LATEST }}
BABEL_LATEST: ${{ steps.get-shas.outputs.BABEL_LATEST }}
TYPESCRIPT_LATEST: ${{ steps.get-shas.outputs.TYPESCRIPT_LATEST }}
# PRETTIER_LATEST: ${{ steps.get-shas.outputs.PRETTIER_LATEST }}
ESTREE_CONFORMANCE_LATEST: ${{ steps.get-shas.outputs.ESTREE_CONFORMANCE_LATEST }}
NODE_COMPAT_TABLE_LATEST: ${{ steps.get-shas.outputs.NODE_COMPAT_TABLE_LATEST }}
TEST262_CURRENT: ${{ steps.current-shas.outputs.TEST262_CURRENT }}
BABEL_CURRENT: ${{ steps.current-shas.outputs.BABEL_CURRENT }}
TYPESCRIPT_CURRENT: ${{ steps.current-shas.outputs.TYPESCRIPT_CURRENT }}
# PRETTIER_CURRENT: ${{ steps.current-shas.outputs.PRETTIER_CURRENT }}
ESTREE_CONFORMANCE_CURRENT: ${{ steps.current-shas.outputs.ESTREE_CONFORMANCE_CURRENT }}
NODE_COMPAT_TABLE_CURRENT: ${{ steps.current-shas.outputs.NODE_COMPAT_TABLE_CURRENT }}
run: |
@@ -248,11 +234,6 @@ jobs:
echo "Updated TYPESCRIPT_SHA"
fi
# if [ "$PRETTIER_LATEST" != "$PRETTIER_CURRENT" ]; then
# sed -i "s/const PRETTIER_SHA = \"$PRETTIER_CURRENT\";/const PRETTIER_SHA = \"$PRETTIER_LATEST\";/g" "$js_script"
# echo "Updated PRETTIER_SHA"
# fi
if [ "$ESTREE_CONFORMANCE_LATEST" != "$ESTREE_CONFORMANCE_CURRENT" ]; then
sed -i "s/const ESTREE_CONFORMANCE_SHA = \"$ESTREE_CONFORMANCE_CURRENT\";/const ESTREE_CONFORMANCE_SHA = \"$ESTREE_CONFORMANCE_LATEST\";/g" "$js_script"
echo "Updated ESTREE_CONFORMANCE_SHA"
+1 -1
View File
@@ -28,7 +28,7 @@ tasks/coverage/typescript/
tasks/coverage/estree-conformance/
tasks/coverage/estree-conformance-diff/
tasks/coverage/node-compat-table/
tasks/prettier_conformance/prettier/
crates/oxc_formatter_tests/prettier/
# Ignore accidental files from the root
/*.js
+1 -1
View File
@@ -6,7 +6,7 @@ target/**
tasks/coverage/test262/**
tasks/coverage/babel/**
tasks/coverage/typescript/**
tasks/prettier_conformance/prettier/**
crates/oxc_formatter_tests/prettier/**
apps/**/dist
**/*.snap
+1 -1
View File
@@ -10,6 +10,7 @@ extend-exclude = [
"apps/oxlint/src-js/package/config.generated.ts",
"apps/oxfmt/test/**/fixtures/**",
"apps/oxfmt/conformance/snapshots/diffs/**/",
"crates/oxc_formatter_tests/prettier",
"crates/oxc_linter/fixtures",
"crates/oxc_react_compiler/fixtures",
"crates/oxc_linter/src/rules/eslint/no_undefined.rs",
@@ -29,7 +30,6 @@ extend-exclude = [
"tasks/coverage/typescript",
"tasks/coverage/snapshots",
"tasks/prettier_conformance/jsdoc",
"tasks/prettier_conformance/prettier",
"tasks/prettier_conformance/snapshots",
"tasks/transform_conformance/tests/babel-plugin-transform-react-jsx/test/fixtures/text/escapes/input.jsx",
"tasks/transform_conformance/tests/**/output.js",
+1 -1
View File
@@ -20,6 +20,7 @@
// Paths are workspace-root relative. Globs are not supported, so each is listed explicitly.
"rust-analyzer.files.exclude": [
"apps/oxfmt/node_modules",
"crates/oxc_formatter_tests/prettier",
"apps/oxlint/conformance/submodules",
"apps/oxlint/node_modules",
"apps/shared/node_modules",
@@ -37,7 +38,6 @@
"tasks/coverage/babel",
"tasks/coverage/typescript",
"tasks/coverage/estree-conformance",
"tasks/prettier_conformance/prettier",
"tasks/transform_conformance/node_modules"
]
}
+8 -8
View File
@@ -250,13 +250,14 @@ just test-transform --filter <path> # Filter tests
Git submodules managed via `just submodules`:
| Submodule | Description | Location | Used by Crates |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------------------------- |
| `test262` | **ECMAScript Conformance Suite**<br>Official JavaScript test suite from TC39, testing compliance with the ECMAScript specification | `tasks/coverage/test262` | parser, semantic, codegen, transformer, minifier, estree |
| `babel` | **Babel Test Suite**<br>Comprehensive transformation and parsing tests from the Babel compiler, covering modern JavaScript features and edge cases | `tasks/coverage/babel` | parser, semantic, codegen, transformer, minifier |
| `typescript` | **TypeScript Test Suite**<br>Microsoft's TypeScript compiler tests, ensuring correct handling of TypeScript syntax and semantics | `tasks/coverage/typescript` | parser, semantic, codegen, transformer, estree |
| `prettier` | **Prettier Formatting Tests**<br>Prettier's comprehensive formatting test suite, ensuring code formatting matches industry standards | `tasks/prettier_conformance/prettier` | formatter (conformance) |
| `estree-conformance` | **ESTree Conformance Tests**<br>Test262, TypeScript, and acorn-jsx suites adapted for ESTree format validation, ensuring correct AST structure | `tasks/coverage/estree-conformance` | estree |
| Submodule | Description | Location | Used by Crates |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -------------------------------------------------------- |
| `test262` | **ECMAScript Conformance Suite**<br>Official JavaScript test suite from TC39, testing compliance with the ECMAScript specification | `tasks/coverage/test262` | parser, semantic, codegen, transformer, minifier, estree |
| `babel` | **Babel Test Suite**<br>Comprehensive transformation and parsing tests from the Babel compiler, covering modern JavaScript features and edge cases | `tasks/coverage/babel` | parser, semantic, codegen, transformer, minifier |
| `typescript` | **TypeScript Test Suite**<br>Microsoft's TypeScript compiler tests, ensuring correct handling of TypeScript syntax and semantics | `tasks/coverage/typescript` | parser, semantic, codegen, transformer, estree |
| `estree-conformance` | **ESTree Conformance Tests**<br>Test262, TypeScript, and acorn-jsx suites adapted for ESTree format validation, ensuring correct AST structure | `tasks/coverage/estree-conformance` | estree |
The **Prettier Formatting Tests** suite (`crates/oxc_formatter_tests/prettier`, used by formatter conformance) is NOT managed by `just submodules`: `oxc_formatter_tests` self-provisions it on demand, pinned by the `prettier` version in `apps/oxfmt/package.json` (the same Prettier oxfmt bundles as the oracle).
**These suites provide:**
@@ -274,7 +275,6 @@ These test suites are pre-cloned and ready to search:
- **Test262** (`tasks/coverage/test262/`) - ECMAScript spec compliance
- **Babel** (`tasks/coverage/babel/`) - Parsing and transformation edge cases
- **TypeScript** (`tasks/coverage/typescript/`) - TypeScript syntax and semantics
- **Prettier** (`tasks/prettier_conformance/prettier/`) - Formatting expectations
NOTE: These suites are script-cloned and fully gitignored. ripgrep respects `.gitignore`, so a plain `rg` inside them silently returns nothing.
Use `rg --no-ignore` (or `-u`) when searching them.
Generated
+1
View File
@@ -2452,6 +2452,7 @@ dependencies = [
"oxc_formatter_css",
"oxc_formatter_graphql",
"oxc_formatter_json",
"oxc_formatter_tests",
"oxc_formatter_yaml",
"oxc_parser",
"oxc_span",
+11 -1
View File
@@ -1,7 +1,7 @@
// oxlint-disable no-console, no-await-in-loop
import { exec } from "node:child_process";
import { rmSync } from "node:fs";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { promisify } from "node:util";
import pkg from "../package.json" with { type: "json" };
@@ -89,10 +89,20 @@ await Promise.all(
[...sourcesByRepo.values()].map(async (group) => {
for (const { name, repo, version } of group) {
const dest = join(externalsDir, name);
// Stamp-based skip (same scheme as `oxc_formatter_tests`' suite provisioning):
// the stamp is written last, so a half-downloaded tree is always re-done.
const stamp = join(dest, ".version");
const pin = `${repo}#${version}`;
if (existsSync(stamp) && readFileSync(stamp, "utf8").trim() === pin) {
console.log(`Up-to-date: ${name}@${version}`);
continue;
}
rmSync(dest, { recursive: true, force: true });
console.log(`Downloading ${name}@${version} fixtures...`);
await execAsync(`pnpm exec degit ${repo}#${version} "${dest}"`, { cwd });
writeFileSync(stamp, pin);
console.log(`Done: ${name}@${version}`);
}
}),
@@ -30,7 +30,7 @@ Each crate's `AGENTS.md` holds only language-specific rules and the crate-local
- Implementation strategies legitimately differ (e.g. Prettier pre-classifies comments per context; we decide on the spot with positional cursors)
- Compatibility is judged on bytes out, not code shape
- Before matching a mismatch, always consider whether it is a Prettier bug or artifact (see "Known divergences")
- The oracle version is the one pinned by the conformance submodule and `apps/oxfmt` bundles; fixtures are verified against it
- The oracle version is the `prettier` pinned in `apps/oxfmt/package.json`: the bundle, the conformance suite (via `oxc_formatter_tests`), and fixture verification all derive from that one version
- The LATEST Prettier is still worth consulting as a forward-looking aid: whether a bug we diverged on has been fixed upstream, or a behavior is about to change
- When the pin catches up to an upstream fix, converge and drop the divergence entry
+9
View File
@@ -4,6 +4,15 @@ Test infrastructure shared by the formatter crates (e.g. `oxc_formatter`, `oxc_f
- `codegen`: build-script helper — consumers call `generate_tests` from `build.rs` via `[build-dependencies]` to emit one `#[test]` per fixture file
- `harness`: fixture runtime — consumers implement `FixtureFormatter` in `tests/fixtures/mod.rs` via `[dev-dependencies]`
- `suite`: Prettier test-suite provisioning — `ensure_prettier_suite()` maintains the pinned suite at `prettier/` (gitignored), no separate clone step anywhere
## Prettier suite
The pin is the `prettier` version in `apps/oxfmt/package.json` — the same Prettier oxfmt bundles as the oracle, so suite and oracle cannot drift. Provisioning is degit-style: the release tarball from codeload, `tests/format/` extracted only (~40MB, no git objects), `.version` stamp written last. A warm suite is verified offline by reading the stamp; a stale one is wiped and re-extracted. Tarball extraction always yields LF content, so Windows needs no autocrlf care. CI only mounts `prettier/` as a cache volume — there is no clone step to keep in sync.
- Bumping Prettier = bump `apps/oxfmt/package.json` + regenerate conformance snapshots (one change set; the suite re-provisions itself on the next run)
- Fixture-test callers treat provisioning `Err` (offline, no curl/tar) as skip, not failure; the conformance runner fails loudly
- Requires `curl` and `tar` on PATH (standard on macOS, Linux, and Windows 10+)
## Dependency rule
+2
View File
@@ -9,9 +9,11 @@
mod codegen;
mod harness;
mod suite;
pub use codegen::{GenerateConfig, generate_tests};
pub use harness::{
FixtureFormatter, FixtureSnapshot, OptionSet, apply_core_options, build_fixture_snapshot,
format_options_display, resolve_options,
};
pub use suite::{ensure_prettier_suite, prettier_suite_root};
+129
View File
@@ -0,0 +1,129 @@
//! Prettier test-suite provisioning.
//!
//! The conformance tests compare output against the Prettier repository's own test suite (`tests/format`).
//! The pin is the `prettier` version in `apps/oxfmt/package.json`,
//! the same Prettier that oxfmt bundles as the oracle.
//! So the suite and the oracle cannot drift apart.
//! [`ensure_prettier_suite`] downloads the release tarball on demand (degit-style, no git objects, `tests/format` only),
//! so neither CI nor local runs need a separate clone step;
//! a warm checkout is verified offline.
//!
//! Bumping Prettier =
//! bumping `apps/oxfmt/package.json` + regenerating the conformance snapshots against it
//! (they must change together; the suite re-provisions itself).
use std::{
fs::{self, File},
path::Path,
process::Command,
sync::OnceLock,
};
/// The version pin. Also serves as the cross-process provisioning lock.
const PACKAGE_JSON: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../apps/oxfmt/package.json");
/// Root of the extracted Prettier suite (gitignored). Contains `tests/format`
/// plus a `.version` stamp written after a successful extraction.
#[must_use]
pub fn prettier_suite_root() -> &'static Path {
Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/prettier"))
}
/// Ensures the suite at [`prettier_suite_root`] matches the pinned Prettier version
/// and returns the root. Convergent:
///
/// - `.version` stamp matches the pin: return immediately — no network, no subprocess
/// - missing / stale (version bumped): wipe, download the release tarball from
/// codeload, extract `tests/format`, stamp
///
/// Cross-process exclusion (parallel test binaries, e.g. under nextest) uses an
/// advisory lock on the package.json handle; within a process the result is memoized.
///
/// # Errors
/// Any download/extraction failure, as a display string. Fixture-test callers skip on
/// `Err` so offline `cargo test` runs stay green; the conformance runner fails
/// loudly instead.
pub fn ensure_prettier_suite() -> Result<&'static Path, String> {
static RESULT: OnceLock<Result<(), String>> = OnceLock::new();
RESULT.get_or_init(provision).clone()?;
Ok(prettier_suite_root())
}
fn provision() -> Result<(), String> {
let root = prettier_suite_root();
let stamp = root.join(".version");
// Advisory lock; released when the handle drops. Locking the pin file itself
// spares a separate (gitignore-managed) lockfile, and the handle doubles as the
// single source of the pinned version.
let pkg_json = File::open(PACKAGE_JSON).map_err(|e| format!("open {PACKAGE_JSON}: {e}"))?;
pkg_json.lock().map_err(|e| format!("lock {PACKAGE_JSON}: {e}"))?;
let version = prettier_version(&pkg_json)?;
if fs::read_to_string(&stamp).is_ok_and(|s| s.trim() == version) {
return Ok(());
}
// Wipe-and-extract keeps this convergent; the stamp is written last, so a
// half-provisioned tree is always re-done. Only the CONTENTS are wiped: in CI
// the root is a cache-volume mount point, and removing it fails with EBUSY.
if root.exists() {
for entry in fs::read_dir(root).map_err(|e| format!("read {}: {e}", root.display()))? {
let path = entry.map_err(|e| format!("read {}: {e}", root.display()))?.path();
if path.is_dir() { fs::remove_dir_all(&path) } else { fs::remove_file(&path) }
.map_err(|e| format!("remove {}: {e}", path.display()))?;
}
} else {
fs::create_dir_all(root).map_err(|e| format!("create {}: {e}", root.display()))?;
}
let tarball = std::env::temp_dir().join(format!("oxc-prettier-{version}.tar.gz"));
let url = format!("https://codeload.github.com/prettier/prettier/tar.gz/refs/tags/{version}");
run("curl", &["-fsSL", "-o", &tarball.to_string_lossy(), &url], root)?;
// Extract only what the conformance tests read; drop the `prettier-<version>/` prefix.
run(
"tar",
&[
"-xzf",
&tarball.to_string_lossy(),
"--strip-components=1",
&format!("prettier-{version}/tests/format"),
],
root,
)?;
let _ = fs::remove_file(&tarball);
fs::write(&stamp, &version).map_err(|e| format!("write {}: {e}", stamp.display()))?;
Ok(())
}
/// Reads `dependencies.prettier` from the (already-opened) oxfmt package.json.
fn prettier_version(pkg_json: &File) -> Result<String, String> {
let json: serde_json::Value =
serde_json::from_reader(pkg_json).map_err(|e| format!("parse {PACKAGE_JSON}: {e}"))?;
let version = json["dependencies"]["prettier"]
.as_str()
.ok_or_else(|| format!("no dependencies.prettier in {PACKAGE_JSON}"))?;
// The tarball URL needs an exact tag; a semver range would mean the pin is gone.
if !version.chars().next().is_some_and(|c| c.is_ascii_digit()) {
return Err(format!("dependencies.prettier must be an exact version, got {version}"));
}
Ok(version.to_string())
}
fn run(program: &str, args: &[&str], cwd: &Path) -> Result<(), String> {
let output = Command::new(program)
.args(args)
.current_dir(cwd)
.output()
.map_err(|e| format!("spawn {program}: {e}"))?;
if output.status.success() {
Ok(())
} else {
Err(format!(
"{program} {}: {}",
args.join(" "),
String::from_utf8_lossy(&output.stderr).trim()
))
}
}
+1
View File
@@ -31,6 +31,7 @@ oxc_formatter_core = { workspace = true }
oxc_formatter_css = { workspace = true }
oxc_formatter_graphql = { workspace = true }
oxc_formatter_json = { workspace = true }
oxc_formatter_tests = { workspace = true }
oxc_formatter_yaml = { workspace = true }
oxc_parser = { workspace = true }
oxc_span = { workspace = true }
+4 -1
View File
@@ -50,7 +50,10 @@ fn root() -> PathBuf {
}
fn fixtures_root() -> PathBuf {
root().join("prettier").join("tests").join("format")
oxc_formatter_tests::ensure_prettier_suite()
.expect("failed to provision the Prettier suite")
.join("tests")
.join("format")
}
fn snap_root() -> PathBuf {