Repository: htmlhint/HTMLHint

Issue:
feat: New rule: h1 require
This is a good practise and important for accessibility.

REF: https://dequeuniversity.com/rules/axe/4.1/page-has-heading-one

Hints from issue comments:

Solution patch:
diff --git a/.cspell.json b/.cspell.json
index 25e2931..d619c3d 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -42,6 +42,7 @@
     "onttt",
     "opencollective",
     "Rehype",
+    "Rollup",
     "ruleid",
     "rulesdir",
     "ruleset",
@@ -57,8 +58,8 @@
     "testhref",
     "testtitle",
     "ufeff",
-    "uffff",
     "ufff",
+    "uffff",
     "unmin",
     "withastro",
     "xiang",
diff --git a/.eslintignore b/.eslintignore
index cc79531..134abc2 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -5,4 +5,6 @@ bin
 dist
 
 # Website directory
+website/.astro/
+website/build/
 website/src/
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 73b3ba4..29c3dc4 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -8,3 +8,9 @@
 - Rollup is used for building the project.
 - Node v20 is used for development.
 - Core code is in TypeScript v4.
+- All new rules for HTMLHint should be placed in the rules directory.
+- Tests for new rules should be added in rules and follow the naming pattern <rule-name>.spec.js.
+- Do not use deprecated Node.js or TypeScript features.
+- All user-facing messages and documentation should use clear, concise US English.
+- Keep dependencies up to date and avoid unnecessary packages.
+- All website content should be placed in the website directory and follow Astro Starlight conventions.
diff --git a/.prettierignore b/.prettierignore
index 387d904..2c329ac 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -17,9 +17,6 @@ npm-debug.log*
 # Ignore formatting of ***x*** to **_x_**
 .github/PULL_REQUEST_TEMPLATE.md
 
-/website/playground
-/website/build
-
 /test/**/*.html
 /test/**/*.xml
 
@@ -33,3 +30,9 @@ report.html
 CODEOWNERS
 *.mdx
 *.txt
+
+## Website
+/website/.astro
+/website/dist
+/website/build
+/website/public
diff --git a/dist/core/rules/index.js b/dist/core/rules/index.js
index 6262f13..b3af49e 100644
--- a/dist/core/rules/index.js
+++ b/dist/core/rules/index.js
@@ -1,14 +1,16 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.attrNoUnnecessaryWhitespace = exports.tagsCheck = exports.titleRequire = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.emptyTagNotSelfClosed = exports.tagSelfClose = exports.tagPair = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrNoDuplication = exports.attrSort = exports.attrLowercase = exports.altRequire = void 0;
+exports.titleRequire = exports.tagSelfClose = exports.tagsCheck = exports.tagPair = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.h1Require = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSort = exports.attrNoUnnecessaryWhitespace = exports.attrNoDuplication = exports.attrLowercase = exports.altRequire = void 0;
 var alt_require_1 = require("./alt-require");
 Object.defineProperty(exports, "altRequire", { enumerable: true, get: function () { return alt_require_1.default; } });
 var attr_lowercase_1 = require("./attr-lowercase");
 Object.defineProperty(exports, "attrLowercase", { enumerable: true, get: function () { return attr_lowercase_1.default; } });
-var attr_sorted_1 = require("./attr-sorted");
-Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } });
 var attr_no_duplication_1 = require("./attr-no-duplication");
 Object.defineProperty(exports, "attrNoDuplication", { enumerable: true, get: function () { return attr_no_duplication_1.default; } });
+var attr_no_unnecessary_whitespace_1 = require("./attr-no-unnecessary-whitespace");
+Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } });
+var attr_sorted_1 = require("./attr-sorted");
+Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } });
 var attr_unsafe_chars_1 = require("./attr-unsafe-chars");
 Object.defineProperty(exports, "attrUnsafeChars", { enumerable: true, get: function () { return attr_unsafe_chars_1.default; } });
 var attr_value_double_quotes_1 = require("./attr-value-double-quotes");
@@ -23,6 +25,10 @@ var doctype_first_1 = require("./doctype-first");
 Object.defineProperty(exports, "doctypeFirst", { enumerable: true, get: function () { return doctype_first_1.default; } });
 var doctype_html5_1 = require("./doctype-html5");
 Object.defineProperty(exports, "doctypeHTML5", { enumerable: true, get: function () { return doctype_html5_1.default; } });
+var empty_tag_not_self_closed_1 = require("./empty-tag-not-self-closed");
+Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } });
+var h1_require_1 = require("./h1-require");
+Object.defineProperty(exports, "h1Require", { enumerable: true, get: function () { return h1_require_1.default; } });
 var head_script_disabled_1 = require("./head-script-disabled");
 Object.defineProperty(exports, "headScriptDisabled", { enumerable: true, get: function () { return head_script_disabled_1.default; } });
 var href_abs_or_rel_1 = require("./href-abs-or-rel");
@@ -51,20 +57,16 @@ var src_not_empty_1 = require("./src-not-empty");
 Object.defineProperty(exports, "srcNotEmpty", { enumerable: true, get: function () { return src_not_empty_1.default; } });
 var style_disabled_1 = require("./style-disabled");
 Object.defineProperty(exports, "styleDisabled", { enumerable: true, get: function () { return style_disabled_1.default; } });
-var tag_pair_1 = require("./tag-pair");
-Object.defineProperty(exports, "tagPair", { enumerable: true, get: function () { return tag_pair_1.default; } });
-var tag_self_close_1 = require("./tag-self-close");
-Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } });
-var empty_tag_not_self_closed_1 = require("./empty-tag-not-self-closed");
-Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } });
 var tagname_lowercase_1 = require("./tagname-lowercase");
 Object.defineProperty(exports, "tagnameLowercase", { enumerable: true, get: function () { return tagname_lowercase_1.default; } });
 var tagname_specialchars_1 = require("./tagname-specialchars");
 Object.defineProperty(exports, "tagnameSpecialChars", { enumerable: true, get: function () { return tagname_specialchars_1.default; } });
-var title_require_1 = require("./title-require");
-Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } });
+var tag_pair_1 = require("./tag-pair");
+Object.defineProperty(exports, "tagPair", { enumerable: true, get: function () { return tag_pair_1.default; } });
 var tags_check_1 = require("./tags-check");
 Object.defineProperty(exports, "tagsCheck", { enumerable: true, get: function () { return tags_check_1.default; } });
-var attr_no_unnecessary_whitespace_1 = require("./attr-no-unnecessary-whitespace");
-Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } });
-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9ydWxlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBcUQ7QUFBNUMseUdBQUEsT0FBTyxPQUFjO0FBQzlCLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLDZDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQVk7QUFDNUIsNkRBQW9FO0FBQTNELHdIQUFBLE9BQU8sT0FBcUI7QUFDckMseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsK0RBQXNFO0FBQTdELDBIQUFBLE9BQU8sT0FBc0I7QUFDdEMscURBQTJEO0FBQWxELCtHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUNBQWlEO0FBQXhDLHFHQUFBLE9BQU8sT0FBWTtBQUM1QixtRUFBMEU7QUFBakUsOEhBQUEsT0FBTyxPQUF3QjtBQUN4QyxpRUFBd0U7QUFBL0QsNEhBQUEsT0FBTyxPQUF1QjtBQUN2QywrREFBc0U7QUFBN0QsMEhBQUEsT0FBTyxPQUFzQjtBQUN0QyxxREFBNkQ7QUFBcEQsaUhBQUEsT0FBTyxPQUFrQjtBQUNsQyx1RUFBNkU7QUFBcEUsaUlBQUEsT0FBTyxPQUF5QjtBQUN6Qyx1REFBOEQ7QUFBckQsa0hBQUEsT0FBTyxPQUFrQjtBQUNsQyxpREFBd0Q7QUFBL0MsNEdBQUEsT0FBTyxPQUFlO0FBQy9CLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLHVDQUErQztBQUF0QyxtR0FBQSxPQUFPLE9BQVc7QUFDM0IsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBeUI7QUFDekMseURBQWlFO0FBQXhELHFIQUFBLE9BQU8sT0FBb0I7QUFDcEMsK0RBQXVFO0FBQTlELDJIQUFBLE9BQU8sT0FBdUI7QUFDdkMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsMkNBQW1EO0FBQTFDLHVHQUFBLE9BQU8sT0FBYTtBQUM3QixtRkFBeUY7QUFBaEYsNklBQUEsT0FBTyxPQUErQiJ9
\ No newline at end of file
+var tag_self_close_1 = require("./tag-self-close");
+Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } });
+var title_require_1 = require("./title-require");
+Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } });
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9ydWxlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBcUQ7QUFBNUMseUdBQUEsT0FBTyxPQUFjO0FBQzlCLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLDZEQUFvRTtBQUEzRCx3SEFBQSxPQUFPLE9BQXFCO0FBQ3JDLG1GQUF5RjtBQUFoRiw2SUFBQSxPQUFPLE9BQStCO0FBQy9DLDZDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQVk7QUFDNUIseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBeUI7QUFDekMsMkNBQW1EO0FBQTFDLHVHQUFBLE9BQU8sT0FBYTtBQUM3QiwrREFBc0U7QUFBN0QsMEhBQUEsT0FBTyxPQUFzQjtBQUN0QyxxREFBMkQ7QUFBbEQsK0dBQUEsT0FBTyxPQUFnQjtBQUNoQyx5REFBZ0U7QUFBdkQsb0hBQUEsT0FBTyxPQUFtQjtBQUNuQywrREFBcUU7QUFBNUQseUhBQUEsT0FBTyxPQUFxQjtBQUNyQyxtREFBMEQ7QUFBakQsOEdBQUEsT0FBTyxPQUFnQjtBQUNoQyx5Q0FBaUQ7QUFBeEMscUdBQUEsT0FBTyxPQUFZO0FBQzVCLG1FQUEwRTtBQUFqRSw4SEFBQSxPQUFPLE9BQXdCO0FBQ3hDLGlFQUF3RTtBQUEvRCw0SEFBQSxPQUFPLE9BQXVCO0FBQ3ZDLCtEQUFzRTtBQUE3RCwwSEFBQSxPQUFPLE9BQXNCO0FBQ3RDLHFEQUE2RDtBQUFwRCxpSEFBQSxPQUFPLE9BQWtCO0FBQ2xDLHVFQUE2RTtBQUFwRSxpSUFBQSxPQUFPLE9BQXlCO0FBQ3pDLHVEQUE4RDtBQUFyRCxrSEFBQSxPQUFPLE9BQWtCO0FBQ2xDLGlEQUF3RDtBQUEvQyw0R0FBQSxPQUFPLE9BQWU7QUFDL0IsbURBQTJEO0FBQWxELCtHQUFBLE9BQU8sT0FBaUI7QUFDakMseURBQWlFO0FBQXhELHFIQUFBLE9BQU8sT0FBb0I7QUFDcEMsK0RBQXVFO0FBQTlELDJIQUFBLE9BQU8sT0FBdUI7QUFDdkMsdUNBQStDO0FBQXRDLG1HQUFBLE9BQU8sT0FBVztBQUMzQiwyQ0FBbUQ7QUFBMUMsdUdBQUEsT0FBTyxPQUFhO0FBQzdCLG1EQUEwRDtBQUFqRCw4R0FBQSxPQUFPLE9BQWdCO0FBQ2hDLGlEQUF5RDtBQUFoRCw2R0FBQSxPQUFPLE9BQWdCIn0=
\ No newline at end of file
diff --git a/dist/htmlhint.js b/dist/htmlhint.js
index 936bd5c..8fb131b 100644
--- a/dist/htmlhint.js
+++ b/dist/htmlhint.js
@@ -483,6 +483,70 @@
 		return attrLowercase;
 	}
 
+	var attrNoDuplication = {};
+
+	var hasRequiredAttrNoDuplication;
+
+	function requireAttrNoDuplication () {
+		if (hasRequiredAttrNoDuplication) return attrNoDuplication;
+		hasRequiredAttrNoDuplication = 1;
+		Object.defineProperty(attrNoDuplication, "__esModule", { value: true });
+		attrNoDuplication.default = {
+		    id: 'attr-no-duplication',
+		    description: 'Elements cannot have duplicate attributes.',
+		    init(parser, reporter) {
+		        parser.addListener('tagstart', (event) => {
+		            const attrs = event.attrs;
+		            let attr;
+		            let attrName;
+		            const col = event.col + event.tagName.length + 1;
+		            const mapAttrName = {};
+		            for (let i = 0, l = attrs.length; i < l; i++) {
+		                attr = attrs[i];
+		                attrName = attr.name;
+		                if (mapAttrName[attrName] === true) {
+		                    reporter.error(`Duplicate of attribute name [ ${attr.name} ] was found.`, event.line, col + attr.index, this, attr.raw);
+		                }
+		                mapAttrName[attrName] = true;
+		            }
+		        });
+		    },
+		};
+		
+		return attrNoDuplication;
+	}
+
+	var attrNoUnnecessaryWhitespace = {};
+
+	var hasRequiredAttrNoUnnecessaryWhitespace;
+
+	function requireAttrNoUnnecessaryWhitespace () {
+		if (hasRequiredAttrNoUnnecessaryWhitespace) return attrNoUnnecessaryWhitespace;
+		hasRequiredAttrNoUnnecessaryWhitespace = 1;
+		Object.defineProperty(attrNoUnnecessaryWhitespace, "__esModule", { value: true });
+		attrNoUnnecessaryWhitespace.default = {
+		    id: 'attr-no-unnecessary-whitespace',
+		    description: 'No spaces between attribute names and values.',
+		    init(parser, reporter, options) {
+		        const exceptions = Array.isArray(options) ? options : [];
+		        parser.addListener('tagstart', (event) => {
+		            const attrs = event.attrs;
+		            const col = event.col + event.tagName.length + 1;
+		            for (let i = 0; i < attrs.length; i++) {
+		                if (exceptions.indexOf(attrs[i].name) === -1) {
+		                    const match = /(\s*)=(\s*)/.exec(attrs[i].raw.trim());
+		                    if (match && (match[1].length !== 0 || match[2].length !== 0)) {
+		                        reporter.error(`The attribute '${attrs[i].name}' must not have spaces between the name and value.`, event.line, col + attrs[i].index, this, attrs[i].raw);
+		                    }
+		                }
+		            }
+		        });
+		    },
+		};
+		
+		return attrNoUnnecessaryWhitespace;
+	}
+
 	var attrSorted = {};
 
 	var hasRequiredAttrSorted;
@@ -551,39 +615,6 @@
 		return attrSorted;
 	}
 
