# 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:
| 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:**
```markdown
1. List item
This is a continuation paragraph (4 spaces = continuation)
This is a code block (8 spaces = continuation indent + 4)
```
- **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:
| `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:
| `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
This line can be as long as needed
Alternative syntax also supported
```
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
```
2. **Test with existing config:**
```bash
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
| **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:
| 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)