Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default defineConfig([
| [`no-html`](./docs/rules/no-html.md) | Disallow HTML tags | no |
| [`no-invalid-label-refs`](./docs/rules/no-invalid-label-refs.md) | Disallow invalid label references | yes |
| [`no-missing-label-refs`](./docs/rules/no-missing-label-refs.md) | Disallow missing label references | yes |
| [`no-multiple-h1`](./docs/rules/no-multiple-h1.md) | Disallow multiple h1 headings in the same document | yes |
<!-- Rule Table End -->

**Note:** This plugin does not provide formatting rules. We recommend using a source code formatter such as [Prettier](https://prettier.io) for that purpose.
Expand Down
34 changes: 34 additions & 0 deletions docs/rules/no-multiple-h1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# no-multiple-h1

Disallow multiple h1 headings in the same document.

## Background

An h1 heading is meant to define the main heading of a page, providing important structural information for both users and assistive technologies. Using more than one h1 heading per page can cause confusion for screen readers, dilute SEO signals, and break the logical content hierarchy. While modern search engines are more forgiving, best practice is to use a single h1 heading to ensure clarity and accessibility.

## Rule Details

This rule warns when it finds more than one h1 heading in a Markdown document, whether written as ATX or Setext style.

Examples of incorrect code:

```markdown
# Heading 1

# Another h1 heading
```

```markdown
# Heading 1

Another h1 heading
==================
```

## When Not to Use It

If you have a specific use case that requires multiple h1 headings in a single Markdown document, you can safely disable this rule. However, this is rarely recommended.

## Prior Art

* [MD025 - Multiple top-level headings in the same document](https://github.com/DavidAnson/markdownlint/blob/main/doc/md025.md)
52 changes: 52 additions & 0 deletions src/rules/no-multiple-h1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @fileoverview Rule to enforce at most one h1 heading in Markdown.
* @author Pixel998
*/

//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------

/**
* @typedef {import("../types.ts").MarkdownRuleDefinition<{ RuleOptions: []; }>}
* NoMultipleH1RuleDefinition
*/

//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------

/** @type {NoMultipleH1RuleDefinition} */
export default {
meta: {
type: "problem",

docs: {
recommended: true,
description: "Disallow multiple h1 headings in the same document",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-multiple-h1.md",
},

messages: {
multipleH1: "More than one h1 heading found.",
},
},

create(context) {
let h1Count = 0;

return {
heading(node) {
if (node.depth === 1) {
h1Count++;
if (h1Count > 1) {
context.report({
loc: node.position,
messageId: "multipleH1",
});
}
}
},
};
},
};
108 changes: 108 additions & 0 deletions tests/rules/no-multiple-h1.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @fileoverview Tests for no-multiple-h1 rule.
* @author Pixel998
*/

//------------------------------------------------------------------------------
// Imports
//------------------------------------------------------------------------------

import rule from "../../src/rules/no-multiple-h1.js";
import markdown from "../../src/index.js";
import { RuleTester } from "eslint";
import dedent from "dedent";

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
plugins: {
markdown,
},
language: "markdown/commonmark",
});

ruleTester.run("no-multiple-h1", rule, {
valid: [
"# Only one h1",
dedent`
# Heading 1
Text
`,
dedent`
# Heading 1
## Heading 2
`,
dedent`
Only one h1
===========
`,
dedent`
Heading 1
==========
Text
`,
dedent`
Heading 1
==========
Heading 2
----------
`,
dedent`
# Heading 1
\`\`\`markdown
# Heading 1-2
\`\`\`
`,
],
invalid: [
{
code: dedent`
# Heading 1
# Another H1
`,
errors: [
{
messageId: "multipleH1",
line: 2,
column: 1,
endLine: 2,
endColumn: 13,
},
],
},
{
code: dedent`
# Heading 1
Another H1
==========
`,
errors: [
{
messageId: "multipleH1",
line: 2,
column: 1,
endLine: 3,
endColumn: 11,
},
],
},
{
code: dedent`
Another H1
==========
# Heading 1
`,
errors: [
{
messageId: "multipleH1",
line: 3,
column: 1,
endLine: 3,
endColumn: 12,
},
],
},
],
});