rumdl 0.1.88

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
443
444
445
446
# Comparison with markdownlint

This document provides a detailed comparison between rumdl and markdownlint, covering rule compatibility, intentional design differences, and features unique to each tool.

## Quick Summary

rumdl offers **high markdownlint compatibility with intentional differences** while also adding performance improvements and newer features. All 53 markdownlint rules are implemented, but rumdl prefers predictable CommonMark-oriented behavior over bug-for-bug compatibility in a few documented areas.

**Key Differences:**

- **Performance**: rumdl is significantly faster (30-100x in many cases) thanks to Rust and intelligent caching
- **Rule Coverage**: All 53 markdownlint rules are implemented, with a small number of intentional behavioral differences documented below
- **Unique Features**: 18 additional rules (MD057, MD061-MD077), built-in LSP server, VS Code extension, 6 Markdown flavors
- **Configuration**: Automatic markdownlint config discovery and conversion

## Rule Coverage

### Implemented Rules

rumdl implements **71 rules total**: all 53 markdownlint rules plus 18 unique rules.

**Markdownlint-compatible rules (53):** All markdownlint rules are implemented with full compatibility. See the [Rules Reference](rules.md) for the complete list.

**Note:** Rule numbers MD001-MD060 have gaps (MD002, MD006, MD008, MD015-MD017 were never implemented in markdownlint). rumdl maintains these gaps for compatibility.

### Rules Unique to rumdl

rumdl implements 18 additional rules not found in markdownlint:

| Rule   | Name                           | Description                                                |
| ------ | ------------------------------ | ---------------------------------------------------------- |
| MD057  | Relative links                 | Validates that relative file links point to existing files |
| MD061  | Forbidden terms                | Flags usage of configurable forbidden terms                |
| MD062  | Link destination whitespace    | No whitespace in link destinations                         |
| MD063  | Heading capitalization         | Enforces consistent heading capitalization style           |
| MD064  | No multiple consecutive spaces | Flags multiple consecutive spaces in content               |
| MD065  | Blanks around horizontal rules | Horizontal rules should have surrounding blank lines       |
| MD066  | Footnote validation            | Validates footnote references have definitions             |
| MD067  | Footnote definition order      | Footnotes should appear in order of reference              |
| MD068  | Empty footnote definitions     | Footnote definitions should not be empty                   |
| MD069  | No duplicate list markers      | Flags duplicate markers like `- - text` from copy-paste    |
| MD070  | Nested code fence              | Detects nested fence collisions (opt-in)                   |
| MD071  | Blank line after frontmatter   | Frontmatter should be followed by a blank line             |
| MD072  | Frontmatter key sort           | Frontmatter keys should be sorted (opt-in)                 |
| MD073  | TOC validation                 | Table of Contents should match headings (opt-in)           |
| MD074  | MkDocs nav validation          | Validates MkDocs nav entries against the docs tree         |
| MD075  | Orphaned table rows            | Detects headerless pipe tables and orphaned table rows     |
| MD076  | List item spacing              | Enforces consistent blank lines between list items         |
| MD077  | List continuation indent       | Enforces indentation for list continuation content         |

**Opt-in rules:** MD060, MD063, MD070, MD072, MD073, and MD074 are disabled by default. Enable them explicitly in your configuration.

## Intentional Design Differences

### 1. CommonMark Specification Compliance

**rumdl prioritizes CommonMark specification compliance** over bug-for-bug compatibility with markdownlint's parsing.

**Example - List Continuation vs Code Blocks:**

<!-- markdownlint-disable MD046 -->

```markdown
1. List item

    This is a continuation paragraph (4 spaces = continuation)

        This is a code block (8 spaces = continuation indent + 4)
```

<!-- markdownlint-enable MD046 -->

- **markdownlint**: May incorrectly treat 4-space indented paragraphs as code blocks
- **rumdl**: Follows CommonMark: 4 spaces = list continuation, 8 spaces = code block within list
- **Rationale**: Reduces false positives and aligns with the official Markdown spec

**References:**

