Commit c93c63b1 authored by David Sveningsson's avatar David Sveningsson

feat(rules): new rule `void-content`

refs #58
parent 9ede2907
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/void-content.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/void-content.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 7,
"context": "img",
"line": 1,
"message": "End tag for <img> must be omitted",
"offset": 6,
"ruleId": "void-content",
"selector": null,
"severity": 2,
"size": 4,
},
],
"source": "<img></img>",
"warningCount": 0,
},
]
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/void-content.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/void-content.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 7,
"context": "img",
"line": 1,
"message": "End tag for <img> must be omitted",
"offset": 6,
"ruleId": "void-content",
"selector": null,
"severity": 2,
"size": 4,
},
],
"source": "<img></img>
<div/>",
"warningCount": 0,
},
]
`;
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<img></img>`;
markup["correct"] = `<img>
<img/>`;
describe("docs/rules/void-content.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"void-content":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"void-content":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<img></img>
<div/>`;
markup["correct"] = `<img>
<div></div>`;
describe("docs/rules/void-content.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"void-content":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"void-content":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
---
docType: rule
name: void-content
category: content-model
summary: Disallow void element with content
---
# Disallows void element with content (`void-content`)
HTML [void elements](https://www.w3.org/TR/html5/syntax.html#void-contents)
cannot have any content and must not have an end tag.
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="void-content">
<img></img>
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="void-content">
<img>
<img/>
</validate>
This diff is collapsed.
......@@ -6,6 +6,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "off",
......@@ -64,6 +65,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "off",
......@@ -155,6 +157,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "off",
......@@ -210,6 +213,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "off",
......@@ -269,6 +273,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "error",
......@@ -327,6 +332,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "error",
......@@ -368,6 +374,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "error",
......@@ -409,6 +416,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "error",
......@@ -426,6 +434,7 @@ Object {
"html-validate:recommended",
],
"plugins": Array [],
"root": true,
"rules": Object {
"deprecated": "error",
"element-permitted-content": "error",
......
......@@ -36,6 +36,7 @@ module.exports = {
"svg-focusable": "error",
"unrecognized-char-ref": "error",
void: "error",
"void-content": "error",
"wcag/h30": "error",
"wcag/h32": "error",
"wcag/h36": "error",
......
......@@ -77,6 +77,7 @@ describe("Engine", () => {
extends: ["html-validate:recommended"],
rules: {
deprecated: "off",
"void-content": "off",
},
});
config.init();
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rule void-content should contain documentation 1`] = `
Object {
"description": "HTML void elements cannot have any content and must not have content or end tag.",
"url": "https://html-validate.org/rules/void-content.html",
}
`;
exports[`rule void-content should provide contextual documentation 1`] = `
Object {
"description": "<foo> is a void element and must not have content or end tag.",
"url": "https://html-validate.org/rules/void-content.html",
}
`;
import HtmlValidate from "../htmlvalidate";
import "../matchers";
describe("rule void-content", () => {
let htmlvalidate: HtmlValidate;
beforeAll(() => {
htmlvalidate = new HtmlValidate({
rules: { "void-content": "error" },
});
});
it("should not report when void element omitted end tag", () => {
const report = htmlvalidate.validateString("<input>");
expect(report).toBeValid();
});
it("should not report when void element is self-closed", () => {
const report = htmlvalidate.validateString("<input/>");
expect(report).toBeValid();
});
it("should not report when non-void element has end tag", () => {
const report = htmlvalidate.validateString("<div></div>");
expect(report).toBeValid();
});
it("should not report when non-void element is self-closed", () => {
const report = htmlvalidate.validateString("<div/>");
expect(report).toBeValid();
});
it("should not report when unknown element is self-closed", () => {
const report = htmlvalidate.validateString("<custom-element/>");
expect(report).toBeValid();
});
it("should not report when unknown element has end tag", () => {
const report = htmlvalidate.validateString(
"<custom-element></custom-element>"
);
expect(report).toBeValid();
});
it("should not report when xml namespaces is used", () => {
const report = htmlvalidate.validateString("<xi:include/>");
expect(report).toBeValid();
});
it("should report when void element has end tag", () => {
const report = htmlvalidate.validateString("<input></input>");
expect(report).toHaveError(
"void-content",
"End tag for <input> must be omitted",
"input"
);
});
it("should handle stray end tag", () => {
const report = htmlvalidate.validateString("</div>");
expect(report).toBeValid();
});
it("should handle missing end tag", () => {
const report = htmlvalidate.validateString("<div>");
expect(report).toBeValid();
});
it("should contain documentation", () => {
expect(htmlvalidate.getRuleDocumentation("void-content")).toMatchSnapshot();
});
it("should provide contextual documentation", () => {
expect(
htmlvalidate.getRuleDocumentation("void-content", null, "foo")
).toMatchSnapshot();
});
});
import { NodeClosed } from "../dom";
import { TagCloseEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
class VoidElement extends Rule<string> {
public documentation(tagName: string): RuleDocumentation {
const doc: RuleDocumentation = {
description:
"HTML void elements cannot have any content and must not have content or end tag.",
url: ruleDocumentationUrl(__filename),
};
if (tagName) {
doc.description = `<${tagName}> is a void element and must not have content or end tag.`;
}
return doc;
}
public setup(): void {
this.on("tag:close", (event: TagCloseEvent) => {
const node = event.target; // The current element being closed.
if (!node) {
return;
}
if (!node.voidElement) {
return;
}
if (node.closed === NodeClosed.EndTag) {
this.report(
null,
`End tag for <${node.tagName}> must be omitted`,
node.location,
node.tagName
);
}
});
}
}
module.exports = VoidElement;
{
"root": true,
"rules": {
"void": "error"
"void": "error",
"void-content": "off"
}
}
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