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"]') + // }) +})