- [CommonMark List Specification]https://spec.commonmark.org/0.31.2/#lists
- [rumdl Issue #128]https://github.com/rvben/rumdl/issues/128 - False positive fix

### 2. Performance Architecture

**rumdl uses Rust and intelligent caching** for significant performance gains:

- **Cold start**: 30-100x faster than markdownlint on large repositories
- **Incremental**: Only re-lints changed files (Ruff-style caching)
- **Parallel processing**: Multi-threaded file processing and rule execution
- **Zero dependencies**: Single binary, no Node.js runtime required

**Benchmark:** See the [performance comparison](../README.md#performance) in the main README, which shows detailed benchmarks on the Rust Book repository (478 markdown files).

### 3. Auto-fix Mode Differences

Both tools support auto-fixing, but with different philosophies:

**markdownlint:**

- Fixes issues in-place
- Requires `--fix` flag

**rumdl:**

- Two modes: `rumdl fmt` (formatter-style, exits 0) and `rumdl check --fix` (linter-style, exits 0 if all violations fixed, 1 if violations remain)
- `--diff` mode to preview changes
- Parallel file fixing (4.8x faster on multi-file projects)

**Why two modes?**

- `fmt`: Designed for editor integration (doesn't fail on unfixable issues)
- `check --fix`: Designed for CI/CD (fails if violations remain after fixing)

### 4. Configuration Philosophy

**Automatic Discovery:**

rumdl automatically discovers and loads both markdownlint and markdownlint-cli2 config files:

```bash
# rumdl automatically finds and uses these (in precedence order):
.markdownlint-cli2.jsonc
.markdownlint-cli2.yaml
.markdownlint-cli2.yml
.markdownlint.json
.markdownlint.yaml
markdownlint.json
```

For markdownlint-cli2 files, rumdl extracts rule configuration from the `config:` key and ignores cli2-specific keys like `globs` and `ignores`.

**Conversion Tool:**

```bash
# Convert markdownlint config to rumdl format:
rumdl import .markdownlint.json --output .rumdl.toml
```

**Multiple Formats:**

- Native: `.rumdl.toml` (TOML, with JSON schema support)
- Python projects: `pyproject.toml` with `[tool.rumdl]` section
- Markdownlint: Automatic compatibility mode

### 5. Editor Integration

**rumdl includes a built-in Language Server Protocol (LSP) implementation:**

```bash
# Start LSP server
rumdl server

# Install VS Code extension
rumdl vscode
```

**Features:**

- Real-time linting as you type
- Quick fixes for supported rules
- Hover documentation for rules
- Zero configuration required

### 6. Markdown Flavors

**rumdl supports 6 Markdown flavors** to adapt rule behavior for different documentation systems:

| Flavor     | Use Case                     | Key Adjustments                          |
| ---------- | ---------------------------- | ---------------------------------------- |
| `standard` | Default Markdown             | CommonMark + GFM extensions              |
| `gfm`      | GitHub Flavored Markdown     | Security-sensitive HTML, autolinks       |
| `mkdocs`   | MkDocs / Material for MkDocs | Admonitions, tabs, mkdocstrings          |
| `mdx`      | MDX (JSX in Markdown)        | JSX components, ESM imports              |
| `obsidian` | Obsidian knowledge base      | Callouts, Dataview, Templater, wikilinks |
| `quarto`   | Quarto / RMarkdown           | Citations, shortcodes, executable code   |

**Configuration:**

```toml
[global]
flavor = "mkdocs"

[per-file-flavor]
"docs/**/*.md" = "mkdocs"
"**/*.mdx" = "mdx"
```

markdownlint does not have built-in flavor support; users must configure individual rules manually.

## Configuration Compatibility

### Markdownlint Config Auto-Detection

rumdl automatically discovers and loads markdownlint and markdownlint-cli2 configurations:

```yaml
# .markdownlint.yaml (automatically loaded)
MD013: false
MD033:
  allowed_elements: ['br', 'img']
```

```yaml
# .markdownlint-cli2.yaml (also automatically loaded)
config:
  MD013: false
  MD033:
    allowed_elements: ['br', 'img']
```

### Equivalent rumdl Configuration

```toml
# .rumdl.toml
[global]
disable = ["MD013"]

[MD033]
allowed_elements = ["br", "img"]
```

### Configuration Mapping

Most markdownlint options map directly to rumdl:

| markdownlint                 | rumdl                  |
| ---------------------------- | ---------------------- |
| `default: true`              | `[global]` section     |
| Rule by number (`MD013`)     | Same (`[MD013]`)       |
| Rule by name (`line-length`) | Same (`[line-length]`) |
| Disabling: `"MD013": false`  | `disable = ["MD013"]`  |

### Per-File Ignores

Both support per-file rule configuration:

```toml
# rumdl
[per-file-ignores]
"README.md" = ["MD033"]  # Allow HTML in README
"docs/api/**/*.md" = ["MD013"]  # Relax line length in API docs
```

markdownlint uses glob patterns in separate config files or inline comments.

## Inline Configuration Compatibility

rumdl supports both `rumdl` and `markdownlint` inline comment styles:

```markdown
<!-- markdownlint-disable MD013 -->
This line can be as long as needed
<!-- markdownlint-enable MD013 -->

<!-- rumdl-disable MD013 -->
Alternative syntax also supported
<!-- rumdl-enable MD013 -->
```

Both syntaxes work identically in rumdl for seamless migration.

## CLI Differences

### Command Structure

**markdownlint:**

```bash
markdownlint README.md
markdownlint --fix **/*.md
markdownlint --config .markdownlint.json docs/
```

**rumdl:**

```bash
rumdl check README.md
rumdl check --fix .  # or: rumdl fmt .
rumdl check --config .rumdl.toml docs/
```

### Output Formats

Both support:

- Text output (colored, human-readable)
- JSON output (for tool integration)

rumdl additionally supports:

- Source line display with caret underlines (`--output-format full`)
- GitHub Actions annotations (`--output-format github`)
- GitLab, Azure, SARIF, JUnit, and Pylint formats
- Statistics summary (`--statistics`)
- Profiling information (`--profile`)

### Exit Codes

**markdownlint-cli:**

- `0`: No violations
- `1`: Violations found
- `2`: Unable to write output
- `3`: Unable to load custom rules
- `4`: Unexpected error

**rumdl:**

- `0`: Success (or `rumdl fmt` completed successfully)
- `1`: Violations found (or remain after `--fix`)
- `2`: Tool error

## Migration Guide

### From markdownlint to rumdl

1. **Install rumdl:**

    ```bash
   uv tool install rumdl
   # or: cargo install rumdl
   # or: pip install rumdl
   # or: brew install rumdl (See README.md)
    ```

2. **Test with existing config:**

    ```bash
   # rumdl automatically discovers .markdownlint.json
   rumdl check .
    ```

3. **(Optional) Convert config:**

    ```bash
   rumdl import .markdownlint.json
    ```

4. **Update CI/CD:**

    ```yaml
   # Before (markdownlint)
   - run: markdownlint '**/*.md'

   # After (rumdl)
   - run: rumdl check .
    ```

### Known Behavioral Differences

These are intentional deviations where rumdl produces different results than markdownlint. They are design decisions, not bugs.

**MD004 (unordered-list-style):** In `consistent` mode, rumdl uses prevalence-based detection (most common marker wins, ties prefer dash). markdownlint uses the first marker as the standard.

**MD005/MD007 (list-indent / ul-indent):** rumdl uses parent-based dynamic indentation, properly handling ordered lists with variable marker widths (e.g., `1.` vs `10.`). markdownlint may treat
children at different indentation levels as inconsistent.

**MD012 (no-multiple-blanks):** rumdl uses the `filtered_lines()` architecture to skip frontmatter, code blocks, and flavor-specific constructs. This may produce slightly different counts near block
boundaries.

**MD013 (line-length):** rumdl exempts entire lines that are completely unbreakable (URLs with no spaces, long code spans). It also supports `line_length = 0` to mean unlimited. markdownlint exempts
more selectively. rumdl supports markdownlint's `stern`, `heading_line_length`, and `code_block_line_length` options for context-specific limits and a stricter trailing-token policy. One known
divergence: markdownlint exempts heading lines that contain any link, autolink, or image (its `linkOnlyLineNumbers` covers headings because heading text is not classified as paragraph data); rumdl
applies its URL/inline-link suppression to heading lines but does not exempt heading lines that contain bare autolinks. Affected lines can be tagged with `<!-- rumdl-disable-line MD013 -->` if needed.

**MD027 (no-multiple-space-blockquote):** rumdl's `list-items` option defaults to `false`; markdownlint's `list_items` defaults to `true`. List items inside blockquotes inherently need extra
indentation (`>  - item`, continuation lines), so flagging them by default produces noise. Set `list-items = true` to opt into strict markdownlint behavior.

**MD029 (ordered-list-prefix):** rumdl uses CommonMark AST start values. A list starting at `11` expects items 11, 12, 13. rumdl only auto-fixes when `start_value == 1` to preserve explicit numbering
intent.

**MD051 (link-fragments):** rumdl's `ignore-case` option defaults to `true`; markdownlint's `ignore_case` defaults to `false`. Permissive matching better fits multi-platform docs (where some
processors lowercase fragments and others don't), and it preserves rumdl's long-standing behavior. Set `ignore-case = false` to opt into strict markdownlint parity. The companion `ignored-pattern`
option matches markdownlint exactly.

If you encounter other compatibility issues, please [file an issue](https://github.com/rvben/rumdl/issues).

## Feature Comparison Table

| Feature                  | markdownlint       | rumdl                       |
| ------------------------ | ------------------ | --------------------------- |
| **Core Functionality**   |                    |                             |
| Rule count               | 53 implemented     | 71 (53 compatible + 18 new) |
| Auto-fix                 |||
| Configuration file       | ✅ JSON/YAML       | ✅ TOML/JSON/JSONC/YAML/cli2 |
| Inline config            || ✅ (compatible)             |
| Custom rules             | ✅ (JavaScript)    ||
| Markdown flavors         || ✅ 6 flavors                |
| **Performance**          |                    |                             |
| Single file              | Fast               | Very Fast (10-30x)          |
| Large repos (100+ files) | Slow               | Very Fast (30-100x)         |
| Incremental mode         || ✅ (caching)                |
| Parallel processing      | Partial            | ✅ Full                     |
| **Developer Experience** |                    |                             |
| Built-in LSP             |||
| VS Code extension        | ✅ (separate)      | ✅ (built-in)               |
| Watch mode               | Via external tools |`--watch`                |
| Stdin/stdout             |||
| Diff preview             ||`--diff`                 |
| **Installation**         |                    |                             |
| Node.js required         |||
| Python pip               |||
| Rust cargo               |||
| Single binary            |||
| Homebrew                 |||
| **Output & Integration** |                    |                             |
| Text format              |||
| JSON format              |||
| GitHub Actions           || ✅ Enhanced                 |
| Statistics               |||
| Profiling                |||

## CommonMark Compliance

rumdl prioritizes **CommonMark specification compliance** to reduce false positives and align with modern Markdown standards:

| Aspect                    | markdownlint    | rumdl                                  |
| ------------------------- | --------------- | -------------------------------------- |
| List continuation indent  | Custom logic    | CommonMark spec                        |
| Code blocks in lists      | May misdetect   | Spec-compliant (8 spaces)              |
| Heading anchor generation | GitHub-flavored | Multiple styles (GitHub, GitLab, etc.) |
| Reference definitions     | Basic           | Full spec support                      |

### Reporting Compatibility Issues

If you find a compatibility issue with markdownlint:

1. Check [existing issues]https://github.com/rvben/rumdl/issues?q=is%3Aissue+label%3Acompatibility
2. Verify with both tools: `markdownlint file.md` and `rumdl check file.md`
3. [File an issue]https://github.com/rvben/rumdl/issues/new with:
    - Markdown sample
    - Expected behavior (markdownlint output)
    - Actual behavior (rumdl output)
    - Versions of both tools

## See Also

- [Tool Comparison Matrix]comparison.md - Broad comparison of Markdown linters and formatters
- [Comparison with mdformat]mdformat-comparison.md - For users coming from the mdformat formatter

## References

- [markdownlint documentation]https://github.com/DavidAnson/markdownlint
- [CommonMark specification]https://spec.commonmark.org/
- [rumdl GitHub repository]https://github.com/rvben/rumdl
- [rumdl rules documentation]rules.md
- [rumdl flavors documentation]flavors.md