-	var attrNoDuplication = {};
-
-	var hasRequiredAttrNoDuplication;
-
-	function requireAttrNoDuplication () {
-		if (hasRequiredAttrNoDuplication) return attrNoDuplication;
-		hasRequiredAttrNoDuplication = 1;
-		Object.defineProperty(attrNoDuplication, "__esModule", { value: true });
-		attrNoDuplication.default = {
-		    id: 'attr-no-duplication',
-		    description: 'Elements cannot have duplicate attributes.',
-		    init(parser, reporter) {
-		        parser.addListener('tagstart', (event) => {
-		            const attrs = event.attrs;
-		            let attr;
-		            let attrName;
-		            const col = event.col + event.tagName.length + 1;
-		            const mapAttrName = {};
-		            for (let i = 0, l = attrs.length; i < l; i++) {
-		                attr = attrs[i];
-		                attrName = attr.name;
-		                if (mapAttrName[attrName] === true) {
-		                    reporter.error(`Duplicate of attribute name [ ${attr.name} ] was found.`, event.line, col + attr.index, this, attr.raw);
-		                }
-		                mapAttrName[attrName] = true;
-		            }
-		        });
-		    },
-		};
-		
-		return attrNoDuplication;
-	}
-
 	var attrUnsafeChars = {};
 
 	var hasRequiredAttrUnsafeChars;
@@ -807,6 +838,103 @@
 		return doctypeHtml5;
 	}
 
+	var emptyTagNotSelfClosed = {};
+
+	var hasRequiredEmptyTagNotSelfClosed;
+
+	function requireEmptyTagNotSelfClosed () {
+		if (hasRequiredEmptyTagNotSelfClosed) return emptyTagNotSelfClosed;
+		hasRequiredEmptyTagNotSelfClosed = 1;
+		Object.defineProperty(emptyTagNotSelfClosed, "__esModule", { value: true });
+		emptyTagNotSelfClosed.default = {
+		    id: 'empty-tag-not-self-closed',
+		    description: 'Empty tags must not use self closed syntax.',
+		    init(parser, reporter) {
+		        const mapEmptyTags = parser.makeMap('area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
+		        parser.addListener('tagstart', (event) => {
+		            const tagName = event.tagName.toLowerCase();
+		            if (mapEmptyTags[tagName] !== undefined) {
+		                if (event.close) {
+		                    reporter.error(`The empty tag : [ ${tagName} ] must not use self closed syntax.`, event.line, event.col, this, event.raw);
+		                }
+		            }
+		        });
+		    },
+		};
+		
+		return emptyTagNotSelfClosed;
+	}
+
+	var h1Require = {};
+
+	var hasRequiredH1Require;
+
+	function requireH1Require () {
+		if (hasRequiredH1Require) return h1Require;
+		hasRequiredH1Require = 1;
+		Object.defineProperty(h1Require, "__esModule", { value: true });
+		h1Require.default = {
+		    id: 'h1-require',
+		    description: '<h1> must be present in <body> tag and not be empty.',
+		    init(parser, reporter) {
+		        let bodyDepth = 0;
+		        let hasH1InBody = false;
+		        let bodyTagEvent = null;
+		        let currentH1Event = null;
+		        let h1IsEmpty = false;
+		        const onTagStart = (event) => {
+		            const tagName = event.tagName.toLowerCase();
+		            if (tagName === 'body') {
+		                bodyDepth++;
+		                if (bodyDepth === 1) {
+		                    hasH1InBody = false;
+		                    bodyTagEvent = event;
+		                }
+		            }
+		            else if (tagName === 'h1' && bodyDepth > 0) {
+		                hasH1InBody = true;
+		                currentH1Event = event;
+		                h1IsEmpty = true;
+		            }
+		        };
+		        const onText = (event) => {
+		            if (currentH1Event && h1IsEmpty) {
+		                if (event.raw && !/^\s*$/.test(event.raw)) {
+		                    h1IsEmpty = false;
+		                }
+		            }
+		        };
+		        const onTagEnd = (event) => {
+		            const tagName = event.tagName.toLowerCase();
+		            if (tagName === 'h1' && currentH1Event) {
+		                if (h1IsEmpty) {
+		                    reporter.warn('<h1> tag must not be empty.', currentH1Event.line, currentH1Event.col, this, currentH1Event.raw);
+		                }
+		                currentH1Event = null;
+		            }
+		            else if (tagName === 'body') {
+		                if (bodyDepth === 1 && !hasH1InBody && bodyTagEvent) {
+		                    reporter.warn('<h1> must be present in <body> tag.', bodyTagEvent.line, bodyTagEvent.col, this, bodyTagEvent.raw);
+		                }
+		                bodyDepth--;
+		                if (bodyDepth < 0)
+		                    bodyDepth = 0;
+		            }
+		        };
+		        parser.addListener('tagstart', onTagStart);
+		        parser.addListener('tagend', onTagEnd);
+		        parser.addListener('text', onText);
+		        parser.addListener('end', () => {
+		            if (bodyDepth > 0 && !hasH1InBody && bodyTagEvent) {
+		                reporter.warn('<h1> must be present in <body> tag.', bodyTagEvent.line, bodyTagEvent.col, this, bodyTagEvent.raw);
+		            }
+		        });
+		    },
+		};
+		
+		return h1Require;
+	}
+
 	var headScriptDisabled = {};
 
 	var hasRequiredHeadScriptDisabled;
@@ -1366,124 +1494,6 @@
 		return styleDisabled;
 	}
 
-	var tagPair = {};
-
-	var hasRequiredTagPair;
-
-	function requireTagPair () {
-		if (hasRequiredTagPair) return tagPair;
-		hasRequiredTagPair = 1;
-		Object.defineProperty(tagPair, "__esModule", { value: true });
-		tagPair.default = {
-		    id: 'tag-pair',
-		    description: 'Tag must be paired.',
-		    init(parser, reporter) {
-		        const stack = [];
-		        const mapEmptyTags = parser.makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
-		        parser.addListener('tagstart', (event) => {
-		            const tagName = event.tagName.toLowerCase();
-		            if (mapEmptyTags[tagName] === undefined && !event.close) {
-		                stack.push({
-		                    tagName: tagName,
-		                    line: event.line,
-		                    col: event.col,
-		                    raw: event.raw,
-		                });
-		            }
-		        });
-		        parser.addListener('tagend', (event) => {
-		            const tagName = event.tagName.toLowerCase();
-		            let pos;
-		            for (pos = stack.length - 1; pos >= 0; pos--) {
-		                if (stack[pos].tagName === tagName) {
-		                    break;
-		                }
-		            }
-		            if (pos >= 0) {
-		                const arrTags = [];
-		                for (let i = stack.length - 1; i > pos; i--) {
-		                    arrTags.push(`</${stack[i].tagName}>`);
-		                }
-		                if (arrTags.length > 0) {
-		                    const lastEvent = stack[stack.length - 1];
-		                    reporter.error(`Tag must be paired, missing: [ ${arrTags.join('')} ], start tag match failed [ ${lastEvent.raw} ] on line ${lastEvent.line}.`, lastEvent.line || event.line, lastEvent.col || event.col, this, event.raw);
-		                }
-		                stack.length = pos;
-		            }
-		            else {
-		                reporter.error(`Tag must be paired, no start tag: [ ${event.raw} ]`, event.line, event.col, this, event.raw);
-		            }
-		        });
-		        parser.addListener('end', (event) => {
-		            const arrTags = [];
-		            for (let i = stack.length - 1; i >= 0; i--) {
-		                arrTags.push(`</${stack[i].tagName}>`);
-		            }
-		            if (arrTags.length > 0) {
-		                const lastEvent = stack[stack.length - 1];
-		                reporter.error(`Tag must be paired, missing: [ ${arrTags.join('')} ], open tag match failed [ ${lastEvent.raw} ] on line ${lastEvent.line}.`, event.line, event.col, this, '');
-		            }
-		        });
-		    },
-		};
-		
-		return tagPair;
-	}
-
-	var tagSelfClose = {};
-
-	var hasRequiredTagSelfClose;
-
-	function requireTagSelfClose () {
-		if (hasRequiredTagSelfClose) return tagSelfClose;
-		hasRequiredTagSelfClose = 1;
-		Object.defineProperty(tagSelfClose, "__esModule", { value: true });
-		tagSelfClose.default = {
-		    id: 'tag-self-close',
-		    description: 'Empty tags must be self closed.',
-		    init(parser, reporter) {
-		        const mapEmptyTags = parser.makeMap('area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
-		        parser.addListener('tagstart', (event) => {
-		            const tagName = event.tagName.toLowerCase();
-		            if (mapEmptyTags[tagName] !== undefined) {
-		                if (!event.close) {
-		                    reporter.warn(`The empty tag : [ ${tagName} ] must be self closed.`, event.line, event.col, this, event.raw);
-		                }
-		            }
-		        });
-		    },
-		};
-		
-		return tagSelfClose;
-	}
-
-	var emptyTagNotSelfClosed = {};
-
-	var hasRequiredEmptyTagNotSelfClosed;
-
-	function requireEmptyTagNotSelfClosed () {
-		if (hasRequiredEmptyTagNotSelfClosed) return emptyTagNotSelfClosed;
-		hasRequiredEmptyTagNotSelfClosed = 1;
-		Object.defineProperty(emptyTagNotSelfClosed, "__esModule", { value: true });
-		emptyTagNotSelfClosed.default = {
-		    id: 'empty-tag-not-self-closed',
-		    description: 'Empty tags must not use self closed syntax.',
-		    init(parser, reporter) {
-		        const mapEmptyTags = parser.makeMap('area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
-		        parser.addListener('tagstart', (event) => {
-		            const tagName = event.tagName.toLowerCase();
-		            if (mapEmptyTags[tagName] !== undefined) {
-		                if (event.close) {
-		                    reporter.error(`The empty tag : [ ${tagName} ] must not use self closed syntax.`, event.line, event.col, this, event.raw);
-		                }
-		            }
-		        });
-		    },
-		};
-		
-		return emptyTagNotSelfClosed;
-	}
-
 	var tagnameLowercase = {};
 
 	var hasRequiredTagnameLowercase;
@@ -1537,52 +1547,68 @@
 		return tagnameSpecialchars;
 	}
 
-	var titleRequire = {};
+	var tagPair = {};
 
-	var hasRequiredTitleRequire;
+	var hasRequiredTagPair;
 
-	function requireTitleRequire () {
-		if (hasRequiredTitleRequire) return titleRequire;
-		hasRequiredTitleRequire = 1;
-		Object.defineProperty(titleRequire, "__esModule", { value: true });
-		titleRequire.default = {
-		    id: 'title-require',
-		    description: '<title> must be present in <head> tag.',
+	function requireTagPair () {
+		if (hasRequiredTagPair) return tagPair;
+		hasRequiredTagPair = 1;
+		Object.defineProperty(tagPair, "__esModule", { value: true });
+		tagPair.default = {
+		    id: 'tag-pair',
+		    description: 'Tag must be paired.',
 		    init(parser, reporter) {
-		        let headBegin = false;
-		        let hasTitle = false;
-		        const onTagStart = (event) => {
+		        const stack = [];
+		        const mapEmptyTags = parser.makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
+		        parser.addListener('tagstart', (event) => {
 		            const tagName = event.tagName.toLowerCase();
-		            if (tagName === 'head') {
-		                headBegin = true;
-		            }
-		            else if (tagName === 'title' && headBegin) {
-		                hasTitle = true;
+		            if (mapEmptyTags[tagName] === undefined && !event.close) {
+		                stack.push({
+		                    tagName: tagName,
+		                    line: event.line,
+		                    col: event.col,
+		                    raw: event.raw,
+		                });
 		            }
-		        };
-		        const onTagEnd = (event) => {
+		        });
+		        parser.addListener('tagend', (event) => {
 		            const tagName = event.tagName.toLowerCase();
-		            if (hasTitle && tagName === 'title') {
-		                const lastEvent = event.lastEvent;
-		                if (lastEvent.type !== 'text' ||
-		                    (lastEvent.type === 'text' && /^\s*$/.test(lastEvent.raw) === true)) {
-		                    reporter.error('<title></title> must not be empty.', event.line, event.col, this, event.raw);
+		            let pos;
+		            for (pos = stack.length - 1; pos >= 0; pos--) {
+		                if (stack[pos].tagName === tagName) {
+		                    break;
 		                }
 		            }
-		            else if (tagName === 'head') {
-		                if (hasTitle === false) {
-		                    reporter.error('<title> must be present in <head> tag.', event.line, event.col, this, event.raw);
+		            if (pos >= 0) {
+		                const arrTags = [];
+		                for (let i = stack.length - 1; i > pos; i--) {
+		                    arrTags.push(`</${stack[i].tagName}>`);
 		                }
-		                parser.removeListener('tagstart', onTagStart);
-		                parser.removeListener('tagend', onTagEnd);
+		                if (arrTags.length > 0) {
+		                    const lastEvent = stack[stack.length - 1];
+		                    reporter.error(`Tag must be paired, missing: [ ${arrTags.join('')} ], start tag match failed [ ${lastEvent.raw} ] on line ${lastEvent.line}.`, lastEvent.line || event.line, lastEvent.col || event.col, this, event.raw);
+		                }
+		                stack.length = pos;
 		            }
-		        };
-		        parser.addListener('tagstart', onTagStart);
-		        parser.addListener('tagend', onTagEnd);
+		            else {
+		                reporter.error(`Tag must be paired, no start tag: [ ${event.raw} ]`, event.line, event.col, this, event.raw);
+		            }
+		        });
+		        parser.addListener('end', (event) => {
+		            const arrTags = [];
+		            for (let i = stack.length - 1; i >= 0; i--) {
+		                arrTags.push(`</${stack[i].tagName}>`);
+		            }
+		            if (arrTags.length > 0) {
+		                const lastEvent = stack[stack.length - 1];
+		                reporter.error(`Tag must be paired, missing: [ ${arrTags.join('')} ], open tag match failed [ ${lastEvent.raw} ] on line ${lastEvent.line}.`, event.line, event.col, this, '');
+		            }
+		        });
 		    },
 		};
 		
-		return titleRequire;
+		return tagPair;
 	}
 
 	var tagsCheck = {};
@@ -1696,35 +1722,79 @@
 		return tagsCheck;
 	}
 
-	var attrNoUnnecessaryWhitespace = {};
+	var tagSelfClose = {};
 
-	var hasRequiredAttrNoUnnecessaryWhitespace;
+	var hasRequiredTagSelfClose;
 
-	function requireAttrNoUnnecessaryWhitespace () {
-		if (hasRequiredAttrNoUnnecessaryWhitespace) return attrNoUnnecessaryWhitespace;
-		hasRequiredAttrNoUnnecessaryWhitespace = 1;
-		Object.defineProperty(attrNoUnnecessaryWhitespace, "__esModule", { value: true });
-		attrNoUnnecessaryWhitespace.default = {
-		    id: 'attr-no-unnecessary-whitespace',
-		    description: 'No spaces between attribute names and values.',
-		    init(parser, reporter, options) {
-		        const exceptions = Array.isArray(options) ? options : [];
+	function requireTagSelfClose () {
+		if (hasRequiredTagSelfClose) return tagSelfClose;
+		hasRequiredTagSelfClose = 1;
+		Object.defineProperty(tagSelfClose, "__esModule", { value: true });
+		tagSelfClose.default = {
+		    id: 'tag-self-close',
+		    description: 'Empty tags must be self closed.',
+		    init(parser, reporter) {
+		        const mapEmptyTags = parser.makeMap('area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr');
 		        parser.addListener('tagstart', (event) => {
-		            const attrs = event.attrs;
-		            const col = event.col + event.tagName.length + 1;
-		            for (let i = 0; i < attrs.length; i++) {
-		                if (exceptions.indexOf(attrs[i].name) === -1) {
-		                    const match = /(\s*)=(\s*)/.exec(attrs[i].raw.trim());
-		                    if (match && (match[1].length !== 0 || match[2].length !== 0)) {
-		                        reporter.error(`The attribute '${attrs[i].name}' must not have spaces between the name and value.`, event.line, col + attrs[i].index, this, attrs[i].raw);
-		                    }
+		            const tagName = event.tagName.toLowerCase();
+		            if (mapEmptyTags[tagName] !== undefined) {
+		                if (!event.close) {
+		                    reporter.warn(`The empty tag : [ ${tagName} ] must be self closed.`, event.line, event.col, this, event.raw);
 		                }
 		            }
 		        });
 		    },
 		};
 		
-		return attrNoUnnecessaryWhitespace;
+		return tagSelfClose;
+	}
+
+	var titleRequire = {};
+
+	var hasRequiredTitleRequire;
+
+	function requireTitleRequire () {
+		if (hasRequiredTitleRequire) return titleRequire;
+		hasRequiredTitleRequire = 1;
+		Object.defineProperty(titleRequire, "__esModule", { value: true });
+		titleRequire.default = {
+		    id: 'title-require',
+		    description: '<title> must be present in <head> tag.',
+		    init(parser, reporter) {
+		        let headBegin = false;
+		        let hasTitle = false;
+		        const onTagStart = (event) => {
+		            const tagName = event.tagName.toLowerCase();
+		            if (tagName === 'head') {
+		                headBegin = true;
+		            }
+		            else if (tagName === 'title' && headBegin) {
+		                hasTitle = true;
+		            }
+		        };
+		        const onTagEnd = (event) => {
+		            const tagName = event.tagName.toLowerCase();
+		            if (hasTitle && tagName === 'title') {
+		                const lastEvent = event.lastEvent;
+		                if (lastEvent.type !== 'text' ||
+		                    (lastEvent.type === 'text' && /^\s*$/.test(lastEvent.raw) === true)) {
+		                    reporter.error('<title></title> must not be empty.', event.line, event.col, this, event.raw);
+		                }
+		            }
+		            else if (tagName === 'head') {
+		                if (hasTitle === false) {
+		                    reporter.error('<title> must be present in <head> tag.', event.line, event.col, this, event.raw);
+		                }
+		                parser.removeListener('tagstart', onTagStart);
+		                parser.removeListener('tagend', onTagEnd);
+		            }
+		        };
+		        parser.addListener('tagstart', onTagStart);
+		        parser.addListener('tagend', onTagEnd);
+		    },
+		};
+		
+		return titleRequire;
 	}
 
 	var hasRequiredRules;
@@ -1734,15 +1804,17 @@
 		hasRequiredRules = 1;
 		(function (exports) {
 			Object.defineProperty(exports, "__esModule", { value: true });
-			exports.attrNoUnnecessaryWhitespace = exports.tagsCheck = exports.titleRequire = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.emptyTagNotSelfClosed = exports.tagSelfClose = exports.tagPair = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrNoDuplication = exports.attrSort = exports.attrLowercase = exports.altRequire = void 0;
+			exports.titleRequire = exports.tagSelfClose = exports.tagsCheck = exports.tagPair = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.h1Require = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSort = exports.attrNoUnnecessaryWhitespace = exports.attrNoDuplication = exports.attrLowercase = exports.altRequire = void 0;
 			var alt_require_1 = requireAltRequire();
 			Object.defineProperty(exports, "altRequire", { enumerable: true, get: function () { return alt_require_1.default; } });
 			var attr_lowercase_1 = requireAttrLowercase();
 			Object.defineProperty(exports, "attrLowercase", { enumerable: true, get: function () { return attr_lowercase_1.default; } });
-			var attr_sorted_1 = requireAttrSorted();
-			Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } });
 			var attr_no_duplication_1 = requireAttrNoDuplication();
 			Object.defineProperty(exports, "attrNoDuplication", { enumerable: true, get: function () { return attr_no_duplication_1.default; } });
+			var attr_no_unnecessary_whitespace_1 = requireAttrNoUnnecessaryWhitespace();
+			Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } });
+			var attr_sorted_1 = requireAttrSorted();
+			Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } });
 			var attr_unsafe_chars_1 = requireAttrUnsafeChars();
 			Object.defineProperty(exports, "attrUnsafeChars", { enumerable: true, get: function () { return attr_unsafe_chars_1.default; } });
 			var attr_value_double_quotes_1 = requireAttrValueDoubleQuotes();
@@ -1757,6 +1829,10 @@
 			Object.defineProperty(exports, "doctypeFirst", { enumerable: true, get: function () { return doctype_first_1.default; } });
 			var doctype_html5_1 = requireDoctypeHtml5();
 			Object.defineProperty(exports, "doctypeHTML5", { enumerable: true, get: function () { return doctype_html5_1.default; } });
+			var empty_tag_not_self_closed_1 = requireEmptyTagNotSelfClosed();
+			Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } });
+			var h1_require_1 = requireH1Require();
+			Object.defineProperty(exports, "h1Require", { enumerable: true, get: function () { return h1_require_1.default; } });
 			var head_script_disabled_1 = requireHeadScriptDisabled();
 			Object.defineProperty(exports, "headScriptDisabled", { enumerable: true, get: function () { return head_script_disabled_1.default; } });
 			var href_abs_or_rel_1 = requireHrefAbsOrRel();
@@ -1785,22 +1861,18 @@
 			Object.defineProperty(exports, "srcNotEmpty", { enumerable: true, get: function () { return src_not_empty_1.default; } });
 			var style_disabled_1 = requireStyleDisabled();
 			Object.defineProperty(exports, "styleDisabled", { enumerable: true, get: function () { return style_disabled_1.default; } });
-			var tag_pair_1 = requireTagPair();
-			Object.defineProperty(exports, "tagPair", { enumerable: true, get: function () { return tag_pair_1.default; } });
-			var tag_self_close_1 = requireTagSelfClose();
-			Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } });
-			var empty_tag_not_self_closed_1 = requireEmptyTagNotSelfClosed();
-			Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } });
 			var tagname_lowercase_1 = requireTagnameLowercase();
 			Object.defineProperty(exports, "tagnameLowercase", { enumerable: true, get: function () { return tagname_lowercase_1.default; } });
 			var tagname_specialchars_1 = requireTagnameSpecialchars();
 			Object.defineProperty(exports, "tagnameSpecialChars", { enumerable: true, get: function () { return tagname_specialchars_1.default; } });
