From 531e58ed9a76e08b32f885341df2fd43de0d69d3 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:20:19 -0700 Subject: [PATCH] Update MD045/no-alt-text to report instances of HTML "img" tags missing an "alt" attribute (fixes #992). --- demo/markdownlint-browser.js | 47 ++++++- doc-build/md045.md | 6 + doc/Rules.md | 6 + doc/md045.md | 6 + helpers/helpers.js | 4 +- helpers/shared.js | 3 + lib/md033.js | 4 +- lib/md045.js | 36 ++++- test/h1-image-as-top-level-heading.md | 2 +- test/html-tags.md | 2 +- test/no-alt-text.md | 38 ++++++ test/proper-names-no-html.md | 2 +- test/proper-names.md | 2 +- .../markdownlint-test-micromark.mjs.snap | Bin 20821 -> 20813 bytes test/snapshots/markdownlint-test-repos.js.md | 13 +- .../snapshots/markdownlint-test-repos.js.snap | Bin 3664 -> 3845 bytes .../markdownlint-test-scenarios.js.md | 128 +++++++++++++++++- .../markdownlint-test-scenarios.js.snap | Bin 200900 -> 201449 bytes 18 files changed, 274 insertions(+), 25 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 02e42707..24e8b625 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -46,8 +46,10 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var micromark = __webpack_require__(/*! ./micromark.cjs */ "../helpers/micromark.cjs"); var _require = __webpack_require__(/*! ./shared.js */ "../helpers/shared.js"), - newLineRe = _require.newLineRe; + newLineRe = _require.newLineRe, + nextLinesRe = _require.nextLinesRe; module.exports.newLineRe = newLineRe; +module.exports.nextLinesRe = nextLinesRe; // Regular expression for matching common front matter (YAML and TOML) module.exports.frontMatterRe = @@ -1064,6 +1066,9 @@ module.exports.expandTildePath = expandTildePath; // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js module.exports.newLineRe = /\r\n?|\n/g; +// Regular expression for matching next lines +module.exports.nextLinesRe = /[\r\n][\s\S]*$/; + /***/ }), /***/ "markdown-it": @@ -5043,11 +5048,11 @@ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), - addError = _require.addError; + addError = _require.addError, + nextLinesRe = _require.nextLinesRe; var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"), filterByTypes = _require2.filterByTypes, getHtmlTagInfo = _require2.getHtmlTagInfo; -var nextLinesRe = /[\r\n][\s\S]*$/; module.exports = { "names": ["MD033", "no-inline-html"], "description": "Inline HTML", @@ -5986,15 +5991,23 @@ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), - addError = _require.addError; + addError = _require.addError, + nextLinesRe = _require.nextLinesRe; var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"), - filterByTypes = _require2.filterByTypes; + filterByTypes = _require2.filterByTypes, + getHtmlTagInfo = _require2.getHtmlTagInfo; + +// Regular expression for identifying alt attribute +var altRe = /\salt=/i; module.exports = { "names": ["MD045", "no-alt-text"], "description": "Images should have alternate text (alt text)", "tags": ["accessibility", "images"], "function": function MD045(params, onError) { - var images = filterByTypes(params.parsers.micromark.tokens, ["image"]); + var tokens = params.parsers.micromark.tokens; + + // Process Markdown images + var images = filterByTypes(tokens, ["image"]); var _iterator = _createForOfIteratorHelper(images), _step; try { @@ -6008,11 +6021,33 @@ module.exports = { addError(onError, image.startLine, undefined, undefined, range); } } + + // Process HTML images } catch (err) { _iterator.e(err); } finally { _iterator.f(); } + var htmlTexts = filterByTypes(tokens, ["htmlText"]); + var _iterator2 = _createForOfIteratorHelper(htmlTexts), + _step2; + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var htmlText = _step2.value; + var startColumn = htmlText.startColumn, + startLine = htmlText.startLine, + text = htmlText.text; + var htmlTagInfo = getHtmlTagInfo(htmlText); + if (htmlTagInfo && htmlTagInfo.name.toLowerCase() === "img" && !altRe.test(text)) { + var _range = [startColumn, text.replace(nextLinesRe, "").length]; + addError(onError, startLine, undefined, undefined, _range); + } + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } } }; diff --git a/doc-build/md045.md b/doc-build/md045.md index c10df017..c99f4227 100644 --- a/doc-build/md045.md +++ b/doc-build/md045.md @@ -17,6 +17,12 @@ Or with reference syntax as: [ref]: image.jpg "Optional title" ``` +Or with HTML as: + +```html +Alternate text +``` + Guidance for writing alternate text is available from the [W3C][w3c], [Wikipedia][wikipedia], and [other locations][phase2technology]. diff --git a/doc/Rules.md b/doc/Rules.md index 6b59b6b1..2c5dfceb 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1955,6 +1955,12 @@ Or with reference syntax as: [ref]: image.jpg "Optional title" ``` +Or with HTML as: + +```html +Alternate text +``` + Guidance for writing alternate text is available from the [W3C][w3c], [Wikipedia][wikipedia], and [other locations][phase2technology]. diff --git a/doc/md045.md b/doc/md045.md index 7326d697..5d214f6f 100644 --- a/doc/md045.md +++ b/doc/md045.md @@ -23,6 +23,12 @@ Or with reference syntax as: [ref]: image.jpg "Optional title" ``` +Or with HTML as: + +```html +Alternate text +``` + Guidance for writing alternate text is available from the [W3C][w3c], [Wikipedia][wikipedia], and [other locations][phase2technology]. diff --git a/helpers/helpers.js b/helpers/helpers.js index 66e1e33a..2ef5a297 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -4,8 +4,10 @@ const micromark = require("./micromark.cjs"); -const { newLineRe } = require("./shared.js"); +const { newLineRe, nextLinesRe } = require("./shared.js"); + module.exports.newLineRe = newLineRe; +module.exports.nextLinesRe = nextLinesRe; // Regular expression for matching common front matter (YAML and TOML) module.exports.frontMatterRe = diff --git a/helpers/shared.js b/helpers/shared.js index 019222cb..f66cc5e7 100644 --- a/helpers/shared.js +++ b/helpers/shared.js @@ -5,3 +5,6 @@ // Regular expression for matching common newline characters // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js module.exports.newLineRe = /\r\n?|\n/g; + +// Regular expression for matching next lines +module.exports.nextLinesRe = /[\r\n][\s\S]*$/; diff --git a/lib/md033.js b/lib/md033.js index 54efce1a..9fbceea1 100644 --- a/lib/md033.js +++ b/lib/md033.js @@ -2,12 +2,10 @@ "use strict"; -const { addError } = require("../helpers"); +const { addError, nextLinesRe } = require("../helpers"); const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs"); -const nextLinesRe = /[\r\n][\s\S]*$/; - module.exports = { "names": [ "MD033", "no-inline-html" ], "description": "Inline HTML", diff --git a/lib/md045.js b/lib/md045.js index 891dd165..e5ab4a7e 100644 --- a/lib/md045.js +++ b/lib/md045.js @@ -2,15 +2,21 @@ "use strict"; -const { addError } = require("../helpers"); -const { filterByTypes } = require("../helpers/micromark.cjs"); +const { addError, nextLinesRe } = require("../helpers"); +const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs"); + +// Regular expression for identifying alt attribute +const altRe = /\salt=/i; module.exports = { "names": [ "MD045", "no-alt-text" ], "description": "Images should have alternate text (alt text)", "tags": [ "accessibility", "images" ], "function": function MD045(params, onError) { - const images = filterByTypes(params.parsers.micromark.tokens, [ "image" ]); + const { tokens } = params.parsers.micromark; + + // Process Markdown images + const images = filterByTypes(tokens, [ "image" ]); for (const image of images) { const labelTexts = filterByTypes(image.children, [ "labelText" ]); if (labelTexts.some((labelText) => labelText.text.length === 0)) { @@ -26,5 +32,29 @@ module.exports = { ); } } + + // Process HTML images + const htmlTexts = filterByTypes(tokens, [ "htmlText" ]); + for (const htmlText of htmlTexts) { + const { startColumn, startLine, text } = htmlText; + const htmlTagInfo = getHtmlTagInfo(htmlText); + if ( + htmlTagInfo && + (htmlTagInfo.name.toLowerCase() === "img") && + !altRe.test(text) + ) { + const range = [ + startColumn, + text.replace(nextLinesRe, "").length + ]; + addError( + onError, + startLine, + undefined, + undefined, + range + ); + } + } } }; diff --git a/test/h1-image-as-top-level-heading.md b/test/h1-image-as-top-level-heading.md index a36f0113..c2ccbeae 100644 --- a/test/h1-image-as-top-level-heading.md +++ b/test/h1-image-as-top-level-heading.md @@ -1,4 +1,4 @@ -

+

A kitten

Text diff --git a/test/html-tags.md b/test/html-tags.md index 50ce36ca..10277ec2 100644 --- a/test/html-tags.md +++ b/test/html-tags.md @@ -88,7 +88,7 @@ Text ``text text`` text Text Text inline {MD033} text -text text {MD033} +text Description text {MD033} Text diff --git a/test/no-alt-text.md b/test/no-alt-text.md index b685ef8d..cbd585a7 100644 --- a/test/no-alt-text.md +++ b/test/no-alt-text.md @@ -28,5 +28,43 @@ Multi-line image with alternate text ![Alternate text](image.jpg "Title" Multi-line image without alternate text ![](image.jpg "Title" ) {MD045:28} + + +Image tag with alt attribute set to text +Descriptive text + +Image tag with alt attribute not set + {MD045} + +Image tag with alt attribute set to decorative with an empty double-quote string + + +Image tag with alt attribute set to decorative with an empty single-quote string + + +Image tag with no alt attribute {MD045} + +Multi-line image tag with no alt text + {MD045:48} + +Multi-line image tag with alt attribute not set + {MD045:52} + +Multi-line image tag with alt text +Description + +Uppercase image tag with alt attribute set +Descriptive text + +Uppercase image tag with no alt set {MD045} + + + [notitle]: image.jpg [title]: image.jpg "Title" diff --git a/test/proper-names-no-html.md b/test/proper-names-no-html.md index a4cc8ffd..a209b94c 100644 --- a/test/proper-names-no-html.md +++ b/test/proper-names-no-html.md @@ -6,7 +6,7 @@ Bad text javascript. {MD044} Bad code `javascript`. {MD044} - +Description