rumdl 0.2.59

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# MD013 - Keep lines short for better readability

Aliases: `line-length`

## What this rule does

Checks that lines don't exceed a maximum length to ensure your content is easy to read on all devices.

## Why this matters

- **Improves readability**: Shorter lines are easier to scan and understand quickly
- **Works everywhere**: Content displays properly on mobile devices, terminals, and narrow windows
- **Better for code reviews**: Side-by-side comparisons work better with reasonable line lengths
- **Accessibility**: Screen readers and assistive technologies handle shorter lines more effectively

## Examples

<!-- rumdl-disable MD013 -->

### ✅ Correct

```markdown
This line is a reasonable length that's easy to read
and displays well on all devices.

Even with links, you can keep lines manageable by using
[reference-style links][1] instead of inline URLs.

[1]: https://example.com/very-long-url-that-would-make-the-line-too-long
```

### ❌ Incorrect

```markdown
This is an extremely long line that goes on and on and makes it difficult to read the content, especially on mobile devices or when viewing files in split-screen editors or during code reviews where horizontal space is limited.
```

### 🔧 Fixed

```markdown
This is a line that has been wrapped to stay within
the maximum length, making it much easier to read
and work with in various contexts.
```

<!-- rumdl-enable MD013 -->

## Configuration

```toml
[MD013]
line-length = 100  # Maximum characters per line (default: 80)
code-blocks = false  # Don't check code blocks (default: true)
code-spans = false  # Don't flag lines whose only overflow is an unbreakable inline code span (default: true)
tables = false  # Don't check tables (default: false)
headings = true  # Check headings (default: true)
math-blocks = false  # Don't check display-math blocks (default: true)
paragraphs = true  # Check paragraph/regular text (default: true)
blockquotes = true  # Check blockquote content (default: true)
strict = false  # Disables exceptions for URLs, etc. (default: false)
stern = false  # Like strict, but unwrappable single-token lines are still permitted (default: false)
ignore-link-urls = true  # Ignore inline link/image URLs when measuring line length (default: true)
heading-line-length = 100  # Optional per-context limit for headings; falls back to line-length when unset
code-block-line-length = 120  # Optional per-context limit for code blocks; falls back to line-length when unset
reflow = false  # Enable automatic text reflow/wrapping (default: false)
reflow-mode = "default"  # Reflow mode: "default", "normalize", "sentence-per-line", or "semantic-line-breaks" (default: "default")
length-mode = "visual"  # How to count line length: "visual", "chars", or "bytes" (default: "visual")
abbreviations = ["Assn", "Univ"]  # Add custom abbreviations for sentence-per-line mode
require-sentence-capital = true  # Require uppercase to start the next sentence (default: true)
atomic-spans = true  # Treat code/emphasis spans as atomic units during reflow (default: true)
reflow-length-exemptions = false  # Measure reflowed lines with the same exemptions the check applies (default: false)
```

### Configuration options explained

- `line-length`: The maximum number of characters allowed per line (set to `0` to disable all line length checks)
- `code-blocks`: Whether to check line length in code blocks (default: `true`)
- `code-spans`: Whether to check lines whose length comes from an inline code span (default: `true`). Inline code spans (`` `like this` ``) cannot be wrapped, so reflow cannot shorten a line whose excess length is one. When `false`, a line is not reported if it would fit within the limit once its inline code spans are excluded - useful with `reflow` so an unbreakable code incantation does not fail an otherwise-clean file
- `tables`: Whether to check line length in tables (default: `false`)
- `headings`: Whether to check line length in headings (default: `true`)
- `math-blocks`: Whether to check line length in display-math blocks (default: `true`). A `$$ ... $$` block is unbreakable in the same way a code block or a table row is: LaTeX cannot be wrapped without changing the equation. When `false`, lines that hold nothing but display math are not reported, covering both a multi-line block and a whole line that is one complete `$$...$$` span, delimiter lines included (as `code-blocks = false` also exempts the surrounding fences). A delimiter line that also carries Markdown (`$$ trailing prose`, `leading prose $$`) is still measured, since that prose is ordinary text. Inline `$...$` math is not covered, for the same reason `code-spans` is a separate option from `code-blocks`. Like the other block flags, this has no effect under `strict`. Independently of this setting, reflow never rewrites a multi-line display-math block, because a TeX `%` comment runs to the end of its line and joining lines would pull later rows into the comment
- `paragraphs`: Whether to check line length in regular text/paragraphs (default: `true`). When false, `line-length` is still used for reflow but no warnings are reported. Blockquote content is treated as paragraph text, so `paragraphs = false` also skips blockquotes
- `blockquotes`: Whether to check line length in blockquote content (default: `true`). Set to `false` to skip blockquote lines specifically without disabling all paragraph checks
- `strict`: When true, disables exceptions for URLs and other special content (default: `false`). Overrides `stern`.
- `stern`: Tighter than the default but laxer than strict. Disables the trailing-token forgiveness used in default mode (so a line is flagged even when only the final token spills past the limit), but keeps the "unwrappable line" exemption — lines that consist of a single non-whitespace token, optionally prefixed by `#` heading or `>` blockquote markers, are still permitted (default: `false`). Mirrors markdownlint's `stern` option.
- `ignore-link-urls`: Whether to forgive a line that exceeds the limit only because of the URL inside an inline `[text](url)` / `![alt](url)` (default: `true`; see [Ignoring inline link URLs](#ignoring-inline-link-urls-non-strict-mode)). Set to `false` to count those URLs toward the line length. Combined with `stern`, this flags a link line that has wrappable text around it while still exempting a line that is a single unbreakable token (a bare URL or a standalone link): stricter than `stern` about links, but without `strict` flagging genuinely unbreakable link lines. Has no effect under `strict` (which already disables all forgiveness). The former key `semantic-link-understanding` is still accepted as an alias.
- `heading-line-length`: Per-context maximum length for heading lines. Unset (`null`) falls back to `line-length`; `0` means "no limit for headings". Mirrors markdownlint's `heading_line_length` option.
- `code-block-line-length`: Per-context maximum length for fenced or indented code-block lines. Unset (`null`) falls back to `line-length`; `0` means "no limit for code blocks". Mirrors markdownlint's `code_block_line_length` option.
- `reflow`: When true, enables automatic text reflow to wrap long lines intelligently (default: `false`)
- `reflow-mode`: Controls how text is reflowed when `reflow` is true (default: `"default"`, see Reflow Modes section below)
- `length-mode`: How to calculate line length (default: `"visual"`):
  - `"visual"`: Count visual display width (emoji = 2 columns, CJK = 2 columns).
    **Recommended and default**. Correctly handles international content and matches terminal display.
  - `"chars"`: Count Unicode characters (emoji = 1, CJK = 1). Use only for backward compatibility.
  - `"bytes"`: Count raw UTF-8 bytes (not recommended for Unicode text).
- `abbreviations`: Custom abbreviations for sentence-per-line mode (optional)
  - Periods are optional: both `"Dr"` and `"Dr."` work the same
  - Added to built-in defaults: `Mr`, `Mrs`, `Ms`, `Dr`, `Prof`, `Sr`, `Jr`, `i.e`, `e.g`, `vs`, `fig`, `no`, `vol`, `ch`, `sec`, `al`
- `require-sentence-capital`: Whether a sentence boundary requires the next sentence to start with an uppercase letter (default: `true`)
  - When `true`, only `word. Capital` is treated as a sentence boundary (fewer false positives). A digit counts as opening a sentence too, since a lowercase continuation is what the setting guards against: `The count was 5. 2 of them failed.` and `Released in 2020. 3rd edition.` split, `See fig. 3` and `The count was 5. and that was all` do not
  - When `false`, `word. lowercase` is also treated as a sentence boundary (more splitting)
  - A bare `!` or `?` is always a sentence boundary, since neither is ambiguous the way a period is (which also ends abbreviations, decimals and initials). Inside a quotation the setting does apply, because the question can belong to the quoted phrase rather than to the sentence carrying it: `A "Is this a test?" guide` stays one sentence
- `atomic-spans`: Whether to hold emphasis/strong/strikethrough and code spans atomic during reflow (default: `true`). When `true`, these spans are treated as atomic units. When `false`, they can be wrapped word-by-word like normal text. An emphasis/strong/strikethrough span that is on its own longer than `line-length` is wrapped even when `atomic-spans` is `true`, since holding it whole would leave a line that can never fit. Such a span wraps at whitespace outside any nested construct. A nested code span, link, image or HTML tag is never broken, since the whitespace inside one is literal (code) or structural (a link destination), so a line break there would rewrite the document. A nested emphasis, strong or strikethrough span does wrap: the whitespace inside it is ordinary prose whitespace, and only its delimiters are held to the words they flank. This is what lets a span whose entire content is another span, such as `***text***`, wrap at all. A span containing a marker character that is not part of a well-formed nested span (a stray or backslash-escaped `` ` ``, `*`, `_` or `~`) is kept atomic rather than broken on a guess. Code spans themselves are never broken while `atomic-spans` is `true`.
- `reflow-length-exemptions`: Whether reflow measures a line with the same length exemptions the check applies (default: `false`). Off, reflow measures the markdown as written, so a paragraph whose only excess is an inline link destination is rewrapped even though the check forgives it. On, reflow consults `ignore-link-urls` (an inline `[text](url)` costs `[text]`, `![alt](url)` costs `![alt]`) and `code-spans` (a code span costs nothing), and leaves such a paragraph alone. See [Measuring reflowed lines the way the check measures them](#measuring-reflowed-lines-the-way-the-check-measures-them).

## Ignoring inline link URLs (non-strict mode)

In non-strict mode, this rule understands that inline links and images often contain
long URLs that the author cannot reasonably shorten. When a line exceeds the limit,
the rule checks whether replacing each `[text](url)` with just `[text]` (and
`![alt](url)` with `![alt]`) would bring the line within the limit. If so, the
warning is suppressed.

This applies to:

- Inline links: `[text](url)` and `[text](url "title")`
- Inline images: `![alt](url)`
- Nested badge patterns: `[![alt](img-url)](link-url)`

This does **not** apply to:

- Reference links (`[text][ref]`) — these already have no inline URL
- Autolinks (`<url>`) — the URL itself is the visible content
- Strict mode — all exceptions are disabled
- **Reflow, by default.** `ignore-link-urls` decides whether a line is
  *reported*; on its own it does not change how reflow *measures* one. Reflow
  measures the markdown as written, including link destinations, so a paragraph
  whose only over-length cause is a link URL is still rewrapped and the link
  still lands on its own line. prettier (`--prose-wrap always`) and mdformat
  (`--wrap`) wrap such a paragraph the same way. `stern` and `code-spans` leave
  reflow alone in the same way. `strict` is the exception: it removes the
  standalone-link and HTML-line exemptions everywhere, including inside reflow,
  so it can make reflow report a paragraph that is otherwise left alone. Set
  `reflow-length-exemptions = true` to have reflow honor this exemption, or
  `reflow = false` to keep those lines as authored.
- `ignore-link-urls = false`: count inline link/image URLs toward the line length. With `stern`, this flags a link line that has wrappable text around it, while a line that is a single unbreakable token (a bare URL or a standalone link) is still permitted by stern's unwrappable-line exemption.

## Measuring reflowed lines the way the check measures them

By default the two halves of MD013 measure differently. The check forgives a line
whose only excess is an inline link destination or an inline code span; reflow
measures every character it can see. On a paragraph like

```markdown
The release notes for [the migration guide](https://example.com/docs/guides/migration/v3) explain it.
```

the check says nothing, and `rumdl fmt` still rewraps the paragraph so the link
sits alone on a line that remains over the limit. Nothing is wrong with the
result, but the formatter has rewritten a paragraph the linter never objected to.

`reflow-length-exemptions = true` makes reflow ask the same question the check
asks. It reads no new options: `ignore-link-urls` and `code-spans` already
describe which exemptions are in force, and reflow now consults exactly those.
The paragraph above is left as authored, and a paragraph that is genuinely too
long is wrapped as before.

The two exemptions stay independent, as they are in the check. A line is left
alone when discounting link destinations brings it under the limit, or when
discounting code spans does, never when only the two savings together would.
What the option cannot produce is a line the check reports and reflow declines
to wrap: reflow stays at least as willing to wrap as the check is to complain.

Only inline links and images are discounted. Reference (`[text][ref]`),
collapsed and shortcut forms carry no destination and are measured in full, and a
construct reflow cannot resolve is measured in full as well, so the setting never
leaves behind a line the check would report.

### Example

With `line-length = 80`:

```markdown
<!-- No warning: text without URLs is only ~30 chars -->
See the [installation guide](https://example.com/docs/getting-started/installation/v2) for details.

<!-- Warning: text alone still exceeds 80 chars -->
This is already a very long sentence with lots of words that pushes past the limit even [without](https://example.com) the URL.
```

## Automatic fixes

When `reflow` is set to `true`, this rule can automatically wrap long lines while preserving Markdown formatting:

- Intelligently breaks lines at appropriate points
- Preserves bold, italic, links, code spans, and other Markdown elements
- Reflows list items using the configured marker spacing and aligns continuation lines to the content column (see [List reflow and marker spacing](#list-reflow-and-marker-spacing))
- Reflows blockquote paragraphs (including lazy continuation lines) while preserving input style
- Preserves hard line breaks (two trailing spaces)
- Does not wrap code blocks, tables, headings, or reference definitions
- Does not wrap a multi-line display-math block. A TeX `%` comment runs to the end
  of its line, so joining the lines would pull whatever followed on later lines
  into the comment and drop it from the rendered equation, possibly leaving an
  environment unclosed. Use `math-blocks = false` to also stop reporting the
  length of those lines

### List reflow and marker spacing

When reflowing list items (including nested and blockquote-nested lists), rumdl
does not hard-code the spacing after a list marker. In the spacing-normalizing
modes (`default` and `normalize`) it derives the number of spaces after the
marker from the [MD030](md030.md) configuration — `ul-single`/`ul-multi` for
unordered lists, `ol-single`/`ol-multi` for ordered lists, and the
`ol-align-column` override — choosing the single- or multi-line value the same
way MD030 does. Continuation (wrapped) lines are then indented to the resulting
content column, so wrapped text aligns under the item's text exactly as
[MD007](md007.md)'s text-aligned style expects. With the default MD030 setting
(a single space) the output is unchanged.

The `sentence-per-line` and `semantic-line-breaks` modes only change where lines
break, so they preserve the marker spacing and indentation already in the
source. The MkDocs flavor keeps its own fixed 4-space structural indent.

Example (`line-length = 50`, MD030 `ol-multi = 2`):

```markdown
# Before
1. This is a fairly long numbered item that wraps onto another line.

# After
1.  This is a fairly long numbered item that wraps
    onto another line.
```

### Blockquote reflow style preservation

When reflowing blockquote paragraphs, rumdl preserves the source style:

- Explicit continuation input stays explicit (`> ` on wrapped lines)
- Lazy continuation input stays lazy when safe
- Mixed explicit/lazy input uses deterministic style selection (explicit on ties)
- Lazy output is automatically upgraded to explicit for lines that would otherwise start a new block structure

Example (`line-length = 60`):

```markdown
# Before
> This is a very long blockquote line that should be wrapped while preserving the source style and markdown structure.

# After
> This is a very long blockquote line that should be wrapped
> while preserving the source style and markdown structure.
```

### Reflow Modes

The `reflow-mode` option controls how text is reformatted when `reflow` is true:

#### `default` mode

Standard text wrapping that breaks lines at word boundaries to fit within the configured line length.

```toml
[MD013]
line-length = 80
reflow = true
reflow-mode = "default"
```

#### `normalize` mode

Normalizes all paragraph text to consistently wrap at the configured line length, removing irregular line breaks.

```toml
[MD013]
line-length = 80
reflow = true
reflow-mode = "normalize"
```

This mode is useful for standardizing documents with inconsistent line wrapping.

#### `sentence-per-line` mode

Enforces one sentence per line, making diffs cleaner and easier to review, among [other advantages](https://nick.groenen.me/notes/one-sentence-per-line/). This mode:

- Detects sentence boundaries (periods, exclamation marks, question marks)
- Handles common abbreviations (e.g., i.e., Mr., Dr., Ph.D., Inc., etc.) without breaking sentences
- Preserves decimal numbers and ellipses
- Works with markdown formatting: a boundary is only recognized in prose, never
  inside a link's text, destination or title, an image's alt text, a code span,
  a wikilink, a math span or an HTML tag, so `[See fig. 2. Then read on](url)`
  stays whole. A sentence may start with a link or image; the capital behind
  the opener counts as its first letter, so
  `First sentence. [Second sentence](url) here.` splits before the link. A
  bracket is a link only where the parser reads one, so `[First](unterminated`
  and a citation like `Claim. [Smith 2020]` are prose and do not start a
  sentence, unless `[smith 2020]: url` is defined in the document, which makes
  the citation a shortcut reference link and a sentence opener

```toml
[MD013]
line-length = 80
reflow = true
reflow-mode = "sentence-per-line"
```

Example transformation:

```markdown
# Before
This is the first sentence. This is the second sentence. And this is the third.

# After
This is the first sentence.
This is the second sentence.
And this is the third.
```

This mode is particularly useful for:

- Technical documentation where each sentence often contains a single concept
- Documents maintained in version control where sentence-level diffs are clearer
- Collaborative writing where different authors work on different sentences

**Safety exception:** a sentence is kept on the previous line when placing it
on its own line would change the document's structure. Text that starts with a
list marker (`- `, `* `, `+ `, `1. `), blockquote marker (`>`), heading marker
(`# `), code fence, horizontal rule, reference definition (`[label]:`), or
block-level HTML tag becomes real block syntax at the start of a line, so
reflow never puts it there:

```markdown
# Stays on one line (the dash clause would otherwise become a list item)
Google Calendar (Can't we get rid of this dependency? - I don't really see the need)
```

Structure always wins over strict one-sentence-per-line in these cases. The
same guard applies to line wrapping in all reflow modes, where a wrapped line
is instead broken one word earlier (or left slightly over the limit when no
earlier break point exists).

When wrapping, an ordered marker counts only when it could genuinely interrupt
the paragraph, which means numbered 1 with content after it. `7. item` and a
bare `123456.` are prose to the parser, so a wrapped line may start with them.
Sentence-per-line output is stricter: every line it produces ends a sentence,
and a line opening `2. Do that.` under one reads as a list item (MD032 reports
it as one), so an inline enumerator of any number stays on the line of the
sentence before it: `Steps: 1. Do this. 2. Do that.` becomes `Steps: 1.`,
`Do this. 2.`, `Do that.` rather than a line opening with `2.`.

#### `semantic-line-breaks` mode

Breaks lines at semantic boundaries using a cascading strategy. Unlike `sentence-per-line` (which always breaks at every sentence boundary only), this mode also splits long sentences at clause punctuation and break-words to keep lines within the configured line length.

**Cascading priority:**

| Priority | Split point | When applied |
|----------|-------------|--------------|
| 1 | Sentence boundaries (`.` `!` `?`) | Always |
| 2 | Clause punctuation (`,` `;` `:` `—`) | When line > line-length |
| 3 | Break-words (`and`, `or`, `but`, `which`, `because`, ...) | When line still > line-length |
| 4 | Word wrap | Fallback |

```toml
[MD013]
line-length = 80
reflow = true
reflow-mode = "semantic-line-breaks"
```

Example transformation:

```markdown
# Before
All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.

# After (with line-length = 80)
All human beings are born free and equal in dignity and rights.
They are endowed with reason and conscience
and should act towards one another in a spirit of brotherhood.
```

**Break-words list:** `and`, `or`, `but`, `nor`, `yet`, `so`, `for`, `which`, `that`, `because`, `when`, `if`, `while`, `where`, `although`, `though`, `unless`, `since`, `after`, `before`, `until`, `as`, `once`, `whether`, `however`, `therefore`, `moreover`, `furthermore`, `nevertheless`, `whereas`

With `line-length = 0`, only sentence boundaries are used (no cascading), behaving like `sentence-per-line`.

#### Sentence-per-line without line length warnings

If you want to use sentence-per-line mode for formatting but don't want warnings about long sentences, you can disable paragraph checking while keeping code blocks and tables checked:

```toml
[MD013]
line-length = 80
paragraphs = false  # Don't warn about long paragraphs
code-blocks = true  # Still check code blocks
tables = true  # Still check tables
reflow = true
reflow-mode = "sentence-per-line"
```

This configuration is useful when:

- You want automatic sentence-per-line formatting without validation noise
- You care about line length in code blocks and tables but not in regular text
- You're using semantic line breaks where sentence length is determined by content, not arbitrary limits

#### Disabling all line length checks

If you want to completely disable all line length checking (for paragraphs, headings, code blocks, and tables), set `line-length` to `0`:

```toml
[MD013]
line-length = 0  # Disable all line length checks
reflow = true
reflow-mode = "sentence-per-line"
```

When `line-length` is set to `0`, no line length warnings will be reported for any content type. This is particularly useful for:

- Projects using semantic line breaks where lines break at logical boundaries regardless of length
- Sentence-per-line workflows where line length is not a concern
- Documentation where long lines (e.g., URLs, technical terms) are unavoidable and acceptable

**Note**: With `line-length: 0` and `reflow-mode: "sentence-per-line"`, rumdl will:

- Split multiple sentences that share a line into separate lines
- Join single sentences that span multiple lines into one line (since there's no length constraint)
- Provide consistent "one sentence = one line" formatting throughout your document

### Example with automatic reflow

```toml
[MD013]
line-length = 80
reflow = true
```

With this configuration, long lines will be automatically wrapped to fit within 80 characters while maintaining proper Markdown formatting.

**Note**: When `reflow` is `false` (default), automatic fixes are not available and you'll need to manually wrap long lines.

## Learn more

- [Line length best practices](https://en.wikipedia.org/wiki/Line_length)
- [Readability and line length](https://baymard.com/blog/line-length-readability)

## Related rules

- [MD009](md009.md): Remove trailing spaces at line ends
- [MD010](md010.md): Use spaces instead of tabs
- [MD047](md047.md): End files with a single newline