diff --git a/dist/core/rules/index.js b/dist/core/rules/index.js index 79f9c9ced..32478ed10 100644 --- a/dist/core/rules/index.js +++ b/dist/core/rules/index.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.titleRequire = exports.tagSelfClose = exports.tagsCheck = exports.tagPair = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.metaViewportRequire = exports.metaDescriptionRequire = exports.mainRequire = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.h1Require = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.buttonTypeRequire = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSort = exports.attrNoUnnecessaryWhitespace = exports.attrNoDuplication = exports.attrLowercase = exports.altRequire = void 0; +exports.titleRequire = exports.tagSelfClose = exports.tagsCheck = exports.tagPair = exports.tagnameSpecialChars = exports.tagnameLowercase = exports.styleDisabled = exports.srcNotEmpty = exports.specCharEscape = exports.spaceTabMixedDisabled = exports.scriptDisabled = exports.metaViewportRequire = exports.metaDescriptionRequire = exports.metaCharsetRequire = exports.mainRequire = exports.inputRequiresLabel = exports.inlineStyleDisabled = exports.inlineScriptDisabled = exports.idUnique = exports.idClassValue = exports.idClassAdDisabled = exports.htmlLangRequire = exports.hrefAbsOrRel = exports.headScriptDisabled = exports.h1Require = exports.emptyTagNotSelfClosed = exports.doctypeHTML5 = exports.doctypeFirst = exports.buttonTypeRequire = exports.attrWhitespace = exports.attrValueSingleQuotes = exports.attrValueNotEmpty = exports.attrValueDoubleQuotes = exports.attrUnsafeChars = exports.attrSort = exports.attrNoUnnecessaryWhitespace = exports.attrNoDuplication = exports.attrLowercase = exports.altRequire = void 0; var alt_require_1 = require("./alt-require"); Object.defineProperty(exports, "altRequire", { enumerable: true, get: function () { return alt_require_1.default; } }); var attr_lowercase_1 = require("./attr-lowercase"); @@ -51,6 +51,8 @@ var input_requires_label_1 = require("./input-requires-label"); Object.defineProperty(exports, "inputRequiresLabel", { enumerable: true, get: function () { return input_requires_label_1.default; } }); var main_require_1 = require("./main-require"); Object.defineProperty(exports, "mainRequire", { enumerable: true, get: function () { return main_require_1.default; } }); +var meta_charset_require_1 = require("./meta-charset-require"); +Object.defineProperty(exports, "metaCharsetRequire", { enumerable: true, get: function () { return meta_charset_require_1.default; } }); var meta_description_require_1 = require("./meta-description-require"); Object.defineProperty(exports, "metaDescriptionRequire", { enumerable: true, get: function () { return meta_description_require_1.default; } }); var meta_viewport_require_1 = require("./meta-viewport-require"); @@ -77,4 +79,4 @@ var tag_self_close_1 = require("./tag-self-close"); Object.defineProperty(exports, "tagSelfClose", { enumerable: true, get: function () { return tag_self_close_1.default; } }); var title_require_1 = require("./title-require"); Object.defineProperty(exports, "titleRequire", { enumerable: true, get: function () { return title_require_1.default; } }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9ydWxlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBcUQ7QUFBNUMseUdBQUEsT0FBTyxPQUFjO0FBQzlCLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLDZEQUFvRTtBQUEzRCx3SEFBQSxPQUFPLE9BQXFCO0FBQ3JDLG1GQUF5RjtBQUFoRiw2SUFBQSxPQUFPLE9BQStCO0FBQy9DLDZDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQVk7QUFDNUIseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsNkRBQW9FO0FBQTNELHdIQUFBLE9BQU8sT0FBcUI7QUFDckMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBeUI7QUFDekMsMkNBQW1EO0FBQTFDLHVHQUFBLE9BQU8sT0FBYTtBQUM3QiwrREFBc0U7QUFBN0QsMEhBQUEsT0FBTyxPQUFzQjtBQUN0QyxxREFBMkQ7QUFBbEQsK0dBQUEsT0FBTyxPQUFnQjtBQUNoQyx5REFBZ0U7QUFBdkQsb0hBQUEsT0FBTyxPQUFtQjtBQUNuQywrREFBcUU7QUFBNUQseUhBQUEsT0FBTyxPQUFxQjtBQUNyQyxtREFBMEQ7QUFBakQsOEdBQUEsT0FBTyxPQUFnQjtBQUNoQyx5Q0FBaUQ7QUFBeEMscUdBQUEsT0FBTyxPQUFZO0FBQzVCLG1FQUEwRTtBQUFqRSw4SEFBQSxPQUFPLE9BQXdCO0FBQ3hDLGlFQUF3RTtBQUEvRCw0SEFBQSxPQUFPLE9BQXVCO0FBQ3ZDLCtEQUFzRTtBQUE3RCwwSEFBQSxPQUFPLE9BQXNCO0FBQ3RDLCtDQUF1RDtBQUE5QywyR0FBQSxPQUFPLE9BQWU7QUFDL0IsdUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBMEI7QUFDMUMsaUVBQXdFO0FBQS9ELDRIQUFBLE9BQU8sT0FBdUI7QUFDdkMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsdURBQThEO0FBQXJELGtIQUFBLE9BQU8sT0FBa0I7QUFDbEMsaURBQXdEO0FBQS9DLDRHQUFBLE9BQU8sT0FBZTtBQUMvQixtREFBMkQ7QUFBbEQsK0dBQUEsT0FBTyxPQUFpQjtBQUNqQyx5REFBaUU7QUFBeEQscUhBQUEsT0FBTyxPQUFvQjtBQUNwQywrREFBdUU7QUFBOUQsMkhBQUEsT0FBTyxPQUF1QjtBQUN2Qyx1Q0FBK0M7QUFBdEMsbUdBQUEsT0FBTyxPQUFXO0FBQzNCLDJDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQWE7QUFDN0IsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0IifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9ydWxlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBcUQ7QUFBNUMseUdBQUEsT0FBTyxPQUFjO0FBQzlCLG1EQUEyRDtBQUFsRCwrR0FBQSxPQUFPLE9BQWlCO0FBQ2pDLDZEQUFvRTtBQUEzRCx3SEFBQSxPQUFPLE9BQXFCO0FBQ3JDLG1GQUF5RjtBQUFoRiw2SUFBQSxPQUFPLE9BQStCO0FBQy9DLDZDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQVk7QUFDNUIseURBQWdFO0FBQXZELG9IQUFBLE9BQU8sT0FBbUI7QUFDbkMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsK0RBQXFFO0FBQTVELHlIQUFBLE9BQU8sT0FBcUI7QUFDckMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsNkRBQW9FO0FBQTNELHdIQUFBLE9BQU8sT0FBcUI7QUFDckMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0I7QUFDaEMseUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBeUI7QUFDekMsMkNBQW1EO0FBQTFDLHVHQUFBLE9BQU8sT0FBYTtBQUM3QiwrREFBc0U7QUFBN0QsMEhBQUEsT0FBTyxPQUFzQjtBQUN0QyxxREFBMkQ7QUFBbEQsK0dBQUEsT0FBTyxPQUFnQjtBQUNoQyx5REFBZ0U7QUFBdkQsb0hBQUEsT0FBTyxPQUFtQjtBQUNuQywrREFBcUU7QUFBNUQseUhBQUEsT0FBTyxPQUFxQjtBQUNyQyxtREFBMEQ7QUFBakQsOEdBQUEsT0FBTyxPQUFnQjtBQUNoQyx5Q0FBaUQ7QUFBeEMscUdBQUEsT0FBTyxPQUFZO0FBQzVCLG1FQUEwRTtBQUFqRSw4SEFBQSxPQUFPLE9BQXdCO0FBQ3hDLGlFQUF3RTtBQUEvRCw0SEFBQSxPQUFPLE9BQXVCO0FBQ3ZDLCtEQUFzRTtBQUE3RCwwSEFBQSxPQUFPLE9BQXNCO0FBQ3RDLCtDQUF1RDtBQUE5QywyR0FBQSxPQUFPLE9BQWU7QUFDL0IsK0RBQXNFO0FBQTdELDBIQUFBLE9BQU8sT0FBc0I7QUFDdEMsdUVBQThFO0FBQXJFLGtJQUFBLE9BQU8sT0FBMEI7QUFDMUMsaUVBQXdFO0FBQS9ELDRIQUFBLE9BQU8sT0FBdUI7QUFDdkMscURBQTZEO0FBQXBELGlIQUFBLE9BQU8sT0FBa0I7QUFDbEMsdUVBQTZFO0FBQXBFLGlJQUFBLE9BQU8sT0FBeUI7QUFDekMsdURBQThEO0FBQXJELGtIQUFBLE9BQU8sT0FBa0I7QUFDbEMsaURBQXdEO0FBQS9DLDRHQUFBLE9BQU8sT0FBZTtBQUMvQixtREFBMkQ7QUFBbEQsK0dBQUEsT0FBTyxPQUFpQjtBQUNqQyx5REFBaUU7QUFBeEQscUhBQUEsT0FBTyxPQUFvQjtBQUNwQywrREFBdUU7QUFBOUQsMkhBQUEsT0FBTyxPQUF1QjtBQUN2Qyx1Q0FBK0M7QUFBdEMsbUdBQUEsT0FBTyxPQUFXO0FBQzNCLDJDQUFtRDtBQUExQyx1R0FBQSxPQUFPLE9BQWE7QUFDN0IsbURBQTBEO0FBQWpELDhHQUFBLE9BQU8sT0FBZ0I7QUFDaEMsaURBQXlEO0FBQWhELDZHQUFBLE9BQU8sT0FBZ0IifQ== \ No newline at end of file diff --git a/src/core/rules/index.ts b/src/core/rules/index.ts index e01f88afc..e736e7ac5 100644 --- a/src/core/rules/index.ts +++ b/src/core/rules/index.ts @@ -23,6 +23,7 @@ export { default as inlineScriptDisabled } from './inline-script-disabled' export { default as inlineStyleDisabled } from './inline-style-disabled' export { default as inputRequiresLabel } from './input-requires-label' export { default as mainRequire } from './main-require' +export { default as metaCharsetRequire } from './meta-charset-require' export { default as metaDescriptionRequire } from './meta-description-require' export { default as metaViewportRequire } from './meta-viewport-require' export { default as scriptDisabled } from './script-disabled' diff --git a/src/core/rules/meta-charset-require.ts b/src/core/rules/meta-charset-require.ts new file mode 100644 index 000000000..25facf604 --- /dev/null +++ b/src/core/rules/meta-charset-require.ts @@ -0,0 +1,50 @@ +import { Block, Listener } from '../htmlparser' +import { Rule } from '../types' + +export default { + id: 'meta-charset-require', + description: ' must be present in
tag.', + init(parser, reporter) { + let headSeen = false + let metaCharsetSeen = false + let metaCharsetContent = '' + let headEvent: Block | null = null + + const onTagStart: Listener = (event) => { + const tagName = event.tagName.toLowerCase() + if (tagName === 'head') { + headSeen = true + headEvent = event + } else if (tagName === 'meta') { + const mapAttrs = parser.getMapAttrs(event.attrs) + if (mapAttrs['charset'] !== undefined) { + metaCharsetSeen = true + metaCharsetContent = mapAttrs['charset'] || '' + } + } + } + + parser.addListener('tagstart', onTagStart) + parser.addListener('end', () => { + if (headSeen && headEvent) { + if (!metaCharsetSeen) { + reporter.error( + ' must be present in tag.', + headEvent.line, + headEvent.col, + this, + headEvent.raw + ) + } else if (metaCharsetContent.trim() === '') { + reporter.error( + ' value must not be empty.', + headEvent.line, + headEvent.col, + this, + headEvent.raw + ) + } + } + }) + }, +} as Rule diff --git a/test/rules/meta-charset-require.spec.js b/test/rules/meta-charset-require.spec.js new file mode 100644 index 000000000..8f6833b0d --- /dev/null +++ b/test/rules/meta-charset-require.spec.js @@ -0,0 +1,46 @@ +const HTMLHint = require('../../dist/htmlhint.js').HTMLHint +const ruleId = 'meta-charset-require' + +describe('Rule: meta-charset-require', () => { + it('should not report an error when a valid meta charset is present', () => { + const code = `` + const messages = HTMLHint.verify(code, { [ruleId]: true }) + expect(messages.length).toBe(0) + }) + + it('should report an error when meta charset is missing', () => { + const code = `` + const messages = HTMLHint.verify(code, { [ruleId]: true }) + expect(messages.length).toBe(1) + expect(messages[0].message).toBe( + ' must be present in tag.' + ) + }) + + it('should report an error when meta charset value is blank', () => { + const code = `` + const messages = HTMLHint.verify(code, { [ruleId]: true }) + expect(messages.length).toBe(1) + expect(messages[0].message).toBe( + ' value must not be empty.' + ) + }) + + it('should report an error when meta charset value is only whitespace', () => { + const code = `` + const messages = HTMLHint.verify(code, { [ruleId]: true }) + expect(messages.length).toBe(1) + expect(messages[0].message).toBe( + ' value must not be empty.' + ) + }) + + it('should report an error when meta charset is missing and only other meta tags are present', () => { + const code = `` + const messages = HTMLHint.verify(code, { [ruleId]: true }) + expect(messages.length).toBe(1) + expect(messages[0].message).toBe( + ' must be present in tag.' + ) + }) +}) diff --git a/website/src/content/docs/list-rules.md b/website/src/content/docs/list-rules.md index b97e1b8cc..a5a571744 100644 --- a/website/src/content/docs/list-rules.md +++ b/website/src/content/docs/list-rules.md @@ -2,6 +2,7 @@ id: list-rules title: List of rules description: A complete list of all the rules for HTMLHint +draft: true --- ## Doctype and Head @@ -10,6 +11,7 @@ description: A complete list of all the rules for HTMLHint - [`doctype-html5`](/rules/doctype-html5): Invalid doctype. - [`head-script-disabled`](/rules/head-script-disabled): The `