Update MD051/link-fragments to handle links and images in headings (fixes #945).

This commit is contained in:
David Anson 2023-09-04 12:26:38 -07:00
parent 6a2b86753b
commit a736588958
5 changed files with 133 additions and 47 deletions

View file

@ -6139,6 +6139,7 @@ var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
addError = _require.addError, addError = _require.addError,
addErrorDetailIf = _require.addErrorDetailIf; addErrorDetailIf = _require.addErrorDetailIf;
var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"), var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
filterByPredicate = _require2.filterByPredicate,
filterByTypes = _require2.filterByTypes, filterByTypes = _require2.filterByTypes,
getHtmlTagInfo = _require2.getHtmlTagInfo; getHtmlTagInfo = _require2.getHtmlTagInfo;
@ -6147,6 +6148,10 @@ var idRe = /[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]i
var nameRe = /[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]name[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*=[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*["']?((?:(?![\t-\r "'>\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uD800-\uDFFF\uFEFF])[\s\S]|[\uD800-\uDBFF][\uDC00-\uDFFF])+)/i; var nameRe = /[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]name[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*=[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*["']?((?:(?![\t-\r "'>\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uD800-\uDFFF\uFEFF])[\s\S]|[\uD800-\uDBFF][\uDC00-\uDFFF])+)/i;
var anchorRe = /\{(#[0-9a-z]+(?:[\x2D_][0-9a-z]+)*)\}/g; var anchorRe = /\{(#[0-9a-z]+(?:[\x2D_][0-9a-z]+)*)\}/g;
// Sets for filtering heading tokens during conversion
var childrenExclude = new Set(["image", "reference", "resource"]);
var tokensInclude = new Set(["codeTextData", "data"]);
/** /**
* Converts a Markdown heading into an HTML fragment according to the rules * Converts a Markdown heading into an HTML fragment according to the rules
* used by GitHub. * used by GitHub.
@ -6155,7 +6160,11 @@ var anchorRe = /\{(#[0-9a-z]+(?:[\x2D_][0-9a-z]+)*)\}/g;
* @returns {string} Fragment string for heading. * @returns {string} Fragment string for heading.
*/ */
function convertHeadingToHTMLFragment(headingText) { function convertHeadingToHTMLFragment(headingText) {
var inlineText = filterByTypes(headingText.children, ["codeTextData", "data"]).map(function (token) { var inlineText = filterByPredicate(headingText.children, function (token) {
return tokensInclude.has(token.type);
}, function (token) {
return childrenExclude.has(token.type) ? [] : token.children;
}).map(function (token) {
return token.text; return token.text;
}).join(""); }).join("");
return "#" + encodeURIComponent(inlineText.toLowerCase() return "#" + encodeURIComponent(inlineText.toLowerCase()
@ -6181,18 +6190,20 @@ module.exports = {
for (_iterator.s(); !(_step = _iterator.n()).done;) { for (_iterator.s(); !(_step = _iterator.n()).done;) {
var headingText = _step.value; var headingText = _step.value;
var fragment = convertHeadingToHTMLFragment(headingText); var fragment = convertHeadingToHTMLFragment(headingText);
var count = fragments.get(fragment) || 0; if (fragment !== "#") {
if (count) { var count = fragments.get(fragment) || 0;
fragments.set("".concat(fragment, "-").concat(count), 0); if (count) {
} fragments.set("".concat(fragment, "-").concat(count), 0);
fragments.set(fragment, count + 1); }
var match = null; fragments.set(fragment, count + 1);
while ((match = anchorRe.exec(headingText.text)) !== null) { var match = null;
var _match = match, while ((match = anchorRe.exec(headingText.text)) !== null) {
_match2 = _slicedToArray(_match, 2), var _match = match,
anchor = _match2[1]; _match2 = _slicedToArray(_match, 2),
if (!fragments.has(anchor)) { anchor = _match2[1];
fragments.set(anchor, 1); if (!fragments.has(anchor)) {
fragments.set(anchor, 1);
}
} }
} }
} }

View file

@ -3,13 +3,18 @@
"use strict"; "use strict";
const { addError, addErrorDetailIf } = require("../helpers"); const { addError, addErrorDetailIf } = require("../helpers");
const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs"); const { filterByPredicate, filterByTypes, getHtmlTagInfo } =
require("../helpers/micromark.cjs");
// Regular expression for identifying HTML anchor names // Regular expression for identifying HTML anchor names
const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu; const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu;
const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu; const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu;
const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu; const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
// Sets for filtering heading tokens during conversion
const childrenExclude = new Set([ "image", "reference", "resource" ]);
const tokensInclude = new Set([ "codeTextData", "data" ]);
/** /**
* Converts a Markdown heading into an HTML fragment according to the rules * Converts a Markdown heading into an HTML fragment according to the rules
* used by GitHub. * used by GitHub.
@ -19,7 +24,11 @@ const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
*/ */
function convertHeadingToHTMLFragment(headingText) { function convertHeadingToHTMLFragment(headingText) {
const inlineText = const inlineText =
filterByTypes(headingText.children, [ "codeTextData", "data" ]) filterByPredicate(
headingText.children,
(token) => tokensInclude.has(token.type),
(token) => (childrenExclude.has(token.type) ? [] : token.children)
)
.map((token) => token.text) .map((token) => token.text)
.join(""); .join("");
return "#" + encodeURIComponent( return "#" + encodeURIComponent(
@ -52,16 +61,18 @@ module.exports = {
); );
for (const headingText of headingTexts) { for (const headingText of headingTexts) {
const fragment = convertHeadingToHTMLFragment(headingText); const fragment = convertHeadingToHTMLFragment(headingText);
const count = fragments.get(fragment) || 0; if (fragment !== "#") {
if (count) { const count = fragments.get(fragment) || 0;
fragments.set(`${fragment}-${count}`, 0); if (count) {
} fragments.set(`${fragment}-${count}`, 0);
fragments.set(fragment, count + 1); }
let match = null; fragments.set(fragment, count + 1);
while ((match = anchorRe.exec(headingText.text)) !== null) { let match = null;
const [ , anchor ] = match; while ((match = anchorRe.exec(headingText.text)) !== null) {
if (!fragments.has(anchor)) { const [ , anchor ] = match;
fragments.set(anchor, 1); if (!fragments.has(anchor)) {
fragments.set(anchor, 1);
}
} }
} }
} }

View file

@ -46,6 +46,16 @@
[Valid](#en-t%C3%AAte-valide-dans-fran%C3%A7ais-pour-v%C3%A9rification) [Valid](#en-t%C3%AAte-valide-dans-fran%C3%A7ais-pour-v%C3%A9rification)
[Valid](#valid-heading-is-a-link)
[Valid](#valid-heading-has-a-link)
[Valid](#valid-heading-is-a-reference-link)
[Valid](#valid-heading-has-a-reference-link)
[Valid](#valid-heading-has-)
[Valid](#namedlink) [Valid](#namedlink)
[Valid](#idlink) [Valid](#idlink)
@ -127,6 +137,18 @@ Text
### En-tête Valide Dans Français Pour Vérification ### En-tête Valide Dans Français Pour Vérification
### [Valid Heading Is a Link](https://example.com)
### Valid Heading [Has a Link](https://example.com)
### [Valid Heading Is a Reference Link][goodref]
### Valid Heading [Has a Reference Link][goodref]
### ![Valid Heading Is an Image](https://example.com)
### Valid Heading Has ![an Image](https://example.com)
<a name="namedlink"></a> <a name="namedlink"></a>
<a id = idlink></a> <a id = idlink></a>
@ -147,6 +169,8 @@ Text
## Invalid Fragments ## Invalid Fragments
[Invalid](#valid-heading-is-an-image) {MD051}
[Invalid](#valid-heading-2004-) {MD051} [Invalid](#valid-heading-2004-) {MD051}
[Invalid](#valid-repeated-heading-3) {MD051} [Invalid](#valid-repeated-heading-3) {MD051}

View file

@ -23961,6 +23961,22 @@ Generated by [AVA](https://avajs.dev).
{ {
errors: [ errors: [
{
errorContext: '[Invalid](#valid-heading-is-an-image)',
errorDetail: null,
errorRange: [
1,
37,
],
fixInfo: null,
lineNumber: 172,
ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [
'MD051',
'link-fragments',
],
},
{ {
errorContext: '[Invalid](#valid-heading-2004-)', errorContext: '[Invalid](#valid-heading-2004-)',
errorDetail: null, errorDetail: null,
@ -23969,7 +23985,7 @@ Generated by [AVA](https://avajs.dev).
31, 31,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 150, lineNumber: 174,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -23985,7 +24001,7 @@ Generated by [AVA](https://avajs.dev).
36, 36,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 152, lineNumber: 176,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24001,7 +24017,7 @@ Generated by [AVA](https://avajs.dev).
28, 28,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 154, lineNumber: 178,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24017,7 +24033,7 @@ Generated by [AVA](https://avajs.dev).
18, 18,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 156, lineNumber: 180,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24037,7 +24053,7 @@ Generated by [AVA](https://avajs.dev).
editColumn: 11, editColumn: 11,
insertText: '#HREFandID', insertText: '#HREFandID',
}, },
lineNumber: 158, lineNumber: 182,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24053,7 +24069,7 @@ Generated by [AVA](https://avajs.dev).
34, 34,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 160, lineNumber: 184,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24069,7 +24085,7 @@ Generated by [AVA](https://avajs.dev).
34, 34,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 162, lineNumber: 186,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24085,7 +24101,7 @@ Generated by [AVA](https://avajs.dev).
39, 39,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 164, lineNumber: 188,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24098,7 +24114,7 @@ Generated by [AVA](https://avajs.dev).
errorDetail: null, errorDetail: null,
errorRange: null, errorRange: null,
fixInfo: null, fixInfo: null,
lineNumber: 166, lineNumber: 190,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24114,7 +24130,7 @@ Generated by [AVA](https://avajs.dev).
28, 28,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 171, lineNumber: 195,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24134,7 +24150,7 @@ Generated by [AVA](https://avajs.dev).
editColumn: 9, editColumn: 9,
insertText: '#valid-fragments', insertText: '#valid-fragments',
}, },
lineNumber: 175, lineNumber: 199,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24154,7 +24170,7 @@ Generated by [AVA](https://avajs.dev).
editColumn: 12, editColumn: 12,
insertText: '#namedlink', insertText: '#namedlink',
}, },
lineNumber: 177, lineNumber: 201,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24167,7 +24183,7 @@ Generated by [AVA](https://avajs.dev).
errorDetail: 'Expected: #namedlink; Actual: #NAMEDLINK', errorDetail: 'Expected: #namedlink; Actual: #NAMEDLINK',
errorRange: null, errorRange: null,
fixInfo: null, fixInfo: null,
lineNumber: 179, lineNumber: 203,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24187,7 +24203,7 @@ Generated by [AVA](https://avajs.dev).
editColumn: 13, editColumn: 13,
insertText: '#idlink', insertText: '#idlink',
}, },
lineNumber: 184, lineNumber: 208,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24203,7 +24219,7 @@ Generated by [AVA](https://avajs.dev).
26, 26,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 219, lineNumber: 243,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24219,7 +24235,7 @@ Generated by [AVA](https://avajs.dev).
26, 26,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 221, lineNumber: 245,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24235,7 +24251,7 @@ Generated by [AVA](https://avajs.dev).
20, 20,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 223, lineNumber: 247,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24251,7 +24267,7 @@ Generated by [AVA](https://avajs.dev).
23, 23,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 225, lineNumber: 249,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24267,7 +24283,7 @@ Generated by [AVA](https://avajs.dev).
22, 22,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 227, lineNumber: 251,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24283,7 +24299,7 @@ Generated by [AVA](https://avajs.dev).
42, 42,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 229, lineNumber: 253,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24299,7 +24315,7 @@ Generated by [AVA](https://avajs.dev).
21, 21,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 231, lineNumber: 255,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24315,7 +24331,7 @@ Generated by [AVA](https://avajs.dev).
21, 21,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 233, lineNumber: 257,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [ ruleNames: [
@ -24372,6 +24388,16 @@ Generated by [AVA](https://avajs.dev).
[Valid](#en-t%C3%AAte-valide-dans-fran%C3%A7ais-pour-v%C3%A9rification)␊ [Valid](#en-t%C3%AAte-valide-dans-fran%C3%A7ais-pour-v%C3%A9rification)␊
[Valid](#valid-heading-is-a-link)␊
[Valid](#valid-heading-has-a-link)␊
[Valid](#valid-heading-is-a-reference-link)␊
[Valid](#valid-heading-has-a-reference-link)␊
[Valid](#valid-heading-has-)␊
[Valid](#namedlink)␊ [Valid](#namedlink)␊
[Valid](#idlink)␊ [Valid](#idlink)␊
@ -24453,6 +24479,18 @@ Generated by [AVA](https://avajs.dev).
### En-tête Valide Dans Français Pour Vérification␊ ### En-tête Valide Dans Français Pour Vérification␊
### [Valid Heading Is a Link](https://example.com)␊
### Valid Heading [Has a Link](https://example.com)␊
### [Valid Heading Is a Reference Link][goodref]␊
### Valid Heading [Has a Reference Link][goodref]␊
### ![Valid Heading Is an Image](https://example.com)␊
### Valid Heading Has ![an Image](https://example.com)␊
<a name="namedlink"></a> <a name="namedlink"></a>
<a id = idlink></a> <a id = idlink></a>
@ -24473,6 +24511,8 @@ Generated by [AVA](https://avajs.dev).
## Invalid Fragments␊ ## Invalid Fragments␊
[Invalid](#valid-heading-is-an-image) {MD051}␊
[Invalid](#valid-heading-2004-) {MD051}␊ [Invalid](#valid-heading-2004-) {MD051}␊
[Invalid](#valid-repeated-heading-3) {MD051}␊ [Invalid](#valid-repeated-heading-3) {MD051}␊