-			var title_require_1 = requireTitleRequire();
-			Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } });
+			var tag_pair_1 = requireTagPair();
+			Object.defineProperty(exports, "tagPair", { enumerable: true, get: function () { return tag_pair_1.default; } });
 			var tags_check_1 = requireTagsCheck();
 			Object.defineProperty(exports, "tagsCheck", { enumerable: true, get: function () { return tags_check_1.default; } });
-			var attr_no_unnecessary_whitespace_1 = requireAttrNoUnnecessaryWhitespace();
-			Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } });
+			var tag_self_close_1 = requireTagSelfClose();
+			Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } });
+			var title_require_1 = requireTitleRequire();
+			Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } });
 			
 		} (rules));
 		return rules;
diff --git a/dist/htmlhint.min.js b/dist/htmlhint.min.js
index d66e1d6..1d05705 100644
--- a/dist/htmlhint.min.js
+++ b/dist/htmlhint.min.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).HTMLHint=t()}(this,(function(){"use strict";function e(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,a={},r={};var n,s={};var i,l={},o={};var d,u={};var c,f={};var g,h={};var p,m={};var b,v={};var y,w={};var x,L={};var O,j={};var P,_={};var $,T={};var A,M={};var C,N={};var k,q={};var E,R={};var S,z={};var D,U={};var Z,H={};var I,F={};var V,W={};function B(){return V||(V=1,Object.defineProperty(W,"__esModule",{value:!0}),W.default={id:"input-requires-label",description:"All [ input ] tags must have a corresponding [ label ] tag. ",init(e,t){const a=[],r=[];e.addListener("tagstart",(t=>{const n=t.tagName.toLowerCase(),s=e.getMapAttrs(t.attrs),i=t.col+n.length+1;"input"===n&&"hidden"!==s.type&&r.push({event:t,col:i,id:s.id}),"label"===n&&"for"in s&&""!==s.for&&a.push({event:t,col:i,forValue:s.for})})),e.addListener("end",(()=>{r.forEach((e=>{(function(e){let t=!1;return a.forEach((a=>{e.id&&e.id===a.forValue&&(t=!0)})),t})(e)||t.warn("No matching [ label ] tag found.",e.event.line,e.col,this,e.event.raw)}))}))}}),W}var Y,J={};var Q,X={};var G,K={};var ee,te={};var ae,re={};var ne,se={};var ie,le={};var oe,de={};var ue,ce={};var fe,ge={};var he,pe={};var me,be={};var ve,ye,we,xe={};function Le(){return ye||(ye=1,function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.attrNoUnnecessaryWhitespace=e.tagsCheck=e.titleRequire=e.tagnameSpecialChars=e.tagnameLowercase=e.emptyTagNotSelfClosed=e.tagSelfClose=e.tagPair=e.styleDisabled=e.srcNotEmpty=e.specCharEscape=e.spaceTabMixedDisabled=e.scriptDisabled=e.inputRequiresLabel=e.inlineStyleDisabled=e.inlineScriptDisabled=e.idUnique=e.idClassValue=e.idClassAdDisabled=e.htmlLangRequire=e.hrefAbsOrRel=e.headScriptDisabled=e.doctypeHTML5=e.doctypeFirst=e.attrWhitespace=e.attrValueSingleQuotes=e.attrValueNotEmpty=e.attrValueDoubleQuotes=e.attrUnsafeChars=e.attrNoDuplication=e.attrSort=e.attrLowercase=e.altRequire=void 0;var t=(i||(i=1,Object.defineProperty(o,"__esModule",{value:!0}),o.default={id:"alt-require",description:"The alt attribute of an <img> element must be present and alt attribute of area[href] and input[type=image] must have a value.",init(e,t){e.addListener("tagstart",(a=>{const r=a.tagName.toLowerCase(),n=e.getMapAttrs(a.attrs),s=a.col+r.length+1;let i;"img"!==r||"alt"in n?("area"===r&&"href"in n||"input"===r&&"image"===n.type)&&("alt"in n&&""!==n.alt||(i="area"===r?"area[href]":"input[type=image]",t.warn(`The alt attribute of ${i} must have a value.`,a.line,s,this,a.raw))):t.warn("An alt attribute must be present on <img> elements.",a.line,s,this,a.raw)}))}}),o);Object.defineProperty(e,"altRequire",{enumerable:!0,get:function(){return t.default}});var a=function(){if(d)return u;d=1,Object.defineProperty(u,"__esModule",{value:!0});const e=["allowReorder","attributeName","attributeType","autoReverse","baseFrequency","baseProfile","calcMode","clipPath","clipPathUnits","contentScriptType","contentStyleType","diffuseConstant","edgeMode","externalResourcesRequired","filterRes","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","onBlur","onChange","onClick","onFocus","onKeyUp","onLoad","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"];function t(e,t){if(t instanceof RegExp)return!!t.test(e)&&{match:e,pattern:t};const a=t[0],r=t[t.length-1],n=t[t.length-2],s="/"===a&&("/"===r||"/"===n&&"i"===r);return s?s&&"i"===r?new RegExp(t.slice(1,-2),"i").test(e):new RegExp(t.slice(1,-1)).test(e):e===t}return u.default={id:"attr-lowercase",description:"All attribute names must be in lowercase.",init(a,r,n){const s=(Array.isArray(n)?n:[]).concat(e);a.addListener("tagstart",(e=>{const a=e.attrs;let n;const i=e.col+e.tagName.length+1;for(let l=0,o=a.length;l<o;l++){n=a[l];const o=n.name;s.find((e=>t(o,e)))||o===o.toLowerCase()||r.error(`The attribute name of [ ${o} ] must be in lowercase.`,e.line,i+n.index,this,n.raw)}}))}},u}();Object.defineProperty(e,"attrLowercase",{enumerable:!0,get:function(){return a.default}});var r=(c||(c=1,Object.defineProperty(f,"__esModule",{value:!0}),f.default={id:"attr-sorted",description:"Attribute tags must be in proper order.",init(e,t){const a={},r=["class","id","name","src","for","type","rel","href","value","title","alt","role"];for(let e=0;e<r.length;e++)a[r[e]]=e;e.addListener("tagstart",(e=>{const r=e.attrs,n=[];for(let e=0;e<r.length;e++)n.push(r[e].name);const s=JSON.stringify(n);n.sort(((e,t)=>void 0!==a[e]?void 0!==a[t]?a[e]-a[t]:-1:e.startsWith("data-")?t.startsWith("data-")?e.localeCompare(t):1:void 0!==a[t]?1:t.startsWith("data-")?-1:e.localeCompare(t))),s!==JSON.stringify(n)&&t.error(`Inaccurate order ${s} should be in hierarchy ${JSON.stringify(n)} `,e.line,e.col,this,e.raw)}))}}),f);Object.defineProperty(e,"attrSort",{enumerable:!0,get:function(){return r.default}});var n=(g||(g=1,Object.defineProperty(h,"__esModule",{value:!0}),h.default={id:"attr-no-duplication",description:"Elements cannot have duplicate attributes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r,n;const s=e.col+e.tagName.length+1,i={};for(let l=0,o=a.length;l<o;l++)r=a[l],n=r.name,!0===i[n]&&t.error(`Duplicate of attribute name [ ${r.name} ] was found.`,e.line,s+r.index,this,r.raw),i[n]=!0}))}}),h);Object.defineProperty(e,"attrNoDuplication",{enumerable:!0,get:function(){return n.default}});var s=(p||(p=1,Object.defineProperty(m,"__esModule",{value:!0}),m.default={id:"attr-unsafe-chars",description:"Attribute values cannot contain unsafe chars.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1,s=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;let i;for(let l=0,o=a.length;l<o;l++)if(r=a[l],i=s.exec(r.value),null!==i){const a=escape(i[0]).replace(/%u/,"\\u").replace(/%/,"\\x");t.warn(`The value of attribute [ ${r.name} ] cannot contain an unsafe char [ ${a} ].`,e.line,n+r.index,this,r.raw)}}))}}),m);Object.defineProperty(e,"attrUnsafeChars",{enumerable:!0,get:function(){return s.default}});var l=(b||(b=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default={id:"attr-value-double-quotes",description:"Attribute values must be in double quotes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],(""!==r.value&&'"'!==r.quote||""===r.value&&"'"===r.quote)&&t.error(`The value of attribute [ ${r.name} ] must be in double quotes.`,e.line,n+r.index,this,r.raw)}))}}),v);Object.defineProperty(e,"attrValueDoubleQuotes",{enumerable:!0,get:function(){return l.default}});var V=(y||(y=1,Object.defineProperty(w,"__esModule",{value:!0}),w.default={id:"attr-value-not-empty",description:"All attributes must have values.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],""===r.quote&&""===r.value&&t.warn(`The attribute [ ${r.name} ] must have a value.`,e.line,n+r.index,this,r.raw)}))}}),w);Object.defineProperty(e,"attrValueNotEmpty",{enumerable:!0,get:function(){return V.default}});var W=(x||(x=1,Object.defineProperty(L,"__esModule",{value:!0}),L.default={id:"attr-value-single-quotes",description:"Attribute values must be in single quotes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],(""!==r.value&&"'"!==r.quote||""===r.value&&'"'===r.quote)&&t.error(`The value of attribute [ ${r.name} ] must be in single quotes.`,e.line,n+r.index,this,r.raw)}))}}),L);Object.defineProperty(e,"attrValueSingleQuotes",{enumerable:!0,get:function(){return W.default}});var ye=(O||(O=1,Object.defineProperty(j,"__esModule",{value:!0}),j.default={id:"attr-whitespace",description:"All attributes should be separated by only one space and not have leading/trailing whitespace.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart",(e=>{const a=e.attrs;let n;const s=e.col+e.tagName.length+1;a.forEach((a=>{n=a;const i=a.name;-1===r.indexOf(i)&&(a.value.trim()!==a.value&&t.error(`The attributes of [ ${i} ] must not have leading or trailing whitespace.`,e.line,s+n.index,this,n.raw),a.value.replace(/ +(?= )/g,"")!==a.value&&t.error(`The attributes of [ ${i} ] must be separated by only one space.`,e.line,s+n.index,this,n.raw))}))}))}}),j);Object.defineProperty(e,"attrWhitespace",{enumerable:!0,get:function(){return ye.default}});var we=(P||(P=1,Object.defineProperty(_,"__esModule",{value:!0}),_.default={id:"doctype-first",description:"Doctype must be declared first.",init(e,t){const a=r=>{"start"===r.type||"text"===r.type&&/^\s*$/.test(r.raw)||(("comment"!==r.type&&!1===r.long||!1===/^DOCTYPE\s+/i.test(r.content))&&t.error("Doctype must be declared first.",r.line,r.col,this,r.raw),e.removeListener("all",a))};e.addListener("all",a)}}),_);Object.defineProperty(e,"doctypeFirst",{enumerable:!0,get:function(){return we.default}});var Le=($||($=1,Object.defineProperty(T,"__esModule",{value:!0}),T.default={id:"doctype-html5",description:'Invalid doctype. Use: "<!DOCTYPE html>"',init(e,t){const a=e=>{!1===e.long&&"doctype html"!==e.content.toLowerCase()&&t.warn('Invalid doctype. Use: "<!DOCTYPE html>"',e.line,e.col,this,e.raw)},r=()=>{e.removeListener("comment",a),e.removeListener("tagstart",r)};e.addListener("all",a),e.addListener("tagstart",r)}}),T);Object.defineProperty(e,"doctypeHTML5",{enumerable:!0,get:function(){return Le.default}});var Oe=(A||(A=1,Object.defineProperty(M,"__esModule",{value:!0}),M.default={id:"head-script-disabled",description:"The <script> tag cannot be used in a <head> tag.",init(e,t){const a=/^(text\/javascript|application\/javascript)$/i;let r=!1;const n=n=>{const s=e.getMapAttrs(n.attrs).type,i=n.tagName.toLowerCase();"head"===i&&(r=!0),!0!==r||"script"!==i||s&&!0!==a.test(s)||t.warn("The <script> tag cannot be used in a <head> tag.",n.line,n.col,this,n.raw)},s=t=>{"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",n),e.removeListener("tagend",s))};e.addListener("tagstart",n),e.addListener("tagend",s)}}),M);Object.defineProperty(e,"headScriptDisabled",{enumerable:!0,get:function(){return Oe.default}});var je=(C||(C=1,Object.defineProperty(N,"__esModule",{value:!0}),N.default={id:"href-abs-or-rel",description:"An href attribute must be either absolute or relative.",init(e,t,a){const r="abs"===a?"absolute":"relative";e.addListener("tagstart",(e=>{const a=e.attrs;let n;const s=e.col+e.tagName.length+1;for(let i=0,l=a.length;i<l;i++)if(n=a[i],"href"===n.name){("absolute"===r&&!1===/^\w+?:/.test(n.value)||"relative"===r&&!0===/^https?:\/\//.test(n.value))&&t.warn(`The value of the href attribute [ ${n.value} ] must be ${r}.`,e.line,s+n.index,this,n.raw);break}}))}}),N);Object.defineProperty(e,"hrefAbsOrRel",{enumerable:!0,get:function(){return je.default}});var Pe=(k||(k=1,Object.defineProperty(q,"__esModule",{value:!0}),q.default={id:"html-lang-require",description:"The lang attribute of an <html> element must be present and should be valid.",init(e,t){e.addListener("tagstart",(a=>{const r=a.tagName.toLowerCase(),n=e.getMapAttrs(a.attrs),s=a.col+r.length+1,i=new RegExp("((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse2>x(-[A-Za-z0-9]{1,8})+))","g");"html"===r&&("lang"in n?n.lang?i.test(n.lang)||t.warn("The lang attribute value of <html> element must be a valid BCP47.",a.line,s,this,a.raw):t.warn("The lang attribute of <html> element must have a value.",a.line,s,this,a.raw):t.warn("An lang attribute must be present on <html> elements.",a.line,s,this,a.raw))}))}}),q);Object.defineProperty(e,"htmlLangRequire",{enumerable:!0,get:function(){return Pe.default}});var _e=(E||(E=1,Object.defineProperty(R,"__esModule",{value:!0}),R.default={id:"id-class-ad-disabled",description:"The id and class attributes cannot use the ad keyword, it will be blocked by adblock software.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r,n;const s=e.col+e.tagName.length+1;for(let i=0,l=a.length;i<l;i++)r=a[i],n=r.name,/^(id|class)$/i.test(n)&&/(^|[-_])ad([-_]|$)/i.test(r.value)&&t.warn(`The value of attribute ${n} cannot use the ad keyword.`,e.line,s+r.index,this,r.raw)}))}}),R);Object.defineProperty(e,"idClassAdDisabled",{enumerable:!0,get:function(){return _e.default}});var $e=(S||(S=1,Object.defineProperty(z,"__esModule",{value:!0}),z.default={id:"id-class-value",description:"The id and class attribute values must meet the specified rules.",init(e,t,a){let r;if(r="string"==typeof a?{underline:{regId:/^[a-z\d]+(_[a-z\d]+)*$/,message:"The id and class attribute values must be in lowercase and split by an underscore."},dash:{regId:/^[a-z\d]+(-[a-z\d]+)*$/,message:"The id and class attribute values must be in lowercase and split by a dash."},hump:{regId:/^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,message:"The id and class attribute values must meet the camelCase style."}}[a]:a,"object"==typeof r&&r.regId){let a=r.regId;const n=r.message;a instanceof RegExp||(a=new RegExp(a)),e.addListener("tagstart",(e=>{const r=e.attrs;let s;const i=e.col+e.tagName.length+1;for(let l=0,o=r.length;l<o;l++)if(s=r[l],"id"===s.name.toLowerCase()&&!1===a.test(s.value)&&t.warn(n,e.line,i+s.index,this,s.raw),"class"===s.name.toLowerCase()){const r=s.value.split(/\s+/g);let l;for(let o=0,d=r.length;o<d;o++)l=r[o],l&&!1===a.test(l)&&t.warn(n,e.line,i+s.index,this,l)}}))}}}),z);Object.defineProperty(e,"idClassValue",{enumerable:!0,get:function(){return $e.default}});var Te=(D||(D=1,Object.defineProperty(U,"__esModule",{value:!0}),U.default={id:"id-unique",description:"The value of id attributes must be unique.",init(e,t){const a={};e.addListener("tagstart",(e=>{const r=e.attrs;let n,s;const i=e.col+e.tagName.length+1;for(let l=0,o=r.length;l<o;l++)if(n=r[l],"id"===n.name.toLowerCase()){s=n.value,s&&(void 0===a[s]?a[s]=1:a[s]++,a[s]>1&&t.error(`The id value [ ${s} ] must be unique.`,e.line,i+n.index,this,n.raw));break}}))}}),U);Object.defineProperty(e,"idUnique",{enumerable:!0,get:function(){return Te.default}});var Ae=(Z||(Z=1,Object.defineProperty(H,"__esModule",{value:!0}),H.default={id:"inline-script-disabled",description:"Inline script cannot be used.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;let s;const i=/^on(unload|message|submit|select|scroll|resize|mouseover|mouseout|mousemove|mouseleave|mouseenter|mousedown|load|keyup|keypress|keydown|focus|dblclick|click|change|blur|error)$/i;for(let l=0,o=a.length;l<o;l++)r=a[l],s=r.name.toLowerCase(),!0===i.test(s)?t.warn(`Inline script [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw):"src"!==s&&"href"!==s||/^\s*javascript:/i.test(r.value)&&t.warn(`Inline script [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw)}))}}),H);Object.defineProperty(e,"inlineScriptDisabled",{enumerable:!0,get:function(){return Ae.default}});var Me=(I||(I=1,Object.defineProperty(F,"__esModule",{value:!0}),F.default={id:"inline-style-disabled",description:"Inline style cannot be used.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],"style"===r.name.toLowerCase()&&t.warn(`Inline style [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw)}))}}),F);Object.defineProperty(e,"inlineStyleDisabled",{enumerable:!0,get:function(){return Me.default}});var Ce=B();Object.defineProperty(e,"inputRequiresLabel",{enumerable:!0,get:function(){return Ce.default}});var Ne=(Y||(Y=1,Object.defineProperty(J,"__esModule",{value:!0}),J.default={id:"script-disabled",description:"The <script> tag cannot be used.",init(e,t){e.addListener("tagstart",(e=>{"script"===e.tagName.toLowerCase()&&t.error("The <script> tag cannot be used.",e.line,e.col,this,e.raw)}))}}),J);Object.defineProperty(e,"scriptDisabled",{enumerable:!0,get:function(){return Ne.default}});var ke=(Q||(Q=1,Object.defineProperty(X,"__esModule",{value:!0}),X.default={id:"space-tab-mixed-disabled",description:"Do not mix tabs and spaces for indentation.",init(e,t,a){let r="nomix",n=null;if("string"==typeof a){const e=/^([a-z]+)(\d+)?/.exec(a);e&&(r=e[1],n=e[2]&&parseInt(e[2],10))}e.addListener("text",(a=>{const s=a.raw,i=/(^|\r?\n)([ \t]+)/g;let l;for(;l=i.exec(s);){const s=e.fixPos(a,l.index+l[1].length);if(1!==s.col)continue;const i=l[2];"space"===r?n?!1!==/^ +$/.test(i)&&i.length%n===0||t.warn(`Please use space for indentation and keep ${n} length.`,s.line,1,this,a.raw):!1===/^ +$/.test(i)&&t.warn("Please use space for indentation.",s.line,1,this,a.raw):"tab"===r&&!1===/^\t+$/.test(i)?t.warn("Please use tab for indentation.",s.line,1,this,a.raw):!0===/ +\t|\t+ /.test(i)&&t.warn("Do not mix tabs and spaces for indentation.",s.line,1,this,a.raw)}}))}}),X);Object.defineProperty(e,"spaceTabMixedDisabled",{enumerable:!0,get:function(){return ke.default}});var qe=(G||(G=1,Object.defineProperty(K,"__esModule",{value:!0}),K.default={id:"spec-char-escape",description:"Special characters must be escaped.",init(e,t){e.addListener("text",(a=>{const r=a.raw,n=/([<>])/g;let s;for(;s=n.exec(r);){const r=e.fixPos(a,s.index);t.error(`Special characters must be escaped : [ ${s[0]} ].`,r.line,r.col,this,a.raw)}}))}}),K);Object.defineProperty(e,"specCharEscape",{enumerable:!0,get:function(){return qe.default}});var Ee=(ee||(ee=1,Object.defineProperty(te,"__esModule",{value:!0}),te.default={id:"src-not-empty",description:"The src attribute of an img(script,link) must have a value.",init(e,t){e.addListener("tagstart",(e=>{const a=e.tagName,r=e.attrs;let n;const s=e.col+a.length+1;for(let i=0,l=r.length;i<l;i++)n=r[i],(!0===/^(img|script|embed|bgsound|iframe)$/.test(a)&&"src"===n.name||"link"===a&&"href"===n.name||"object"===a&&"data"===n.name)&&""===n.value&&t.error(`The attribute [ ${n.name} ] of the tag [ ${a} ] must have a value.`,e.line,s+n.index,this,n.raw)}))}}),te);Object.defineProperty(e,"srcNotEmpty",{enumerable:!0,get:function(){return Ee.default}});var Re=(ae||(ae=1,Object.defineProperty(re,"__esModule",{value:!0}),re.default={id:"style-disabled",description:"<style> tags cannot be used.",init(e,t){e.addListener("tagstart",(e=>{"style"===e.tagName.toLowerCase()&&t.warn("The <style> tag cannot be used.",e.line,e.col,this,e.raw)}))}}),re);Object.defineProperty(e,"styleDisabled",{enumerable:!0,get:function(){return Re.default}});var Se=(ne||(ne=1,Object.defineProperty(se,"__esModule",{value:!0}),se.default={id:"tag-pair",description:"Tag must be paired.",init(e,t){const a=[],r=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const t=e.tagName.toLowerCase();void 0!==r[t]||e.close||a.push({tagName:t,line:e.line,col:e.col,raw:e.raw})})),e.addListener("tagend",(e=>{const r=e.tagName.toLowerCase();let n;for(n=a.length-1;n>=0&&a[n].tagName!==r;n--);if(n>=0){const r=[];for(let e=a.length-1;e>n;e--)r.push(`</${a[e].tagName}>`);if(r.length>0){const n=a[a.length-1];t.error(`Tag must be paired, missing: [ ${r.join("")} ], start tag match failed [ ${n.raw} ] on line ${n.line}.`,n.line||e.line,n.col||e.col,this,e.raw)}a.length=n}else t.error(`Tag must be paired, no start tag: [ ${e.raw} ]`,e.line,e.col,this,e.raw)})),e.addListener("end",(e=>{const r=[];for(let e=a.length-1;e>=0;e--)r.push(`</${a[e].tagName}>`);if(r.length>0){const n=a[a.length-1];t.error(`Tag must be paired, missing: [ ${r.join("")} ], open tag match failed [ ${n.raw} ] on line ${n.line}.`,e.line,e.col,this,"")}}))}}),se);Object.defineProperty(e,"tagPair",{enumerable:!0,get:function(){return Se.default}});var ze=(ie||(ie=1,Object.defineProperty(le,"__esModule",{value:!0}),le.default={id:"tag-self-close",description:"Empty tags must be self closed.",init(e,t){const a=e.makeMap("area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const r=e.tagName.toLowerCase();void 0!==a[r]&&(e.close||t.warn(`The empty tag : [ ${r} ] must be self closed.`,e.line,e.col,this,e.raw))}))}}),le);Object.defineProperty(e,"tagSelfClose",{enumerable:!0,get:function(){return ze.default}});var De=(oe||(oe=1,Object.defineProperty(de,"__esModule",{value:!0}),de.default={id:"empty-tag-not-self-closed",description:"Empty tags must not use self closed syntax.",init(e,t){const a=e.makeMap("area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const r=e.tagName.toLowerCase();void 0!==a[r]&&e.close&&t.error(`The empty tag : [ ${r} ] must not use self closed syntax.`,e.line,e.col,this,e.raw)}))}}),de);Object.defineProperty(e,"emptyTagNotSelfClosed",{enumerable:!0,get:function(){return De.default}});var Ue=(ue||(ue=1,Object.defineProperty(ce,"__esModule",{value:!0}),ce.default={id:"tagname-lowercase",description:"All html element names must be in lowercase.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart,tagend",(e=>{const a=e.tagName;-1===r.indexOf(a)&&a!==a.toLowerCase()&&t.error(`The html element name of [ ${a} ] must be in lowercase.`,e.line,e.col,this,e.raw)}))}}),ce);Object.defineProperty(e,"tagnameLowercase",{enumerable:!0,get:function(){return Ue.default}});var Ze=(fe||(fe=1,Object.defineProperty(ge,"__esModule",{value:!0}),ge.default={id:"tagname-specialchars",description:"All special characters must be escaped.",init(e,t){const a=/[^a-zA-Z0-9\-:_]/;e.addListener("tagstart,tagend",(e=>{const r=e.tagName;a.test(r)&&t.error(`The html element name of [ ${r} ] contains special character.`,e.line,e.col,this,e.raw)}))}}),ge);Object.defineProperty(e,"tagnameSpecialChars",{enumerable:!0,get:function(){return Ze.default}});var He=(he||(he=1,Object.defineProperty(pe,"__esModule",{value:!0}),pe.default={id:"title-require",description:"<title> must be present in <head> tag.",init(e,t){let a=!1,r=!1;const n=e=>{const t=e.tagName.toLowerCase();"head"===t?a=!0:"title"===t&&a&&(r=!0)},s=a=>{const i=a.tagName.toLowerCase();if(r&&"title"===i){const e=a.lastEvent;("text"!==e.type||"text"===e.type&&!0===/^\s*$/.test(e.raw))&&t.error("<title></title> must not be empty.",a.line,a.col,this,a.raw)}else"head"===i&&(!1===r&&t.error("<title> must be present in <head> tag.",a.line,a.col,this,a.raw),e.removeListener("tagstart",n),e.removeListener("tagend",s))};e.addListener("tagstart",n),e.addListener("tagend",s)}}),pe);Object.defineProperty(e,"titleRequire",{enumerable:!0,get:function(){return He.default}});var Ie=function(){if(me)return be;me=1,Object.defineProperty(be,"__esModule",{value:!0});let e={a:{selfclosing:!1,attrsRequired:["href","title"],redundantAttrs:["alt"]},div:{selfclosing:!1},main:{selfclosing:!1,redundantAttrs:["role"]},nav:{selfclosing:!1,redundantAttrs:["role"]},script:{attrsOptional:[["async","async"],["defer","defer"]]},img:{selfclosing:!0,attrsRequired:["src","alt","title"]}};return be.default={id:"tags-check",description:"Checks html tags.",init(t,a,r){e=Object.assign(Object.assign({},e),r),t.addListener("tagstart",(t=>{const r=t.attrs,n=t.col+t.tagName.length+1,s=t.tagName.toLowerCase();if(e[s]){const i=e[s];!0!==i.selfclosing||t.close?!1===i.selfclosing&&t.close&&a.warn(`The <${s}> tag must not be selfclosing.`,t.line,t.col,this,t.raw):a.warn(`The <${s}> tag must be selfclosing.`,t.line,t.col,this,t.raw),Array.isArray(i.attrsRequired)&&i.attrsRequired.forEach((e=>{if(Array.isArray(e)){const i=e.map((e=>e)),l=i.shift(),o=i;r.some((e=>e.name===l))?r.forEach((e=>{e.name===l&&-1===o.indexOf(e.value)&&a.error(`The <${s}> tag must have attr '${l}' with one value of '${o.join("' or '")}'.`,t.line,n,this,t.raw)})):a.error(`The <${s}> tag must have attr '${l}'.`,t.line,n,this,t.raw)}else r.some((t=>-1!==e.split("|").indexOf(t.name)))||a.error(`The <${s}> tag must have attr '${e}'.`,t.line,n,this,t.raw)})),Array.isArray(i.attrsOptional)&&i.attrsOptional.forEach((e=>{if(Array.isArray(e)){const i=e.map((e=>e)),l=i.shift(),o=i;r.some((e=>e.name===l))&&r.forEach((e=>{e.name===l&&-1===o.indexOf(e.value)&&a.error(`The <${s}> tag must have optional attr '${l}' with one value of '${o.join("' or '")}'.`,t.line,n,this,t.raw)}))}})),Array.isArray(i.redundantAttrs)&&i.redundantAttrs.forEach((e=>{r.some((t=>t.name===e))&&a.error(`The attr '${e}' is redundant for <${s}> and should be omitted.`,t.line,n,this,t.raw)}))}}))}},be}();Object.defineProperty(e,"tagsCheck",{enumerable:!0,get:function(){return Ie.default}});var Fe=(ve||(ve=1,Object.defineProperty(xe,"__esModule",{value:!0}),xe.default={id:"attr-no-unnecessary-whitespace",description:"No spaces between attribute names and values.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart",(e=>{const a=e.attrs,n=e.col+e.tagName.length+1;for(let s=0;s<a.length;s++)if(-1===r.indexOf(a[s].name)){const r=/(\s*)=(\s*)/.exec(a[s].raw.trim());!r||0===r[1].length&&0===r[2].length||t.error(`The attribute '${a[s].name}' must not have spaces between the name and value.`,e.line,n+a[s].index,this,a[s].raw)}}))}}),xe);Object.defineProperty(e,"attrNoUnnecessaryWhitespace",{enumerable:!0,get:function(){return Fe.default}})}(l)),l}var Oe=(we||(we=1,function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLParser=e.Reporter=e.HTMLRules=e.HTMLHint=void 0;const a=(t||(t=1,Object.defineProperty(r,"__esModule",{value:!0}),r.default=class{constructor(){this._listeners={},this._mapCdataTags=this.makeMap("script,style"),this._arrBlocks=[],this.lastEvent=null}makeMap(e){const t={},a=e.split(",");for(let e=0;e<a.length;e++)t[a[e]]=!0;return t}parse(e){const t=this._mapCdataTags,a=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g,r=/\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g,n=/\r?\n/g;let s,i,l,o,d,u,c=0,f=null,g=[],h=0,p=0,m=1;const b=this._arrBlocks;this.fire("start",{pos:0,line:1,col:1});const v=()=>{const e=o.find((e=>"type"===e.name))||{value:""};return t[l]&&-1===e.value.indexOf("text/ng-template")},y=(e,t,a,r)=>{const s=a-p+1;for(void 0===r&&(r={}),r.raw=t,r.pos=a,r.line=m,r.col=s,b.push(r),this.fire(e,r);n.exec(t);)m++,p=a+n.lastIndex};for(;s=a.exec(e);)if(i=s.index,i>c&&(u=e.substring(c,i),f?g.push(u):y("text",u,c)),c=a.lastIndex,!(l=s[1])||(f&&l===f&&(u=g.join(""),y("cdata",u,h,{tagName:f,attrs:d}),f=null,d=void 0,g=[]),f))if(f)g.push(s[0]);else if(l=s[4]){o=[];const e=s[5];let t,a=0;for(;t=r.exec(e);){const e=t[1],r=t[2]?t[2]:t[4]?t[4]:"",n=t[3]?t[3]:t[5]?t[5]:t[6]?t[6]:"";o.push({name:e,value:n,quote:r,index:t.index,raw:t[0]}),a+=t[0].length}a===e.length?(y("tagstart",s[0],i,{tagName:l,attrs:o,close:s[6]}),v()&&(f=l,d=o.concat(),g=[],h=c)):y("text",s[0],i)}else(s[2]||s[3])&&y("comment",s[0],i,{content:s[2]||s[3],long:!!s[2]});else y("tagend",s[0],i,{tagName:l});e.length>c&&(u=e.substring(c,e.length),y("text",u,c)),this.fire("end",{pos:c,line:m,col:e.length-p+1})}addListener(e,t){const a=this._listeners,r=e.split(/[,\s]/);let n;for(let e=0,s=r.length;e<s;e++)n=r[e],void 0===a[n]&&(a[n]=[]),a[n].push(t)}fire(e,t){void 0===t&&(t={}),t.type=e;let a=[];const r=this._listeners[e],n=this._listeners.all;void 0!==r&&(a=a.concat(r)),void 0!==n&&(a=a.concat(n));const s=this.lastEvent;null!==s&&(delete s.lastEvent,t.lastEvent=s),this.lastEvent=t;for(let e=0,r=a.length;e<r;e++)a[e].call(this,t)}removeListener(e,t){const a=this._listeners[e];if(void 0!==a)for(let e=0,r=a.length;e<r;e++)if(a[e]===t){a.splice(e,1);break}}fixPos(e,t){const a=e.raw.substr(0,t).split(/\r?\n/),r=a.length-1;let n,s=e.line;return r>0?(s+=r,n=a[r].length+1):n=e.col+t,{line:s,col:n}}getMapAttrs(e){const t={};let a;for(let r=0,n=e.length;r<n;r++)a=e[r],t[a.name]=a.value;return t}}),r);e.HTMLParser=a.default;const i=(n||(n=1,Object.defineProperty(s,"__esModule",{value:!0}),s.default=class{constructor(e,t){this.html=e,this.lines=e.split(/\r?\n/);const a=/\r?\n/.exec(e);this.brLen=null!==a?a[0].length:0,this.ruleset=t,this.messages=[]}info(e,t,a,r,n){this.report("info",e,t,a,r,n)}warn(e,t,a,r,n){this.report("warning",e,t,a,r,n)}error(e,t,a,r,n){this.report("error",e,t,a,r,n)}report(e,t,a,r,n,s){const i=this.lines,l=this.brLen;let o="",d=0;for(let e=a-1,t=i.length;e<t&&(o=i[e],d=o.length,r>d&&a<t);e++)a++,1!==(r-=d)&&(r-=l);this.messages.push({type:e,message:t,raw:s,evidence:o,line:a,col:r,rule:{id:n.id,description:n.description,link:`https://htmlhint.com/docs/user-guide/rules/${n.id}`}})}}),s);e.Reporter=i.default;const l=Le();function o(e,t){return new Array(e+1).join(" ")}e.HTMLRules=l,e.HTMLHint=new class{constructor(){this.rules={},this.defaultRuleset={"tagname-lowercase":!0,"attr-lowercase":!0,"attr-value-double-quotes":!0,"doctype-first":!0,"tag-pair":!0,"spec-char-escape":!0,"id-unique":!0,"src-not-empty":!0,"attr-no-duplication":!0,"title-require":!0}}addRule(e){this.rules[e.id]=e}verify(e,t=this.defaultRuleset){0===Object.keys(t).length&&(t=this.defaultRuleset),e=e.replace(/^\s*<!--\s*htmlhint\s+([^\r\n]+?)\s*-->/i,((e,a)=>(a.replace(/(?:^|,)\s*([^:,]+)\s*(?:\:\s*([^,\s]+))?/g,((e,a,r)=>(t[a]=!(void 0!==r&&r.length>0)||JSON.parse(r),""))),"")));const r=new a.default,n=new i.default(e,t),s=this.rules;let l;for(const e in t)l=s[e],void 0!==l&&!1!==t[e]&&l.init(r,n,t[e]);return r.parse(e),n.messages}format(e,t={}){const a=[],r={white:"",grey:"",red:"",reset:""};t.colors&&(r.white="[37m",r.grey="[90m",r.red="[31m",r.reset="[39m");const n=t.indent||0;return e.forEach((e=>{let t=e.evidence;const s=e.line,i=e.col,l=t.length;let d=i>41?i-40:1,u=t.length>i+60?i+60:l;i<41&&(u+=40-i+1),t=t.replace(/\t/g," ").substring(d-1,u),d>1&&(t=`...${t}`,d-=3),u<l&&(t+="..."),a.push(`${r.white+o(n)}L${s} |${r.grey}${t}${r.reset}`);let c=i-d;const f=t.substring(0,c).match(/[^\u0000-\u00ff]/g);null!==f&&(c+=f.length),a.push(`${r.white+o(n)+o(String(s).length+3+c)}^ ${r.red}${e.message} (${e.rule.id})${r.reset}`)})),a}},Object.keys(l).forEach((t=>{e.HTMLHint.addRule(l[t])}))}(a)),a);return e(Oe)}));
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).HTMLHint=t()}(this,(function(){"use strict";function e(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,a={},r={};var n,s={};var i,l={},o={};var d,u={};var c,f={};var h,g={};var p,m={};var b,v={};var y,w={};var L,O={};var x,j={};var P,_={};var $,T={};var A,M={};var C,N={};var k,q={};var E,R={};var S,z={};var D,U={};var Z,H={};var I,F={};var V,W={};var B,Y={};var J,Q={};var X,G={};function K(){return X||(X=1,Object.defineProperty(G,"__esModule",{value:!0}),G.default={id:"input-requires-label",description:"All [ input ] tags must have a corresponding [ label ] tag. ",init(e,t){const a=[],r=[];e.addListener("tagstart",(t=>{const n=t.tagName.toLowerCase(),s=e.getMapAttrs(t.attrs),i=t.col+n.length+1;"input"===n&&"hidden"!==s.type&&r.push({event:t,col:i,id:s.id}),"label"===n&&"for"in s&&""!==s.for&&a.push({event:t,col:i,forValue:s.for})})),e.addListener("end",(()=>{r.forEach((e=>{(function(e){let t=!1;return a.forEach((a=>{e.id&&e.id===a.forValue&&(t=!0)})),t})(e)||t.warn("No matching [ label ] tag found.",e.event.line,e.col,this,e.event.raw)}))}))}}),G}var ee,te={};var ae,re={};var ne,se={};var ie,le={};var oe,de={};var ue,ce={};var fe,he={};var ge,pe={};var me,be={};var ve,ye={};var we,Le,Oe,xe={};function je(){return Le||(Le=1,function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.titleRequire=e.tagSelfClose=e.tagsCheck=e.tagPair=e.tagnameSpecialChars=e.tagnameLowercase=e.styleDisabled=e.srcNotEmpty=e.specCharEscape=e.spaceTabMixedDisabled=e.scriptDisabled=e.inputRequiresLabel=e.inlineStyleDisabled=e.inlineScriptDisabled=e.idUnique=e.idClassValue=e.idClassAdDisabled=e.htmlLangRequire=e.hrefAbsOrRel=e.headScriptDisabled=e.h1Require=e.emptyTagNotSelfClosed=e.doctypeHTML5=e.doctypeFirst=e.attrWhitespace=e.attrValueSingleQuotes=e.attrValueNotEmpty=e.attrValueDoubleQuotes=e.attrUnsafeChars=e.attrSort=e.attrNoUnnecessaryWhitespace=e.attrNoDuplication=e.attrLowercase=e.altRequire=void 0;var t=(i||(i=1,Object.defineProperty(o,"__esModule",{value:!0}),o.default={id:"alt-require",description:"The alt attribute of an <img> element must be present and alt attribute of area[href] and input[type=image] must have a value.",init(e,t){e.addListener("tagstart",(a=>{const r=a.tagName.toLowerCase(),n=e.getMapAttrs(a.attrs),s=a.col+r.length+1;let i;"img"!==r||"alt"in n?("area"===r&&"href"in n||"input"===r&&"image"===n.type)&&("alt"in n&&""!==n.alt||(i="area"===r?"area[href]":"input[type=image]",t.warn(`The alt attribute of ${i} must have a value.`,a.line,s,this,a.raw))):t.warn("An alt attribute must be present on <img> elements.",a.line,s,this,a.raw)}))}}),o);Object.defineProperty(e,"altRequire",{enumerable:!0,get:function(){return t.default}});var a=function(){if(d)return u;d=1,Object.defineProperty(u,"__esModule",{value:!0});const e=["allowReorder","attributeName","attributeType","autoReverse","baseFrequency","baseProfile","calcMode","clipPath","clipPathUnits","contentScriptType","contentStyleType","diffuseConstant","edgeMode","externalResourcesRequired","filterRes","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","onBlur","onChange","onClick","onFocus","onKeyUp","onLoad","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"];function t(e,t){if(t instanceof RegExp)return!!t.test(e)&&{match:e,pattern:t};const a=t[0],r=t[t.length-1],n=t[t.length-2],s="/"===a&&("/"===r||"/"===n&&"i"===r);return s?s&&"i"===r?new RegExp(t.slice(1,-2),"i").test(e):new RegExp(t.slice(1,-1)).test(e):e===t}return u.default={id:"attr-lowercase",description:"All attribute names must be in lowercase.",init(a,r,n){const s=(Array.isArray(n)?n:[]).concat(e);a.addListener("tagstart",(e=>{const a=e.attrs;let n;const i=e.col+e.tagName.length+1;for(let l=0,o=a.length;l<o;l++){n=a[l];const o=n.name;s.find((e=>t(o,e)))||o===o.toLowerCase()||r.error(`The attribute name of [ ${o} ] must be in lowercase.`,e.line,i+n.index,this,n.raw)}}))}},u}();Object.defineProperty(e,"attrLowercase",{enumerable:!0,get:function(){return a.default}});var r=(c||(c=1,Object.defineProperty(f,"__esModule",{value:!0}),f.default={id:"attr-no-duplication",description:"Elements cannot have duplicate attributes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r,n;const s=e.col+e.tagName.length+1,i={};for(let l=0,o=a.length;l<o;l++)r=a[l],n=r.name,!0===i[n]&&t.error(`Duplicate of attribute name [ ${r.name} ] was found.`,e.line,s+r.index,this,r.raw),i[n]=!0}))}}),f);Object.defineProperty(e,"attrNoDuplication",{enumerable:!0,get:function(){return r.default}});var n=(h||(h=1,Object.defineProperty(g,"__esModule",{value:!0}),g.default={id:"attr-no-unnecessary-whitespace",description:"No spaces between attribute names and values.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart",(e=>{const a=e.attrs,n=e.col+e.tagName.length+1;for(let s=0;s<a.length;s++)if(-1===r.indexOf(a[s].name)){const r=/(\s*)=(\s*)/.exec(a[s].raw.trim());!r||0===r[1].length&&0===r[2].length||t.error(`The attribute '${a[s].name}' must not have spaces between the name and value.`,e.line,n+a[s].index,this,a[s].raw)}}))}}),g);Object.defineProperty(e,"attrNoUnnecessaryWhitespace",{enumerable:!0,get:function(){return n.default}});var s=(p||(p=1,Object.defineProperty(m,"__esModule",{value:!0}),m.default={id:"attr-sorted",description:"Attribute tags must be in proper order.",init(e,t){const a={},r=["class","id","name","src","for","type","rel","href","value","title","alt","role"];for(let e=0;e<r.length;e++)a[r[e]]=e;e.addListener("tagstart",(e=>{const r=e.attrs,n=[];for(let e=0;e<r.length;e++)n.push(r[e].name);const s=JSON.stringify(n);n.sort(((e,t)=>void 0!==a[e]?void 0!==a[t]?a[e]-a[t]:-1:e.startsWith("data-")?t.startsWith("data-")?e.localeCompare(t):1:void 0!==a[t]?1:t.startsWith("data-")?-1:e.localeCompare(t))),s!==JSON.stringify(n)&&t.error(`Inaccurate order ${s} should be in hierarchy ${JSON.stringify(n)} `,e.line,e.col,this,e.raw)}))}}),m);Object.defineProperty(e,"attrSort",{enumerable:!0,get:function(){return s.default}});var l=(b||(b=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default={id:"attr-unsafe-chars",description:"Attribute values cannot contain unsafe chars.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1,s=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;let i;for(let l=0,o=a.length;l<o;l++)if(r=a[l],i=s.exec(r.value),null!==i){const a=escape(i[0]).replace(/%u/,"\\u").replace(/%/,"\\x");t.warn(`The value of attribute [ ${r.name} ] cannot contain an unsafe char [ ${a} ].`,e.line,n+r.index,this,r.raw)}}))}}),v);Object.defineProperty(e,"attrUnsafeChars",{enumerable:!0,get:function(){return l.default}});var X=(y||(y=1,Object.defineProperty(w,"__esModule",{value:!0}),w.default={id:"attr-value-double-quotes",description:"Attribute values must be in double quotes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],(""!==r.value&&'"'!==r.quote||""===r.value&&"'"===r.quote)&&t.error(`The value of attribute [ ${r.name} ] must be in double quotes.`,e.line,n+r.index,this,r.raw)}))}}),w);Object.defineProperty(e,"attrValueDoubleQuotes",{enumerable:!0,get:function(){return X.default}});var G=(L||(L=1,Object.defineProperty(O,"__esModule",{value:!0}),O.default={id:"attr-value-not-empty",description:"All attributes must have values.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],""===r.quote&&""===r.value&&t.warn(`The attribute [ ${r.name} ] must have a value.`,e.line,n+r.index,this,r.raw)}))}}),O);Object.defineProperty(e,"attrValueNotEmpty",{enumerable:!0,get:function(){return G.default}});var Le=(x||(x=1,Object.defineProperty(j,"__esModule",{value:!0}),j.default={id:"attr-value-single-quotes",description:"Attribute values must be in single quotes.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],(""!==r.value&&"'"!==r.quote||""===r.value&&'"'===r.quote)&&t.error(`The value of attribute [ ${r.name} ] must be in single quotes.`,e.line,n+r.index,this,r.raw)}))}}),j);Object.defineProperty(e,"attrValueSingleQuotes",{enumerable:!0,get:function(){return Le.default}});var Oe=(P||(P=1,Object.defineProperty(_,"__esModule",{value:!0}),_.default={id:"attr-whitespace",description:"All attributes should be separated by only one space and not have leading/trailing whitespace.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart",(e=>{const a=e.attrs;let n;const s=e.col+e.tagName.length+1;a.forEach((a=>{n=a;const i=a.name;-1===r.indexOf(i)&&(a.value.trim()!==a.value&&t.error(`The attributes of [ ${i} ] must not have leading or trailing whitespace.`,e.line,s+n.index,this,n.raw),a.value.replace(/ +(?= )/g,"")!==a.value&&t.error(`The attributes of [ ${i} ] must be separated by only one space.`,e.line,s+n.index,this,n.raw))}))}))}}),_);Object.defineProperty(e,"attrWhitespace",{enumerable:!0,get:function(){return Oe.default}});var je=($||($=1,Object.defineProperty(T,"__esModule",{value:!0}),T.default={id:"doctype-first",description:"Doctype must be declared first.",init(e,t){const a=r=>{"start"===r.type||"text"===r.type&&/^\s*$/.test(r.raw)||(("comment"!==r.type&&!1===r.long||!1===/^DOCTYPE\s+/i.test(r.content))&&t.error("Doctype must be declared first.",r.line,r.col,this,r.raw),e.removeListener("all",a))};e.addListener("all",a)}}),T);Object.defineProperty(e,"doctypeFirst",{enumerable:!0,get:function(){return je.default}});var Pe=(A||(A=1,Object.defineProperty(M,"__esModule",{value:!0}),M.default={id:"doctype-html5",description:'Invalid doctype. Use: "<!DOCTYPE html>"',init(e,t){const a=e=>{!1===e.long&&"doctype html"!==e.content.toLowerCase()&&t.warn('Invalid doctype. Use: "<!DOCTYPE html>"',e.line,e.col,this,e.raw)},r=()=>{e.removeListener("comment",a),e.removeListener("tagstart",r)};e.addListener("all",a),e.addListener("tagstart",r)}}),M);Object.defineProperty(e,"doctypeHTML5",{enumerable:!0,get:function(){return Pe.default}});var _e=(C||(C=1,Object.defineProperty(N,"__esModule",{value:!0}),N.default={id:"empty-tag-not-self-closed",description:"Empty tags must not use self closed syntax.",init(e,t){const a=e.makeMap("area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const r=e.tagName.toLowerCase();void 0!==a[r]&&e.close&&t.error(`The empty tag : [ ${r} ] must not use self closed syntax.`,e.line,e.col,this,e.raw)}))}}),N);Object.defineProperty(e,"emptyTagNotSelfClosed",{enumerable:!0,get:function(){return _e.default}});var $e=(k||(k=1,Object.defineProperty(q,"__esModule",{value:!0}),q.default={id:"h1-require",description:"<h1> must be present in <body> tag and not be empty.",init(e,t){let a=0,r=!1,n=null,s=null,i=!1;e.addListener("tagstart",(e=>{const t=e.tagName.toLowerCase();"body"===t?(a++,1===a&&(r=!1,n=e)):"h1"===t&&a>0&&(r=!0,s=e,i=!0)})),e.addListener("tagend",(e=>{const l=e.tagName.toLowerCase();"h1"===l&&s?(i&&t.warn("<h1> tag must not be empty.",s.line,s.col,this,s.raw),s=null):"body"===l&&(1===a&&!r&&n&&t.warn("<h1> must be present in <body> tag.",n.line,n.col,this,n.raw),a--,a<0&&(a=0))})),e.addListener("text",(e=>{s&&i&&e.raw&&!/^\s*$/.test(e.raw)&&(i=!1)})),e.addListener("end",(()=>{a>0&&!r&&n&&t.warn("<h1> must be present in <body> tag.",n.line,n.col,this,n.raw)}))}}),q);Object.defineProperty(e,"h1Require",{enumerable:!0,get:function(){return $e.default}});var Te=(E||(E=1,Object.defineProperty(R,"__esModule",{value:!0}),R.default={id:"head-script-disabled",description:"The <script> tag cannot be used in a <head> tag.",init(e,t){const a=/^(text\/javascript|application\/javascript)$/i;let r=!1;const n=n=>{const s=e.getMapAttrs(n.attrs).type,i=n.tagName.toLowerCase();"head"===i&&(r=!0),!0!==r||"script"!==i||s&&!0!==a.test(s)||t.warn("The <script> tag cannot be used in a <head> tag.",n.line,n.col,this,n.raw)},s=t=>{"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",n),e.removeListener("tagend",s))};e.addListener("tagstart",n),e.addListener("tagend",s)}}),R);Object.defineProperty(e,"headScriptDisabled",{enumerable:!0,get:function(){return Te.default}});var Ae=(S||(S=1,Object.defineProperty(z,"__esModule",{value:!0}),z.default={id:"href-abs-or-rel",description:"An href attribute must be either absolute or relative.",init(e,t,a){const r="abs"===a?"absolute":"relative";e.addListener("tagstart",(e=>{const a=e.attrs;let n;const s=e.col+e.tagName.length+1;for(let i=0,l=a.length;i<l;i++)if(n=a[i],"href"===n.name){("absolute"===r&&!1===/^\w+?:/.test(n.value)||"relative"===r&&!0===/^https?:\/\//.test(n.value))&&t.warn(`The value of the href attribute [ ${n.value} ] must be ${r}.`,e.line,s+n.index,this,n.raw);break}}))}}),z);Object.defineProperty(e,"hrefAbsOrRel",{enumerable:!0,get:function(){return Ae.default}});var Me=(D||(D=1,Object.defineProperty(U,"__esModule",{value:!0}),U.default={id:"html-lang-require",description:"The lang attribute of an <html> element must be present and should be valid.",init(e,t){e.addListener("tagstart",(a=>{const r=a.tagName.toLowerCase(),n=e.getMapAttrs(a.attrs),s=a.col+r.length+1,i=new RegExp("((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse2>x(-[A-Za-z0-9]{1,8})+))","g");"html"===r&&("lang"in n?n.lang?i.test(n.lang)||t.warn("The lang attribute value of <html> element must be a valid BCP47.",a.line,s,this,a.raw):t.warn("The lang attribute of <html> element must have a value.",a.line,s,this,a.raw):t.warn("An lang attribute must be present on <html> elements.",a.line,s,this,a.raw))}))}}),U);Object.defineProperty(e,"htmlLangRequire",{enumerable:!0,get:function(){return Me.default}});var Ce=(Z||(Z=1,Object.defineProperty(H,"__esModule",{value:!0}),H.default={id:"id-class-ad-disabled",description:"The id and class attributes cannot use the ad keyword, it will be blocked by adblock software.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r,n;const s=e.col+e.tagName.length+1;for(let i=0,l=a.length;i<l;i++)r=a[i],n=r.name,/^(id|class)$/i.test(n)&&/(^|[-_])ad([-_]|$)/i.test(r.value)&&t.warn(`The value of attribute ${n} cannot use the ad keyword.`,e.line,s+r.index,this,r.raw)}))}}),H);Object.defineProperty(e,"idClassAdDisabled",{enumerable:!0,get:function(){return Ce.default}});var Ne=(I||(I=1,Object.defineProperty(F,"__esModule",{value:!0}),F.default={id:"id-class-value",description:"The id and class attribute values must meet the specified rules.",init(e,t,a){let r;if(r="string"==typeof a?{underline:{regId:/^[a-z\d]+(_[a-z\d]+)*$/,message:"The id and class attribute values must be in lowercase and split by an underscore."},dash:{regId:/^[a-z\d]+(-[a-z\d]+)*$/,message:"The id and class attribute values must be in lowercase and split by a dash."},hump:{regId:/^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,message:"The id and class attribute values must meet the camelCase style."}}[a]:a,"object"==typeof r&&r.regId){let a=r.regId;const n=r.message;a instanceof RegExp||(a=new RegExp(a)),e.addListener("tagstart",(e=>{const r=e.attrs;let s;const i=e.col+e.tagName.length+1;for(let l=0,o=r.length;l<o;l++)if(s=r[l],"id"===s.name.toLowerCase()&&!1===a.test(s.value)&&t.warn(n,e.line,i+s.index,this,s.raw),"class"===s.name.toLowerCase()){const r=s.value.split(/\s+/g);let l;for(let o=0,d=r.length;o<d;o++)l=r[o],l&&!1===a.test(l)&&t.warn(n,e.line,i+s.index,this,l)}}))}}}),F);Object.defineProperty(e,"idClassValue",{enumerable:!0,get:function(){return Ne.default}});var ke=(V||(V=1,Object.defineProperty(W,"__esModule",{value:!0}),W.default={id:"id-unique",description:"The value of id attributes must be unique.",init(e,t){const a={};e.addListener("tagstart",(e=>{const r=e.attrs;let n,s;const i=e.col+e.tagName.length+1;for(let l=0,o=r.length;l<o;l++)if(n=r[l],"id"===n.name.toLowerCase()){s=n.value,s&&(void 0===a[s]?a[s]=1:a[s]++,a[s]>1&&t.error(`The id value [ ${s} ] must be unique.`,e.line,i+n.index,this,n.raw));break}}))}}),W);Object.defineProperty(e,"idUnique",{enumerable:!0,get:function(){return ke.default}});var qe=(B||(B=1,Object.defineProperty(Y,"__esModule",{value:!0}),Y.default={id:"inline-script-disabled",description:"Inline script cannot be used.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;let s;const i=/^on(unload|message|submit|select|scroll|resize|mouseover|mouseout|mousemove|mouseleave|mouseenter|mousedown|load|keyup|keypress|keydown|focus|dblclick|click|change|blur|error)$/i;for(let l=0,o=a.length;l<o;l++)r=a[l],s=r.name.toLowerCase(),!0===i.test(s)?t.warn(`Inline script [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw):"src"!==s&&"href"!==s||/^\s*javascript:/i.test(r.value)&&t.warn(`Inline script [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw)}))}}),Y);Object.defineProperty(e,"inlineScriptDisabled",{enumerable:!0,get:function(){return qe.default}});var Ee=(J||(J=1,Object.defineProperty(Q,"__esModule",{value:!0}),Q.default={id:"inline-style-disabled",description:"Inline style cannot be used.",init(e,t){e.addListener("tagstart",(e=>{const a=e.attrs;let r;const n=e.col+e.tagName.length+1;for(let s=0,i=a.length;s<i;s++)r=a[s],"style"===r.name.toLowerCase()&&t.warn(`Inline style [ ${r.raw} ] cannot be used.`,e.line,n+r.index,this,r.raw)}))}}),Q);Object.defineProperty(e,"inlineStyleDisabled",{enumerable:!0,get:function(){return Ee.default}});var Re=K();Object.defineProperty(e,"inputRequiresLabel",{enumerable:!0,get:function(){return Re.default}});var Se=(ee||(ee=1,Object.defineProperty(te,"__esModule",{value:!0}),te.default={id:"script-disabled",description:"The <script> tag cannot be used.",init(e,t){e.addListener("tagstart",(e=>{"script"===e.tagName.toLowerCase()&&t.error("The <script> tag cannot be used.",e.line,e.col,this,e.raw)}))}}),te);Object.defineProperty(e,"scriptDisabled",{enumerable:!0,get:function(){return Se.default}});var ze=(ae||(ae=1,Object.defineProperty(re,"__esModule",{value:!0}),re.default={id:"space-tab-mixed-disabled",description:"Do not mix tabs and spaces for indentation.",init(e,t,a){let r="nomix",n=null;if("string"==typeof a){const e=/^([a-z]+)(\d+)?/.exec(a);e&&(r=e[1],n=e[2]&&parseInt(e[2],10))}e.addListener("text",(a=>{const s=a.raw,i=/(^|\r?\n)([ \t]+)/g;let l;for(;l=i.exec(s);){const s=e.fixPos(a,l.index+l[1].length);if(1!==s.col)continue;const i=l[2];"space"===r?n?!1!==/^ +$/.test(i)&&i.length%n===0||t.warn(`Please use space for indentation and keep ${n} length.`,s.line,1,this,a.raw):!1===/^ +$/.test(i)&&t.warn("Please use space for indentation.",s.line,1,this,a.raw):"tab"===r&&!1===/^\t+$/.test(i)?t.warn("Please use tab for indentation.",s.line,1,this,a.raw):!0===/ +\t|\t+ /.test(i)&&t.warn("Do not mix tabs and spaces for indentation.",s.line,1,this,a.raw)}}))}}),re);Object.defineProperty(e,"spaceTabMixedDisabled",{enumerable:!0,get:function(){return ze.default}});var De=(ne||(ne=1,Object.defineProperty(se,"__esModule",{value:!0}),se.default={id:"spec-char-escape",description:"Special characters must be escaped.",init(e,t){e.addListener("text",(a=>{const r=a.raw,n=/([<>])/g;let s;for(;s=n.exec(r);){const r=e.fixPos(a,s.index);t.error(`Special characters must be escaped : [ ${s[0]} ].`,r.line,r.col,this,a.raw)}}))}}),se);Object.defineProperty(e,"specCharEscape",{enumerable:!0,get:function(){return De.default}});var Ue=(ie||(ie=1,Object.defineProperty(le,"__esModule",{value:!0}),le.default={id:"src-not-empty",description:"The src attribute of an img(script,link) must have a value.",init(e,t){e.addListener("tagstart",(e=>{const a=e.tagName,r=e.attrs;let n;const s=e.col+a.length+1;for(let i=0,l=r.length;i<l;i++)n=r[i],(!0===/^(img|script|embed|bgsound|iframe)$/.test(a)&&"src"===n.name||"link"===a&&"href"===n.name||"object"===a&&"data"===n.name)&&""===n.value&&t.error(`The attribute [ ${n.name} ] of the tag [ ${a} ] must have a value.`,e.line,s+n.index,this,n.raw)}))}}),le);Object.defineProperty(e,"srcNotEmpty",{enumerable:!0,get:function(){return Ue.default}});var Ze=(oe||(oe=1,Object.defineProperty(de,"__esModule",{value:!0}),de.default={id:"style-disabled",description:"<style> tags cannot be used.",init(e,t){e.addListener("tagstart",(e=>{"style"===e.tagName.toLowerCase()&&t.warn("The <style> tag cannot be used.",e.line,e.col,this,e.raw)}))}}),de);Object.defineProperty(e,"styleDisabled",{enumerable:!0,get:function(){return Ze.default}});var He=(ue||(ue=1,Object.defineProperty(ce,"__esModule",{value:!0}),ce.default={id:"tagname-lowercase",description:"All html element names must be in lowercase.",init(e,t,a){const r=Array.isArray(a)?a:[];e.addListener("tagstart,tagend",(e=>{const a=e.tagName;-1===r.indexOf(a)&&a!==a.toLowerCase()&&t.error(`The html element name of [ ${a} ] must be in lowercase.`,e.line,e.col,this,e.raw)}))}}),ce);Object.defineProperty(e,"tagnameLowercase",{enumerable:!0,get:function(){return He.default}});var Ie=(fe||(fe=1,Object.defineProperty(he,"__esModule",{value:!0}),he.default={id:"tagname-specialchars",description:"All special characters must be escaped.",init(e,t){const a=/[^a-zA-Z0-9\-:_]/;e.addListener("tagstart,tagend",(e=>{const r=e.tagName;a.test(r)&&t.error(`The html element name of [ ${r} ] contains special character.`,e.line,e.col,this,e.raw)}))}}),he);Object.defineProperty(e,"tagnameSpecialChars",{enumerable:!0,get:function(){return Ie.default}});var Fe=(ge||(ge=1,Object.defineProperty(pe,"__esModule",{value:!0}),pe.default={id:"tag-pair",description:"Tag must be paired.",init(e,t){const a=[],r=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const t=e.tagName.toLowerCase();void 0!==r[t]||e.close||a.push({tagName:t,line:e.line,col:e.col,raw:e.raw})})),e.addListener("tagend",(e=>{const r=e.tagName.toLowerCase();let n;for(n=a.length-1;n>=0&&a[n].tagName!==r;n--);if(n>=0){const r=[];for(let e=a.length-1;e>n;e--)r.push(`</${a[e].tagName}>`);if(r.length>0){const n=a[a.length-1];t.error(`Tag must be paired, missing: [ ${r.join("")} ], start tag match failed [ ${n.raw} ] on line ${n.line}.`,n.line||e.line,n.col||e.col,this,e.raw)}a.length=n}else t.error(`Tag must be paired, no start tag: [ ${e.raw} ]`,e.line,e.col,this,e.raw)})),e.addListener("end",(e=>{const r=[];for(let e=a.length-1;e>=0;e--)r.push(`</${a[e].tagName}>`);if(r.length>0){const n=a[a.length-1];t.error(`Tag must be paired, missing: [ ${r.join("")} ], open tag match failed [ ${n.raw} ] on line ${n.line}.`,e.line,e.col,this,"")}}))}}),pe);Object.defineProperty(e,"tagPair",{enumerable:!0,get:function(){return Fe.default}});var Ve=function(){if(me)return be;me=1,Object.defineProperty(be,"__esModule",{value:!0});let e={a:{selfclosing:!1,attrsRequired:["href","title"],redundantAttrs:["alt"]},div:{selfclosing:!1},main:{selfclosing:!1,redundantAttrs:["role"]},nav:{selfclosing:!1,redundantAttrs:["role"]},script:{attrsOptional:[["async","async"],["defer","defer"]]},img:{selfclosing:!0,attrsRequired:["src","alt","title"]}};return be.default={id:"tags-check",description:"Checks html tags.",init(t,a,r){e=Object.assign(Object.assign({},e),r),t.addListener("tagstart",(t=>{const r=t.attrs,n=t.col+t.tagName.length+1,s=t.tagName.toLowerCase();if(e[s]){const i=e[s];!0!==i.selfclosing||t.close?!1===i.selfclosing&&t.close&&a.warn(`The <${s}> tag must not be selfclosing.`,t.line,t.col,this,t.raw):a.warn(`The <${s}> tag must be selfclosing.`,t.line,t.col,this,t.raw),Array.isArray(i.attrsRequired)&&i.attrsRequired.forEach((e=>{if(Array.isArray(e)){const i=e.map((e=>e)),l=i.shift(),o=i;r.some((e=>e.name===l))?r.forEach((e=>{e.name===l&&-1===o.indexOf(e.value)&&a.error(`The <${s}> tag must have attr '${l}' with one value of '${o.join("' or '")}'.`,t.line,n,this,t.raw)})):a.error(`The <${s}> tag must have attr '${l}'.`,t.line,n,this,t.raw)}else r.some((t=>-1!==e.split("|").indexOf(t.name)))||a.error(`The <${s}> tag must have attr '${e}'.`,t.line,n,this,t.raw)})),Array.isArray(i.attrsOptional)&&i.attrsOptional.forEach((e=>{if(Array.isArray(e)){const i=e.map((e=>e)),l=i.shift(),o=i;r.some((e=>e.name===l))&&r.forEach((e=>{e.name===l&&-1===o.indexOf(e.value)&&a.error(`The <${s}> tag must have optional attr '${l}' with one value of '${o.join("' or '")}'.`,t.line,n,this,t.raw)}))}})),Array.isArray(i.redundantAttrs)&&i.redundantAttrs.forEach((e=>{r.some((t=>t.name===e))&&a.error(`The attr '${e}' is redundant for <${s}> and should be omitted.`,t.line,n,this,t.raw)}))}}))}},be}();Object.defineProperty(e,"tagsCheck",{enumerable:!0,get:function(){return Ve.default}});var We=(ve||(ve=1,Object.defineProperty(ye,"__esModule",{value:!0}),ye.default={id:"tag-self-close",description:"Empty tags must be self closed.",init(e,t){const a=e.makeMap("area,base,basefont,bgsound,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr");e.addListener("tagstart",(e=>{const r=e.tagName.toLowerCase();void 0!==a[r]&&(e.close||t.warn(`The empty tag : [ ${r} ] must be self closed.`,e.line,e.col,this,e.raw))}))}}),ye);Object.defineProperty(e,"tagSelfClose",{enumerable:!0,get:function(){return We.default}});var Be=(we||(we=1,Object.defineProperty(xe,"__esModule",{value:!0}),xe.default={id:"title-require",description:"<title> must be present in <head> tag.",init(e,t){let a=!1,r=!1;const n=e=>{const t=e.tagName.toLowerCase();"head"===t?a=!0:"title"===t&&a&&(r=!0)},s=a=>{const i=a.tagName.toLowerCase();if(r&&"title"===i){const e=a.lastEvent;("text"!==e.type||"text"===e.type&&!0===/^\s*$/.test(e.raw))&&t.error("<title></title> must not be empty.",a.line,a.col,this,a.raw)}else"head"===i&&(!1===r&&t.error("<title> must be present in <head> tag.",a.line,a.col,this,a.raw),e.removeListener("tagstart",n),e.removeListener("tagend",s))};e.addListener("tagstart",n),e.addListener("tagend",s)}}),xe);Object.defineProperty(e,"titleRequire",{enumerable:!0,get:function(){return Be.default}})}(l)),l}var Pe=(Oe||(Oe=1,function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLParser=e.Reporter=e.HTMLRules=e.HTMLHint=void 0;const a=(t||(t=1,Object.defineProperty(r,"__esModule",{value:!0}),r.default=class{constructor(){this._listeners={},this._mapCdataTags=this.makeMap("script,style"),this._arrBlocks=[],this.lastEvent=null}makeMap(e){const t={},a=e.split(",");for(let e=0;e<a.length;e++)t[a[e]]=!0;return t}parse(e){const t=this._mapCdataTags,a=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g,r=/\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g,n=/\r?\n/g;let s,i,l,o,d,u,c=0,f=null,h=[],g=0,p=0,m=1;const b=this._arrBlocks;this.fire("start",{pos:0,line:1,col:1});const v=()=>{const e=o.find((e=>"type"===e.name))||{value:""};return t[l]&&-1===e.value.indexOf("text/ng-template")},y=(e,t,a,r)=>{const s=a-p+1;for(void 0===r&&(r={}),r.raw=t,r.pos=a,r.line=m,r.col=s,b.push(r),this.fire(e,r);n.exec(t);)m++,p=a+n.lastIndex};for(;s=a.exec(e);)if(i=s.index,i>c&&(u=e.substring(c,i),f?h.push(u):y("text",u,c)),c=a.lastIndex,!(l=s[1])||(f&&l===f&&(u=h.join(""),y("cdata",u,g,{tagName:f,attrs:d}),f=null,d=void 0,h=[]),f))if(f)h.push(s[0]);else if(l=s[4]){o=[];const e=s[5];let t,a=0;for(;t=r.exec(e);){const e=t[1],r=t[2]?t[2]:t[4]?t[4]:"",n=t[3]?t[3]:t[5]?t[5]:t[6]?t[6]:"";o.push({name:e,value:n,quote:r,index:t.index,raw:t[0]}),a+=t[0].length}a===e.length?(y("tagstart",s[0],i,{tagName:l,attrs:o,close:s[6]}),v()&&(f=l,d=o.concat(),h=[],g=c)):y("text",s[0],i)}else(s[2]||s[3])&&y("comment",s[0],i,{content:s[2]||s[3],long:!!s[2]});else y("tagend",s[0],i,{tagName:l});e.length>c&&(u=e.substring(c,e.length),y("text",u,c)),this.fire("end",{pos:c,line:m,col:e.length-p+1})}addListener(e,t){const a=this._listeners,r=e.split(/[,\s]/);let n;for(let e=0,s=r.length;e<s;e++)n=r[e],void 0===a[n]&&(a[n]=[]),a[n].push(t)}fire(e,t){void 0===t&&(t={}),t.type=e;let a=[];const r=this._listeners[e],n=this._listeners.all;void 0!==r&&(a=a.concat(r)),void 0!==n&&(a=a.concat(n));const s=this.lastEvent;null!==s&&(delete s.lastEvent,t.lastEvent=s),this.lastEvent=t;for(let e=0,r=a.length;e<r;e++)a[e].call(this,t)}removeListener(e,t){const a=this._listeners[e];if(void 0!==a)for(let e=0,r=a.length;e<r;e++)if(a[e]===t){a.splice(e,1);break}}fixPos(e,t){const a=e.raw.substr(0,t).split(/\r?\n/),r=a.length-1;let n,s=e.line;return r>0?(s+=r,n=a[r].length+1):n=e.col+t,{line:s,col:n}}getMapAttrs(e){const t={};let a;for(let r=0,n=e.length;r<n;r++)a=e[r],t[a.name]=a.value;return t}}),r);e.HTMLParser=a.default;const i=(n||(n=1,Object.defineProperty(s,"__esModule",{value:!0}),s.default=class{constructor(e,t){this.html=e,this.lines=e.split(/\r?\n/);const a=/\r?\n/.exec(e);this.brLen=null!==a?a[0].length:0,this.ruleset=t,this.messages=[]}info(e,t,a,r,n){this.report("info",e,t,a,r,n)}warn(e,t,a,r,n){this.report("warning",e,t,a,r,n)}error(e,t,a,r,n){this.report("error",e,t,a,r,n)}report(e,t,a,r,n,s){const i=this.lines,l=this.brLen;let o="",d=0;for(let e=a-1,t=i.length;e<t&&(o=i[e],d=o.length,r>d&&a<t);e++)a++,1!==(r-=d)&&(r-=l);this.messages.push({type:e,message:t,raw:s,evidence:o,line:a,col:r,rule:{id:n.id,description:n.description,link:`https://htmlhint.com/docs/user-guide/rules/${n.id}`}})}}),s);e.Reporter=i.default;const l=je();function o(e,t){return new Array(e+1).join(" ")}e.HTMLRules=l,e.HTMLHint=new class{constructor(){this.rules={},this.defaultRuleset={"tagname-lowercase":!0,"attr-lowercase":!0,"attr-value-double-quotes":!0,"doctype-first":!0,"tag-pair":!0,"spec-char-escape":!0,"id-unique":!0,"src-not-empty":!0,"attr-no-duplication":!0,"title-require":!0}}addRule(e){this.rules[e.id]=e}verify(e,t=this.defaultRuleset){0===Object.keys(t).length&&(t=this.defaultRuleset),e=e.replace(/^\s*<!--\s*htmlhint\s+([^\r\n]+?)\s*-->/i,((e,a)=>(a.replace(/(?:^|,)\s*([^:,]+)\s*(?:\:\s*([^,\s]+))?/g,((e,a,r)=>(t[a]=!(void 0!==r&&r.length>0)||JSON.parse(r),""))),"")));const r=new a.default,n=new i.default(e,t),s=this.rules;let l;for(const e in t)l=s[e],void 0!==l&&!1!==t[e]&&l.init(r,n,t[e]);return r.parse(e),n.messages}format(e,t={}){const a=[],r={white:"",grey:"",red:"",reset:""};t.colors&&(r.white="[37m",r.grey="[90m",r.red="[31m",r.reset="[39m");const n=t.indent||0;return e.forEach((e=>{let t=e.evidence;const s=e.line,i=e.col,l=t.length;let d=i>41?i-40:1,u=t.length>i+60?i+60:l;i<41&&(u+=40-i+1),t=t.replace(/\t/g," ").substring(d-1,u),d>1&&(t=`...${t}`,d-=3),u<l&&(t+="..."),a.push(`${r.white+o(n)}L${s} |${r.grey}${t}${r.reset}`);let c=i-d;const f=t.substring(0,c).match(/[^\u0000-\u00ff]/g);null!==f&&(c+=f.length),a.push(`${r.white+o(n)+o(String(s).length+3+c)}^ ${r.red}${e.message} (${e.rule.id})${r.reset}`)})),a}},Object.keys(l).forEach((t=>{e.HTMLHint.addRule(l[t])}))}(a)),a);return e(Pe)}));
diff --git a/src/core/rules/h1-require.ts b/src/core/rules/h1-require.ts
new file mode 100644
index 0000000..2379965
--- /dev/null
+++ b/src/core/rules/h1-require.ts
@@ -0,0 +1,82 @@
+import { Block, Listener } from '../htmlparser'
+import { Rule } from '../types'
+
+export default {
+  id: 'h1-require',
+  description: '<h1> must be present in <body> tag and not be empty.',
+  init(parser, reporter) {
+    let bodyDepth = 0
+    let hasH1InBody = false
+    let bodyTagEvent: Block | null = null
+    let currentH1Event: Block | null = null
+    let h1IsEmpty = false
+
+    const onTagStart: Listener = (event: Block) => {
+      const tagName = event.tagName.toLowerCase()
+      if (tagName === 'body') {
+        bodyDepth++
+        if (bodyDepth === 1) {
+          hasH1InBody = false
+          bodyTagEvent = event
+        }
+      } else if (tagName === 'h1' && bodyDepth > 0) {
+        hasH1InBody = true
+        currentH1Event = event
+        h1IsEmpty = true // Assume empty until we find content
+      }
+    }
+
+    const onText: Listener = (event: Block) => {
+      // If we're inside an h1 tag and find non-whitespace text, mark it as not empty
+      if (currentH1Event && h1IsEmpty) {
+        if (event.raw && !/^\s*$/.test(event.raw)) {
+          h1IsEmpty = false
+        }
+      }
+    }
+
+    const onTagEnd: Listener = (event: Block) => {
+      const tagName = event.tagName.toLowerCase()
+      if (tagName === 'h1' && currentH1Event) {
+        if (h1IsEmpty) {
+          reporter.warn(
+            '<h1> tag must not be empty.',
+            currentH1Event.line,
+            currentH1Event.col,
+            this,
+            currentH1Event.raw
+          )
+        }
+        currentH1Event = null
+      } else if (tagName === 'body') {
+        if (bodyDepth === 1 && !hasH1InBody && bodyTagEvent) {
+          reporter.warn(
+            '<h1> must be present in <body> tag.',
+            bodyTagEvent.line,
+            bodyTagEvent.col,
+            this,
+            bodyTagEvent.raw
+          )
+        }
+        bodyDepth--
+        if (bodyDepth < 0) bodyDepth = 0
+      }
+    }
+
+    parser.addListener('tagstart', onTagStart)
+    parser.addListener('tagend', onTagEnd)
+    parser.addListener('text', onText)
+    parser.addListener('end', () => {
+      // Handle case where <body> is not closed (malformed HTML)
+      if (bodyDepth > 0 && !hasH1InBody && bodyTagEvent) {
+        reporter.warn(
+          '<h1> must be present in <body> tag.',
+          bodyTagEvent.line,
+          bodyTagEvent.col,
+          this,
+          bodyTagEvent.raw
+        )
+      }
+    })
+  },
+} as Rule
diff --git a/src/core/rules/index.ts b/src/core/rules/index.ts
index c231623..584c10c 100644
--- a/src/core/rules/index.ts
+++ b/src/core/rules/index.ts
@@ -1,7 +1,8 @@
 export { default as altRequire } from './alt-require'
 export { default as attrLowercase } from './attr-lowercase'
-export { default as attrSort } from './attr-sorted'
 export { default as attrNoDuplication } from './attr-no-duplication'
+export { default as attrNoUnnecessaryWhitespace } from './attr-no-unnecessary-whitespace'
+export { default as attrSort } from './attr-sorted'
 export { default as attrUnsafeChars } from './attr-unsafe-chars'
 export { default as attrValueDoubleQuotes } from './attr-value-double-quotes'
 export { default as attrValueNotEmpty } from './attr-value-not-empty'
@@ -9,6 +10,8 @@ export { default as attrValueSingleQuotes } from './attr-value-single-quotes'
 export { default as attrWhitespace } from './attr-whitespace'
 export { default as doctypeFirst } from './doctype-first'
 export { default as doctypeHTML5 } from './doctype-html5'
+export { default as emptyTagNotSelfClosed } from './empty-tag-not-self-closed'
+export { default as h1Require } from './h1-require'
 export { default as headScriptDisabled } from './head-script-disabled'
 export { default as hrefAbsOrRel } from './href-abs-or-rel'
 export { default as htmlLangRequire } from './html-lang-require'
@@ -23,11 +26,9 @@ export { default as spaceTabMixedDisabled } from './space-tab-mixed-disabled'
 export { default as specCharEscape } from './spec-char-escape'
 export { default as srcNotEmpty } from './src-not-empty'
 export { default as styleDisabled } from './style-disabled'
-export { default as tagPair } from './tag-pair'
-export { default as tagSelfClose } from './tag-self-close'
-export { default as emptyTagNotSelfClosed } from './empty-tag-not-self-closed'
 export { default as tagnameLowercase } from './tagname-lowercase'
 export { default as tagnameSpecialChars } from './tagname-specialchars'
-export { default as titleRequire } from './title-require'
+export { default as tagPair } from './tag-pair'
 export { default as tagsCheck } from './tags-check'
-export { default as attrNoUnnecessaryWhitespace } from './attr-no-unnecessary-whitespace'
+export { default as tagSelfClose } from './tag-self-close'
+export { default as titleRequire } from './title-require'
diff --git a/website/src/content/docs/list-rules.md b/website/src/content/docs/list-rules.md
index b11a00c..4689703 100644
--- a/website/src/content/docs/list-rules.md
+++ b/website/src/content/docs/list-rules.md
@@ -38,6 +38,7 @@ description: A complete list of all the rules for HTMLHint
 - [`empty-tag-not-self-closed`](/docs/user-guide/rules/empty-tag-not-self-closed): The empty tag should not be closed by self.
 - [`src-not-empty`](/docs/user-guide/rules/src-not-empty): The src attribute of an img(script,link) must have a value.
 - [`href-abs-or-rel`](/docs/user-guide/rules/href-abs-or-rel): An href attribute must be either absolute or relative.
+- [`h1-require`](/docs/user-guide/rules/h1-require): A document must have at least one `<h1>` element.
 
 ## Id
 
diff --git a/website/src/content/docs/rules/h1-require.mdx b/website/src/content/docs/rules/h1-require.mdx
new file mode 100644
index 0000000..6cd36db
--- /dev/null
+++ b/website/src/content/docs/rules/h1-require.mdx
@@ -0,0 +1,36 @@
+---
+id: h1-require
+title: h1-require
+description: Ensures that an HTML document contains at least one `<h1>` element for proper document structure and accessibility.
+---
+import { Badge } from '@astrojs/starlight/components';
+
+A H1 heading element is required in HTML documents. The heading can not be empty. This rule ensures that the document has a clear and accessible structure, which is important for both users and search engines.
+
+Level: <Badge text="Warning" variant="caution" />
+
+## Config value
+
+1. true: enable rule
+2. false: disable rule
+
+
+### The following patterns are **not** considered rule violations:
+
+```html
+<html><body><h1>Title</h1></body></html>
+```
+
+### The following patterns are considered rule violations:
+
+```html
+<html><body><p>No heading</p></body></html>
+```
+
+```html
+<html><body><h1></h1></body></html>
+```
+
+```html
+<html><body><h1>   </h1></body></html>
+```
diff --git a/website/src/content/docs/rules/index.mdx b/website/src/content/docs/rules/index.mdx
index 9f6fdb6..64c74cf 100644
--- a/website/src/content/docs/rules/index.mdx
+++ b/website/src/content/docs/rules/index.mdx
@@ -37,6 +37,7 @@ description: A complete list of all the rules for HTMLHint
 - [`empty-tag-not-self-closed`](empty-tag-not-self-closed/): The empty tag should not be closed by self.
 - [`src-not-empty`](src-not-empty/): The src attribute of an img(script,link) must have a value.
 - [`href-abs-or-rel`](href-abs-or-rel/): An href attribute must be either absolute or relative.
+- [`h1-require`](h1-require/): A document must have at least one `<h1>` element.
 
 ## Id
 



Test patch:
diff --git a/test/rules/h1-require.spec.js b/test/rules/h1-require.spec.js
new file mode 100644
index 0000000..31c7b69
--- /dev/null
+++ b/test/rules/h1-require.spec.js
@@ -0,0 +1,35 @@
+const HTMLHint = require('../../dist/htmlhint.js').HTMLHint
+
+const ruleId = 'h1-require'
+
+describe('Rule: h1-require', () => {
+  it('should not report an error when <h1> is present in <body>', () => {
+    const code = '<html><body><h1>Title</h1></body></html>'
+    const messages = HTMLHint.verify(code, { [ruleId]: true })
+    expect(messages.length).toBe(0)
+  })
+
+  it('should report an error when <h1> is missing in <body>', () => {
+    const code = '<html><body><p>No heading</p></body></html>'
+    const messages = HTMLHint.verify(code, { [ruleId]: true })
+    expect(messages.length).toBe(1)
+    expect(messages[0].rule.id).toBe(ruleId)
+    expect(messages[0].message).toBe('<h1> must be present in <body> tag.')
+  })
+
+  it('should report an error when <h1> is empty in <body>', () => {
+    const code = '<html><body><h1></h1></body></html>'
+    const messages = HTMLHint.verify(code, { [ruleId]: true })
+    expect(messages.length).toBe(1)
+    expect(messages[0].rule.id).toBe(ruleId)
+    expect(messages[0].message).toBe('<h1> tag must not be empty.')
+  })
+
+  it('should report an error when <h1> only contains whitespace', () => {
+    const code = '<html><body><h1>   \t   </h1></body></html>'
+    const messages = HTMLHint.verify(code, { [ruleId]: true })
+    expect(messages.length).toBe(1)
+    expect(messages[0].rule.id).toBe(ruleId)
+    expect(messages[0].message).toBe('<h1> tag must not be empty.')
+  })
+})