Commit a33071d1 authored by Mihkel Eidast's avatar Mihkel Eidast
Browse files

feat(rules): add `allowMultipleH1` option to `heading-level`

parent 3d24f81d
......@@ -25,3 +25,17 @@ Examples of **correct** code for this rule:
<h1>Heading 1</h1>
<h2>Subheading</h2>
</validate>
## Options
This rule takes an optional object:
```json
{
"allowMultipleH1": false
}
```
### AllowMultipleH1
Set `allowMultipleH1` to `true` to allow multiple `<h1>` elements in a document.
......@@ -48,6 +48,23 @@ describe("rule heading-level", () => {
expect(report).toHaveError("heading-level", "Initial heading level must be h1");
});
it("should report error when multiple <h1> are used", () => {
expect.assertions(2);
const report = htmlvalidate.validateString("<h1>heading 1</h1><h1>heading 1</h1>");
expect(report).toBeInvalid();
expect(report).toHaveError("heading-level", "Multiple h1 are not allowed");
});
it("should not report error when multiple <h1> are used but allowed via option", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
rules: { "heading-level": ["error", { allowMultipleH1: true }] },
elements: ["html5", { "custom-heading": { heading: true } }],
});
const report = htmlvalidate.validateString("<h1>heading 1</h1><h1>heading 1</h1>");
expect(report).toBeValid();
});
it("should handle custom elements marked as heading", () => {
expect.assertions(1);
const report = htmlvalidate.validateString("<custom-heading></custom-heading>");
......
......@@ -3,7 +3,19 @@ import { HtmlElement } from "../dom";
import { TagOpenEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
export default class HeadingLevel extends Rule {
interface Options {
allowMultipleH1: boolean;
}
const defaults: Options = {
allowMultipleH1: false,
};
export default class HeadingLevel extends Rule<void, Options> {
public constructor(options: Partial<Options>) {
super({ ...defaults, ...options });
}
public documentation(): RuleDocumentation {
return {
description:
......@@ -14,6 +26,7 @@ export default class HeadingLevel extends Rule {
public setup(): void {
let current = 0;
let h1Count = 0;
this.on("tag:open", (event: TagOpenEvent) => {
/* ensure it is a heading */
if (!this.isHeading(event.target)) return;
......@@ -22,6 +35,17 @@ export default class HeadingLevel extends Rule {
const level = this.extractLevel(event.target);
if (!level) return;
/* do not allow multiple h1 */
if (!this.options.allowMultipleH1 && level === 1) {
if (h1Count >= 1) {
const location = sliceLocation(event.location, 1);
this.report(event.target, `Multiple h1 are not allowed`, location);
return;
}
h1Count++;
}
/* allow same level or decreasing to any level (e.g. from h4 to h2) */
if (level <= current) {
current = level;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment