rumdl 0.2.62

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
# MD040 - Code blocks should have a language specified

Aliases: `fenced-code-language`

## What this rule does

Ensures code blocks (```) specify what programming language they contain. Optionally enforces consistent language labels and restricts which languages are allowed.

## Why this matters

- **Syntax highlighting**: Editors and renderers can color-code the syntax correctly
- **Clarity**: Readers immediately know what language they're looking at
- **Consistency**: Using the same label (e.g., always `bash` instead of mixing `sh`/`bash`/`zsh`) keeps documentation uniform
- **Tools**: Some tools use language hints for processing or validation

## Examples

### ✅ Correct

````markdown
```python
def hello():
    print("Hello, world!")
```

```javascript
console.log("Hello, world!");
```

```bash
echo "Hello, world!"
```
````

### ❌ Incorrect

<!-- rumdl-disable MD040 MD031 -->

````markdown
```
def hello():
    print("Hello, world!")
```

```
console.log("Hello, world!");
```
````

<!-- rumdl-enable MD040 MD031 -->

### 🔧 Fixed

````markdown
```text
def hello():
    print("Hello, world!")
```

```text
console.log("Hello, world!");
```
````

> **Note**: The fix adds `text` as a default language hint when none is specified.

## Configuration

```toml
[MD040]
# Language label normalization mode
# - "disabled" (default): Only check for missing language
# - "consistent": Normalize to most prevalent alias per language
style = "disabled"

# Override preferred label for specific languages
# Keys are GitHub Linguist canonical names, values are your preferred alias
preferred-aliases = { Shell = "bash", JavaScript = "js" }

# Restrict which languages are allowed (empty = allow all)
# Uses GitHub Linguist canonical language names
allowed-languages = ["Python", "Shell", "JavaScript", "TypeScript", "JSON", "YAML"]

# Block specific languages (ignored if allowed-languages is non-empty)
disallowed-languages = ["Java", "C++"]

# Action for unknown language labels not in GitHub Linguist.
# A label is known when it matches a Linguist language name, alias, or file
# extension (e.g. `pytb`), mirroring what GitHub accepts for highlighting.
# - "ignore" (default): Silently ignore unknown languages
# - "warn": Emit a warning for unknown languages
# - "error": Treat unknown languages as errors
unknown-language-action = "ignore"

# Languages Linguist does not know that this project uses anyway
# Matched against fence labels case-insensitively
custom-languages = ["cddl", "jsonnet"]
```

### Consistent Mode

When `style = "consistent"`, the rule ensures all code blocks that refer to the same language use the same label. For example, if your document has:

````markdown
```bash
echo "one"
```

```sh
echo "two"
```

```bash
echo "three"
```
````

The rule will flag `sh` as inconsistent because `bash` is more prevalent (2 occurrences vs 1).

**With `--fix`**, inconsistent labels are automatically normalized to the most prevalent one:

````markdown
```bash
echo "one"
```

```bash
echo "two"
```

```bash
echo "three"
```
````

### Preferred Aliases

Use `preferred-aliases` to override which label is used regardless of prevalence:

```toml
[MD040]
style = "consistent"
preferred-aliases = { Shell = "sh" }  # Always use "sh" instead of "bash"
```

### Language Restrictions

Restrict which languages can appear in your documentation:

```toml
[MD040]
# Only allow these languages
allowed-languages = ["Python", "Shell", "JSON"]
```

Or block specific languages:

```toml
[MD040]
# Block these languages (only works if allowed-languages is empty)
disallowed-languages = ["Java", "C++"]
```

### Unknown Languages

By default, language labels not recognized by GitHub Linguist are silently ignored. Use `unknown-language-action` to change this behavior:

```toml
[MD040]
# Warn about unknown languages
unknown-language-action = "warn"

# Or treat unknown languages as errors
unknown-language-action = "error"
```

This is useful for enforcing that all language labels are valid and will receive proper syntax highlighting on GitHub.

### Custom Languages

Some projects use fence labels for languages Linguist has no entry for. List them in `custom-languages` so they are accepted without loosening the check for every other unknown label:

```toml
[MD040]
unknown-language-action = "error"
custom-languages = ["cddl", "jsonnet"]
```

A declared label is matched case-insensitively, counts as a known language for `allowed-languages` and `disallowed-languages`, and takes part in `style = "consistent"` normalization. Linguist stays authoritative: declaring a label it already knows changes nothing, so `sh` still normalizes together with the rest of Shell.

A custom language has no aliases, so a `preferred-aliases` entry for one can only name a spelling of the declared name itself.

A fence label is the first word of the info string, so an entry containing whitespace could never match one. Such an entry is reported as a configuration error rather than silently doing nothing.

Declared languages are also offered by the language server's code fence completion, listed ahead of the Linguist entries.

## Linguist Integration

This rule uses [GitHub Linguist](https://github.com/github-linguist/linguist) as the source of truth for language names and aliases. This ensures compatibility with GitHub's syntax highlighting.

Common language mappings:
- `sh`, `bash`, `zsh`, `shell-script` → Shell
- `js`, `node` → JavaScript
- `ts` → TypeScript
- `python`, `python3` → Python

## Automatic fixes

- Missing language: Adds `text` as the default outside the `mdg` flavor
- Inconsistent labels (when `style = "consistent"`): Normalizes to the preferred/prevalent label outside the `mdg` flavor

## Markdown with Gherkin

Under the `mdg` flavor, a backtick-fenced code block is a Gherkin Doc String and
its language label is the Doc String's media type. MD040 still reports missing
and inconsistent labels, but offers no fix: adding the standard `text` fallback
would change the parsed media type from absent (`null`) to `text`, while
normalizing an existing label would replace an explicitly chosen media type.
Step definitions can observe either change.

Add the intended media type manually when the Doc String has one, or disable
MD040 when an absent media type is intentional. Under other flavors, the
standard `text` fix remains available.

See [Markdown with Gherkin Flavor](flavors/mdg.md) for the full flavor
specification.

## Learn more

- [CommonMark fenced code blocks]https://spec.commonmark.org/0.31.2/#fenced-code-blocks - Technical specification
- [GitHub Flavored Markdown]https://github.github.com/gfm/#info-string - Language hints in code blocks
- [GitHub Linguist]https://github.com/github-linguist/linguist - Language detection and aliases

## Related rules

- [MD046]md046.md - Code block style should be consistent
- [MD048]md048.md - Code fence style should be consistent
- [MD031]md031.md - Code blocks should be surrounded by blank lines