rumdl 0.3.3

A fast Markdown linter and formatter written in Rust
Documentation
# MD088 - Normalize to ASCII quotes and dashes

Aliases: `quotes-dashes`

**Disabled by default.** This rule is opt-in: enable it explicitly with
`extend-enable`.

## What this rule does

This rule detects Unicode quotes and dashes that look like their ASCII equivalents, and replaces them with plain ASCII characters.

By default, it will only replace multiple styles of single and double quotes by their simple ASCII equivalents.

You can optionally enable replacement for dashes and for non-ASCII spaces.

Characters covered by this rule:

| Unicode Character Name                     | Character | Codepoint | ASCII Replacement |
| ------------------------------------------ | --------- | --------- | ----------------- |
| Left Single Quotation Mark                 | ‘         | U+2018    | '                 |
| Right Single Quotation Mark                | ’         | U+2019    | '                 |
| Single Low-9 Quotation Mark                | ‚         | U+201A    | '                 |
| Single High-Reversed-9 Quotation Mark      | ‛         | U+201B    | '                 |
| Prime                                      | ′         | U+2032    | '                 |
| Left Double Quotation Mark                 | “         | U+201C    | "                 |
| Right Double Quotation Mark                | ”         | U+201D    | "                 |
| Double Low-9 Quotation Mark                | „         | U+201E    | "                 |
| Double High-Reversed-9 Quotation Mark      | ‟         | U+201F    | "                 |
| Double Prime                               | ″         | U+2033    | "                 |
| Hyphen                                     | ‐         | U+2010    | -                 |
| Non-Breaking Hyphen                        | ‑         | U+2011    | -                 |
| Figure Dash                                | ‒         | U+2012    | -                 |
| En Dash                                    | –         | U+2013    | -                 |
| Em Dash                                    | —         | U+2014    | -                 |
| Horizontal Bar                             | ―         | U+2015    | -                 |

With `normalize-spaces` enabled, these non-ASCII spaces are replaced by a plain
ASCII space (U+0020):

| Unicode Character Name    | Codepoint        |
| ------------------------- | ---------------- |
| No-Break Space            | U+00A0           |
| En Quad, Em Quad          | U+2000, U+2001   |
| En Space, Em Space        | U+2002, U+2003   |
| Three-Per-Em Space        | U+2004           |
| Four-Per-Em Space         | U+2005           |
| Six-Per-Em Space          | U+2006           |
| Figure Space              | U+2007           |
| Punctuation Space         | U+2008           |
| Thin Space                | U+2009           |
| Hair Space                | U+200A           |
| Narrow No-Break Space     | U+202F           |
| Medium Mathematical Space | U+205F           |
| Ideographic Space         | U+3000           |

Spaces are invisible, so the diagnostic names the codepoint (for example
`U+00A0 (No-Break Space)`) instead of printing the character. The zero-width
characters belong to MD084, the line and paragraph separators (U+2028, U+2029,
U+0085) are line breaks rather than spaces, and U+1680 (Ogham space mark) is
specific to one script, so none of them are covered.

Many of these spaces are intentional: French and Dutch typography puts a no-break
space or a narrow no-break space before `:;?!`, and CJK text uses the ideographic
space. That is why the option is off by default; list the ones to keep in `allow`.
The HTML entities ` ` and ` ` are not affected, as they are written in
ASCII in the source.

Characters that a language spells words with are not covered, because replacing
them would change the word rather than its typography. That includes the modifier
letters `ʼ` (U+02BC, Crimean Tatar, Nenets, Chechen) and `ʻ` (U+02BB, the Hawaiian
okina), and the guillemets `«` and `»`, which are the quotation marks of several
languages rather than look-alikes for ASCII ones.

This rule checks only prose content and skips:

- YAML front matter
- fenced code blocks
- indented code blocks
- inline code spans
- math, both `$$ ... $$` blocks and inline `$ ... $` spans, where `′` and `″` are
  the prime and double prime of a derivative or a unit

## Why this matters

While more typographically correct, those characters only offer a very marginal readability gain.

Fancy quotes and dashes often come from smart punctuation features or
copy/paste from rich text sources. These characters can create variability, noisy diffs and
unexpected behavior in tooling that expects ASCII punctuation.

Bad handling of Unicode characters in editors/IDEs is also a source of "Mojibake",
which are sequences of strange characters generated by decoding a Unicode-encoded
character with another charset like ISO-8859-x or CP125x. See MD083 for a rule that
detects such Mojibake.

Normalizing quotes and dashes to ASCII keeps Markdown content predictable and avoids
hidden character drift across contributors and platforms.

## Examples

### Incorrect

```text
It’s “quoted” text with ‛variants’.
```

### Correct

```text
It's "quoted" text with 'variants'.
```

### Auto-fix behavior for spaces

CommonMark gives only an ASCII space or tab a structural meaning, so replacing a
non-ASCII space can turn a paragraph into something else. rumdl therefore parses the
document both as written and with the replacement applied, and only rewrites a space
when the Markdown structure is identical: same blocks, same inline elements, same
link and heading attributes. If the replacement would change the structure, for
example by creating a heading, a list item, a task marker, a closing `#` sequence, a
table, a link reference definition, a hard break or an HTML block, or by changing
emphasis or a link, the space is reported without an automatic fix. Spaces whose
replacements only matter together are all left unfixed. Each top-level block (and
each item of a top-level list) is checked on its own text, then the spaces judged
safe are checked together against the whole document. The check has a fixed parsing
budget proportional to the size of the document; a space the budget cannot cover is
reported without a fix, never fixed unchecked. For example, these lines are
paragraphs today and stay reported only (`~` stands for a no-break space):

```text
#~Not a heading
1.~Not a list item
trailing text~
~Leading space
```

### Auto-fix behavior in code

```text
Prose ’quote’ and `code ’quote’`
```

Fixes to:

```text
Prose 'quote' and `code ’quote’`
```

The inline code span is intentionally unchanged.

## Configuration

```toml
[MD088]
normalize-quotes = true
normalize-dashes = false
normalize-spaces = false
allow = []
```

- `normalize-quotes`: if enabled (by default), replace Unicode quotes by simple ASCII ones.
- `normalize-dashes`: if enabled, replace Unicode dashes by simple ASCII ones.
- `normalize-spaces`: if enabled, replace non-ASCII spaces by a plain ASCII space
  (default: `false`). Spaces where the replacement could change the Markdown
  structure are reported without an automatic fix.
- `allow`: A list of codepoints to preserve, written as the direct Unicode character,
  or as `U+XXXX`, `U+XXXXX`, or `U+XXXXXX` (for example `U+2019`).
  Any allowed codepoint is ignored by MD088 and will not be replaced.

Example:

```toml
[MD088]
enabled = true
normalize-quotes = true
normalize-dashes = true
allow = ["U+2032", "U+2033"]
```

With this configuration, the rule is enabled, quotes and dashes are replaced,
excepted "prime" `′` and "double prime" `″`.