mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
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:
@@ -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}"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Vendored
+1
-1
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
@@ -2452,6 +2452,7 @@ dependencies = [
|
||||
"oxc_formatter_css",
|
||||
"oxc_formatter_graphql",
|
||||
"oxc_formatter_json",
|
||||
"oxc_formatter_tests",
|
||||
"oxc_formatter_yaml",
|
||||
"oxc_parser",
|
||||
"oxc_span",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user