mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
docs(formatter_core): document text width divergence (#28095)
This commit is contained in:
1 parent
e0f8937ae4
commit
db55461552
5 files changed
+92
No files matched your search
@@ -987,3 +987,33 @@ When every right side of a logical chain is an inlined object/array/JSX literal,
|
||||
Prettier prints a single-link chain without an indent, but a flattened one indents its tail, which only shifts the last literal.
|
||||
A chain with a non-inlined right side breaks at the operators and keeps the indent.
|
||||
Prettier `main` drops the indent since #19792; the pin (3.9.10) still indents.
|
||||
|
||||
## text-display-width
|
||||
|
||||
- Why: uniform-rule (text width is the display width; oxc#23863, prettier/prettier#19538)
|
||||
- Pin: `tests/fixtures/js/unicode/text-display-width.js`
|
||||
|
||||
```js
|
||||
// input (printWidth 80)
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
|
||||
// ours
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
|
||||
// prettier
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort:
|
||||
"ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
```
|
||||
|
||||
Zero-width characters count as 0 toward the print width (FORMATTER_POLICY.md "Text width"), so the line above fits.
|
||||
Prettier counts the Tibetan subjoined letter `ླ` (`U+0FB3`, a nonspacing mark) as 1 and breaks.
|
||||
Fixed in Prettier `main` by prettier/prettier#19538, which we follow.
|
||||
|
||||
Format characters (`U+200B..U+200F`, `U+FEFF`) are 1 on Prettier `main` too,
|
||||
so a string padded with NBSP / ZWNJ pairs, as in an email preview, breaks in Prettier and stays on one line in ours.
|
||||
@@ -0,0 +1,7 @@
|
||||
// Zero-width characters count as 0 (DIVERGENCES.md#text-display-width).
|
||||
// Tibetan subjoined letters (Mn)
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
// NBSP and ZWNJ (Cf) alternating, like an email preview padding
|
||||
const preview = " ";
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
source: crates/oxc_formatter/tests/fixtures/mod.rs
|
||||
---
|
||||
==================== Input ====================
|
||||
// Zero-width characters count as 0 (DIVERGENCES.md#text-display-width).
|
||||
// Tibetan subjoined letters (Mn)
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
// NBSP and ZWNJ (Cf) alternating, like an email preview padding
|
||||
const preview = " ";
|
||||
|
||||
==================== Output ====================
|
||||
------------------
|
||||
{ printWidth: 80 }
|
||||
------------------
|
||||
// Zero-width characters count as 0 (DIVERGENCES.md#text-display-width).
|
||||
// Tibetan subjoined letters (Mn)
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
// NBSP and ZWNJ (Cf) alternating, like an email preview padding
|
||||
const preview = " ";
|
||||
|
||||
-------------------
|
||||
{ printWidth: 100 }
|
||||
-------------------
|
||||
// Zero-width characters count as 0 (DIVERGENCES.md#text-display-width).
|
||||
// Tibetan subjoined letters (Mn)
|
||||
export default moment.defineLocale("bo", {
|
||||
monthsShort: "ཟླ་1_ཟླ་2_ཟླ་3_ཟླ་4_ཟླ་5_ཟླ་6_ཟླ་7_ཟླ་8_ཟླ་9_ཟླ་10_ཟླ་11_ཟླ་12".split("_"),
|
||||
});
|
||||
// NBSP and ZWNJ (Cf) alternating, like an email preview padding
|
||||
const preview = " ";
|
||||
|
||||
===================== End =====================
|
||||
@@ -62,6 +62,8 @@ Rules:
|
||||
|
||||
- Every divergence has an entry in the owning layer's `DIVERGENCES.md` and a pin fixture
|
||||
- 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, not an entry;
|
||||
each language crate where it shows pins it as an entry citing that section
|
||||
- The pin is a minimal fixture in the owner's `tests/fixtures/`, distilled from whatever exposed the divergence; a conformance file is never the pin, it stays a regression net
|
||||
- The fixture points back at its entry: it is named after the slug, or carries a one-line `DIVERGENCES.md#<slug>` marker
|
||||
(a fixture pinning several entries, or a language where a comment is content); the why and the deviating lines live in the entry
|
||||
@@ -112,6 +114,17 @@ The invariants (placement only; losing a comment is the lossless contract under
|
||||
|
||||
Per-language translations (which tokens are terminators, the compat tables, cursor bounds disciplines) live in each crate's AGENTS.md.
|
||||
|
||||
## Text width
|
||||
|
||||
Printed text is measured by its display width (`TextWidth` / `text_width`, via `unicode-width`):
|
||||
|
||||
- East Asian wide and fullwidth characters, and emoji presentation sequences count as 2
|
||||
- Zero-width characters count as 0: nonspacing and enclosing marks (`Mn` / `Me`), and format characters (`U+200B..U+200F`, `U+FEFF`, ...)
|
||||
- Everything else counts as 1, ambiguous width included
|
||||
|
||||
This holds for the printer's line width and for the width heuristics a language crate measures printed text with (e.g. a short object key).
|
||||
A heuristic Prettier defines by a JS `.length` keeps that measure (e.g. JSDoc wrapping in `oxc_formatter`).
|
||||
|
||||
## Error semantics
|
||||
|
||||
`format()` / `format_to_ir()` return `Err` whenever they cannot produce output they can stand behind:
|
||||
|
||||
@@ -489,6 +489,12 @@ impl TextWidth {
|
||||
/// NOTE: Uses `UnicodeWidthStr::width()` for accurate emoji sequence handling.
|
||||
/// Counting by `char` can lead to incorrect widths for complex Unicode sequences.
|
||||
/// e.g. "🗑️" (U+1F5D1 U+FE0F) is a single emoji with width 2, but counting chars gives width 1.
|
||||
///
|
||||
/// NOTE: Zero-width format characters (U+200B..U+200F, U+FEFF, ...) count as 0 here, Prettier counts them as 1
|
||||
/// (see FORMATTER_POLICY.md "Text width").
|
||||
/// Editors often render them visibly (e.g. VS Code highlights invisible characters),
|
||||
/// so counting them as 1 is a possible alignment.
|
||||
/// It needs a table of them, since `unicode-width` exposes no general category.
|
||||
pub(crate) fn from_text(text: &str, indent_width: IndentWidth) -> TextWidth {
|
||||
let (width, multiline) = Self::measure_text(text, indent_width);
|
||||
Self::encode(width, multiline)
|
||||
|
||||
Reference in new issue
Block a user