markdownlint-rs 0.3.15

A fast, flexible, configuration-based command-line interface for linting Markdown/CommonMark files
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
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# mdlint

[![CI](https://github.com/swanysimon/mdlint/workflows/CI/badge.svg)](https://github.com/swanysimon/mdlint/actions/workflows/ci.yml?query=branch%3Amain)
[![Crates.io](https://img.shields.io/crates/v/markdownlint-rs.svg)](https://crates.io/crates/markdownlint-rs)

An opinionated Markdown formatter and linter, written in Rust.

What [ruff](https://github.com/astral-sh/ruff/) did for Python and [gofmt](https://pkg.go.dev/cmd/gofmt) did for Go,
`mdlint` aims to do for Markdown: enforce a single, consistent canonical style so that style debates disappear and diffs
stay meaningful. As AI coding agents increasingly read and write Markdown, well-structured files matter more than ever.
Run `mdlint format` and stop thinking about it.

**Project Status**: Active development, in between my day job.

## Features

- **Formatter first**: `mdlint format` rewrites files to a canonical style — no configuration required
- **Linter second**: `mdlint check` reports violations; most are auto-fixable by the formatter
- **Fast**: written in Rust for performance
- **Portable**: single, small, 0-dependency binary (Linux x86_64/ARM64, macOS Intel/Apple Silicon, Windows)
- **Git-aware**: respects `.gitignore` files by default

## Installation

Pick between downloading binaries from GitHub releases, pulling from
[crates.io](https://crates.io/crates/markdownlint-rs), or using a
[Docker container](https://github.com/swanysimon/mdlint/pkgs/container/mdlint).
A [Homebrew](https://brew.sh) formula is planned.

### From GitHub Releases (Recommended)

Download the latest release for your platform from the
[releases page](https://github.com/swanysimon/mdlint/releases), or download the binary via the command line.
For example, to download the latest Linux x86_64 build:

```shell
curl -LO https://github.com/swanysimon/mdlint/releases/latest/download/mdlint-linux-x86_64.tar.gz
tar xzf mdlint-linux-x86_64.tar.gz

# verify checksum
sha256sum -c mdlint-*.sha256

# add to PATH
sudo mv mdlint /usr/local/bin/
```

### From crates.io

```shell
cargo install markdownlint-rs
```

### From Docker

```shell
# check files in the current directory
docker run --rm -v "$PWD:/workspace" ghcr.io/swanysimon/mdlint:latest check

# format files in the current directory
docker run --rm -v "$PWD:/workspace" ghcr.io/swanysimon/mdlint:latest format
```

The Docker image supports both `linux/amd64` and `linux/arm64` platforms.

### From pip

```shell
pip install markdownlint-rs
```

### From npm

```shell
npm install --save-dev markdownlint-rs
npx mdlint check
```

Or globally:

```shell
npm install -g markdownlint-rs
mdlint check
```

### From source

```shell
git clone https://github.com/swanysimon/mdlint.git
cd mdlint
cargo build --release
sudo cp target/release/mdlint /usr/local/bin/
```

## Usage

### Basic Usage

**Check** Markdown files for issues:

```shell
# check all Markdown files (auto-detected)
mdlint check

# check specific files or directories
mdlint check README.md docs/

# check and apply auto-fixes
mdlint check --fix
```

**Format** Markdown files (opinionated, fixes everything):

```shell
# format all Markdown files
mdlint format

# verify formatting without modifying files
mdlint format --check

# format specific files or directories
mdlint format README.md docs/
```

### Command-Line Options

#### `mdlint check`

Lint Markdown files and report issues.

```text
Usage: mdlint check [OPTIONS] [FILES]...

Arguments:
  [FILES]...              Files or directories to check (defaults to current directory)

Options:
      --fix               Apply auto-fixes where possible
      --format <FORMAT>   Output format: default or json [default: default]
      --exclude <PATH>    Exclude files or directories
      --config <CONFIG>   Path to configuration file
  -v, --verbose           Print each file name as it is checked
      --color <COLOR>     Color output: auto, always, never [default: auto]
  -h, --help              Print help
```

#### `mdlint format`

Format Markdown files with opinionated fixes.

```text
Usage: mdlint format [OPTIONS] [FILES]...

Arguments:
  [FILES]...              Files or directories to format (defaults to current directory)

Options:
      --check             Only verify formatting, don't modify files
      --exclude <PATH>    Exclude files or directories
      --config <CONFIG>   Path to configuration file
      --color <COLOR>     Color output: auto, always, never [default: auto]
  -h, --help              Print help
```

### Examples

**Check with auto-fix:**

```bash
mdlint check --fix
```

**Check with custom config file:**

```bash
mdlint check --config mdlint.toml
```

**Check with JSON output:**

```bash
mdlint check --format json
```

**Check specific files:**

```bash
mdlint check README.md CONTRIBUTING.md docs/
```

**Format all files:**

```bash
mdlint format
```

**Verify formatting in CI:**

```bash
mdlint format --check
```

**Disable color output:**

```bash
mdlint check --color never
```

**Show each file as it is checked:**

```bash
mdlint check --verbose
```

## Configuration

mdlint uses TOML configuration files, similar to how ruff uses `ruff.toml`.
The tool automatically discovers configuration files by searching up from the current directory.

### Configuration File Locations

The tool searches for these files in order (first found wins per directory level):

1. `mdlint.toml`
2. `.mdlint.toml`

### Configuration File Format

Create a `mdlint.toml` file in your project root:

```toml
# Enable all rules by default
default_enabled = true

# Respect .gitignore files when discovering files
gitignore = true

# Disable inline configuration comments
no_inline_config = false

# Rule-specific configuration
[rules.MD013]
line_length = 120
heading_line_length = 80
code_blocks = false

[rules.MD003]
style = "atx"

[rules.MD004]
style = "asterisk"

[rules.MD007]
indent = 2

# Disable specific rules
[rules.MD034]
enabled = false
```

### Configuration Options

See [`mdlint.default.toml`](mdlint.default.toml) for every option with its default value and a
description of what it does. The global options are summarised below.

#### Global Options

- `default_enabled` (boolean): When `true`, all rules are enabled unless explicitly disabled. Default: `false`
- `gitignore` (boolean): Respect `.gitignore` files when discovering markdown files. Default: `true`
- `no_inline_config` (boolean): Disable inline configuration via HTML comments. Default: `false`
- `exclude` (array): Paths to exclude from file discovery; merged with any `--exclude` CLI flags. Default: `[]`
- `custom_rules` (array): Paths to custom rule modules (future feature). Default: `[]`
- `front_matter` (string): Pattern for front matter detection. Default: auto-detects YAML (`---`) and TOML (`+++`)
- `fix` (boolean): When `true`, `mdlint check` automatically applies all auto-fixable violations,
  equivalent to passing `--fix` on the command line. Default: `true`

#### Rule Configuration

Rules can be configured in three ways:

1. **Enable/Disable a rule:**

   ```toml
   [rules.MD013]
   enabled = false
   ```

2. **Configure rule parameters:**

   ```toml
   [rules.MD013]
   line_length = 100
   code_blocks = false
   ```

3. **Use both (parameters implicitly enable the rule):**

   ```toml
   [rules.MD003]
   style = "atx"
   ```

### Configuration Hierarchy

Configurations are discovered by walking up the directory tree. When multiple configs are found, they are merged with
the following precedence (highest to lowest):

1. Command-line options (`--config`)
2. Local directory config (`mdlint.toml` in current dir)
3. Parent directory configs (walking up to root)
4. Default configuration

Later configs override earlier ones for scalar values. When a rule is configured in multiple places, the most specific
configuration wins.

See the [markdownlint rules documentation](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) for
details on each rule and its configuration options.

### Inline Configuration

Rules can be suppressed for specific lines using HTML comments, without modifying `mdlint.toml`:

```markdown
<!-- mdlint-disable-next-line MD013 -->
This line may be longer than the configured limit.

<!-- mdlint-disable MD033 -->
<div>Raw HTML block that needs to stay as-is</div>
<!-- mdlint-enable MD033 -->
```

| Comment | Effect |
| --- | --- |
| `<!-- mdlint-disable MD001 -->` | Disable rule from this line onward |
| `<!-- mdlint-enable MD001 -->` | Re-enable rule from this line onward |
| `<!-- mdlint-disable-next-line MD001 -->` | Disable rule for the next line only |
| `<!-- mdlint-disable -->` | Disable all rules from this line onward |
| `<!-- mdlint-enable -->` | Re-enable all rules |

Multiple rules: `<!-- mdlint-disable MD001 MD013 -->` — space-separate rule codes.
Set `no_inline_config = true` in `mdlint.toml` to ignore all inline comments.

## Exit Codes

- **0**: Success - no linting errors found (or files successfully formatted with `format`)
- **1**: Linting errors found (or formatting issues found with `format --check`)
- **2**: Runtime error (invalid config, file not found, etc.)

Use exit codes in CI/CD pipelines:

```bash
# Fail build on linting errors
mdlint check || exit 1

# Fail build if files need formatting
mdlint format --check || exit 1
```

## Supported Rules

mdlint implements the [markdownlint](https://github.com/DavidAnson/markdownlint) rule set. Rules marked
✅ are enforced automatically by `mdlint format`; rules marked ❌ are reported by `mdlint check` only.

| Rule | Description | Format fixes |
| --- | --- | --- |
| MD001 | Heading levels should only increment by one level at a time ||
| MD003 | Heading style ||
| MD004 | Unordered list style ||
| MD005 | Inconsistent indentation for list items at the same level ||
| MD007 | Unordered list indentation ||
| MD009 | Trailing spaces ||
| MD010 | Hard tabs ||
| MD011 | Reversed link syntax ||
| MD012 | Multiple consecutive blank lines ||
| MD013 | Line length ||
| MD018 | No space after hash on atx style heading ||
| MD019 | Multiple spaces after hash on atx style heading ||
| MD022 | Headings should be surrounded by blank lines ||
| MD023 | Headings must start at the beginning of the line ||
| MD025 | Multiple top-level headings in the same document ||
| ... | See [markdownlint rules]https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md | ... |

## Pre-commit Hooks

### Native git hook

Create `.git/hooks/pre-commit` (and make it executable with `chmod +x`):

```bash
#!/bin/sh
mdlint format --check
```

This causes `git commit` to fail if any staged Markdown file needs formatting.

### pre-commit framework

Add to `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/swanysimon/mdlint
    rev: v0.3.3  # use the latest release tag
    hooks:
      - id: mdlint-format-check
        name: mdlint format --check
        language: system
        entry: mdlint format --check
        types: [markdown]
      - id: mdlint-check
        name: mdlint check
        language: system
        entry: mdlint check
        types: [markdown]
```

Or use `mdlint check --fix` to auto-fix and stage the result:

```yaml
      - id: mdlint-fix
        name: mdlint check --fix
        language: system
        entry: mdlint check --fix
        types: [markdown]
        pass_filenames: false
```

### GitHub Actions

```yaml
- name: Check Markdown formatting
  run: mdlint format --check

- name: Lint Markdown
  run: mdlint check
```

## Contributing

Contributions are welcome!

### Development setup

Prerequisites: [mise](https://mise.jdx.dev/) and [Rust](https://rustup.rs/).
Optionally, Docker is needed for Dockerfile linting.
[uv](https://docs.astral.sh/uv/) is required only if working on the Python package.

```bash
git clone https://github.com/swanysimon/mdlint.git
cd mdlint
mise install   # installs prek, tombi, hadolint
cargo build
```

### Code quality

All quality checks run via `prek run -a`. This must pass before submitting a pull request.

### Pull request process

1. Create a feature branch from `main`
2. Make focused commits with clear messages
3. Add tests for new functionality
4. Run `prek run -a` and fix any failures
5. Submit a PR with a description of what changed and why

### Release process

Releases use [`cargo-release`](https://github.com/crate-ci/cargo-release), which bumps all
package manifests in sync and pushes the tag that triggers CI to build, package, and publish
everything automatically:

```bash
cargo release patch --execute   # or minor / major
```

Once the tag is pushed, CI verifies manifest versions, builds binaries for all 7 platforms,
and publishes to crates.io, PyPI, and npm via trusted publishing (no tokens required).

## License

The Unlicense - see [LICENSE](LICENSE) for details.

## Acknowledgments

- [markdownlint]https://github.com/DavidAnson/markdownlint by David Anson — original rule definitions
- [mdformat]https://github.com/hukkin/mdformat — inspiration for the formatter-first approach
- [pulldown-cmark]https://github.com/raphlinus/pulldown-cmark — Markdown parsing

## Resources

- [Documentation]https://github.com/swanysimon/mdlint/tree/main/.github
- [Issue Tracker]https://github.com/swanysimon/mdlint/issues
- [Changelog]https://github.com/swanysimon/mdlint/releases
- [markdownlint Rules Reference]https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md