Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
688 changes: 644 additions & 44 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ schemars = "1.1"
unicode-width = "0.2"
memory-stats = { version = "1.2.0", optional = true }
phf = { version = "0.13", features = ["macros"] }
markup_fmt = { version = "0.27.3", features = ["serde"], optional = true }
dprint-plugin-typescript = { version = "0.96.1", optional = true }

# LSP dependencies (native only)
tower-lsp = { version = "0.20", optional = true }
Expand All @@ -94,7 +96,8 @@ clap_complete = "4.5.65"
ec4rs = { version = "1.2.0", features = ["track-source"], optional = true }

[features]
default = ["parallel", "native"]
default = ["parallel", "native", "html-fmt"]
html-fmt = ["dep:markup_fmt", "dep:dprint-plugin-typescript"]
parallel = ["rayon"]
profiling = []
native = [
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Python linting, rumdl brings similar speed and developer experience improvements
It offers:

- ⚡️ **Built for speed** with Rust - significantly faster than alternatives
- 🔍 **<!-- RULE_COUNT -->82<!-- /RULE_COUNT --> lint rules** covering common Markdown issues
- 🔍 **<!-- RULE_COUNT -->83<!-- /RULE_COUNT --> lint rules** covering common Markdown issues
- 🛠️ **Automatic formatting** with `--fix` for files and stdin/stdout
- 📦 **Zero dependencies** - single binary with no runtime requirements
- 🔧 **Highly configurable** with TOML-based config files
Expand Down Expand Up @@ -526,7 +526,7 @@ The action ref (`rvben/rumdl@v0`) selects the GitHub Action version, while the o

## Rules

rumdl implements <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> lint rules for Markdown files. Here are some key rule categories:
rumdl implements <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> lint rules for Markdown files. Here are some key rule categories:

| Category | Description | Example Rules |
| -------------- | ---------------------------------------- | ------------------- |
Expand Down
8 changes: 4 additions & 4 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For detailed comparisons with specific tools, see:

| Tool | Type | Language | Rules | Auto-fix | Flavors | Config format | Plugins | LSP |
| --------------------- | ------------- | -------- | ----------------------------------------- | -------- | ------- | ----------------------- | ------------ | --- |
| **rumdl** | Lint + Format | Rust | <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> | Yes | 9 | TOML, JSON, YAML | No | Yes |
| **rumdl** | Lint + Format | Rust | <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> | Yes | 9 | TOML, JSON, YAML | No | Yes |
| **markdownlint-cli** | Lint | Node.js | 53 | Yes | No | JSON, JSONC, YAML, TOML | Yes (JS) | No |
| **markdownlint-cli2** | Lint | Node.js | 53 | Yes | No | JSONC, YAML, JS | Yes (JS) | No |
| **remark-lint** | Lint | Node.js | ~80 (via presets) | No | No | JS, JSON, YAML | Yes (JS) | No |
Expand All @@ -33,7 +33,7 @@ cover common cases. No built-in auto-fix for lint violations (remark itself can

**mado** is a Rust-based linter with 38 rules (33 stable, 5 unstable). It has no auto-fix and no plugin system.

**rumdl** implements all 53 markdownlint rules plus <!-- RULE_COUNT_ADDITIONAL -->29<!-- /RULE_COUNT_ADDITIONAL --> additional rules (<!-- RULE_COUNT -->82<!-- /RULE_COUNT --> total). It supports
**rumdl** implements all 53 markdownlint rules plus <!-- RULE_COUNT_ADDITIONAL -->30<!-- /RULE_COUNT_ADDITIONAL --> additional rules (<!-- RULE_COUNT -->83<!-- /RULE_COUNT --> total). It supports
auto-fix for most rules and includes rules not found in other tools, such as relative link validation (MD057), footnote checks (MD066-MD068), nested code fence detection (MD070), and TOC validation
(MD073).

Expand Down Expand Up @@ -136,7 +136,7 @@ Cold start benchmark on the Rust Book (478 Markdown files), measured with [hyper

**Notes:**

- **mado** is faster in cold-start benchmarks because it does less work per file: fewer rules (38 vs <!-- RULE_COUNT -->82<!-- /RULE_COUNT -->), no fix generation, and no flavor detection.
- **mado** is faster in cold-start benchmarks because it does less work per file: fewer rules (38 vs <!-- RULE_COUNT -->83<!-- /RULE_COUNT -->), no fix generation, and no flavor detection.
The gap reflects feature surface area, not implementation quality.
- **rumdl** supports result caching (`rumdl check` without `--no-cache`), which skips unchanged files on subsequent runs — typically under 50 ms, faster than mado's cold start.
- **pymarkdown** performs well for a Python tool due to its efficient scanner architecture.
Expand All @@ -147,5 +147,5 @@ Cold start benchmark on the Rust Book (478 Markdown files), measured with [hyper

- [Comparison with markdownlint](markdownlint-comparison.md) — detailed rule-by-rule comparison and migration guide
- [Comparison with mdformat](mdformat-comparison.md) — formatting feature comparison and migration guide
- [Rules Reference](rules.md) — complete list of rumdl's <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules
- [Rules Reference](rules.md) — complete list of rumdl's <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules
- [Markdown Flavors](flavors.md) — flavor configuration and per-rule adjustments
2 changes: 2 additions & 0 deletions docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Looking for something more specific?
[Global Settings](../global-settings.md).
- For per-document overrides via HTML comments, see
[Inline Configuration](../inline-configuration.md).
- For formatting HTML and scripts embedded in Markdown, see
[Embedded HTML Formatting](../embedded-html-formatting.md).

## Supported config files

Expand Down
85 changes: 85 additions & 0 deletions docs/embedded-html-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Embedded HTML Formatting

rumdl includes built-in support for checking and formatting HTML blocks embedded inside your Markdown files. It can also format JavaScript and TypeScript code blocks inside `<script>` tags within
those HTML blocks.

All formatting is done directly in Rust via library calls (using the `markup_fmt` and `dprint-plugin-typescript` crates), without spawning any external subprocesses.

## Overview

- **Lint Mode** (`rumdl check`): Validates that HTML blocks and `<script>` blocks inside them are correctly formatted, emitting warnings if not.
- **HTML Formatting**: Handled by `markup_fmt`.
- **Script Formatting**: Handled by `dprint-plugin-typescript` (supports JS, TS, JSX, TSX).
- **Subprocess-free**: Fast execution, entirely integrated into the binary.

## Configuration

Configure HTML formatting in the `[html]` section of your `rumdl.toml` (or `pyproject.toml` under `[tool.rumdl.html]`):

```toml
[html]
# Master switch for HTML checking and formatting (default: true)
enabled = true

# Target line width for the HTML formatter (default: 80)
print-width = 80

# Indentation size for the HTML formatter (default: 2)
indent-width = 2

# Use tab characters for indentation (default: false)
use-tabs = false

# Quote character preference for HTML attributes: "double" or "single" (default: "double")
quotes = "double"

[html.script]
# Whether to format JavaScript/TypeScript inside <script> tags (default: true)
enabled = true

# Semicolon style preference: "always", "prefer", or "asi" (default: "prefer")
semi-colons = "prefer"

# Quote style preference: "always-double", "always-single", "prefer-double", or "prefer-single" (default: "prefer-double")
quote-style = "prefer-double"
```

## Behavior

- **Block-level Only**: rumdl only formats HTML *blocks* (blocks of HTML parsed by the Markdown parser, such as a `<div>` starting on its own line). Inline HTML tags (like `This is <b>bold</b>`) are
ignored to prevent layout disruptions.
- **JSX/Component Skipping**: In MDX documents, component tags (tags starting with an uppercase letter, e.g., `<Header />` or `<MyComponent>`) are recognized as JSX and automatically skipped,
preventing the HTML parser from failing on non-standard tags.
- **Error Resiliency**: If an HTML block contains malformed HTML that fails to parse, rumdl skips formatting that block rather than failing the entire linting run.

## Example

Given the following unformatted Markdown:

```markdown
Some text.

<div class="container">
<p>Hello World</p>
<script>
const a=1;
const b = "double";
</script>
</div>
```

Running `rumdl check` will emit warnings pointing to the HTML block starting at `<div class="container">` and the script block inside it.

Running `rumdl check --fix` will format the block in-place:

```markdown
Some text.

<div class="container">
<p>Hello World</p>
<script>
const a = 1;
const b = 'double';
</script>
</div>
```
2 changes: 1 addition & 1 deletion docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,5 @@ Each line shows:
## Next Steps

- [CLI Commands](../usage/cli.md) - Full command reference
- [Rules Reference](../rules.md) - Explore all <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules
- [Rules Reference](../rules.md) - Explore all <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules
- [Configuration](../global-settings.md) - Advanced configuration options
1 change: 1 addition & 0 deletions docs/global-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -1557,3 +1557,4 @@ find . -name "*.md" -o -name "*.markdown" | head -10
- [CLI Reference](../README.md#command-line-interface) - Command-line options
- [Rule-specific Configuration](../README.md#configuration-file-example) - Configuring individual rules
- [Code Block Tools](code-block-tools.md) - External linters/formatters for code blocks [preview]
- [Embedded HTML Formatting](embedded-html-formatting.md) - Built-in checking and formatting of HTML and script blocks
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ icon: lucide/file-check

[:octicons-arrow-right-24: Benchmarks](#performance)

- :mag:{ .lg .middle } **<!-- RULE_COUNT -->82<!-- /RULE_COUNT --> lint rules**
- :mag:{ .lg .middle } **<!-- RULE_COUNT -->83<!-- /RULE_COUNT --> lint rules**

---

Expand Down Expand Up @@ -76,7 +76,7 @@ With intelligent caching, subsequent runs are even faster - rumdl only re-lints
## Features

- :zap: **Built for speed** with Rust - significantly faster than alternatives
- :mag: **<!-- RULE_COUNT -->82<!-- /RULE_COUNT --> lint rules** covering common Markdown issues
- :mag: **<!-- RULE_COUNT -->83<!-- /RULE_COUNT --> lint rules** covering common Markdown issues
- :wrench: **Automatic formatting** with `--fix` for files and stdin/stdout
- :package: **Zero dependencies** - single binary with no runtime requirements
- :gear: **Highly configurable** with TOML-based config files
Expand All @@ -100,7 +100,7 @@ With intelligent caching, subsequent runs are even faster - rumdl only re-lints

- [:octicons-book-24: **Rules Reference**](rules.md)

Explore all <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> linting rules with examples.
Explore all <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> linting rules with examples.

- [:octicons-gear-24: **Configuration**](global-settings.md)

Expand Down
6 changes: 3 additions & 3 deletions docs/markdownlint-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ rumdl offers **high markdownlint compatibility with intentional differences** wh

- **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**: <!-- RULE_COUNT_ADDITIONAL -->29<!-- /RULE_COUNT_ADDITIONAL --> additional rules (MD057, MD061-<!-- RULE_MAX -->MD088<!-- /RULE_MAX -->), built-in LSP server, VS Code extension, 6 Markdown flavors
- **Unique Features**: <!-- RULE_COUNT_ADDITIONAL -->30<!-- /RULE_COUNT_ADDITIONAL --> additional rules (MD057, MD061-<!-- RULE_MAX -->MD089<!-- /RULE_MAX -->), built-in LSP server, VS Code extension, 6 Markdown flavors
- **Configuration**: Automatic markdownlint config discovery and conversion

## Rule Coverage

### Implemented Rules

rumdl implements **<!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules total**: all 53 markdownlint rules plus <!-- RULE_COUNT_ADDITIONAL -->29<!-- /RULE_COUNT_ADDITIONAL --> unique rules.
rumdl implements **<!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules total**: all 53 markdownlint rules plus <!-- RULE_COUNT_ADDITIONAL -->30<!-- /RULE_COUNT_ADDITIONAL --> 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 <!-- RULE_COUNT_ADDITIONAL -->29<!-- /RULE_COUNT_ADDITIONAL --> additional rules not found in markdownlint:
rumdl implements <!-- RULE_COUNT_ADDITIONAL -->30<!-- /RULE_COUNT_ADDITIONAL --> additional rules not found in markdownlint:

| Rule | Name | Description |
| ------ | ------------------------------ | ---------------------------------------------------------- |
Expand Down
52 changes: 52 additions & 0 deletions docs/md089.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# MD089 - Embedded HTML blocks should be formatted

Aliases: `embedded-html-fmt`

This rule is enabled by default.

## What this rule does

Formats HTML blocks using `markup_fmt`. It can also format JavaScript/TypeScript inside `<script>` tags and Markdown inside HTML comments if configured.

## Why this matters

Consistent formatting improves readability of HTML blocks embedded in Markdown documents.

## Examples

### Incorrect

```markdown
<div>
<p>Unformatted HTML</p>
</div>
```

### Correct

```markdown
<div>
<p>Formatted HTML</p>
</div>
```

## Configuration

This rule is configured via the `[html]` section in the configuration file.

Options:
- `enabled`: Enable or disable the rule (boolean, default: true)
- `print_width`: Maximum line width for formatting (integer, default: 80)
- `use_tabs`: Use tabs for indentation (boolean, default: false)
- `indent_width`: Indentation width (integer, default: 2)
- `quotes`: Quote style ("double" or "single", default: "double")
- `format_comments_as_markdown`: Format Markdown inside HTML comments (boolean, default: false)

Script formatting options (via `[html.script]`):
- `enabled`: Enable script formatting (boolean, default: false)
- `semi_colons`: Semicolon style ("always", "prefer", "asi", default: "always")
- `quote_style`: Quote style ("always-double", "always-single", "prefer-double", "prefer-single", default: "prefer-double")

## Automatic fixes

This rule supports automatic fixes. Running `rumdl --fix` or `rumdl fmt` will format the HTML blocks in place.
8 changes: 4 additions & 4 deletions docs/mdformat-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This document compares rumdl and mdformat, focusing on their formatting capabili
Both tools format Markdown files, but serve different purposes:

- **mdformat**: Pure formatter focused on consistent Markdown output
- **rumdl**: Combined linter and formatter with <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules plus formatting
- **rumdl**: Combined linter and formatter with <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules plus formatting

**Key Differences:**

Expand All @@ -16,7 +16,7 @@ Both tools format Markdown files, but serve different purposes:
| Primary purpose | Formatting only | Linting + formatting |
| Language | Python | Rust |
| Performance | Good | Faster (native + caching) |
| Linting rules | ❌ | ✅ <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules |
| Linting rules | ❌ | ✅ <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules |
| Extensibility | Plugin ecosystem | Built-in flavors |
| CommonMark | Strict compliance | Strict compliance |

Expand Down Expand Up @@ -47,7 +47,7 @@ Both tools format Markdown to a consistent style:

### Linting (rumdl only)

rumdl provides <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> linting rules that mdformat does not have:
rumdl provides <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> linting rules that mdformat does not have:

- **Broken link detection** (MD051, MD052, MD057)
- **Accessibility checks** (MD045 - image alt text)
Expand Down Expand Up @@ -299,7 +299,7 @@ style = "atx"
| Capability | mdformat | rumdl |
| ----------------------- | ------------------ | ---------------------- |
| Markdown formatting | ✅ Primary focus | ✅ Via `rumdl fmt` |
| Markdown linting | ❌ | ✅ <!-- RULE_COUNT -->82<!-- /RULE_COUNT --> rules |
| Markdown linting | ❌ | ✅ <!-- RULE_COUNT -->83<!-- /RULE_COUNT --> rules |
| Performance | Good | Faster (native binary) |
| Extended syntax | Plugins | Built-in flavors |
| Editor integration | Basic | LSP + VS Code |
Expand Down
11 changes: 9 additions & 2 deletions docs/playground.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Try rumdl directly in your browser — no installation needed. Powered by [rumdl
<option value="links">Links and images</option>
<option value="clean">Clean document</option>
</select>
<button id="pg-fix-btn" class="pg-btn pg-btn-primary" disabled>Fix all</button>
<button id="pg-fix-btn" class="pg-btn pg-btn-primary" disabled>
Fix all
</button>
<button id="pg-clear-btn" class="pg-btn">Clear</button>
</div>
</div>
Expand All @@ -33,7 +35,12 @@ Try rumdl directly in your browser — no installation needed. Powered by [rumdl
<span>Markdown input</span>
<span id="pg-char-count" class="pg-meta"></span>
</div>
<textarea id="pg-input" class="pg-editor" spellcheck="false" placeholder="Type or paste markdown here…"></textarea>
<textarea
id="pg-input"
class="pg-editor"
spellcheck="false"
placeholder="Type or paste markdown here…"
></textarea>
</div>
<div class="pg-panel">
<div class="pg-panel-header">
Expand Down
Loading
Loading