Skip to content

Commit a736588

Browse files
committed
Update MD051/link-fragments to handle links and images in headings (fixes #945).
1 parent 6a2b867 commit a736588

File tree

5 files changed

+133
-47
lines changed

5 files changed

+133
-47
lines changed

demo/markdownlint-browser.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6139,6 +6139,7 @@ var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
61396139
addError = _require.addError,
61406140
addErrorDetailIf = _require.addErrorDetailIf;
61416141
var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
6142+
filterByPredicate = _require2.filterByPredicate,
61426143
filterByTypes = _require2.filterByTypes,
61436144
getHtmlTagInfo = _require2.getHtmlTagInfo;
61446145

@@ -6147,6 +6148,10 @@ var idRe = /[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]i
61476148
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;
61486149
var anchorRe = /\{(#[0-9a-z]+(?:[\x2D_][0-9a-z]+)*)\}/g;
61496150

6151+
// Sets for filtering heading tokens during conversion
6152+
var childrenExclude = new Set(["image", "reference", "resource"]);
6153+
var tokensInclude = new Set(["codeTextData", "data"]);
6154+
61506155
/**
61516156
* Converts a Markdown heading into an HTML fragment according to the rules
61526157
* used by GitHub.
@@ -6155,7 +6160,11 @@ var anchorRe = /\{(#[0-9a-z]+(?:[\x2D_][0-9a-z]+)*)\}/g;
61556160
* @returns {string} Fragment string for heading.
61566161
*/
61576162
function convertHeadingToHTMLFragment(headingText) {
6158-
var inlineText = filterByTypes(headingText.children, ["codeTextData", "data"]).map(function (token) {
6163+
var inlineText = filterByPredicate(headingText.children, function (token) {
6164+
return tokensInclude.has(token.type);
6165+
}, function (token) {
6166+
return childrenExclude.has(token.type) ? [] : token.children;
6167+
}).map(function (token) {
61596168
return token.text;
61606169
}).join("");
61616170
return "#" + encodeURIComponent(inlineText.toLowerCase()
@@ -6181,18 +6190,20 @@ module.exports = {
61816190
for (_iterator.s(); !(_step = _iterator.n()).done;) {
61826191
var headingText = _step.value;
61836192
var fragment = convertHeadingToHTMLFragment(headingText);
6184-
var count = fragments.get(fragment) || 0;
6185-
if (count) {
6186-
fragments.set("".concat(fragment, "-").concat(count), 0);
6187-
}
6188-
fragments.set(fragment, count + 1);
6189-
var match = null;
6190-
while ((match = anchorRe.exec(headingText.text)) !== null) {
6191-
var _match = match,
6192-
_match2 = _slicedToArray(_match, 2),
6193-
anchor = _match2[1];
6194-
if (!fragments.has(anchor)) {
6195-
fragments.set(anchor, 1);
6193+
if (fragment !== "#") {
6194+
var count = fragments.get(fragment) || 0;
6195+
if (count) {
6196+
fragments.set("".concat(fragment, "-").concat(count), 0);
6197+
}
6198+
fragments.set(fragment, count + 1);
6199+
var match = null;
6200+
while ((match = anchorRe.exec(headingText.text)) !== null) {
6201+
var _match = match,
6202+
_match2 = _slicedToArray(_match, 2),
6203+
anchor = _match2[1];
6204+
if (!fragments.has(anchor)) {
6205+
fragments.set(anchor, 1);
6206+
}
61966207
}
61976208
}
61986209
}

lib/md051.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
"use strict";
44

55
const { addError, addErrorDetailIf } = require("../helpers");
6-
const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs");
6+
const { filterByPredicate, filterByTypes, getHtmlTagInfo } =
7+
require("../helpers/micromark.cjs");
78

89
// Regular expression for identifying HTML anchor names
910
const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu;
1011
const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu;
1112
const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
1213

14+
// Sets for filtering heading tokens during conversion
15+
const childrenExclude = new Set([ "image", "reference", "resource" ]);
16+
const tokensInclude = new Set([ "codeTextData", "data" ]);
17+
1318
/**
1419
* Converts a Markdown heading into an HTML fragment according to the rules
1520
* used by GitHub.
@@ -19,7 +24,11 @@ const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
1924
*/
2025
function convertHeadingToHTMLFragment(headingText) {
2126
const inlineText =
22-
filterByTypes(headingText.children, [ "codeTextData", "data" ])
27+
filterByPredicate(
28+
headingText.children,
29+
(token) => tokensInclude.has(token.type),
30+
(token) => (childrenExclude.has(token.type) ? [] : token.children)
31+
)
2332
.map((token) => token.text)
2433
.join("");
2534
return "#" + encodeURIComponent(
@@ -52,16 +61,18 @@ module.exports = {
5261
);
5362
for (const headingText of headingTexts) {
5463
const fragment = convertHeadingToHTMLFragment(headingText);
55-
const count = fragments.get(fragment) || 0;
56-
if (count) {
57-
fragments.set(`${fragment}-${count}`, 0);
58-
}
59-
fragments.set(fragment, count + 1);
60-
let match = null;
61-
while ((match = anchorRe.exec(headingText.text)) !== null) {
62-
const [ , anchor ] = match;
63-
if (!fragments.has(anchor)) {
64-
fragments.set(anchor, 1);
64+
if (fragment !== "#") {
65+
const count = fragments.get(fragment) || 0;
66+
if (count) {
67+
fragments.set(`${fragment}-${count}`, 0);
68+
}
69+
fragments.set(fragment, count + 1);
70+
let match = null;
71+
while ((match = anchorRe.exec(headingText.text)) !== null) {
72+
const [ , anchor ] = match;
73+
if (!fragments.has(anchor)) {
74+
fragments.set(anchor, 1);
75+
}
6576
}
6677
}
6778
}

test/link-fragments.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@
4646

4747
[Valid](#en-t%C3%AAte-valide-dans-fran%C3%A7ais-pour-v%C3%A9rification)
4848

49+
[Valid](#valid-heading-is-a-link)
50+
51+
[Valid](#valid-heading-has-a-link)
52+
53+
[Valid](#valid-heading-is-a-reference-link)
54+
55+
[Valid](#valid-heading-has-a-reference-link)
56+
57+
[Valid](#valid-heading-has-)
58+
4959
[Valid](#namedlink)
5060

5161
[Valid](#idlink)
@@ -127,6 +137,18 @@ Text
127137

128138
### En-tête Valide Dans Français Pour Vérification
129139

140+
### [Valid Heading Is a Link](https://example.com)
141+
142+
### Valid Heading [Has a Link](https://example.com)
143+
144+
### [Valid Heading Is a Reference Link][goodref]
145+
146+
### Valid Heading [Has a Reference Link][goodref]
147+
148+
### ![Valid Heading Is an Image](https://example.com)
149+
150+
### Valid Heading Has ![an Image](https://example.com)
151+
130152
<a name="namedlink"></a>
131153

132154
<a id = idlink></a>
@@ -147,6 +169,8 @@ Text
147169

148170
## Invalid Fragments
149171

172+
[Invalid](#valid-heading-is-an-image) {MD051}
173+
150174
[Invalid](#valid-heading-2004-) {MD051}
151175

152176
[Invalid](#valid-repeated-heading-3) {MD051}

0 commit comments

Comments
 (0)