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: 21 additional rules (MD057, MD061-MD080), built-in LSP server, VS Code extension, 6 Markdown flavors
- Configuration: Automatic markdownlint config discovery and conversion
Rule Coverage¶
Implemented Rules¶
rumdl implements 74 rules total: all 53 markdownlint rules plus 21 unique rules.
Markdownlint-compatible rules (53): All markdownlint rules are implemented with full compatibility. See the Rules Reference 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 21 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 |
| MD078 | Missing chunk labels | Executable Quarto chunks should have a label |
| MD079 | Chunk label spaces | Quarto chunk labels must not contain whitespace |
| MD080 | Heading anchor collision | Heading anchors (slugs) must be unique |
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:
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
- rumdl Issue #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 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
--fixflag
rumdl:
- Two modes:
rumdl fmt(formatter-style, exits 0) andrumdl check --fix(linter-style, exits 0 if all violations fixed, 1 if violations remain) --diffmode 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:
# 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:
Multiple Formats:
- Native:
.rumdl.toml(TOML, with JSON schema support) - Python projects:
pyproject.tomlwith[tool.rumdl]section - Markdownlint: Automatic compatibility mode
5. Editor Integration¶
rumdl includes a built-in Language Server Protocol (LSP) implementation:
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:
markdownlint does not have built-in flavor support; users must configure individual rules manually.
7. Rule-Specific Default Differences¶
A few rules ship safer defaults than markdownlint. These are opt-out, not removed - set the documented option to recover markdownlint-exact behavior.
MD010 (Hard tabs) - code_blocks:
- markdownlint: defaults
code_blocks: true, flagging and rewriting tabs inside fenced and indented code blocks. - rumdl: defaults
code-blocks = false, skipping tabs inside both fenced and indented code blocks. - Rationale: tabs are syntactically required in Makefiles and conventional in
gofmt-formatted Go. markdownlint's default silently corrupts such snippets on auto-fix. Skipping code blocks by default is strictly safer. - markdownlint parity: set
code-blocks = trueto flag tabs everywhere, including code blocks.
Reference: rumdl Issue #630 - inconsistent tab handling between fenced and indented code blocks.
Configuration Compatibility¶
Markdownlint Config Auto-Detection¶
rumdl automatically discovers and loads markdownlint and markdownlint-cli2 configurations:
# .markdownlint-cli2.yaml (also automatically loaded)
config:
MD013: false
MD033:
allowed_elements: ['br', 'img']
Equivalent rumdl Configuration¶
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:
# 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:
<!-- 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:
rumdl:
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 violations1: Violations found2: Unable to write output3: Unable to load custom rules4: Unexpected error
rumdl:
0: Success (orrumdl fmtcompleted successfully)1: Violations found (or remain after--fix)2: Tool error
Migration Guide¶
From markdownlint to rumdl¶
-
Install rumdl:
bash uv tool install rumdl # or: cargo install rumdl # or: pip install rumdl # or: brew install rumdl (See README.md) -
Test with existing config:
bash # rumdl automatically discovers .markdownlint.json rumdl check . -
(Optional) Convert config:
bash rumdl import .markdownlint.json -
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.
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:
- Check existing issues
- Verify with both tools:
markdownlint file.mdandrumdl check file.md - File an issue with:
- Markdown sample
- Expected behavior (markdownlint output)
- Actual behavior (rumdl output)
- Versions of both tools
See Also¶
- Tool Comparison Matrix - Broad comparison of Markdown linters and formatters
- Comparison with mdformat - For users coming from the mdformat formatter