# MD086 - Comments should be closed

Aliases: `no-unclosed-comments`

## What this rule does

Reports a comment opener that nothing closes: `<!--` with no `-->` after it, and
in the Obsidian flavor `%%` with no second `%%`.

## Why this matters

An unclosed comment does not fail loudly. It produces a document that still
looks complete in the source while the rendered page is wrong, and which way it
is wrong depends on where the opener sits.

At the start of a line, `<!--` opens an HTML block that runs to the end of the
document. Every heading, list and paragraph below it disappears from the render:

```markdown
# Release notes

<!-- TODO: rewrite this section

## Breaking changes

The `--force-exclude` flag is gone.
```

Here the breaking-changes section is published nowhere. The file is 40 lines
long and the page shows one heading.

In the middle of a paragraph the failure inverts. CommonMark has no comment to
open there, so it renders the marker as literal text and the note the author
meant to hide gets published:

```markdown
The migration is straightforward. <!-- ask legal before publishing this
```

Neither case is caught by the rest of the linter: no other rule reports a missing
closer, and `rumdl fmt` will not add one. Without this rule both documents above
lint clean.

## Examples

### Incorrect

```markdown
<!-- a note that is never closed

# Heading
```

```markdown
Some prose <!-- an aside that runs off the end
```

### Correct

```markdown
<!-- a note that is closed -->

# Heading
```

```markdown
<!--
A comment may span
as many lines as it needs.
-->
```

## Obsidian comments

Under `flavor = "obsidian"` the same check applies to `%%`, whose closer is
another `%%`. Obsidian hides everything from an unclosed `%%` to the end of the
note, so the consequence matches the block form above.

```markdown
%% a note that is never closed

## Section
```

Other flavors treat `%%` as ordinary text and are not checked for it.

The two syntaxes hide each other's delimiters. A `<!--` between a pair of `%%`
is text Obsidian hides, not an opener, so it is not reported:

```markdown
%% reminder: <!-- is how HTML comments start %%
```

This holds both ways round. Obsidian hides everything from an unclosed `%%` to
the end of the note, so a `<!--` below one is inside it. An unclosed `<!--` at
the start of a line opens an HTML block, so a `%%` inside that block is comment
text too. Neither is reported as a second unclosed comment.

An unclosed `<!--` in the middle of a paragraph is different: CommonMark renders
it as literal text, so it opens nothing and a `%%` after it is reported on its
own. The same applies once the block ends, which inside a blockquote or a list
item happens before the end of the document.

## Configuration

This rule has no options.

## What this rule leaves alone

`<!-->` and `<!--->` are complete comments, not unclosed ones. A comment ends at
the first `-->`, and the opener's own dashes may supply that closer's leading
dashes, so these render as an empty comment and everything after them is normal
content.

```markdown
<!--> This text is published, because the comment above it is closed.
```

Openers inside code are shown, not used, so a `<!--` in a fenced code block or a
code span is ignored:

````markdown
```html
<!-- an example of HTML comment syntax
```
````

Front matter is data rather than markdown. A `<!--` in a YAML or TOML value is
part of the value and is not reported:

```markdown
---
description: "the <!-- marker opens a comment"
---
```

## Automatic fixes

None. Where the missing closer belongs is a guess, and both guesses are
destructive: appending `-->` at the end of the document would comment out
everything the author meant to publish, and inserting it after the first line
would assume the comment was a one-liner. The fix is a judgment about intent, so
it stays with the author.

## Related rules

- [MD033 - No inline HTML](md033.md), which governs the HTML that is not a comment
- [MD084 - Invisible characters](md084.md), another rule for content that is
  present in the source but not visible in the render
