From bf5325a295fff745acb5f6c84875e060548a251c Mon Sep 17 00:00:00 2001 From: Justin Kimbrell Date: Mon, 12 Aug 2024 12:49:08 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20added=20new=20rule=20=E2=80=9Cattr-spac?= =?UTF-8?q?e-between=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This rule ensures HTML tags with attributes must have spaces between them. Without this change, tags with attributes without the required space results in the parser interpretting the tag as text. --- dist/core/htmlparser.js | 2 +- dist/core/rules/index.js | 22 +- dist/htmlhint.js | 224 +++++++++++--------- dist/htmlhint.min.js | 2 +- docs/user-guide/list-rules.md | 1 + docs/user-guide/rules/attr-space-between.md | 27 +++ src/core/htmlparser.ts | 2 +- src/core/rules/attr-space-between.ts | 23 ++ src/core/rules/index.ts | 9 +- test/rules/attr-space-between.spec.js | 38 ++++ 10 files changed, 231 insertions(+), 119 deletions(-) create mode 100644 docs/user-guide/rules/attr-space-between.md create mode 100644 src/core/rules/attr-space-between.ts create mode 100644 test/rules/attr-space-between.spec.js diff --git a/dist/core/htmlparser.js b/dist/core/htmlparser.js index 180a1de92..1304989f8 100644 --- a/dist/core/htmlparser.js +++ b/dist/core/htmlparser.js @@ -17,7 +17,7 @@ class HTMLParser { } parse(html) { const mapCdataTags = this._mapCdataTags; - const regTag = /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g; + const regTag = /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s*[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g; const regAttr = /\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g; const regLine = /\r?\n/g; let match; diff --git a/dist/core/rules/index.js b/dist/core/rules/index.js index f9bc873aa..b946393d9 100644 --- a/dist/core/rules/index.js +++ b/dist/core/rules/index.js @@ -1,14 +1,18 @@ "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.idClsasAdDisabled = 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.tagsCheck = exports.tagnameSpecialChars = exports.tagnameLowercase = 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.idClsasAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSpaceBetween = 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_space_between_1 = require("./attr-space-between"); +Object.defineProperty(exports, "attrSpaceBetween", { enumerable: true, get: function () { return attr_space_between_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 +27,8 @@ 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 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"); @@ -55,16 +61,12 @@ 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 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 title_require_1 = require("./title-require"); +Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9ydWxlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBcUQ7QUFBNUMseUdBQUEsT0FBTyxPQUFjO0FBQzlCLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLDZEQUFvRTtBQUEzRCx3SEFBQSxPQUFPLE9BQXFCO0FBQ3JDLG1GQUF5RjtBQUFoRiw2SUFBQSxPQUFPLE9BQStCO0FBQy9DLDZDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQVk7QUFDNUIsMkRBQWtFO0FBQXpELHNIQUFBLE9BQU8sT0FBb0I7QUFDcEMseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXNFO0FBQTdELDBIQUFBLE9BQU8sT0FBc0I7QUFDdEMscURBQTJEO0FBQWxELCtHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUNBQWlEO0FBQXhDLHFHQUFBLE9BQU8sT0FBWTtBQUM1QixtRUFBMEU7QUFBakUsOEhBQUEsT0FBTyxPQUF3QjtBQUN4QyxpRUFBd0U7QUFBL0QsNEhBQUEsT0FBTyxPQUF1QjtBQUN2QywrREFBc0U7QUFBN0QsMEhBQUEsT0FBTyxPQUFzQjtBQUN0QyxxREFBNkQ7QUFBcEQsaUhBQUEsT0FBTyxPQUFrQjtBQUNsQyx1RUFBNkU7QUFBcEUsaUlBQUEsT0FBTyxPQUF5QjtBQUN6Qyx1REFBOEQ7QUFBckQsa0hBQUEsT0FBTyxPQUFrQjtBQUNsQyxpREFBd0Q7QUFBL0MsNEdBQUEsT0FBTyxPQUFlO0FBQy9CLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLHVDQUErQztBQUF0QyxtR0FBQSxPQUFPLE9BQVc7QUFDM0IsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseURBQWlFO0FBQXhELHFIQUFBLE9BQU8sT0FBb0I7QUFDcEMsK0RBQXVFO0FBQTlELDJIQUFBLE9BQU8sT0FBdUI7QUFDdkMsMkNBQW1EO0FBQTFDLHVHQUFBLE9BQU8sT0FBYTtBQUM3QixpREFBeUQ7QUFBaEQsNkdBQUEsT0FBTyxPQUFnQiJ9 \ No newline at end of file diff --git a/dist/htmlhint.js b/dist/htmlhint.js index 55753504b..74908405c 100644 --- a/dist/htmlhint.js +++ b/dist/htmlhint.js @@ -30,7 +30,7 @@ } parse(html) { const mapCdataTags = this._mapCdataTags; - const regTag = /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g; + const regTag = /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s*[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g; const regAttr = /\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g; const regLine = /\r?\n/g; let match; @@ -451,6 +451,54 @@ }, }; + var attrNoDuplication = {}; + + 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; + } + }); + }, + }; + + var attrNoUnnecessaryWhitespace = {}; + + 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); + } + } + } + }); + }, + }; + var attrSorted = {}; Object.defineProperty(attrSorted, "__esModule", { value: true }); @@ -501,26 +549,19 @@ }, }; - var attrNoDuplication = {}; + var attrSpaceBetween = {}; - Object.defineProperty(attrNoDuplication, "__esModule", { value: true }); - attrNoDuplication.default = { - id: 'attr-no-duplication', - description: 'Elements cannot have duplicate attributes.', + Object.defineProperty(attrSpaceBetween, "__esModule", { value: true }); + attrSpaceBetween.default = { + id: 'attr-space-between', + description: 'Attribute must have spaces between.', 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); + for (const { index, name, raw } of event.attrs) { + const col = event.col + event.tagName.length + 1; + if (!raw.match(/^\s/)) { + reporter.error(`Attribute "${name}" must be separated with a space`, event.line, col + index, this, event.raw); } - mapAttrName[attrName] = true; } }); }, @@ -693,6 +734,25 @@ }, }; + var emptyTagNotSelfClosed = {}; + + 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); + } + } + }); + }, + }; + var headScriptDisabled = {}; Object.defineProperty(headScriptDisabled, "__esModule", { value: true }); @@ -1214,25 +1274,6 @@ }, }; - var emptyTagNotSelfClosed = {}; - - 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); - } - } - }); - }, - }; - var tagnameLowercase = {}; Object.defineProperty(tagnameLowercase, "__esModule", { value: true }); @@ -1270,46 +1311,6 @@ }, }; - var titleRequire = {}; - - Object.defineProperty(titleRequire, "__esModule", { value: true }); - titleRequire.default = { - id: 'title-require', - description: ' 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> must not be empty.', event.line, event.col, this, event.raw); - } - } - else if (tagName === 'head') { - if (hasTitle === false) { - reporter.error(' 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); - }, - }; - var tagsCheck = {}; Object.defineProperty(tagsCheck, "__esModule", { value: true }); @@ -1413,40 +1414,61 @@ }, }; - var attrNoUnnecessaryWhitespace = {}; + var titleRequire = {}; - 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); - } + 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> must not be empty.', event.line, event.col, this, event.raw); } } - }); + else if (tagName === 'head') { + if (hasTitle === false) { + reporter.error(' 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); }, }; (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.idClsasAdDisabled = 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.tagsCheck = exports.tagnameSpecialChars = exports.tagnameLowercase = 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.idClsasAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSpaceBetween = exports.attrSort = exports.attrNoUnnecessaryWhitespace = exports.attrNoDuplication = exports.attrLowercase = exports.altRequire = void 0; var alt_require_1 = altRequire; Object.defineProperty(exports, "altRequire", { enumerable: true, get: function () { return alt_require_1.default; } }); var attr_lowercase_1 = attrLowercase; Object.defineProperty(exports, "attrLowercase", { enumerable: true, get: function () { return attr_lowercase_1.default; } }); - var attr_sorted_1 = attrSorted; - Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } }); var attr_no_duplication_1 = attrNoDuplication; Object.defineProperty(exports, "attrNoDuplication", { enumerable: true, get: function () { return attr_no_duplication_1.default; } }); + var attr_no_unnecessary_whitespace_1 = attrNoUnnecessaryWhitespace; + Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } }); + var attr_sorted_1 = attrSorted; + Object.defineProperty(exports, "attrSort", { enumerable: true, get: function () { return attr_sorted_1.default; } }); + var attr_space_between_1 = attrSpaceBetween; + Object.defineProperty(exports, "attrSpaceBetween", { enumerable: true, get: function () { return attr_space_between_1.default; } }); var attr_unsafe_chars_1 = attrUnsafeChars; Object.defineProperty(exports, "attrUnsafeChars", { enumerable: true, get: function () { return attr_unsafe_chars_1.default; } }); var attr_value_double_quotes_1 = attrValueDoubleQuotes; @@ -1461,6 +1483,8 @@ Object.defineProperty(exports, "doctypeFirst", { enumerable: true, get: function () { return doctype_first_1.default; } }); var doctype_html5_1 = doctypeHtml5; Object.defineProperty(exports, "doctypeHTML5", { enumerable: true, get: function () { return doctype_html5_1.default; } }); + var empty_tag_not_self_closed_1 = emptyTagNotSelfClosed; + Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } }); var head_script_disabled_1 = headScriptDisabled; Object.defineProperty(exports, "headScriptDisabled", { enumerable: true, get: function () { return head_script_disabled_1.default; } }); var href_abs_or_rel_1 = hrefAbsOrRel; @@ -1493,18 +1517,14 @@ Object.defineProperty(exports, "tagPair", { enumerable: true, get: function () { return tag_pair_1.default; } }); var tag_self_close_1 = tagSelfClose; Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } }); - var empty_tag_not_self_closed_1 = emptyTagNotSelfClosed; - Object.defineProperty(exports, "emptyTagNotSelfClosed", { enumerable: true, get: function () { return empty_tag_not_self_closed_1.default; } }); var tagname_lowercase_1 = tagnameLowercase; Object.defineProperty(exports, "tagnameLowercase", { enumerable: true, get: function () { return tagname_lowercase_1.default; } }); var tagname_specialchars_1 = tagnameSpecialchars; Object.defineProperty(exports, "tagnameSpecialChars", { enumerable: true, get: function () { return tagname_specialchars_1.default; } }); - var title_require_1 = titleRequire; - Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } }); var tags_check_1 = tagsCheck; Object.defineProperty(exports, "tagsCheck", { enumerable: true, get: function () { return tags_check_1.default; } }); - var attr_no_unnecessary_whitespace_1 = attrNoUnnecessaryWhitespace; - Object.defineProperty(exports, "attrNoUnnecessaryWhitespace", { enumerable: true, get: function () { return attr_no_unnecessary_whitespace_1.default; } }); + var title_require_1 = titleRequire; + Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } }); }(rules)); diff --git a/dist/htmlhint.min.js b/dist/htmlhint.min.js index 21e17322e..0d0fc2c49 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={};Object.defineProperty(a,"__esModule",{value:!0});a.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,u,d,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&&(d=e.substring(c,i),f?g.push(d):y("text",d,c)),c=a.lastIndex,!(l=s[1])||(f&&l===f&&(d=g.join(""),y("cdata",d,h,{tagName:f,attrs:u}),f=null,u=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,u=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&&(d=e.substring(c,e.length),y("text",d,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}};var r={};Object.defineProperty(r,"__esModule",{value:!0});r.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="",u=0;for(let e=a-1,t=i.length;e<t&&(o=i[e],u=o.length,r>u&&a<t);e++)a++,1!==(r-=u)&&(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}`}})}};var n={},s={};Object.defineProperty(s,"__esModule",{value:!0}),s.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)}))}};var i={};Object.defineProperty(i,"__esModule",{value:!0});const l=["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 o(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);if(s){return s&&"i"===r?new RegExp(t.slice(1,-2),"i").test(e):new RegExp(t.slice(1,-1)).test(e)}return e===t}i.default={id:"attr-lowercase",description:"All attribute names must be in lowercase.",init(e,t,a){const r=(Array.isArray(a)?a:[]).concat(l);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++){n=a[i];const l=n.name;r.find((e=>o(l,e)))||l===l.toLowerCase()||t.error(`The attribute name of [ ${l} ] must be in lowercase.`,e.line,s+n.index,this,n.raw)}}))}};var u={};Object.defineProperty(u,"__esModule",{value:!0}),u.default={id:"attr-sorted",description:"Attribute tags must be in proper order.",init(e,t){const a={},r=["class","id","name","src","for","type","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)=>null==a[e]&&null==a[t]?0:null==a[e]?1:null==a[t]?-1:a[e]-a[t]||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)}))}};var d={};Object.defineProperty(d,"__esModule",{value:!0}),d.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}))}};var c={};Object.defineProperty(c,"__esModule",{value:!0}),c.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)}}))}};var f={};Object.defineProperty(f,"__esModule",{value:!0}),f.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)}))}};var g={};Object.defineProperty(g,"__esModule",{value:!0}),g.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)}))}};var h={};Object.defineProperty(h,"__esModule",{value:!0}),h.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)}))}};var p={};Object.defineProperty(p,"__esModule",{value:!0}),p.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))}))}))}};var m={};Object.defineProperty(m,"__esModule",{value:!0}),m.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)}};var b={};Object.defineProperty(b,"__esModule",{value:!0}),b.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)}};var v={};Object.defineProperty(v,"__esModule",{value:!0}),v.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)}};var y={};Object.defineProperty(y,"__esModule",{value:!0}),y.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}}))}};var w={};Object.defineProperty(w,"__esModule",{value:!0});w.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))}))}};var x={};Object.defineProperty(x,"__esModule",{value:!0}),x.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)}))}};var L={};Object.defineProperty(L,"__esModule",{value:!0}),L.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,u=r.length;o<u;o++)l=r[o],l&&!1===a.test(l)&&t.warn(n,e.line,i+s.index,this,l)}}))}}};var O={};Object.defineProperty(O,"__esModule",{value:!0}),O.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}}))}};var j={};Object.defineProperty(j,"__esModule",{value:!0}),j.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)}))}};var P={};Object.defineProperty(P,"__esModule",{value:!0}),P.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)}))}};var _={};Object.defineProperty(_,"__esModule",{value:!0}),_.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)}))}))}};var $={};Object.defineProperty($,"__esModule",{value:!0}),$.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)}))}};var T={};Object.defineProperty(T,"__esModule",{value:!0}),T.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)}}))}};var A={};Object.defineProperty(A,"__esModule",{value:!0}),A.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)}}))}};var M={};Object.defineProperty(M,"__esModule",{value:!0}),M.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)}))}};var C={};Object.defineProperty(C,"__esModule",{value:!0}),C.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)}))}};var N={};Object.defineProperty(N,"__esModule",{value:!0}),N.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,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}.`,e.line,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,"")}}))}};var k={};Object.defineProperty(k,"__esModule",{value:!0}),k.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))}))}};var q={};Object.defineProperty(q,"__esModule",{value:!0}),q.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)}))}};var E={};Object.defineProperty(E,"__esModule",{value:!0}),E.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)}))}};var R={};Object.defineProperty(R,"__esModule",{value:!0}),R.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)}))}};var S={};Object.defineProperty(S,"__esModule",{value:!0}),S.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> must not be empty.",a.line,a.col,this,a.raw)}else"head"===i&&(!1===r&&t.error(" 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)}};var z={};Object.defineProperty(z,"__esModule",{value:!0});let D={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"]}};z.default={id:"tags-check",description:"Checks html tags.",init(e,t,a){D=Object.assign(Object.assign({},D),a),e.addListener("tagstart",(e=>{const a=e.attrs,r=e.col+e.tagName.length+1,n=e.tagName.toLowerCase();if(D[n]){const s=D[n];if(!0!==s.selfclosing||e.close?!1===s.selfclosing&&e.close&&t.warn(`The <${n}> tag must not be selfclosing.`,e.line,e.col,this,e.raw):t.warn(`The <${n}> tag must be selfclosing.`,e.line,e.col,this,e.raw),Array.isArray(s.attrsRequired)){s.attrsRequired.forEach((s=>{if(Array.isArray(s)){const i=s.map((e=>e)),l=i.shift(),o=i;a.some((e=>e.name===l))?a.forEach((a=>{a.name===l&&-1===o.indexOf(a.value)&&t.error(`The <${n}> tag must have attr '${l}' with one value of '${o.join("' or '")}'.`,e.line,r,this,e.raw)})):t.error(`The <${n}> tag must have attr '${l}'.`,e.line,r,this,e.raw)}else a.some((e=>-1!==s.split("|").indexOf(e.name)))||t.error(`The <${n}> tag must have attr '${s}'.`,e.line,r,this,e.raw)}))}if(Array.isArray(s.attrsOptional)){s.attrsOptional.forEach((s=>{if(Array.isArray(s)){const i=s.map((e=>e)),l=i.shift(),o=i;a.some((e=>e.name===l))&&a.forEach((a=>{a.name===l&&-1===o.indexOf(a.value)&&t.error(`The <${n}> tag must have optional attr '${l}' with one value of '${o.join("' or '")}'.`,e.line,r,this,e.raw)}))}}))}if(Array.isArray(s.redundantAttrs)){s.redundantAttrs.forEach((s=>{a.some((e=>e.name===s))&&t.error(`The attr '${s}' is redundant for <${n}> and should be omitted.`,e.line,r,this,e.raw)}))}}}))}};var U={};return Object.defineProperty(U,"__esModule",{value:!0}),U.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)}}))}},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.idClsasAdDisabled=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=s;Object.defineProperty(e,"altRequire",{enumerable:!0,get:function(){return t.default}});var a=i;Object.defineProperty(e,"attrLowercase",{enumerable:!0,get:function(){return a.default}});var r=u;Object.defineProperty(e,"attrSort",{enumerable:!0,get:function(){return r.default}});var n=d;Object.defineProperty(e,"attrNoDuplication",{enumerable:!0,get:function(){return n.default}});var l=c;Object.defineProperty(e,"attrUnsafeChars",{enumerable:!0,get:function(){return l.default}});var o=f;Object.defineProperty(e,"attrValueDoubleQuotes",{enumerable:!0,get:function(){return o.default}});var D=g;Object.defineProperty(e,"attrValueNotEmpty",{enumerable:!0,get:function(){return D.default}});var Z=h;Object.defineProperty(e,"attrValueSingleQuotes",{enumerable:!0,get:function(){return Z.default}});var H=p;Object.defineProperty(e,"attrWhitespace",{enumerable:!0,get:function(){return H.default}});var I=m;Object.defineProperty(e,"doctypeFirst",{enumerable:!0,get:function(){return I.default}});var F=b;Object.defineProperty(e,"doctypeHTML5",{enumerable:!0,get:function(){return F.default}});var V=v;Object.defineProperty(e,"headScriptDisabled",{enumerable:!0,get:function(){return V.default}});var B=y;Object.defineProperty(e,"hrefAbsOrRel",{enumerable:!0,get:function(){return B.default}});var Y=w;Object.defineProperty(e,"htmlLangRequire",{enumerable:!0,get:function(){return Y.default}});var W=x;Object.defineProperty(e,"idClsasAdDisabled",{enumerable:!0,get:function(){return W.default}});var J=L;Object.defineProperty(e,"idClassValue",{enumerable:!0,get:function(){return J.default}});var Q=O;Object.defineProperty(e,"idUnique",{enumerable:!0,get:function(){return Q.default}});var X=j;Object.defineProperty(e,"inlineScriptDisabled",{enumerable:!0,get:function(){return X.default}});var G=P;Object.defineProperty(e,"inlineStyleDisabled",{enumerable:!0,get:function(){return G.default}});var K=_;Object.defineProperty(e,"inputRequiresLabel",{enumerable:!0,get:function(){return K.default}});var ee=$;Object.defineProperty(e,"scriptDisabled",{enumerable:!0,get:function(){return ee.default}});var te=T;Object.defineProperty(e,"spaceTabMixedDisabled",{enumerable:!0,get:function(){return te.default}});var ae=A;Object.defineProperty(e,"specCharEscape",{enumerable:!0,get:function(){return ae.default}});var re=M;Object.defineProperty(e,"srcNotEmpty",{enumerable:!0,get:function(){return re.default}});var ne=C;Object.defineProperty(e,"styleDisabled",{enumerable:!0,get:function(){return ne.default}});var se=N;Object.defineProperty(e,"tagPair",{enumerable:!0,get:function(){return se.default}});var ie=k;Object.defineProperty(e,"tagSelfClose",{enumerable:!0,get:function(){return ie.default}});var le=q;Object.defineProperty(e,"emptyTagNotSelfClosed",{enumerable:!0,get:function(){return le.default}});var oe=E;Object.defineProperty(e,"tagnameLowercase",{enumerable:!0,get:function(){return oe.default}});var ue=R;Object.defineProperty(e,"tagnameSpecialChars",{enumerable:!0,get:function(){return ue.default}});var de=S;Object.defineProperty(e,"titleRequire",{enumerable:!0,get:function(){return de.default}});var ce=z;Object.defineProperty(e,"tagsCheck",{enumerable:!0,get:function(){return ce.default}});var fe=U;Object.defineProperty(e,"attrNoUnnecessaryWhitespace",{enumerable:!0,get:function(){return fe.default}})}(n),function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLParser=e.Reporter=e.HTMLRules=e.HTMLHint=void 0;const t=a;e.HTMLParser=t.default;const s=r;e.Reporter=s.default;const i=n;e.HTMLRules=i;function l(e,t){return new Array(e+1).join(t||" ")}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,a=this.defaultRuleset){0===Object.keys(a).length&&(a=this.defaultRuleset),e=e.replace(/^\s*<!--\s*htmlhint\s+([^\r\n]+?)\s*-->/i,((e,t)=>(t.replace(/(?:^|,)\s*([^:,]+)\s*(?:\:\s*([^,\s]+))?/g,((e,t,r)=>(a[t]=!(void 0!==r&&r.length>0)||JSON.parse(r),""))),"")));const r=new t.default,n=new s.default(e,a),i=this.rules;let l;for(const e in a)l=i[e],void 0!==l&&!1!==a[e]&&l.init(r,n,a[e]);return r.parse(e),n.messages}format(e,t={}){const a=[],r={white:"",grey:"",red:"",reset:""};t.colors&&(r.white="",r.grey="",r.red="",r.reset="");const n=t.indent||0;return e.forEach((e=>{let t=e.evidence;const s=e.line,i=e.col,o=t.length;let u=i>41?i-40:1,d=t.length>i+60?i+60:o;i<41&&(d+=40-i+1),t=t.replace(/\t/g," ").substring(u-1,d),u>1&&(t=`...${t}`,u-=3),d<o&&(t+="..."),a.push(`${r.white+l(n)}L${s} |${r.grey}${t}${r.reset}`);let c=i-u;const f=t.substring(0,c).match(/[^\u0000-\u00ff]/g);null!==f&&(c+=f.length),a.push(`${r.white+l(n)+l(String(s).length+3+c)}^ ${r.red}${e.message} (${e.rule.id})${r.reset}`)})),a}},Object.keys(i).forEach((t=>{e.HTMLHint.addRule(i[t])}))}(t),e(t)})); +!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={};Object.defineProperty(a,"__esModule",{value:!0});a.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,u,d,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&&(d=e.substring(c,i),f?g.push(d):y("text",d,c)),c=a.lastIndex,!(l=s[1])||(f&&l===f&&(d=g.join(""),y("cdata",d,h,{tagName:f,attrs:u}),f=null,u=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,u=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&&(d=e.substring(c,e.length),y("text",d,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}};var r={};Object.defineProperty(r,"__esModule",{value:!0});r.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="",u=0;for(let e=a-1,t=i.length;e<t&&(o=i[e],u=o.length,r>u&&a<t);e++)a++,1!==(r-=u)&&(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}`}})}};var n={},s={};Object.defineProperty(s,"__esModule",{value:!0}),s.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)}))}};var i={};Object.defineProperty(i,"__esModule",{value:!0});const l=["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 o(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);if(s){return s&&"i"===r?new RegExp(t.slice(1,-2),"i").test(e):new RegExp(t.slice(1,-1)).test(e)}return e===t}i.default={id:"attr-lowercase",description:"All attribute names must be in lowercase.",init(e,t,a){const r=(Array.isArray(a)?a:[]).concat(l);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++){n=a[i];const l=n.name;r.find((e=>o(l,e)))||l===l.toLowerCase()||t.error(`The attribute name of [ ${l} ] must be in lowercase.`,e.line,s+n.index,this,n.raw)}}))}};var u={};Object.defineProperty(u,"__esModule",{value:!0}),u.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}))}};var d={};Object.defineProperty(d,"__esModule",{value:!0}),d.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)}}))}};var c={};Object.defineProperty(c,"__esModule",{value:!0}),c.default={id:"attr-sorted",description:"Attribute tags must be in proper order.",init(e,t){const a={},r=["class","id","name","src","for","type","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)=>null==a[e]&&null==a[t]?0:null==a[e]?1:null==a[t]?-1:a[e]-a[t]||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)}))}};var f={};Object.defineProperty(f,"__esModule",{value:!0}),f.default={id:"attr-space-between",description:"Attribute must have spaces between.",init(e,t){e.addListener("tagstart",(e=>{for(const{index:a,name:r,raw:n}of e.attrs){const s=e.col+e.tagName.length+1;n.match(/^\s/)||t.error(`Attribute "${r}" must be separated with a space`,e.line,s+a,this,e.raw)}}))}};var g={};Object.defineProperty(g,"__esModule",{value:!0}),g.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)}}))}};var h={};Object.defineProperty(h,"__esModule",{value:!0}),h.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)}))}};var p={};Object.defineProperty(p,"__esModule",{value:!0}),p.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)}))}};var m={};Object.defineProperty(m,"__esModule",{value:!0}),m.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)}))}};var b={};Object.defineProperty(b,"__esModule",{value:!0}),b.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))}))}))}};var v={};Object.defineProperty(v,"__esModule",{value:!0}),v.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)}};var y={};Object.defineProperty(y,"__esModule",{value:!0}),y.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)}};var w={};Object.defineProperty(w,"__esModule",{value:!0}),w.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)}))}};var O={};Object.defineProperty(O,"__esModule",{value:!0}),O.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)}};var x={};Object.defineProperty(x,"__esModule",{value:!0}),x.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}}))}};var L={};Object.defineProperty(L,"__esModule",{value:!0});L.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))}))}};var j={};Object.defineProperty(j,"__esModule",{value:!0}),j.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)}))}};var P={};Object.defineProperty(P,"__esModule",{value:!0}),P.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,u=r.length;o<u;o++)l=r[o],l&&!1===a.test(l)&&t.warn(n,e.line,i+s.index,this,l)}}))}}};var _={};Object.defineProperty(_,"__esModule",{value:!0}),_.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}}))}};var $={};Object.defineProperty($,"__esModule",{value:!0}),$.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)}))}};var A={};Object.defineProperty(A,"__esModule",{value:!0}),A.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)}))}};var T={};Object.defineProperty(T,"__esModule",{value:!0}),T.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)}))}))}};var M={};Object.defineProperty(M,"__esModule",{value:!0}),M.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)}))}};var C={};Object.defineProperty(C,"__esModule",{value:!0}),C.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)}}))}};var N={};Object.defineProperty(N,"__esModule",{value:!0}),N.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)}}))}};var k={};Object.defineProperty(k,"__esModule",{value:!0}),k.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)}))}};var q={};Object.defineProperty(q,"__esModule",{value:!0}),q.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)}))}};var E={};Object.defineProperty(E,"__esModule",{value:!0}),E.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,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}.`,e.line,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,"")}}))}};var R={};Object.defineProperty(R,"__esModule",{value:!0}),R.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))}))}};var S={};Object.defineProperty(S,"__esModule",{value:!0}),S.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)}))}};var z={};Object.defineProperty(z,"__esModule",{value:!0}),z.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)}))}};var D={};Object.defineProperty(D,"__esModule",{value:!0});let U={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"]}};D.default={id:"tags-check",description:"Checks html tags.",init(e,t,a){U=Object.assign(Object.assign({},U),a),e.addListener("tagstart",(e=>{const a=e.attrs,r=e.col+e.tagName.length+1,n=e.tagName.toLowerCase();if(U[n]){const s=U[n];if(!0!==s.selfclosing||e.close?!1===s.selfclosing&&e.close&&t.warn(`The <${n}> tag must not be selfclosing.`,e.line,e.col,this,e.raw):t.warn(`The <${n}> tag must be selfclosing.`,e.line,e.col,this,e.raw),Array.isArray(s.attrsRequired)){s.attrsRequired.forEach((s=>{if(Array.isArray(s)){const i=s.map((e=>e)),l=i.shift(),o=i;a.some((e=>e.name===l))?a.forEach((a=>{a.name===l&&-1===o.indexOf(a.value)&&t.error(`The <${n}> tag must have attr '${l}' with one value of '${o.join("' or '")}'.`,e.line,r,this,e.raw)})):t.error(`The <${n}> tag must have attr '${l}'.`,e.line,r,this,e.raw)}else a.some((e=>-1!==s.split("|").indexOf(e.name)))||t.error(`The <${n}> tag must have attr '${s}'.`,e.line,r,this,e.raw)}))}if(Array.isArray(s.attrsOptional)){s.attrsOptional.forEach((s=>{if(Array.isArray(s)){const i=s.map((e=>e)),l=i.shift(),o=i;a.some((e=>e.name===l))&&a.forEach((a=>{a.name===l&&-1===o.indexOf(a.value)&&t.error(`The <${n}> tag must have optional attr '${l}' with one value of '${o.join("' or '")}'.`,e.line,r,this,e.raw)}))}}))}if(Array.isArray(s.redundantAttrs)){s.redundantAttrs.forEach((s=>{a.some((e=>e.name===s))&&t.error(`The attr '${s}' is redundant for <${n}> and should be omitted.`,e.line,r,this,e.raw)}))}}}))}};var Z={};return Object.defineProperty(Z,"__esModule",{value:!0}),Z.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> must not be empty.",a.line,a.col,this,a.raw)}else"head"===i&&(!1===r&&t.error(" 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)}},function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.titleRequire=e.tagsCheck=e.tagnameSpecialChars=e.tagnameLowercase=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.idClsasAdDisabled=e.htmlLangRequire=e.hrefAbsOrRel=e.headScriptDisabled=e.emptyTagNotSelfClosed=e.doctypeHTML5=e.doctypeFirst=e.attrWhitespace=e.attrValueSingleQuotes=e.attrValueNotEmpty=e.attrValueDoubleQuotes=e.attrUnsafeChars=e.attrSpaceBetween=e.attrSort=e.attrNoUnnecessaryWhitespace=e.attrNoDuplication=e.attrLowercase=e.altRequire=void 0;var t=s;Object.defineProperty(e,"altRequire",{enumerable:!0,get:function(){return t.default}});var a=i;Object.defineProperty(e,"attrLowercase",{enumerable:!0,get:function(){return a.default}});var r=u;Object.defineProperty(e,"attrNoDuplication",{enumerable:!0,get:function(){return r.default}});var n=d;Object.defineProperty(e,"attrNoUnnecessaryWhitespace",{enumerable:!0,get:function(){return n.default}});var l=c;Object.defineProperty(e,"attrSort",{enumerable:!0,get:function(){return l.default}});var o=f;Object.defineProperty(e,"attrSpaceBetween",{enumerable:!0,get:function(){return o.default}});var U=g;Object.defineProperty(e,"attrUnsafeChars",{enumerable:!0,get:function(){return U.default}});var H=h;Object.defineProperty(e,"attrValueDoubleQuotes",{enumerable:!0,get:function(){return H.default}});var I=p;Object.defineProperty(e,"attrValueNotEmpty",{enumerable:!0,get:function(){return I.default}});var F=m;Object.defineProperty(e,"attrValueSingleQuotes",{enumerable:!0,get:function(){return F.default}});var V=b;Object.defineProperty(e,"attrWhitespace",{enumerable:!0,get:function(){return V.default}});var B=v;Object.defineProperty(e,"doctypeFirst",{enumerable:!0,get:function(){return B.default}});var Y=y;Object.defineProperty(e,"doctypeHTML5",{enumerable:!0,get:function(){return Y.default}});var W=w;Object.defineProperty(e,"emptyTagNotSelfClosed",{enumerable:!0,get:function(){return W.default}});var J=O;Object.defineProperty(e,"headScriptDisabled",{enumerable:!0,get:function(){return J.default}});var Q=x;Object.defineProperty(e,"hrefAbsOrRel",{enumerable:!0,get:function(){return Q.default}});var X=L;Object.defineProperty(e,"htmlLangRequire",{enumerable:!0,get:function(){return X.default}});var G=j;Object.defineProperty(e,"idClsasAdDisabled",{enumerable:!0,get:function(){return G.default}});var K=P;Object.defineProperty(e,"idClassValue",{enumerable:!0,get:function(){return K.default}});var ee=_;Object.defineProperty(e,"idUnique",{enumerable:!0,get:function(){return ee.default}});var te=$;Object.defineProperty(e,"inlineScriptDisabled",{enumerable:!0,get:function(){return te.default}});var ae=A;Object.defineProperty(e,"inlineStyleDisabled",{enumerable:!0,get:function(){return ae.default}});var re=T;Object.defineProperty(e,"inputRequiresLabel",{enumerable:!0,get:function(){return re.default}});var ne=M;Object.defineProperty(e,"scriptDisabled",{enumerable:!0,get:function(){return ne.default}});var se=C;Object.defineProperty(e,"spaceTabMixedDisabled",{enumerable:!0,get:function(){return se.default}});var ie=N;Object.defineProperty(e,"specCharEscape",{enumerable:!0,get:function(){return ie.default}});var le=k;Object.defineProperty(e,"srcNotEmpty",{enumerable:!0,get:function(){return le.default}});var oe=q;Object.defineProperty(e,"styleDisabled",{enumerable:!0,get:function(){return oe.default}});var ue=E;Object.defineProperty(e,"tagPair",{enumerable:!0,get:function(){return ue.default}});var de=R;Object.defineProperty(e,"tagSelfClose",{enumerable:!0,get:function(){return de.default}});var ce=S;Object.defineProperty(e,"tagnameLowercase",{enumerable:!0,get:function(){return ce.default}});var fe=z;Object.defineProperty(e,"tagnameSpecialChars",{enumerable:!0,get:function(){return fe.default}});var ge=D;Object.defineProperty(e,"tagsCheck",{enumerable:!0,get:function(){return ge.default}});var he=Z;Object.defineProperty(e,"titleRequire",{enumerable:!0,get:function(){return he.default}})}(n),function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLParser=e.Reporter=e.HTMLRules=e.HTMLHint=void 0;const t=a;e.HTMLParser=t.default;const s=r;e.Reporter=s.default;const i=n;e.HTMLRules=i;function l(e,t){return new Array(e+1).join(t||" ")}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,a=this.defaultRuleset){0===Object.keys(a).length&&(a=this.defaultRuleset),e=e.replace(/^\s*<!--\s*htmlhint\s+([^\r\n]+?)\s*-->/i,((e,t)=>(t.replace(/(?:^|,)\s*([^:,]+)\s*(?:\:\s*([^,\s]+))?/g,((e,t,r)=>(a[t]=!(void 0!==r&&r.length>0)||JSON.parse(r),""))),"")));const r=new t.default,n=new s.default(e,a),i=this.rules;let l;for(const e in a)l=i[e],void 0!==l&&!1!==a[e]&&l.init(r,n,a[e]);return r.parse(e),n.messages}format(e,t={}){const a=[],r={white:"",grey:"",red:"",reset:""};t.colors&&(r.white="",r.grey="",r.red="",r.reset="");const n=t.indent||0;return e.forEach((e=>{let t=e.evidence;const s=e.line,i=e.col,o=t.length;let u=i>41?i-40:1,d=t.length>i+60?i+60:o;i<41&&(d+=40-i+1),t=t.replace(/\t/g," ").substring(u-1,d),u>1&&(t=`...${t}`,u-=3),d<o&&(t+="..."),a.push(`${r.white+l(n)}L${s} |${r.grey}${t}${r.reset}`);let c=i-u;const f=t.substring(0,c).match(/[^\u0000-\u00ff]/g);null!==f&&(c+=f.length),a.push(`${r.white+l(n)+l(String(s).length+3+c)}^ ${r.red}${e.message} (${e.rule.id})${r.reset}`)})),a}},Object.keys(i).forEach((t=>{e.HTMLHint.addRule(i[t])}))}(t),e(t)})); diff --git a/docs/user-guide/list-rules.md b/docs/user-guide/list-rules.md index ac0aba013..5927641ad 100644 --- a/docs/user-guide/list-rules.md +++ b/docs/user-guide/list-rules.md @@ -25,6 +25,7 @@ title: List of rules - [`attr-value-single-quotes`](/docs/user-guide/rules/attr-value-single-quotes): Attribute values must be in single quotes. - [`attr-value-not-empty`](/docs/user-guide/rules/attr-value-not-empty): All attributes must have values. - [`attr-sorted`](/docs/user-guide/rules/attr-sorted): Attributes should be sorted in order. +- [`attr-space-between`](/docs/user-guide/rules/attr-space-between): Attributes must have spaces between them. - [`attr-whitespace`](/docs/user-guide/rules/attr-whitespace): No leading or trailing spaces in attribute values. - [`alt-require`](/docs/user-guide/rules/alt-require): The alt attribute of an element must be present and alt attribute of area[href] and input[type=image] must have a value. - [`input-requires-label`](/docs/user-guide/rules/input-requires-label): All [ input ] tags must have a corresponding [ label ] tag. diff --git a/docs/user-guide/rules/attr-space-between.md b/docs/user-guide/rules/attr-space-between.md new file mode 100644 index 000000000..f10b5c772 --- /dev/null +++ b/docs/user-guide/rules/attr-space-between.md @@ -0,0 +1,27 @@ +--- +id: attr-space-between +title: attr-space-between +--- + +Attributes must be separated by a space. + +Level: `error` + +## Config value + +1. true: enable rule +2. false: disable rule + +The following pattern are **not** considered violations: + +<!-- prettier-ignore --> +```html +<div class="foo" id="bar"></div> +``` + +The following pattern is considered violation: + +<!-- prettier-ignore --> +```html +<div class="foo"id="bar"></div> +``` diff --git a/src/core/htmlparser.ts b/src/core/htmlparser.ts index f70d1cd32..aedd3b0e5 100644 --- a/src/core/htmlparser.ts +++ b/src/core/htmlparser.ts @@ -54,7 +54,7 @@ export default class HTMLParser { const regTag = // eslint-disable-next-line no-control-regex - /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g + /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s*[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g const regAttr = // eslint-disable-next-line no-control-regex /\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g diff --git a/src/core/rules/attr-space-between.ts b/src/core/rules/attr-space-between.ts new file mode 100644 index 000000000..f2807778c --- /dev/null +++ b/src/core/rules/attr-space-between.ts @@ -0,0 +1,23 @@ +import { Rule } from '../types' + +export default { + id: 'attr-space-between', + description: 'Attribute must have spaces between.', + init(parser, reporter) { + parser.addListener('tagstart', (event) => { + for (const { index, name, raw } of event.attrs) { + const col = event.col + event.tagName.length + 1 + + if (!raw.match(/^\s/)) { + reporter.error( + `Attribute "${name}" must be separated with a space`, + event.line, + col + index, + this, + event.raw + ) + } + } + }) + }, +} as Rule diff --git a/src/core/rules/index.ts b/src/core/rules/index.ts index 5838a9915..6dccd7f93 100644 --- a/src/core/rules/index.ts +++ b/src/core/rules/index.ts @@ -1,7 +1,9 @@ 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 attrSpaceBetween } from './attr-space-between' 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 +11,7 @@ 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 headScriptDisabled } from './head-script-disabled' export { default as hrefAbsOrRel } from './href-abs-or-rel' export { default as htmlLangRequire } from './html-lang-require' @@ -25,9 +28,7 @@ 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 tagsCheck } from './tags-check' -export { default as attrNoUnnecessaryWhitespace } from './attr-no-unnecessary-whitespace' +export { default as titleRequire } from './title-require' diff --git a/test/rules/attr-space-between.spec.js b/test/rules/attr-space-between.spec.js new file mode 100644 index 000000000..a2c72ea31 --- /dev/null +++ b/test/rules/attr-space-between.spec.js @@ -0,0 +1,38 @@ +const HTMLHint = require('../../dist/htmlhint.js').HTMLHint + +const ruleId = 'attr-space-between' +const ruleOptions = {} + +ruleOptions[ruleId] = true + +describe(`Rules: ${ruleId}`, () => { + it('Attribute tags without spaces must result in an error', () => { + const code = '<div id="foo"class="bar"></div>' + + const messages = HTMLHint.verify(code, ruleOptions) + + expect(messages.length).toBe(1) + expect(messages[0].rule.id).toBe(ruleId) + expect(messages[0].message).toBe( + 'Attribute "class" must be separated with a space' + ) + }) + + // it('Attribute unsorted tags that are unrecognizable should not throw error', () => { + // const code = '<div img="image" meta="meta" font="font"></div>' + + // const messages = HTMLHint.verify(code, ruleOptions) + + // expect(messages.length).toBe(0) + // }) + + // it('Attribute unsorted of tags of various types should throw error', () => { + // const code = '<div type="type" img="image" id="id" font="font"></div>' + + // const messages = HTMLHint.verify(code, ruleOptions) + + // expect(messages.length).toBe(1) + // expect(messages[0].rule.id).toBe(ruleId) + // expect(messages[0].message).toContain('["type","img","id","font"]') + // }) +})