mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
test(oxfmt): track classified divergences (#28533)
Finalize on the stack of changes: classifying the divergences and cleaning up.
This commit is contained in:
1 parent
211126a771
commit
e3f90b1f80
22 files changed
+1115
-582
No files matched your search
@@ -346,7 +346,7 @@ jobs:
|
||||
id: conformance-fixtures
|
||||
if: steps.filter-conformance.outputs.changed == 'true'
|
||||
with:
|
||||
# Warm fixtures skip download entirely (`.version` stamps in download-fixtures.js)
|
||||
# Warm fixtures skip download entirely (`.version` stamps in download-fixtures.ts)
|
||||
path: ./apps/oxfmt/conformance/fixtures/externals
|
||||
background: true
|
||||
- if: steps.filter.outputs.changed == 'true' || steps.filter-conformance.outputs.changed == 'true'
|
||||
|
||||
+4
-11
@@ -81,22 +81,15 @@ Known divergences live in DIVERGENCES.md (embedding / dispatch layer only); sing
|
||||
- The pin is a minimal repro in `conformance/fixtures/edge-cases/`
|
||||
- `conformance/run_prettier.ts` compares Prettier's own test suite with its committed jest snapshots, so Prettier never runs
|
||||
- Its `PARSERS` table maps a Prettier parser to the filename oxfmt routes to that language, parsers not listed are not run
|
||||
- Each input runs under its spec's own options only
|
||||
- Each input runs under its spec's own options only, and is also checked for idempotency and its CRLF / CR / BOM variants
|
||||
- `conformance/run_live.ts` compares with Prettier run on the spot
|
||||
- A category's `projects` (real-world repositories) are compared on Prettier's output of each file, the code a Prettier user hands over,
|
||||
so the divergences that keep what was written do not fire in bulk; its `fixtures` stay as written, raw shapes are their job
|
||||
- `conformance/run_live.ts` derives the file-to-entry map from every backticked `externals/` / `edge-cases/` path in every `DIVERGENCES.md`, prints the ref next to each failure,
|
||||
and warns on a failure no entry lists (unclassified) and on a listed file that passes (stale)
|
||||
- Both runners name a file as its report prints it (its path under `conformance/fixtures/`, `<dir>/snippet: <title>` for a snippet), as `DIVERGENCES.md` lists it in `Pin:` / `Conformance:` lines (`conformance/shared.ts`), and fail on a listed path that passes (stale)
|
||||
- Both reports list the entries under each failing file, `❓unclassified` when none does (those files first); `run_live.ts` also warns on an unclassified one
|
||||
- All current entries are embedding-layer decisions that outlive the Prettier delegation;
|
||||
if an entry about the Prettier-fallback boundary itself is ever added, group it under a separate heading in DIVERGENCES.md so the removal scope stays obvious when the fallback goes away
|
||||
|
||||
Conformance failures (`conformance/snapshots/`) no entry accounts for, to fix rather than admit:
|
||||
|
||||
- `jsdoc`: `externals/svelte/compiler/print/index.js`
|
||||
- `if (!(a && b))` hugs `!(` to the head paren since prettier/prettier#18401, not ported yet (Prettier conformance `js/if/condition-break/unary-expression.js`; unrelated to JSDoc)
|
||||
- `astro`: `externals/plugin-astro/other/prettier-ignore-js/input.astro`
|
||||
- A `// prettier-ignore`d expression statement without `;` gets `;` appended (`oxc_formatter` itself, reproducible in plain `.ts`; unrelated to Astro)
|
||||
|
||||
### Embedded language formatting
|
||||
|
||||
Embedded languages (e.g. css-in-js, CSS front matter YAML) go through the `FormatDispatcher` (defined in `oxc_formatter_core`) assembled by `src/core/embed/dispatcher.rs`.
|
||||
@@ -115,7 +108,7 @@ The pure Rust build runs fallback-less, so non-native embeds (angular-in-js, cus
|
||||
The roots install `SessionServices`, all via `embed/services.rs::for_root` (one definition per build; the napi one takes the `ExternalServices` transport, and adds the Prettier fallback / string embedder / Tailwind sorter to the registry dispatcher): the JS/TS, CSS, Markdown and HTML file roots (`core/format.rs`, `PhysicalFile` sessions, both builds) and the Vue/Svelte/Astro `<script>` and Astro frontmatter root (`api/text_to_doc_api.rs`, `VirtualDocument` session, napi only).
|
||||
|
||||
Which languages may dispatch AT ALL from a given host is the host crate's own gate (e.g. `oxc_formatter_css` dispatches only `yaml`/`toml` front matter, `oxc_formatter_markdown` every fenced code block's language); the shared `route_embedded()` table then decides who serves the language.
|
||||
Adding a dispatch call to a host crate is therefore a routing decision (check `route_embedded()` and the embedded conformance when doing so. A root needing a bespoke service set would assemble the `SessionServices` struct literally), none does today.
|
||||
Adding a dispatch call to a host crate is therefore a routing decision (check `route_embedded()` and the xxx-in-yyy categories of `conformance:live` when doing so. A root needing a bespoke service set would assemble the `SessionServices` struct literally), none does today.
|
||||
`embeddedLanguageFormatting: off` installs no dispatcher, every builder consults the same off-gate, `ResolvedDispatchConfig::is_embedded_formatting_enabled`.
|
||||
Tracing span namespaces: `oxfmt::embed::` = pure Rust work, `oxfmt::external::` = napi-crossing calls.
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ Admission reasons and rules: see `crates/oxc_formatter_core/FORMATTER_POLICY.md`
|
||||
|
||||
- Why: uniform-rule (embedded script formats like its standalone file)
|
||||
- Pin: `conformance/fixtures/edge-cases/js-in-vue/generic-trailing-comma.vue`
|
||||
- Conformance: `externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue`
|
||||
- Conformance:
|
||||
- `externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue`
|
||||
|
||||
```vue
|
||||
<!-- input -->
|
||||
@@ -33,7 +34,8 @@ in ts-in-md and plain `.ts` — one rule over that internal inconsistency, the s
|
||||
|
||||
- Why: cost
|
||||
- Pin: `conformance/fixtures/edge-cases/css-in-js/styled-extend-tag.js`
|
||||
- Conformance: `js/multiparser-css/styled-components.js`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/multiparser-css/styled-components.js`
|
||||
|
||||
```js
|
||||
/* input */
|
||||
@@ -124,7 +126,8 @@ Under `embeddedLanguageFormatting: off`, the source shape still decides, like Pr
|
||||
|
||||
- Why: semantics (prettier/prettier#19908)
|
||||
- Pin: `conformance/fixtures/edge-cases/xxx-in-md/nested-fence-length.md`
|
||||
- Conformance: `js/multiparser-markdown/codeblock.js`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/multiparser-markdown/codeblock.js`
|
||||
|
||||
`````markdown
|
||||
<!-- input -->
|
||||
@@ -193,7 +196,8 @@ Prettier formats it when a space separates the language, and keeps `js{1,3}` as
|
||||
|
||||
- Why: uniform-rule (code fence names follow Shiki's vocabulary)
|
||||
- Pin: `conformance/fixtures/edge-cases/xxx-in-md/lwc-code-block.md`
|
||||
- Conformance: `markdown/code/lwc/lwc.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/code/lwc/lwc.md`
|
||||
|
||||
````markdown
|
||||
<!-- input -->
|
||||
@@ -249,7 +253,10 @@ Prettier keeps `json` / `json5` blocks as written: their parser rejects a blank
|
||||
|
||||
- Why: cost
|
||||
- Pin: `conformance/fixtures/edge-cases/astro/style-lang-sass.astro`
|
||||
- Conformance: `externals/plugin-astro/styles/with-sass/input.astro`, `externals/plugin-astro/styles/with-indented-sass/input.astro`, `externals/plugin-astro/styles/format-nested-sass-style-tag-content/input.astro`
|
||||
- Conformance:
|
||||
- `externals/plugin-astro/styles/with-sass/input.astro`
|
||||
- `externals/plugin-astro/styles/with-indented-sass/input.astro`
|
||||
- `externals/plugin-astro/styles/format-nested-sass-style-tag-content/input.astro`
|
||||
|
||||
```astro
|
||||
<!-- input -->
|
||||
@@ -374,7 +381,9 @@ Prettier looks for the placeholder as it wrote it, so the lowercased one stays i
|
||||
|
||||
- Why: uniform-rule (same construct, same output: an expression outside `pre`-like content, whose lines indent from the line its `${` starts on; prettier/prettier#20355)
|
||||
- Pin: `conformance/fixtures/edge-cases/html-in-js/pre-like-template-expression.js`
|
||||
- Conformance: `edge-cases/html-in-js/template-expression-indent.js`, `js/embeded/indention/19518.js`
|
||||
- Conformance:
|
||||
- `edge-cases/html-in-js/template-expression-indent.js`
|
||||
- `externals/prettier/js/embeded/indention/19518.js`
|
||||
|
||||
```js
|
||||
// input
|
||||
@@ -420,7 +429,8 @@ Reformatting the expression changes no content: its source is code, not text in
|
||||
|
||||
- Why: uniform-rule (same construct, same output: an element htm's `<//>` closes, which Prettier formats; prettier/prettier#17849)
|
||||
- Pin: `conformance/fixtures/edge-cases/html-in-js/dynamic-tag-name.js`
|
||||
- Conformance: `externals/webawesome/button/button.ts`
|
||||
- Conformance:
|
||||
- `externals/webawesome/button/button.ts`
|
||||
|
||||
```js
|
||||
// input
|
||||
|
||||
+58
-41
@@ -14,7 +14,7 @@ const execAsync = promisify(exec);
|
||||
const externalsDir = join(import.meta.dirname, "fixtures", "externals");
|
||||
|
||||
// `repo` is `<owner>/<name>` plus the directory to take, `version` is any ref (a tag or a commit)
|
||||
const sources = [
|
||||
export const sources = [
|
||||
// Prettier's own suite (`run_prettier.ts`)
|
||||
{
|
||||
name: "prettier",
|
||||
@@ -118,54 +118,71 @@ const sources = [
|
||||
},
|
||||
];
|
||||
|
||||
// Names given as arguments (e.g. `prettier`) narrow the download, none means every source
|
||||
const names = process.argv.slice(2);
|
||||
type Source = (typeof sources)[number];
|
||||
|
||||
// Group sources by archive, so an archive shared by several sources downloads once.
|
||||
const sourcesByArchive = Map.groupBy(
|
||||
names.length === 0 ? sources : sources.filter(({ name }) => names.includes(name)),
|
||||
({ repo, version }) => `${repo.split("/").slice(0, 2).join("/")}#${version}`,
|
||||
);
|
||||
/** By its stamp, written last, so a half-downloaded tree never counts. */
|
||||
const isDownloaded = ({ name, repo, version }: Source) => {
|
||||
const stamp = join(externalsDir, name, ".version");
|
||||
return existsSync(stamp) && readFileSync(stamp, "utf8").trim() === `${repo}#${version}`;
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
[...sourcesByArchive.values()].map(async (group) => {
|
||||
// Stamp-based skip: the stamp is written last, so a half-downloaded tree is always re-done.
|
||||
const stale = group.filter(({ name, repo, version }) => {
|
||||
const stamp = join(externalsDir, name, ".version");
|
||||
const upToDate =
|
||||
existsSync(stamp) && readFileSync(stamp, "utf8").trim() === `${repo}#${version}`;
|
||||
if (upToDate) console.log(`Up-to-date: ${name}@${version}`);
|
||||
return !upToDate;
|
||||
});
|
||||
if (stale.length === 0) return;
|
||||
/** Throws unless every source in `names` is downloaded at its pinned version, a runner reading an older one reports wrong. */
|
||||
export function assertDownloaded(names: string[]) {
|
||||
const stale = sources.filter((source) => names.includes(source.name) && !isDownloaded(source));
|
||||
if (stale.length > 0) {
|
||||
const list = stale.map(({ name, version }) => `${name}@${version}`).join(", ");
|
||||
throw new Error(`Not downloaded: ${list}, run \`download-fixtures\``);
|
||||
}
|
||||
}
|
||||
|
||||
const [owner, repoName] = group[0].repo.split("/");
|
||||
const { version } = group[0];
|
||||
console.log(`Downloading ${owner}/${repoName}@${version}...`);
|
||||
const tmp = mkdtempSync(join(tmpdir(), "oxfmt-fixtures-"));
|
||||
const tarball = join(tmp, "archive.tar.gz");
|
||||
await execAsync(
|
||||
`curl -fsSL -o "${tarball}" https://codeload.github.com/${owner}/${repoName}/tar.gz/${version}`,
|
||||
);
|
||||
const top = await topDirectory(tarball);
|
||||
// @ts-expect-error -- `import.meta.main` is newer than the `@types/node` (24.1) here
|
||||
if (import.meta.main) {
|
||||
// Names given as arguments (e.g. `prettier`) narrow the download, none means every source
|
||||
const names = process.argv.slice(2);
|
||||
|
||||
for (const { name, repo } of stale) {
|
||||
const dest = join(externalsDir, name);
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
mkdirSync(dest, { recursive: true });
|
||||
const subdir = repo.split("/").slice(2);
|
||||
// Group sources by archive, so an archive shared by several sources downloads once.
|
||||
const sourcesByArchive = Map.groupBy(
|
||||
names.length === 0 ? sources : sources.filter(({ name }) => names.includes(name)),
|
||||
({ repo, version }) => `${repo.split("/").slice(0, 2).join("/")}#${version}`,
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
[...sourcesByArchive.values()].map(async (group) => {
|
||||
const stale = group.filter((source) => {
|
||||
const upToDate = isDownloaded(source);
|
||||
if (upToDate) console.log(`Up-to-date: ${source.name}@${source.version}`);
|
||||
return !upToDate;
|
||||
});
|
||||
if (stale.length === 0) return;
|
||||
|
||||
const [owner, repoName] = group[0].repo.split("/");
|
||||
const { version } = group[0];
|
||||
console.log(`Downloading ${owner}/${repoName}@${version}...`);
|
||||
const tmp = mkdtempSync(join(tmpdir(), "oxfmt-fixtures-"));
|
||||
const tarball = join(tmp, "archive.tar.gz");
|
||||
await execAsync(
|
||||
`tar -xzf "${tarball}" -C "${dest}" --strip-components=${subdir.length + 1} "${[top, ...subdir].join("/")}"`,
|
||||
`curl -fsSL -o "${tarball}" https://codeload.github.com/${owner}/${repoName}/tar.gz/${version}`,
|
||||
);
|
||||
writeFileSync(join(dest, ".version"), `${repo}#${version}`);
|
||||
console.log(`Done: ${name}@${version}`);
|
||||
}
|
||||
rmSync(tmp, { recursive: true });
|
||||
}),
|
||||
);
|
||||
const top = await topDirectory(tarball);
|
||||
|
||||
for (const { name, repo } of stale) {
|
||||
const dest = join(externalsDir, name);
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
mkdirSync(dest, { recursive: true });
|
||||
const subdir = repo.split("/").slice(2);
|
||||
await execAsync(
|
||||
`tar -xzf "${tarball}" -C "${dest}" --strip-components=${subdir.length + 1} "${[top, ...subdir].join("/")}"`,
|
||||
);
|
||||
writeFileSync(join(dest, ".version"), `${repo}#${version}`);
|
||||
console.log(`Done: ${name}@${version}`);
|
||||
}
|
||||
rmSync(tmp, { recursive: true });
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** The archive's top directory, GitHub's `<name>-<ref>` with the ref normalized (e.g. no leading `v`). */
|
||||
async function topDirectory(tarball) {
|
||||
async function topDirectory(tarball: string) {
|
||||
const tar = spawn("tar", ["-tzf", tarball]);
|
||||
const [line] = await once(createInterface({ input: tar.stdout }), "line");
|
||||
tar.kill();
|
||||
@@ -1,29 +1,24 @@
|
||||
// oxlint-disable no-console, no-await-in-loop
|
||||
|
||||
import { createTwoFilesPatch } from "diff";
|
||||
import {
|
||||
existsSync,
|
||||
globSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join, relative } from "node:path";
|
||||
import prettier from "prettier";
|
||||
import * as astroPlugin from "prettier-plugin-astro";
|
||||
import * as sveltePlugin from "prettier-plugin-svelte";
|
||||
import { format } from "../dist/index.js";
|
||||
import { assertDownloaded, sources } from "./download-fixtures.ts";
|
||||
import {
|
||||
collectDivergences,
|
||||
type Divergence,
|
||||
divergencesOf,
|
||||
listDivergences,
|
||||
FIXTURES_DIR,
|
||||
SNAPSHOTS_DIR,
|
||||
checkStaleDivergences,
|
||||
} from "./shared.ts";
|
||||
|
||||
const CONFORMANCE_DIR = import.meta.dirname;
|
||||
const FIXTURES_DIR = join(CONFORMANCE_DIR, "fixtures");
|
||||
const EXTERNALS_DIR = join(FIXTURES_DIR, "externals");
|
||||
const SNAPSHOTS_DIR = join(CONFORMANCE_DIR, "snapshots");
|
||||
const REPO_ROOT = join(CONFORMANCE_DIR, "..", "..", "..");
|
||||
const DIVERGENCES_FILES = globSync("{apps/oxfmt,crates/oxc_formatter*}/DIVERGENCES.md", {
|
||||
cwd: REPO_ROOT,
|
||||
});
|
||||
|
||||
type Category = {
|
||||
name: string;
|
||||
@@ -229,6 +224,7 @@ const categories: Category[] = [
|
||||
|
||||
// ---
|
||||
|
||||
assertDownloaded(sources.map(({ name }) => name).filter((name) => name !== "prettier"));
|
||||
const divergences = collectDivergences();
|
||||
const results: CategoryResult[] = [];
|
||||
|
||||
@@ -255,39 +251,20 @@ for (const category of categories) {
|
||||
|
||||
writeReport(results, divergences);
|
||||
|
||||
const failedNames = new Set(
|
||||
results.flatMap((r) => r.optionSetResults.flatMap((o) => o.failures.map((f) => f.name))),
|
||||
);
|
||||
const failedNames = [
|
||||
...new Set(
|
||||
results.flatMap((r) => r.optionSetResults.flatMap((o) => o.failures.map((f) => f.name))),
|
||||
),
|
||||
];
|
||||
for (const name of failedNames) {
|
||||
if (!divergences.has(name)) {
|
||||
if (divergencesOf(divergences, name).length === 0) {
|
||||
console.warn(`WARNING: "${name}" fails and no DIVERGENCES.md entry lists it (unclassified)`);
|
||||
}
|
||||
}
|
||||
for (const [name, refs] of divergences) {
|
||||
if (!failedNames.has(name)) {
|
||||
console.warn(`WARNING: "${name}" passes but ${refs.join(", ")} lists it, remove it?`);
|
||||
}
|
||||
}
|
||||
checkStaleDivergences(divergences, failedNames, { prettierSuite: false });
|
||||
|
||||
// ---
|
||||
|
||||
/** Fixture name -> `<file>#<slug>` for every backticked `externals/` / `edge-cases/` path in a DIVERGENCES.md entry. */
|
||||
function collectDivergences(): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
for (const file of DIVERGENCES_FILES) {
|
||||
let slug = "";
|
||||
for (const line of readFileSync(join(REPO_ROOT, file), "utf8").split("\n")) {
|
||||
if (line.startsWith("## ")) slug = line.slice(3).trim();
|
||||
for (const [, name] of line.matchAll(
|
||||
/`(?:conformance\/fixtures\/)?((?:externals|edge-cases)\/[^`]+)`/g,
|
||||
)) {
|
||||
map.set(name, [...(map.get(name) ?? []), `${file}#${slug}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
type Fixture = { name: string; fullPath: string; isProject: boolean };
|
||||
|
||||
type Failure = {
|
||||
@@ -400,7 +377,7 @@ async function formatWithOxfmt(
|
||||
return res.errors.length === 0 ? res.code : "ERROR";
|
||||
}
|
||||
|
||||
function writeReport(results: CategoryResult[], divergences: Map<string, string[]>) {
|
||||
function writeReport(results: CategoryResult[], divergences: Divergence[]) {
|
||||
const lines: string[] = [];
|
||||
const diffsDir = join(SNAPSHOTS_DIR, "diffs");
|
||||
|
||||
@@ -451,14 +428,11 @@ function writeReport(results: CategoryResult[], divergences: Map<string, string[
|
||||
lines.push("");
|
||||
|
||||
if (r.failures.length > 0) {
|
||||
for (const failure of r.failures) {
|
||||
const safeName = failure.name.replaceAll("/", "__");
|
||||
const diffRelPath = `diffs/${result.name}/${safeName}.md`;
|
||||
const refs = divergences.get(failure.name)?.join(", ") ?? "unclassified";
|
||||
lines.push(`- [${failure.name}](${diffRelPath})`);
|
||||
lines.push(` - ${refs}`);
|
||||
}
|
||||
lines.push("");
|
||||
const items = r.failures.map(({ name }): [string, string] => {
|
||||
const diffRelPath = `diffs/${result.name}/${name.replaceAll("/", "__")}.md`;
|
||||
return [name, `- [${name}](${diffRelPath})`];
|
||||
});
|
||||
lines.push(...listDivergences(divergences, items), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,22 @@ import { globSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, extname, join, relative } from "node:path";
|
||||
import { parseArgs } from "node:util";
|
||||
import { format, type Language } from "../dist/index.js";
|
||||
import { assertDownloaded } from "./download-fixtures.ts";
|
||||
import {
|
||||
PRETTIER_SUITE,
|
||||
FIXTURES_DIR,
|
||||
SNAPSHOTS_DIR,
|
||||
collectDivergences,
|
||||
type Divergence,
|
||||
listDivergences,
|
||||
checkStaleDivergences,
|
||||
} from "./shared.ts";
|
||||
|
||||
// Compares oxfmt with the committed outputs of Prettier's own test suite (`tests/format/**/__snapshots__`),
|
||||
// so Prettier itself never runs here.
|
||||
// `--filter <substring>` runs only the matching paths and prints their diffs instead of writing the report.
|
||||
|
||||
const SUITE_DIR = join(import.meta.dirname, "fixtures", "externals", "prettier");
|
||||
const SNAPSHOTS_DIR = join(import.meta.dirname, "snapshots");
|
||||
const SUITE_DIR = join(FIXTURES_DIR, PRETTIER_SUITE);
|
||||
|
||||
/** Extension of the filename oxfmt is given → its language (the `associations` name), in the report's order */
|
||||
const LANGUAGES: Record<string, Language> = {
|
||||
@@ -46,6 +55,7 @@ const jsLike = (ext: string) => (name: string) =>
|
||||
/**
|
||||
* Prettier parser → the filename oxfmt routes to that language.
|
||||
* A section runs once per distinct filename of its listed `parsers`, unlisted parsers are not run.
|
||||
* A section listing none (Prettier infers it from the filepath, `misc/parser-inference/`) is not run either.
|
||||
*/
|
||||
const PARSERS: Record<string, (name: string) => string> = {
|
||||
babel: jsLike(".js"),
|
||||
@@ -155,6 +165,10 @@ const IGNORE = [
|
||||
"typescript/tsx/comma/snippet: test.js",
|
||||
"typescript/tsx/comma/snippet: test.cjs",
|
||||
"typescript/tsx/comma/snippet: test.mjs",
|
||||
// No known filepath for Prettier, which keeps `<T,>` and the doctype case, but run here as `.ts` / `.html`
|
||||
"typescript/tsx/comma/snippet: test.unknown",
|
||||
"typescript/tsx/comma/snippet: unnamed",
|
||||
"misc/filename-matters/html-doctype/snippet: #0",
|
||||
"js/babel-plugins/typescript.js",
|
||||
// Ambiguous await
|
||||
"js/top-level-await",
|
||||
@@ -179,9 +193,9 @@ const IGNORE = [
|
||||
"css/atrule/if-else.css",
|
||||
|
||||
// Depends on Prettier's test-only plugins (`tests/config/prettier-plugins/`)
|
||||
"vue/with-plugins/",
|
||||
"misc/front-matter/with-plugins/",
|
||||
"misc/plugins/",
|
||||
"vue/with-plugins/",
|
||||
];
|
||||
|
||||
/** Sections not run, by their options */
|
||||
@@ -190,6 +204,8 @@ const skipSection = (options: Options) =>
|
||||
"cursorOffset" in options
|
||||
// Rejected by oxfmt's config, not supported yet
|
||||
|| options.experimentalTernaries === true
|
||||
// Deprecated alias of `bracketSameLine`, which oxfmt's config does not take
|
||||
|| "jsxBracketSameLine" in options
|
||||
// Beyond what oxfmt's config holds (a `u8`), like `printWidth: Infinity` in `parseSection()`
|
||||
|| (typeof options.tabWidth === "number" && options.tabWidth > 255)
|
||||
// No pragma options in oxfmt
|
||||
@@ -226,6 +242,8 @@ type Row = {
|
||||
};
|
||||
|
||||
const { filter } = parseArgs({ options: { filter: { type: "string" } } }).values;
|
||||
|
||||
assertDownloaded(["prettier"]);
|
||||
const rowsByLanguage = new Map<Language, Map<string, Row>>();
|
||||
|
||||
for (const snapPath of globSync("**/__snapshots__/format.test.js.snap", {
|
||||
@@ -249,7 +267,7 @@ for (const snapPath of globSync("**/__snapshots__/format.test.js.snap", {
|
||||
const name = title.replace(/ - \{.*\}$/, "");
|
||||
const path = `${dir}/${name}`;
|
||||
if (IGNORE.some((ignore) => path.startsWith(ignore))) continue;
|
||||
if (filter && !path.includes(filter)) continue;
|
||||
if (filter && !(PRETTIER_SUITE + path).includes(filter)) continue;
|
||||
|
||||
const { parsers, options, expected } = section;
|
||||
const input = name.startsWith("snippet: ")
|
||||
@@ -274,7 +292,9 @@ for (const snapPath of globSync("**/__snapshots__/format.test.js.snap", {
|
||||
const language = LANGUAGES[extname(filename)];
|
||||
const rows = rowsByLanguage.get(language) ?? new Map<string, Row>();
|
||||
rowsByLanguage.set(language, rows);
|
||||
const row = rows.get(path) ?? {
|
||||
// Named by its fixture path, as DIVERGENCES.md lists it
|
||||
const fixture = PRETTIER_SUITE + path;
|
||||
const row = rows.get(fixture) ?? {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
@@ -283,7 +303,7 @@ for (const snapPath of globSync("**/__snapshots__/format.test.js.snap", {
|
||||
nonIdempotent: null,
|
||||
variants: new Set(),
|
||||
};
|
||||
rows.set(path, row);
|
||||
rows.set(fixture, row);
|
||||
|
||||
row.total++;
|
||||
if (result.error !== undefined) {
|
||||
@@ -299,7 +319,10 @@ for (const snapPath of globSync("**/__snapshots__/format.test.js.snap", {
|
||||
}
|
||||
}
|
||||
|
||||
if (!filter) writeReport();
|
||||
if (!filter) {
|
||||
const divergences = collectDivergences();
|
||||
checkStaleDivergences(divergences, writeReport(divergences), { prettierSuite: true });
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
@@ -425,7 +448,9 @@ function printResult(
|
||||
console.log();
|
||||
}
|
||||
|
||||
function writeReport() {
|
||||
/** Returns the rows given `listDivergences` lines, all a DIVERGENCES.md entry may list. */
|
||||
function writeReport(divergences: Divergence[]) {
|
||||
const listable: string[] = [];
|
||||
const lines: string[] = [];
|
||||
for (const language of new Set(Object.values(LANGUAGES))) {
|
||||
const rows = rowsByLanguage.get(language);
|
||||
@@ -441,19 +466,16 @@ function writeReport() {
|
||||
console.log(`[${language}] ${summary}`);
|
||||
lines.push(`## ${language}`, "", summary, "");
|
||||
|
||||
const failed = sorted.filter(([, row]) => row.failed > 0);
|
||||
if (failed.length > 0) {
|
||||
lines.push("### Failed", "");
|
||||
lines.push("| Spec path | Failed or Passed | Match ratio |");
|
||||
lines.push("| :-------- | :--------------: | :---------: |");
|
||||
for (const [path, row] of failed) {
|
||||
const marks = "💥".repeat(row.failed) + "✨".repeat(row.passed);
|
||||
lines.push(`| ${path} | ${marks} | ${((row.ratio / row.total) * 100).toFixed(2)}% |`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
const lists: [string, (row: Row) => string | null][] = [
|
||||
const lists: [string, (row: Row) => string | null, withRefs?: boolean][] = [
|
||||
[
|
||||
"Failed",
|
||||
(r) => {
|
||||
if (r.failed === 0) return null;
|
||||
const marks = "💥".repeat(r.failed) + "✨".repeat(r.passed);
|
||||
return ` (${marks}, ${((r.ratio / r.total) * 100).toFixed(2)}%)`;
|
||||
},
|
||||
true,
|
||||
],
|
||||
[
|
||||
"Not idempotent",
|
||||
(r) => (r.nonIdempotent === null ? null : r.nonIdempotent ? " (second pass errored)" : ""),
|
||||
@@ -462,14 +484,19 @@ function writeReport() {
|
||||
"Input variants differ",
|
||||
(r) => (r.variants.size > 0 ? ` (${[...r.variants].join(", ")})` : null),
|
||||
],
|
||||
["Error", (r) => (r.error === null ? null : `: ${r.error}`)],
|
||||
["Error", (r) => (r.error === null ? null : `: ${r.error}`), true],
|
||||
];
|
||||
for (const [heading, note] of lists) {
|
||||
const entries = sorted.flatMap(([path, row]) => {
|
||||
for (const [heading, note, withRefs] of lists) {
|
||||
const items = sorted.flatMap(([path, row]): [string, string][] => {
|
||||
const text = note(row);
|
||||
return text === null ? [] : [`- ${path}${text}`];
|
||||
return text === null ? [] : [[path, `- ${path}${text}`]];
|
||||
});
|
||||
if (entries.length > 0) lines.push(`### ${heading}`, "", ...entries, "");
|
||||
if (items.length === 0) continue;
|
||||
if (withRefs) listable.push(...items.map(([path]) => path));
|
||||
const entries = withRefs
|
||||
? listDivergences(divergences, items)
|
||||
: items.map(([, line]) => line);
|
||||
lines.push(`### ${heading}`, "", ...entries, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,4 +504,5 @@ function writeReport() {
|
||||
const outPath = join(SNAPSHOTS_DIR, "prettier.snap.md");
|
||||
writeFileSync(outPath, lines.join("\n"));
|
||||
console.log(`Report written to ${relative(process.cwd(), outPath)}`);
|
||||
return listable;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// oxlint-disable no-console
|
||||
|
||||
import { globSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
// Shared by `run_prettier.ts` and `run_live.ts`.
|
||||
|
||||
const REPO_ROOT = join(import.meta.dirname, "..", "..", "..");
|
||||
export const FIXTURES_DIR = join(import.meta.dirname, "fixtures");
|
||||
export const SNAPSHOTS_DIR = join(import.meta.dirname, "snapshots");
|
||||
|
||||
/** Prettier's own suite, the `run_prettier.ts` part of the fixture paths (relative to `FIXTURES_DIR`) */
|
||||
export const PRETTIER_SUITE = "externals/prettier/";
|
||||
|
||||
/** A fixture path (relative to `FIXTURES_DIR`) and the `<file>#<slug>` of the DIVERGENCES.md entry listing it */
|
||||
export type Divergence = [listed: string, ref: string];
|
||||
|
||||
/**
|
||||
* Every path listed in a `Pin:` or `Conformance:` line, or its continuation lines.
|
||||
* A path ending in `*` lists every path it starts with.
|
||||
*/
|
||||
export function collectDivergences(): Divergence[] {
|
||||
const divergences: Divergence[] = [];
|
||||
for (const file of globSync("{apps/oxfmt,crates/oxc_formatter*}/DIVERGENCES.md", {
|
||||
cwd: REPO_ROOT,
|
||||
})) {
|
||||
let slug = "";
|
||||
let inField = false;
|
||||
for (const line of readFileSync(join(REPO_ROOT, file), "utf8").split("\n")) {
|
||||
if (line.startsWith("## ")) slug = line.slice(3).trim();
|
||||
if (line.startsWith("- ")) inField = /^- (Pin|Conformance):/.test(line);
|
||||
else if (!line.startsWith(" ")) inField = false;
|
||||
if (!inField) continue;
|
||||
for (const [, name] of line.matchAll(
|
||||
/`(?:conformance\/fixtures\/)?((?:externals|edge-cases)\/[^`]+)`/g,
|
||||
)) {
|
||||
divergences.push([name, `${file}#${slug}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return divergences;
|
||||
}
|
||||
|
||||
const covers = (listed: string, name: string) =>
|
||||
listed.endsWith("*") ? name.startsWith(listed.slice(0, -1)) : name === listed;
|
||||
|
||||
/** The entries listing `name`. */
|
||||
export function divergencesOf(divergences: Divergence[], name: string) {
|
||||
return divergences.flatMap(([listed, ref]) => (covers(listed, name) ? [ref] : []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Report lines of the failing `[name, line]`s, each followed by the entries listing it as sub-items.
|
||||
* The ones no entry lists (`❓unclassified`) come first.
|
||||
*/
|
||||
export function listDivergences(
|
||||
divergences: Divergence[],
|
||||
failures: [name: string, line: string][],
|
||||
) {
|
||||
return failures
|
||||
.map(([name, line]) => [line, divergencesOf(divergences, name)] as const)
|
||||
.sort(([, a], [, b]) => Number(a.length > 0) - Number(b.length > 0))
|
||||
.flatMap(([line, refs]) =>
|
||||
[line].concat((refs.length > 0 ? refs : ["❓unclassified"]).map((ref) => ` - ${ref}`)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails the run on every listed path of this runner's part (Prettier's suite or the rest) that no failure is.
|
||||
* The report is written anyway, the exit code is what CI sees.
|
||||
*/
|
||||
export function checkStaleDivergences(
|
||||
divergences: Divergence[],
|
||||
failed: string[],
|
||||
{ prettierSuite }: { prettierSuite: boolean },
|
||||
) {
|
||||
for (const [listed, ref] of divergences) {
|
||||
if (
|
||||
listed.startsWith(PRETTIER_SUITE) === prettierSuite
|
||||
&& !failed.some((name) => covers(listed, name))
|
||||
) {
|
||||
console.error(`STALE: "${listed}" passes but ${ref} lists it, remove it`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@
|
||||
- [externals/vue-vben-admin/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue](diffs/js-in-vue/externals__vue-vben-admin__@core__ui-kit__shadcn-ui__src__components__render-content__render-content.vue.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue](diffs/js-in-vue/externals__vue-vben-admin__effects__common-ui__src__components__api-component__api-component.vue.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry, apps/oxfmt/DIVERGENCES.md#ts-in-vue-generic-trailing-comma
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- apps/oxfmt/DIVERGENCES.md#ts-in-vue-generic-trailing-comma
|
||||
|
||||
### Option 2: 332/334 (99.40%)
|
||||
|
||||
@@ -22,7 +23,8 @@
|
||||
- [edge-cases/js-in-vue/generic-trailing-comma.vue](diffs/js-in-vue/edge-cases__js-in-vue__generic-trailing-comma.vue.md)
|
||||
- apps/oxfmt/DIVERGENCES.md#ts-in-vue-generic-trailing-comma
|
||||
- [externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue](diffs/js-in-vue/externals__vue-vben-admin__effects__common-ui__src__components__api-component__api-component.vue.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry, apps/oxfmt/DIVERGENCES.md#ts-in-vue-generic-trailing-comma
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- apps/oxfmt/DIVERGENCES.md#ts-in-vue-generic-trailing-comma
|
||||
|
||||
## gql-in-js
|
||||
|
||||
@@ -87,7 +89,8 @@
|
||||
- [externals/webawesome/badge/badge.ts](diffs/html-in-js/externals__webawesome__badge__badge.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/webawesome/button/button.ts](diffs/html-in-js/externals__webawesome__button__button.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry, apps/oxfmt/DIVERGENCES.md#dynamic-tag-name-end-tag
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- apps/oxfmt/DIVERGENCES.md#dynamic-tag-name-end-tag
|
||||
- [externals/webawesome/callout/callout.ts](diffs/html-in-js/externals__webawesome__callout__callout.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/webawesome/checkbox/checkbox.ts](diffs/html-in-js/externals__webawesome__checkbox__checkbox.ts.md)
|
||||
@@ -111,7 +114,8 @@
|
||||
- [externals/webawesome/number-input/number-input.ts](diffs/html-in-js/externals__webawesome__number-input__number-input.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/webawesome/page/page.styles.ts](diffs/html-in-js/externals__webawesome__page__page.styles.ts.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position, crates/oxc_formatter_css/DIVERGENCES.md#grid-value-broken-in-function
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#grid-value-broken-in-function
|
||||
- [externals/webawesome/popup/popup.ts](diffs/html-in-js/externals__webawesome__popup__popup.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/webawesome/qr-code/qr-code.ts](diffs/html-in-js/externals__webawesome__qr-code__qr-code.ts.md)
|
||||
@@ -148,7 +152,8 @@
|
||||
- [edge-cases/html-in-js/template-expression-indent.js](diffs/html-in-js/edge-cases__html-in-js__template-expression-indent.js.md)
|
||||
- apps/oxfmt/DIVERGENCES.md#pre-like-template-expression
|
||||
- [externals/webawesome/button/button.ts](diffs/html-in-js/externals__webawesome__button__button.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry, apps/oxfmt/DIVERGENCES.md#dynamic-tag-name-end-tag
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- apps/oxfmt/DIVERGENCES.md#dynamic-tag-name-end-tag
|
||||
- [externals/webawesome/callout/callout.ts](diffs/html-in-js/externals__webawesome__callout__callout.ts.md)
|
||||
- crates/oxc_formatter/DIVERGENCES.md#union-annotation-flat-retry
|
||||
- [externals/webawesome/copy-button/copy-button.ts](diffs/html-in-js/externals__webawesome__copy-button__copy-button.ts.md)
|
||||
@@ -232,10 +237,10 @@
|
||||
{"printWidth":80,"astro":{}}
|
||||
```
|
||||
|
||||
- [externals/plugin-astro/other/prettier-ignore-js/input.astro](diffs/astro/externals__plugin-astro__other__prettier-ignore-js__input.astro.md)
|
||||
- ❓unclassified
|
||||
- [edge-cases/astro/style-lang-sass.astro](diffs/astro/edge-cases__astro__style-lang-sass.astro.md)
|
||||
- apps/oxfmt/DIVERGENCES.md#astro-style-lang-sass
|
||||
- [externals/plugin-astro/other/prettier-ignore-js/input.astro](diffs/astro/externals__plugin-astro__other__prettier-ignore-js__input.astro.md)
|
||||
- unclassified
|
||||
- [externals/plugin-astro/styles/format-nested-sass-style-tag-content/input.astro](diffs/astro/externals__plugin-astro__styles__format-nested-sass-style-tag-content__input.astro.md)
|
||||
- apps/oxfmt/DIVERGENCES.md#astro-style-lang-sass
|
||||
- [externals/plugin-astro/styles/with-indented-sass/input.astro](diffs/astro/externals__plugin-astro__styles__with-indented-sass__input.astro.md)
|
||||
@@ -392,9 +397,11 @@
|
||||
- [externals/ng-zorro-antd/components/style/themes/dark.less](diffs/less/externals__ng-zorro-antd__components__style__themes__dark.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width
|
||||
- [externals/ng-zorro-antd/components/style/themes/default.less](diffs/less/externals__ng-zorro-antd__components__style__themes__default.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width, crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- [externals/ng-zorro-antd/components/style/themes/variable.less](diffs/less/externals__ng-zorro-antd__components__style__themes__variable.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width, crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- [externals/ng-zorro-antd/components/table/style/index.less](diffs/less/externals__ng-zorro-antd__components__table__style__index.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#signed-value-resplit
|
||||
- [externals/ng-zorro-antd/components/table/style/rtl.less](diffs/less/externals__ng-zorro-antd__components__table__style__rtl.less.md)
|
||||
@@ -409,9 +416,11 @@
|
||||
- [externals/ng-zorro-antd/components/style/mixins/customize.less](diffs/less/externals__ng-zorro-antd__components__style__mixins__customize.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#less-guard-list-inline
|
||||
- [externals/ng-zorro-antd/components/style/themes/default.less](diffs/less/externals__ng-zorro-antd__components__style__themes__default.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width, crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- [externals/ng-zorro-antd/components/style/themes/variable.less](diffs/less/externals__ng-zorro-antd__components__style__themes__variable.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width, crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#trailing-line-comment-print-width
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
- [externals/ng-zorro-antd/components/table/style/rtl.less](diffs/less/externals__ng-zorro-antd__components__table__style__rtl.less.md)
|
||||
- crates/oxc_formatter_css/DIVERGENCES.md#fill-break-position
|
||||
|
||||
@@ -535,4 +544,4 @@
|
||||
```
|
||||
|
||||
- [externals/svelte/compiler/print/index.js](diffs/jsdoc/externals__svelte__compiler__print__index.js.md)
|
||||
- unclassified
|
||||
- ❓unclassified
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,7 +18,7 @@
|
||||
"conformance": "pnpm run conformance:prettier && pnpm run conformance:live",
|
||||
"conformance:live": "node conformance/run_live.ts",
|
||||
"conformance:prettier": "node conformance/run_prettier.ts",
|
||||
"download-fixtures": "node conformance/download-fixtures.js",
|
||||
"download-fixtures": "node conformance/download-fixtures.ts",
|
||||
"generate-config-types": "node scripts/generate-config-types.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"target": "ESNext",
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
|
||||
@@ -252,8 +252,3 @@ cargo test -p oxc_formatter --test mod
|
||||
# Run a subset by module path (directory structure = module hierarchy)
|
||||
cargo test -p oxc_formatter fixtures::js::comments
|
||||
```
|
||||
|
||||
### Prettier conformance
|
||||
|
||||
The failing JS/TS files in `apps/oxfmt/conformance/snapshots/prettier.snap.md` are only partly audited:
|
||||
some are covered by an existing DIVERGENCES.md entry, the rest are unclassified, and no per-file list exists yet.
|
||||
@@ -6,7 +6,8 @@ Admission reasons and rules: see `crates/oxc_formatter_core/FORMATTER_POLICY.md`
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/js/comments/array-holes.js`
|
||||
- Conformance: `js/arrays/numbers-with-holes.js`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/arrays/numbers-with-holes.js`
|
||||
|
||||
```js
|
||||
// input
|
||||
@@ -156,7 +157,9 @@ Binary-like chains hoist the comment in both formatters; the own-line invariant
|
||||
|
||||
- Why: uniform-rule (same construct, same output: any other node after the same suppression comment, see #eol-suppression-after-assign-colon)
|
||||
- Pin: `tests/fixtures/ts/union/suppression.ts`
|
||||
- Conformance: `typescript/union/comments/18379.ts`, `typescript/union/consistent-with-flow/prettier-ignore.ts`
|
||||
- Conformance:
|
||||
- `externals/prettier/typescript/union/comments/18379.ts`
|
||||
- `externals/prettier/typescript/union/consistent-with-flow/prettier-ignore.ts`
|
||||
|
||||
A suppression comment before a union covers the whole union, wherever it sits: own-line, on the operator's line, or inline.
|
||||
One after a `|` covers that member (both formatters).
|
||||
@@ -230,7 +233,11 @@ Prettier moves the comment inside for `keyof`/type-operator operands while keepi
|
||||
|
||||
- Why: invariant (prettier/prettier#14617)
|
||||
- Pin: `tests/fixtures/js/comments/assignment-eol-line-comment.js`, `tests/fixtures/ts/comments/operator-eol-line-comment.ts`
|
||||
- Conformance: `js/comments/array-and-object.js`, `js/comments/assignment/variable-declarator.js`, `js/comments/variable-declarator.js`, `typescript/comments/type-literals.ts`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/comments/array-and-object.js`
|
||||
- `externals/prettier/js/comments/assignment/variable-declarator.js`
|
||||
- `externals/prettier/js/comments/variable-declarator.js`
|
||||
- `externals/prettier/typescript/comments/type-literals.ts`
|
||||
|
||||
Prettier's output crosses user content (the value the comment precedes, then the `;`) or changes line (own-lined).
|
||||
An end-of-line line comment right after `=`/`:` keeps its position (`= // c` + mandatory break).
|
||||
@@ -520,9 +527,33 @@ The shapes, each pinned by its fixture:
|
||||
|
||||
- Why: style-hold (oxc#25841)
|
||||
- Pin: `tests/fixtures/ts/union/annotation-flat-retry.ts`
|
||||
- Oxfmt: `externals/vue-vben-admin/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue`, `externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue`,
|
||||
`externals/webawesome/badge/badge.ts`, `externals/webawesome/button/button.ts`, `externals/webawesome/callout/callout.ts`, `externals/webawesome/checkbox/checkbox.ts`, `externals/webawesome/color-picker/color-picker.ts`, `externals/webawesome/copy-button/copy-button.ts`, `externals/webawesome/details/details.ts`, `externals/webawesome/dropdown/dropdown.ts`, `externals/webawesome/dropdown-item/dropdown-item.ts`, `externals/webawesome/format-number/format-number.ts`, `externals/webawesome/icon/icon.ts`, `externals/webawesome/input/input.ts`,
|
||||
`externals/webawesome/number-input/number-input.ts`, `externals/webawesome/page/page.ts`, `externals/webawesome/popup/popup.ts`, `externals/webawesome/qr-code/qr-code.ts`, `externals/webawesome/radio/radio.ts`, `externals/webawesome/radio-group/radio-group.ts`, `externals/webawesome/rating/rating.ts`, `externals/webawesome/select/select.ts`, `externals/webawesome/slider/slider.ts`, `externals/webawesome/switch/switch.ts`, `externals/webawesome/tag/tag.ts`, `externals/webawesome/textarea/textarea.ts`
|
||||
- Conformance:
|
||||
- `externals/vue-vben-admin/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue`
|
||||
- `externals/vue-vben-admin/effects/common-ui/src/components/api-component/api-component.vue`
|
||||
- `externals/webawesome/badge/badge.ts`
|
||||
- `externals/webawesome/button/button.ts`
|
||||
- `externals/webawesome/callout/callout.ts`
|
||||
- `externals/webawesome/checkbox/checkbox.ts`
|
||||
- `externals/webawesome/color-picker/color-picker.ts`
|
||||
- `externals/webawesome/copy-button/copy-button.ts`
|
||||
- `externals/webawesome/details/details.ts`
|
||||
- `externals/webawesome/dropdown/dropdown.ts`
|
||||
- `externals/webawesome/dropdown-item/dropdown-item.ts`
|
||||
- `externals/webawesome/format-number/format-number.ts`
|
||||
- `externals/webawesome/icon/icon.ts`
|
||||
- `externals/webawesome/input/input.ts`
|
||||
- `externals/webawesome/number-input/number-input.ts`
|
||||
- `externals/webawesome/page/page.ts`
|
||||
- `externals/webawesome/popup/popup.ts`
|
||||
- `externals/webawesome/qr-code/qr-code.ts`
|
||||
- `externals/webawesome/radio/radio.ts`
|
||||
- `externals/webawesome/radio-group/radio-group.ts`
|
||||
- `externals/webawesome/rating/rating.ts`
|
||||
- `externals/webawesome/select/select.ts`
|
||||
- `externals/webawesome/slider/slider.ts`
|
||||
- `externals/webawesome/switch/switch.ts`
|
||||
- `externals/webawesome/tag/tag.ts`
|
||||
- `externals/webawesome/textarea/textarea.ts`
|
||||
- Drop when: the wait-and-see on Prettier 3.9's union style resolves (follow, or re-classify)
|
||||
|
||||
```ts
|
||||
@@ -602,7 +633,9 @@ In the head the declaration has no terminator of its own; we keep it verbatim an
|
||||
|
||||
- Why: uniform-rule (same construct, same output: suppressed statement)
|
||||
- Pin: `tests/fixtures/js/semicolons/suppressed-statement.js`, `tests/fixtures/ts/semicolons/suppressed-class-member.ts`
|
||||
- Conformance: `js/ignore/issue-11077.js`, `markdown/markdown/real-world-case.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/ignore/issue-11077.js`
|
||||
- `externals/prettier/markdown/markdown/real-world-case.md`
|
||||
|
||||
```js
|
||||
// input (semi: true)
|
||||
@@ -813,7 +846,9 @@ The full callee list is `contains_a_test_pattern` in `src/utils/call_expression.
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/js/calls/callee-opener-comment.js`, `tests/fixtures/ts/calls/callee-opener-comment.ts`
|
||||
- Conformance: `js/last-argument-expansion/edge_case.js`, `typescript/call/callee-comments.ts`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/last-argument-expansion/edge_case.js`
|
||||
- `externals/prettier/typescript/call/callee-comments.ts`
|
||||
|
||||
A call's `(` and `<` are delimiters (see AGENTS.md "Head-body and operator gaps (class 3)"), so a comment between the callee and the opener stays on the callee side (except AGENTS.md "Open debts").
|
||||
|
||||
@@ -933,7 +968,15 @@ A function type without type parameters, a constructor type (`<new <T>() => T>f`
|
||||
|
||||
- Why: uniform-rule (same construct, same output: expression statement)
|
||||
- Pin: `tests/fixtures/ts/parenthesis/export-default-leftmost-parens.ts`
|
||||
- Conformance: `js/export-default/class_instance.js`, `js/export-default/function_tostring.js`, `typescript/as/export_default_as.ts`, `typescript/export-default/function_as.ts`, `typescript/satisfies-operators/export-default-as.ts`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/export-default/class_instance.js`
|
||||
- `externals/prettier/js/export-default/function_tostring.js`
|
||||
- `externals/prettier/js/export-default/parentheses/snippet: #9`
|
||||
- `externals/prettier/js/export-default/parentheses/snippet: #11`
|
||||
- `externals/prettier/js/export-default/parentheses/snippet: #12`
|
||||
- `externals/prettier/typescript/as/export_default_as.ts`
|
||||
- `externals/prettier/typescript/export-default/function_as.ts`
|
||||
- `externals/prettier/typescript/satisfies-operators/export-default-as.ts`
|
||||
|
||||
```ts
|
||||
// input
|
||||
@@ -960,7 +1003,8 @@ Prettier's wrapping came from a syntax fix (prettier/prettier#6133), whose chang
|
||||
|
||||
- Why: uniform-rule (same construct, same output: `export default` and sequence arrow body)
|
||||
- Pin: `tests/fixtures/ts/parenthesis/start-token-no-double-parens.ts`
|
||||
- Conformance: `js/identifier/parentheses/let.js`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/identifier/parentheses/let.js`
|
||||
|
||||
```ts
|
||||
// input
|
||||
@@ -1204,7 +1248,8 @@ Prettier moves it before the object for `.b`, and after the `.` for `.#b`; both
|
||||
|
||||
- Why: uniform-rule (same construct, same output: `(a + b).c` and `(await a)[c]`)
|
||||
- Pin: `tests/fixtures/js/parenthesis/computed-member-object-paren-break.js`
|
||||
- Conformance: `js/ternaries/indent-after-paren.js`
|
||||
- Conformance:
|
||||
- `externals/prettier/js/ternaries/indent-after-paren.js`
|
||||
|
||||
```js
|
||||
// input (printWidth: 80)
|
||||
|
||||
@@ -64,21 +64,20 @@ Rules:
|
||||
- The owning layer decides the behavior: a language crate for single-language behavior, `apps/oxfmt` for embedding
|
||||
- A behavior core decides for every language (see "Text width") is a rule in this document;
|
||||
each language crate where it shows has its own entry and pin fixture, citing that section
|
||||
- The pin fixture is minimal and lives in the owning layer, distilled from whatever exposed the divergence; a conformance file is never the pin fixture, it stays a regression net
|
||||
- The pin fixture is minimal and lives in the owning layer, distilled from whatever exposed the divergence; a file of an external suite (`conformance/fixtures/externals/`) is never the pin fixture, it stays a regression net
|
||||
- The entry's `Pin:` names the fixture, which needs no link back; the why and the deviating lines live in the entry
|
||||
- `DIVERGENCES.md#<slug>` is the one form for referring to an entry (docs, source, fixtures); never a quoted or bare slug, never an upstream issue alone
|
||||
- Entry format, with OUR behavior as the spec
|
||||
- H2 slug: the stable anchor
|
||||
- `Why:` required; the reason keyword, its parenthetical, then upstream issues as bare references: `uniform-rule (same construct, same output: X; prettier/prettier#NNNN)`
|
||||
- `Pin:` required; the fixture path
|
||||
- `Conformance:` optional; the failing files in Prettier's suite (`apps/oxfmt`'s `conformance:prettier`)
|
||||
- `Oxfmt:` optional, language crates only; the oxfmt externals failing on the entry
|
||||
- `Conformance:` optional; the files failing `apps/oxfmt`'s conformance on the entry, one per sub-item (a note may follow it), as paths under `apps/oxfmt/conformance/fixtures/`; a path ending in `*` covers every path it starts with (e.g. a group of snippets)
|
||||
- `Drop when:` optional; only for a condition more specific than the pin-bump default
|
||||
- Body: an input/ours/prettier code example
|
||||
- No status, dates, or authors: listed = accepted, condition met = delete (git holds history)
|
||||
- Lifecycle
|
||||
- A new conformance failure is acceptable only under this policy; it stays counted as a failure
|
||||
- A failing file no entry lists is unclassified; the crate's `AGENTS.md` conformance section holds only those
|
||||
- A failing file no entry lists is unclassified
|
||||
- At a pin bump (`apps/oxfmt/package.json`, then `download-fixtures`), re-run conformance: an entry no longer observed is deleted, the rest are re-checked against their reason
|
||||
- A documented divergence is never "fixed" by following Prettier
|
||||
- `DIVERGENCES.md` holds entries only, opening with one back-reference to this section; `AGENTS.md` keeps policy translations and mechanism prose
|
||||
@@ -155,7 +154,7 @@ node apps/oxfmt/node_modules/prettier/bin/prettier.cjs --parser <parser> --print
|
||||
|
||||
NOTE: Prettier's default `printWidth` is `80`, but Oxfmt is `100`.
|
||||
|
||||
Fixture tests and Prettier conformance re-format every output and record idempotency violations in their snapshots/reports.
|
||||
Fixture tests and `conformance:prettier` re-format every output and record idempotency violations in their snapshots/reports.
|
||||
Fixture tests also assert the lossless contract through `FixtureFormatter::fingerprint` (`oxc_formatter_tests`): a loss fails the test instead of being pinned.
|
||||
|
||||
### Fixture tests
|
||||
|
||||
@@ -201,18 +201,6 @@ NOT covered: `$(var)` interpolation inside values and selectors (`.icon.is-$(net
|
||||
|
||||
The harness snapshots both `--print-width 80` and `100`; verify fixtures at both widths.
|
||||
|
||||
### Prettier conformance
|
||||
|
||||
Every failing file is accounted for by a DIVERGENCES.md entry's `Conformance:` line, except these unclassified hunks:
|
||||
|
||||
- `css/parens/parens.css` (~93% match) token-soup math spacing; besides the css-glued-minus-paren / unary-plus-glue hunks:
|
||||
- normalization-direction difference (open question, low value)
|
||||
- a math operator adjacent to a function/paren boundary gets uniform `op` spacing from Prettier regardless of source (`round(1.5)+2` -> `round(1.5) + 2`, calc `*`/`/`);
|
||||
- ours preserves the source spacing per token (`prop13/14`, `prop57-60`, `prop73/74`)
|
||||
- within-a-word runs (`1+1+1+1`, `calc(100%+2px)`) match
|
||||
- glued number-ish runs are ONE postcss word and print raw (see `is_word_glued_number`)
|
||||
- `css/font/font.css` run as SCSS (its spec also lists `scss`): `--custom(1px)/2` gets a space after `/` (`--custom(1px)/ 2`)
|
||||
|
||||
### Manual checks
|
||||
|
||||
```sh
|
||||
|
||||
@@ -6,8 +6,14 @@ Admission reasons and rules: see `crates/oxc_formatter_core/FORMATTER_POLICY.md`
|
||||
|
||||
- Why: uniform-rule (line_suffix is never measured)
|
||||
- Pin: `tests/fixtures/format/less/trailing-inline-comment-width.less`
|
||||
- Conformance: `less/less-test-suite/globalVars/extended.less`, `less/less-test-suite/color-functions/rgba.less`, `less/less-test-suite/extend-selector/extend-selector.less`
|
||||
- Oxfmt: `externals/ng-zorro-antd/components/style/themes/default.less`, `externals/ng-zorro-antd/components/style/themes/variable.less`, `externals/ng-zorro-antd/components/style/themes/dark.less`, `externals/gitlab/stylesheets/pages/profile.scss`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-config/globalVars/extended.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/color-functions/rgba.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/extend-selector/extend-selector.less`
|
||||
- `externals/ng-zorro-antd/components/style/themes/default.less`
|
||||
- `externals/ng-zorro-antd/components/style/themes/variable.less`
|
||||
- `externals/ng-zorro-antd/components/style/themes/dark.less`
|
||||
- `externals/gitlab/stylesheets/pages/profile.scss`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -316,7 +322,8 @@ Prettier keeps SELECTOR interpolation verbatim.
|
||||
|
||||
- Why: uniform-rule (option governs: singleQuote)
|
||||
- Pin: `tests/fixtures/format/scss/unknown-at-rule-edges.scss`, `tests/fixtures/format/css/prelude-string-requote.css`
|
||||
- Oxfmt: `externals/mdn-css-guides/values_and_units/textual_data_types/index.md`
|
||||
- Conformance:
|
||||
- `externals/mdn-css-guides/values_and_units/textual_data_types/index.md`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -413,7 +420,9 @@ Prettier double-indents it (closing `)` floating between levels) when the neares
|
||||
|
||||
- Why: uniform-rule (comment presence never changes layout)
|
||||
- Pin: `tests/fixtures/format/scss/map-comment-block-value-comma.scss`, `tests/fixtures/format/scss/variable-inline-comment.scss`
|
||||
- Conformance: `scss/map/comment.scss` (the `key4` indent; its dropped trailing comma is `map-leading-comment-layout`), `scss/parens/2.scss`
|
||||
- Conformance:
|
||||
- `externals/prettier/scss/map/comment.scss` (the `key4` indent)
|
||||
- `externals/prettier/scss/parens/2.scss`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -536,8 +545,21 @@ so a comment, a sibling token or an overflow hard-breaks that outer list around
|
||||
|
||||
- Why: uniform-rule (the formatter owns the trivia up to a terminator)
|
||||
- Pin: `tests/fixtures/format/scss/terminator-gap.scss`
|
||||
- Conformance: `css/stylefmt-repo/at-media/at-media.css`, `css/stylefmt-repo/cssnext-example/cssnext-example.css`, `css/stylefmt-repo/media-queries-ranges/media-queries-ranges.css`, `css/postcss-plugins/postcss-nesting.css`, `css/comments/declaration.css`,
|
||||
`scss/comments/4878.scss`, `scss/map/function-argument/functional-argument.scss`, `scss/parens/issue-16594.scss`, `scss/trailing-comma/comments.scss`, `scss/trailing-comma/list.scss`, `scss/trailing-comma/variable.scss`, `scss/function/arbitrary-arguments-comment.scss`, `scss/map/15193.scss`, `scss/comments/variable-declaration.scss`
|
||||
- Conformance:
|
||||
- `externals/prettier/css/stylefmt-repo/at-media/at-media.css`
|
||||
- `externals/prettier/css/stylefmt-repo/cssnext-example/cssnext-example.css`
|
||||
- `externals/prettier/css/stylefmt-repo/media-queries-ranges/media-queries-ranges.css`
|
||||
- `externals/prettier/css/postcss-plugins/postcss-nesting.css`
|
||||
- `externals/prettier/css/comments/declaration.css`
|
||||
- `externals/prettier/scss/comments/4878.scss`
|
||||
- `externals/prettier/scss/map/function-argument/functional-argument.scss`
|
||||
- `externals/prettier/scss/parens/issue-16594.scss`
|
||||
- `externals/prettier/scss/trailing-comma/comments.scss`
|
||||
- `externals/prettier/scss/trailing-comma/list.scss`
|
||||
- `externals/prettier/scss/trailing-comma/variable.scss`
|
||||
- `externals/prettier/scss/function/arbitrary-arguments-comment.scss`
|
||||
- `externals/prettier/scss/map/15193.scss`
|
||||
- `externals/prettier/scss/comments/variable-declaration.scss`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -632,7 +654,8 @@ Same-line trailing comments still glue (matching Prettier); moving an own-line c
|
||||
|
||||
- Why: uniform-rule (comment presence never changes layout)
|
||||
- Pin: `tests/fixtures/format/scss/map-comment-only.scss`
|
||||
- Conformance: `scss/map/comment.scss` (the map's trailing comma under `trailingComma: es5`)
|
||||
- Conformance:
|
||||
- `externals/prettier/scss/map/comment.scss` (the map's trailing comma under `trailingComma: es5`)
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -726,7 +749,13 @@ the same `join(line)` + deferred `lineSuffix` artifact as "comment-only-map-inde
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/format/scss/line-comment-after-comma.scss`, `tests/fixtures/format/less/line-comment-after-comma.less`
|
||||
- Conformance: `scss/comments/4594.scss`, `scss/comments/lists.scss`, `scss/comments/maps.scss`, `scss/trailing-comma/issue-6920.scss`, `scss/trailing-comma/comments.scss`, `less/comments/value-lists.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/scss/comments/4594.scss`
|
||||
- `externals/prettier/scss/comments/lists.scss`
|
||||
- `externals/prettier/scss/comments/maps.scss`
|
||||
- `externals/prettier/scss/trailing-comma/issue-6920.scss`
|
||||
- `externals/prettier/scss/trailing-comma/comments.scss`
|
||||
- `externals/prettier/less/comments/value-lists.less`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -821,7 +850,9 @@ so the entry BEFORE the comment (`in a,`) breaks away from `$k` too; the same so
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/format/less/variable-value-comments.less`, `tests/fixtures/format/less/important-comments.less`
|
||||
- Conformance: `less/less-test-suite/comments/comments2.less`, `less/less-test-suite/property-name-interp/property-name-interp.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/comments/comments2.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/property-name-interp/property-name-interp.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -845,7 +876,8 @@ and its at-rule printer drops the block comments in there (a trailing one surviv
|
||||
|
||||
- Why: uniform-rule (same construct, same output: selector-position `&:extend(...)`; prettier/prettier#19550)
|
||||
- Pin: `tests/fixtures/format/less/extend-rule.less`
|
||||
- Conformance: `less/less-test-suite/extend-chaining/extend-chaining.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/extend-chaining/extend-chaining.less`
|
||||
- Drop when: the selector-list leak is fixed (prettier/prettier#19550 covered only the indentation) and the pin catches up
|
||||
|
||||
```less
|
||||
@@ -872,7 +904,8 @@ postcss-less models the statement as a rule node, so the top-level selector-list
|
||||
- Why: cost
|
||||
- Pin: `tests/fixtures/format/css/token-soup-math-glue.css` (Css mode),
|
||||
`tests/fixtures/format/less/signed-value-args.less` (Less mode)
|
||||
- Conformance: `css/parens/parens.css` (`prop34`; the file also has unclassified diffs, see AGENTS.md)
|
||||
- Conformance:
|
||||
- `externals/prettier/css/parens/parens.css` (`prop34`; the file also has unclassified diffs)
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -894,8 +927,10 @@ A sign GLUED in the source never gains a space in either implementation (that di
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the item in first position, an unsigned item)
|
||||
- Pin: `tests/fixtures/format/css/signed-value-resplit.css`
|
||||
- Conformance: `css/fill-value/fill.css`
|
||||
- Oxfmt: `externals/gitlab/stylesheets/framework/variables.scss`, `externals/ng-zorro-antd/components/table/style/index.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/css/fill-value/fill.css`
|
||||
- `externals/gitlab/stylesheets/framework/variables.scss`
|
||||
- `externals/ng-zorro-antd/components/table/style/index.less`
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -932,7 +967,8 @@ Ours prints a signed item as one word, like the same item in first position or u
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the `-(` shapes Prettier keeps glued)
|
||||
- Pin: `tests/fixtures/format/css/token-soup-math-glue.css`
|
||||
- Conformance: `css/parens/parens.css` (`prop`/`prop44`; the file also has unclassified diffs, see AGENTS.md)
|
||||
- Conformance:
|
||||
- `externals/prettier/css/parens/parens.css` (`prop`/`prop44`; the file also has unclassified diffs)
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -957,9 +993,15 @@ Css mode only (hence the prefix): in Less and Scss, Prettier keeps `3px -(4px)`
|
||||
|
||||
- Why: cost
|
||||
- Pin: `tests/fixtures/format/scss/fill-break-position.scss`
|
||||
- Oxfmt: `externals/webawesome/number-input/number-input.styles.ts`, `externals/webawesome/page/page.styles.ts`,
|
||||
`externals/ng-zorro-antd/components/style/themes/default.less`, `externals/ng-zorro-antd/components/style/themes/variable.less`, `externals/ng-zorro-antd/components/table/style/rtl.less`,
|
||||
`externals/gitlab/stylesheets/components/content_editor.scss`, `externals/gitlab/stylesheets/page_bundles/_ide_theme_overrides.scss`, `externals/gitlab/stylesheets/framework/sidebar.scss`
|
||||
- Conformance:
|
||||
- `externals/webawesome/number-input/number-input.styles.ts`
|
||||
- `externals/webawesome/page/page.styles.ts`
|
||||
- `externals/ng-zorro-antd/components/style/themes/default.less`
|
||||
- `externals/ng-zorro-antd/components/style/themes/variable.less`
|
||||
- `externals/ng-zorro-antd/components/table/style/rtl.less`
|
||||
- `externals/gitlab/stylesheets/components/content_editor.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/_ide_theme_overrides.scss`
|
||||
- `externals/gitlab/stylesheets/framework/sidebar.scss`
|
||||
|
||||
```scss
|
||||
/* input (nested three levels, print width 80) */
|
||||
@@ -1009,7 +1051,16 @@ we never format what the reference compiler rejects (a parse error is the SAFE f
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the same expression in a declaration value; prettier/prettier#1811)
|
||||
- Pin: `tests/fixtures/format/scss/media-query-operator-spacing.scss`
|
||||
- Oxfmt: `externals/gitlab/stylesheets/framework/diffs.scss`, `externals/gitlab/stylesheets/page_bundles/editor.scss`, `externals/gitlab/stylesheets/page_bundles/issuable_list.scss`, `externals/gitlab/stylesheets/page_bundles/labels.scss`, `externals/gitlab/stylesheets/page_bundles/environments.scss`, `externals/gitlab/stylesheets/page_bundles/merge_requests.scss`, `externals/gitlab/stylesheets/page_bundles/settings.scss`, `externals/gitlab/stylesheets/page_bundles/projects.scss`, `externals/gitlab/stylesheets/pages/settings.scss`
|
||||
- Conformance:
|
||||
- `externals/gitlab/stylesheets/framework/diffs.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/editor.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/issuable_list.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/labels.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/environments.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/merge_requests.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/settings.scss`
|
||||
- `externals/gitlab/stylesheets/page_bundles/projects.scss`
|
||||
- `externals/gitlab/stylesheets/pages/settings.scss`
|
||||
|
||||
```scss
|
||||
/* input */
|
||||
@@ -1118,7 +1169,7 @@ Prettier drops it when the item's value is paren-ish (a call / paren group), an
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/format/css/paren-group-glued-word.css`
|
||||
(`css/postcss-plugins/postcss-simple-vars.css` carries the case too, but its root declarations keep it skipped in conformance)
|
||||
(`css/postcss-plugins/postcss-simple-vars.css` carries the case too, but its root declarations fail to parse)
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -1173,7 +1224,10 @@ Escaped custom property names therefore preserve their source spelling, like nam
|
||||
|
||||
- Why: uniform-rule (raw is verbatim; AGENTS.md "Printing raw vs typed")
|
||||
- Pin: `tests/fixtures/format/css/custom-property-raw-verbatim.css`, `tests/fixtures/format/scss/custom-property-raw-verbatim.scss`, `tests/fixtures/format/scss/custom-property-text.scss`
|
||||
- Conformance: `css/postcss-8-improment/test.css`, `scss/variables/postcss-8-improment.scss`, `less/postcss-8-improment/test.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/css/postcss-8-improment/test.css`
|
||||
- `externals/prettier/scss/variables/postcss-8-improment.scss`
|
||||
- `externals/prettier/less/postcss-8-improment/test.less`
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -1214,7 +1268,8 @@ re-spacing a value we could not read is not a layout rule of ours.
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/format/css/postcss-simple-vars/vars.css`
|
||||
- Conformance: `css/parens/empty-lines.css`
|
||||
- Conformance:
|
||||
- `externals/prettier/css/parens/empty-lines.css`
|
||||
|
||||
```css
|
||||
/* input and ours */
|
||||
@@ -1265,7 +1320,8 @@ and prints any other run verbatim, so a comment inside freezes the keyword's cas
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/format/less/javascript-verbatim.less`
|
||||
- Conformance: `less/less-test-suite/javascript/javascript.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/javascript/javascript.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -1296,7 +1352,9 @@ it pads the backticks, re-quotes JavaScript strings and reflows a multi-line fun
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/format/less/escaped-string-gap.less`
|
||||
- Conformance: `less/less-test-suite/variables/variables.less`, `less/less-test-suite/strings/strings.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/variables/variables.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/strings/strings.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -1316,7 +1374,10 @@ Prettier's `~` handling for escaped strings (`~"..."`) fires on a string whose c
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/format/less/lookup-glue.less`
|
||||
- Conformance: `less/less-test-suite/namespacing/namespacing-functions.less`, `less/less-test-suite/namespacing/namespacing-media.less`, `less/less-test-suite/namespace-targeted/namespace-targeted.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-config/namespacing/namespacing-functions.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-config/namespacing/namespacing-media.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/namespace-targeted/namespace-targeted.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -1348,8 +1409,10 @@ The `$@` half is fixed upstream after v3.9.6 (prettier/prettier#19782 keeps `[$@
|
||||
|
||||
- Why: uniform-rule (same construct, same output: guard alternatives are a list, not selectors; the `less-extend-statement-break` leak again)
|
||||
- Pin: `tests/fixtures/format/less/mixin-selector-list.less`
|
||||
- Conformance: `less/less-test-suite/mixins-guards/mixins-guards.less`, `less/less-test-suite/mixins-guards-default-func/mixins-guards-default-func.less`
|
||||
- Oxfmt: `externals/ng-zorro-antd/components/style/mixins/customize.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/mixins-guards/mixins-guards.less`
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/mixins-guards-default-func/mixins-guards-default-func.less`
|
||||
- `externals/ng-zorro-antd/components/style/mixins/customize.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -1376,7 +1439,8 @@ over the width every word gap is a descendant combinator, so `when`, `and` and e
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the width-wrapped continuation of the same operation)
|
||||
- Pin: `tests/fixtures/format/less/line-comment-continuation-indent.less`, `tests/fixtures/format/scss/line-comment-continuation-indent.scss`
|
||||
- Conformance: `less/less-test-suite/comments/comments2.less`
|
||||
- Conformance:
|
||||
- `externals/prettier/less/less-test-suite/snippet: tests-unit/comments/comments2.less`
|
||||
|
||||
```less
|
||||
/* input */
|
||||
@@ -1521,7 +1585,8 @@ A raw prelude (unknown at-rule params) still takes the same `//` test as Prettie
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the SCSS `if()` branch separator; prettier/prettier#19384)
|
||||
- Pin: `tests/fixtures/format/css/value-semicolon-glue.css`
|
||||
- Oxfmt: `externals/mdn-css-guides/custom_functions_and_mixins/using_custom_functions/index.md`
|
||||
- Conformance:
|
||||
- `externals/mdn-css-guides/custom_functions_and_mixins/using_custom_functions/index.md`
|
||||
|
||||
```css
|
||||
/* input */
|
||||
@@ -1583,7 +1648,9 @@ Prettier indents the arguments and `)` one level further there, its prelude valu
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/format/scss/grid-value-broken-in-function.scss`
|
||||
- Oxfmt: `externals/gitlab/stylesheets/page_bundles/work_items.scss`, `externals/webawesome/page/page.styles.ts`
|
||||
- Conformance:
|
||||
- `externals/gitlab/stylesheets/page_bundles/work_items.scss`
|
||||
- `externals/webawesome/page/page.styles.ts`
|
||||
|
||||
```scss
|
||||
/* input (= ours; Prettier's output for the value on one line) */
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
//! (a divergence needs its own pin; the big conformance file is only a regression net).
|
||||
//!
|
||||
//! Expected outputs were verified against `prettier` by hand;
|
||||
//! when adding a fixture, do the same (`npx prettier@<oxfmt-bundle-version> --parser <variant>`).
|
||||
//! when adding a fixture, do the same with the oxfmt-bundled one
|
||||
//! (`node apps/oxfmt/node_modules/prettier/bin/prettier.cjs --parser <variant>`, see `FORMATTER_POLICY.md` "Verification").
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
@@ -42,10 +42,4 @@ DUMP_IR=1 cargo run -p oxc_formatter_htmlish --example htmlish_formatter [filena
|
||||
node apps/oxfmt/node_modules/prettier/bin/prettier.cjs [filename]
|
||||
```
|
||||
|
||||
### Prettier conformance
|
||||
|
||||
Every failing file is accounted for by a DIVERGENCES.md entry's `Conformance:` line, except this unclassified one:
|
||||
|
||||
- `misc/filename-matters/html-doctype/xhtml-doctype.xhtml` (run as `.html`, also as `.xhtml`) fails to parse: `missing end tag of raw text element` for its self-closing `<script />`
|
||||
|
||||
Fixtures and the example read `.vue` as Vue, `.xhtml` / `.xht` as XHTML and `*.component.html` as Angular.
|
||||
@@ -29,8 +29,9 @@ which changes what `pre` renders and what `textarea` submits.
|
||||
|
||||
- Why: uniform-rule (same construct, same output: the doctype of a `.html` file, which Prettier lowercases)
|
||||
- Pin: `tests/fixtures/vue/doctype-case.vue`
|
||||
- Conformance: `misc/filename-matters/html-doctype/snippet: #0`
|
||||
- Oxfmt: `externals/mdn-learn/css_layout/multiple-column_layout/index.md`, `externals/mdn-learn/css_layout/positioning/index.md`
|
||||
- Conformance:
|
||||
- `externals/mdn-learn/css_layout/multiple-column_layout/index.md`
|
||||
- `externals/mdn-learn/css_layout/positioning/index.md`
|
||||
|
||||
```vue
|
||||
<!-- input -->
|
||||
@@ -213,7 +214,8 @@ Prettier prints `class` and `srcset` in its embed step, which `off` skips togeth
|
||||
|
||||
- Why: semantics (whitespace added on both sides of a `script` renders between its inline neighbors)
|
||||
- Pin: `tests/fixtures/html/script-display-none.html`
|
||||
- Conformance: `html/tags/tags.html`
|
||||
- Conformance:
|
||||
- `externals/prettier/html/tags/tags.html`
|
||||
|
||||
```html
|
||||
<!-- input -->
|
||||
@@ -331,7 +333,8 @@ Prettier lays the content out as blocks, adding and removing whitespace that ren
|
||||
|
||||
- Why: semantics (whitespace added on both sides of a `dialog` renders between its inline neighbors)
|
||||
- Pin: `tests/fixtures/html/dialog-out-of-flow.html`
|
||||
- Conformance: `html/tags/tags2.html`
|
||||
- Conformance:
|
||||
- `externals/prettier/html/tags/tags2.html`
|
||||
|
||||
```html
|
||||
<!-- input -->
|
||||
@@ -489,7 +492,9 @@ An email template pads its preview text this way, NBSP alternating with zero-wid
|
||||
|
||||
- Why: semantics (an end tag added where a fragment ends changes the page it is joined into; prettier/prettier#17761, prettier/prettier#5665)
|
||||
- Pin: `tests/fixtures/html/omitted-end-tag-at-eof.html`
|
||||
- Conformance: `html/basics/broken-html.html`, `html/prettier_ignore/unclosed2.html`
|
||||
- Conformance:
|
||||
- `externals/prettier/html/basics/broken-html.html`
|
||||
- `externals/prettier/html/prettier_ignore/unclosed2.html`
|
||||
|
||||
```html
|
||||
<!-- input (header.html, closed by footer.html) -->
|
||||
@@ -650,7 +655,8 @@ Fixed in Prettier `main` by prettier/prettier#19709 and prettier/prettier#19707,
|
||||
|
||||
- Why: uniform-rule (same construct, same output: `<html:p>` and every other prefixed name, which Prettier lays out as an unknown element)
|
||||
- Pin: `tests/fixtures/html/prefixed-script-style.html`
|
||||
- Conformance: `html/basics/with-colon.html`
|
||||
- Conformance:
|
||||
- `externals/prettier/html/basics/with-colon.html`
|
||||
|
||||
```html
|
||||
<!-- input -->
|
||||
@@ -680,7 +686,9 @@ Angular's compiler reads a prefix as a namespace (`svg:rect`), and so does the A
|
||||
|
||||
- Why: semantics (Prettier ends an unquoted value at `/`: `src=a.png/` becomes `src="a.png"`, another URL)
|
||||
- Pin: `tests/fixtures/html/unquoted-value-with-slash.html`
|
||||
- Oxfmt: `externals/mdn-learn/structuring_content/basic_html_syntax/index.md`, `externals/vite/html/valid.html`
|
||||
- Conformance:
|
||||
- `externals/mdn-learn/structuring_content/basic_html_syntax/index.md`
|
||||
- `externals/vite/html/valid.html`
|
||||
|
||||
```html
|
||||
<!-- input -->
|
||||
|
||||
@@ -44,7 +44,7 @@ A fenced code block with a language dispatches its content as a `VirtualDocument
|
||||
the child IR is wrapped in `mark_as_root` with every newline in its texts made a literal line, so continuation lines keep the fence's column,
|
||||
and the fence outnumbers the backtick runs of the printed content (printed once to count them when a text holds a backtick).
|
||||
No dispatcher, an unknown language or a failed parse keeps the block verbatim, as does a meta with line ranges (`apps/oxfmt/DIVERGENCES.md#line-ranged-code-block`).
|
||||
Embedding is verified end to end by oxfmt's conformance (`conformance:prettier`, and `md-in-js` in `conformance:live`), not here.
|
||||
Embedding is verified end to end by oxfmt's conformance (`conformance:prettier`, and `xxx-in-md` / `markdown-mdn` in `conformance:live`), not here.
|
||||
|
||||
## Dialects
|
||||
|
||||
@@ -78,10 +78,6 @@ node apps/oxfmt/node_modules/prettier/bin/prettier.cjs --parser markdown --no-co
|
||||
node -e 'const p=require("./apps/oxfmt/node_modules/prettier");p.__debug.printToDoc(require("fs").readFileSync(process.argv[1],"utf8"),{parser:"markdown"}).then(d=>p.__debug.formatDoc(d)).then(console.log)' [filename]
|
||||
```
|
||||
|
||||
### Prettier conformance
|
||||
|
||||
Every failing file is accounted for by a DIVERGENCES.md entry's `Conformance:` line; there are no unclassified failures.
|
||||
|
||||
### Fixture fingerprint
|
||||
|
||||
`tests/fixtures/fingerprint.rs` serializes the AST's meaning (structure, decoded text, destinations, labels) and ignores what formatting may change
|
||||
|
||||
@@ -6,7 +6,14 @@ Admission reasons and rules: see `crates/oxc_formatter_core/FORMATTER_POLICY.md`
|
||||
|
||||
- Why: semantics (prettier/prettier#19839)
|
||||
- Pin: `tests/fixtures/markdown/leading-thematic-break.md`
|
||||
- Conformance: `markdown/thematicBreak/simple.md`, `markdown/commonmark-test-suite/snippet: example-11.md`, `example-50.md` to `example-54.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/thematicBreak/simple.md`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-11.md*`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-50.md*`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-51.md*`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-52.md*`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-53.md*`
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-54.md*`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -40,7 +47,8 @@ Prettier `main` prints `***` since #19839; the pin (3.9.10) still prints `---`.
|
||||
|
||||
- Why: semantics (prettier/prettier#19482, prettier/prettier#19849, prettier/prettier#19891)
|
||||
- Pin: `tests/fixtures/markdown/url-escaping.md`
|
||||
- Conformance: `markdown/link/encodedLink.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/link/encodedLink.md`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -62,7 +70,8 @@ Titles get the same `\&` treatment.
|
||||
|
||||
- Why: uniform-rule (same construct, same output: space-indented code block)
|
||||
- Pin: `tests/fixtures/markdown/indented-code-tab.md`
|
||||
- Conformance: `markdown/commonmark-test-suite/snippet: example-2.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-2.md*`
|
||||
|
||||
````markdown
|
||||
<!-- input: two spaces, a tab -->
|
||||
@@ -85,7 +94,8 @@ so an indent written as spaces plus a tab becomes a fenced block.
|
||||
|
||||
- Why: uniform-rule (same construct, same output: a blockquote's trailing blank `>` line under `proseWrap: preserve`)
|
||||
- Pin: `tests/fixtures/markdown/prose-wrap/ignored-block-trailing-quote-line.md`
|
||||
- Conformance: `markdown/blockquote/ignore-code.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/blockquote/ignore-code.md`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -289,7 +299,8 @@ Prettier alternates from the marker it would have printed (`-`), prints `*` next
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/markdown/prose-wrap/container-directive.md`
|
||||
- Conformance: `markdown/paragraph/cjk.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/paragraph/cjk.md`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -323,7 +334,8 @@ so the closing fence is printed explicitly: a reader without directives (CommonM
|
||||
|
||||
- Why: semantics
|
||||
- Pin: `tests/fixtures/markdown/prose-wrap/line-shapes.md`
|
||||
- Conformance: `markdown/blockquote/notext-end.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/blockquote/notext-end.md`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -403,7 +415,8 @@ which its regex takes for a list marker (a marker has at most 9 digits).
|
||||
|
||||
- Why: semantics (prettier/prettier#6035, prettier/prettier#17303, prettier/prettier#17353)
|
||||
- Pin: `tests/fixtures/markdown/prose-wrap/stray-delimiters.md`
|
||||
- Conformance: `markdown/commonmark-test-suite/snippet: example-412.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/commonmark-test-suite/snippet: example-412.md*`
|
||||
|
||||
```markdown
|
||||
<!-- input -->
|
||||
@@ -518,7 +531,8 @@ Prettier prints it as written (the leading blank line that kept it out of front
|
||||
|
||||
- Why: semantics (prettier/prettier#20143)
|
||||
- Pin: `tests/fixtures/markdown/prose-wrap/cj-line-break.md`
|
||||
- Conformance: `markdown/splitCjkText/symbolSpaceNewLine.md`
|
||||
- Conformance:
|
||||
- `externals/prettier/markdown/splitCjkText/symbolSpaceNewLine.md`
|
||||
|
||||
```markdown
|
||||
<!-- input, proseWrap always -->
|
||||
|
||||
@@ -6,7 +6,13 @@ Admission reasons and rules: see `crates/oxc_formatter_core/FORMATTER_POLICY.md`
|
||||
|
||||
- Why: uniform-rule (source order of user tokens is preserved; prettier/prettier#19524, prettier/prettier#19599)
|
||||
- Pin: `tests/fixtures/yaml/anchor-tag-order.yaml`
|
||||
- Conformance: `yaml/spec/anchors-and-tags.yml`, `yaml/spec/node-anchor-and-tag-on-seperate-lines.yml`, `yaml/spec/various-combinations-of-tags-and-anchors.yml` and yaml-test-suite `9KAX`, `BU8L`, `F2C7`
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/spec/anchors-and-tags.yml`
|
||||
- `externals/prettier/yaml/spec/node-anchor-and-tag-on-seperate-lines.yml`
|
||||
- `externals/prettier/yaml/spec/various-combinations-of-tags-and-anchors.yml`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: 9KAX.yaml*`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: BU8L.yaml*`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: F2C7.yaml*`
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -25,8 +31,19 @@ Anchor/tag source order is preserved, never reordered, as mapping keys and seque
|
||||
|
||||
- Why: semantics (prettier/prettier#19764)
|
||||
- Pin: `tests/fixtures/yaml/block-scalar-trailing-spaces.yaml`, `tests/fixtures/yaml/prose-wrap/trailing-spaces.yaml`
|
||||
- Conformance: `yaml/block-folded/block-folded-strip.yml`, the `yaml/block-value` snippets whose scalar ends in a space/tab-only line (64 rows),
|
||||
and yaml-test-suite `L24T`, `L24T-2`, `Y79Y-2`, whose JSON keeps that line as content
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/block-folded/block-folded-strip.yml`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: >\n x\n \n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: >\n x\n \t\n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: >-\n x\n \n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: >-\n x\n \t\n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: |\n x\n \n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: |\n x\n \t\n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: |-\n x\n \n*`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: |-\n x\n \t\n*`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: L24T.yaml*` (its JSON keeps that line as content)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: L24T-2.yaml*` (its JSON keeps that line as content)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: Y79Y-2.yaml*` (its JSON keeps that line as content)
|
||||
|
||||
```yaml
|
||||
# input ("␣" marks a real space)
|
||||
@@ -45,7 +62,10 @@ When converging, keep the blank line after such a scalar: post-prettier/prettier
|
||||
|
||||
- Why: uniform-rule (one final newline)
|
||||
- Pin: `tests/fixtures/yaml/eof-blank-lines.yaml`
|
||||
- Conformance: `yaml/block-value` `"foo: >+\n"` / `"foo: |+\n"` and yaml-test-suite `2G84-4`, where Prettier prints NO final newline after a contentless keep-chomped scalar
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: >+\n"`
|
||||
- `externals/prettier/yaml/block-value/snippet: "foo: |+\n"`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: 2G84-4.yaml*`
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -59,12 +79,14 @@ a: 1
|
||||
```
|
||||
|
||||
Like every other formatter crate, the file always ends with exactly one newline (`|+` keep-chomped verbatim tails excepted); Prettier YAML alone preserves EOF blank lines.
|
||||
After a contentless keep-chomped scalar (its conformance failures), Prettier prints no final newline at all.
|
||||
|
||||
## keep-chomped-space-only-eof-line
|
||||
|
||||
- Why: semantics (prettier/prettier#19256)
|
||||
- Pin: `tests/fixtures/yaml/keep-chomped-eos-spaces-only.yaml`, `tests/fixtures/yaml/keep-chomped-eos-trailing-spaces.yaml`
|
||||
- Conformance: yaml-test-suite `JEF9-3`, for which the suite asserts no JSON value, only yaml@2's dump
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: JEF9-3.yaml*` (the suite asserts no JSON value, only yaml@2's dump)
|
||||
|
||||
```yaml
|
||||
# input (no final newline; the last line is two spaces)
|
||||
@@ -81,7 +103,8 @@ A space-only EOF line at-or-below the block's indent holds no line break, so it
|
||||
|
||||
- Why: uniform-rule (blank lines are never invented; see "blank-lines")
|
||||
- Pin: `tests/fixtures/yaml/empty-document-end-marker.yaml`
|
||||
- Conformance: yaml-test-suite `HWV9`
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: HWV9.yaml*`
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -146,7 +169,8 @@ A suppression comment freezes exactly ONE node, never everything after it; the s
|
||||
|
||||
- Why: uniform-rule (blank-line preservation; prettier/prettier#15528)
|
||||
- Pin: `tests/fixtures/yaml/blank-lines.yaml`, `tests/fixtures/yaml/nested-end-comment-blank.yaml`
|
||||
- Conformance: `yaml/mapping/duplicated-keys/flow-mapping.yml` (a blank after a flow mapping, before `---`)
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/mapping/duplicated-keys/flow-mapping.yml` (a blank after a flow mapping, before `---`)
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -195,8 +219,15 @@ More-indented lines in a folded scalar are never re-flowed under `proseWrap: alw
|
||||
|
||||
- Why: uniform-rule (a group is flat or fully expanded)
|
||||
- Pin: `tests/fixtures/yaml/flow-multiline-pair.yaml`, `tests/fixtures/yaml/flow-comments.yaml`
|
||||
- Conformance: `yaml/spec/spec-example-7-20-single-pair-explicit-entry.yml`, `yaml/spec/spec-example-9-4-explicit-documents.yml` and yaml-test-suite `CT4Q`, `UT92`, `NJ66`, `9SA2` (multiline pairs),
|
||||
`yaml/flow-mapping/comments/key.yml` and yaml-test-suite `K3WX` (key trailing comment)
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/spec/spec-example-7-20-single-pair-explicit-entry.yml`
|
||||
- `externals/prettier/yaml/spec/spec-example-9-4-explicit-documents.yml`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: CT4Q.yaml*` (multiline pairs)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: UT92.yaml*` (multiline pairs)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: NJ66.yaml*` (multiline pairs)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: 9SA2.yaml*` (multiline pairs)
|
||||
- `externals/prettier/yaml/flow-mapping/comments/key.yml` (key trailing comment)
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: K3WX.yaml*` (key trailing comment)
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -224,7 +255,9 @@ Prettier sometimes emits a newline inside flow brackets while keeping them flat
|
||||
|
||||
- Why: invariant
|
||||
- Pin: `tests/fixtures/yaml/flow-comments.yaml`
|
||||
- Conformance: `yaml/spec/spec-example-6-1-indentation-spaces.yml` and yaml-test-suite `6HB6`, the same example
|
||||
- Conformance:
|
||||
- `externals/prettier/yaml/spec/spec-example-6-1-indentation-spaces.yml`
|
||||
- `externals/prettier/yaml/yaml-test-suite/snippet: 6HB6.yaml*` (the same example)
|
||||
|
||||
```yaml
|
||||
# input
|
||||
@@ -250,7 +283,8 @@ A comment stays at its syntactic position; Prettier's attachment hoists a commen
|
||||
|
||||
- Why: uniform-rule (comment presence never changes layout)
|
||||
- Pin: `tests/fixtures/yaml/comment-over-indented.yaml`
|
||||
- Oxfmt: `externals/aws-cloudformation-templates/RainModules/load-balancer.yml`
|
||||
- Conformance:
|
||||
- `externals/aws-cloudformation-templates/RainModules/load-balancer.yml`
|
||||
|
||||
```yaml
|
||||
# input
|
||||
|
||||
Reference in new issue
Block a user