Skip to content

Commit cb1cb61

Browse files
committed
feat: anchor link checks support HTML tags like <a name="foo"></a>
1 parent 71ccb41 commit cb1cb61

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

index.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,32 @@ function performSpecialReplacements(str, opts) {
4343
return str;
4444
}
4545

46+
function removeCodeBlocks(markdown) {
47+
return markdown.replace(/^```[\S\s]+?^```$/gm, '');
48+
}
49+
50+
function extractHtmlSections(markdown) {
51+
markdown =
52+
// remove code blocks
53+
removeCodeBlocks(markdown)
54+
// remove HTML comments
55+
.replace(/<!--[\S\s]+?-->/gm, '')
56+
// remove single line code (if not escaped with "\")
57+
.replace(/(?<!\\)`[\S\s]+?(?<!\\)`/gm, '');
58+
59+
const regexAllId = /<(?<tag>[^\s]+).*?id="(?<id>[^"]*?)".*?>/gmi;
60+
const regexAName = /<a.*?name="(?<name>[^"]*?)".*?>/gmi;
61+
62+
const sections = []
63+
.concat(Array.from(markdown.matchAll(regexAllId), (match) => match.groups.id))
64+
.concat(Array.from(markdown.matchAll(regexAName), (match) => match.groups.name));
65+
66+
return sections
67+
}
68+
4669
function extractSections(markdown) {
4770
// First remove code blocks.
48-
markdown = markdown.replace(/^```[\S\s]+?^```$/mg, '');
71+
markdown = removeCodeBlocks(markdown);
4972

5073
const sectionTitles = markdown.match(/^#+ .*$/gm) || [];
5174

@@ -85,7 +108,7 @@ module.exports = function markdownLinkCheck(markdown, opts, callback) {
85108
}
86109

87110
const links = markdownLinkExtractor(markdown);
88-
const sections = extractSections(markdown);
111+
const sections = extractSections(markdown).concat(extractHtmlSections(markdown));
89112
const linksCollection = _.uniq(links);
90113
const bar = (opts.showProgressBar) ?
91114
new ProgressBar('Checking... [:bar] :percent', {

test/hash-links.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
# Foo
22

33
<!-- markdownlint-disable MD033 -->
4-
<a id="tomato"></a>
4+
<a id="tomato_id"></a>
5+
6+
<a name="tomato_name"></a>
7+
8+
<div id="onion"></div>
9+
<div id="onion_outer">
10+
<div id="onion_inner"></div>
11+
</div>
12+
13+
<!--
14+
<a id="tomato_comment"></a>
15+
-->
16+
517
<!-- markdownlint-enable MD033 -->
618

719
This is a test.
820

21+
HTML anchor in code `<a id="tomato_code"></a>` should be ignored.
22+
23+
<!-- markdownlint-disable-next-line MD033 -->
24+
Ignore escaped backticks \`<a id="tomato_escaped_backticks"></a>\`. Link should work.
25+
926
## Bar
1027

1128
The title is [Foo](#foo).
@@ -18,7 +35,21 @@ The second section is [Bar](#bar).
1835

1936
There is no section named [Potato](#potato).
2037

21-
There is an anchor named [Tomato](#tomato).
38+
There is an anchor named with `id` [Tomato](#tomato_id).
39+
40+
There is an anchor named with `name` [Tomato](#tomato_name).
41+
42+
There is an anchor in code [Tomato in code](#tomato_code).
43+
44+
There is an anchor in escaped code [Tomato in escaped backticks](#tomato_escaped_backticks).
45+
46+
There is an anchor in HTML comment [Tomato in comment](#tomato_comment).
47+
48+
There is an anchor in single div [Onion](#onion).
49+
50+
There is an anchor in outer div [Onion outer](#onion_outer).
51+
52+
There is an anchor in inner div [Onion inner](#onion_inner).
2253

2354
## Header with special char ✨
2455

test/markdown-link-check.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,14 @@ describe('markdown-link-check', function () {
377377
{ link: '#foo', statusCode: 200, err: null, status: 'alive' },
378378
{ link: '#bar', statusCode: 200, err: null, status: 'alive' },
379379
{ link: '#potato', statusCode: 404, err: null, status: 'dead' },
380-
{ link: '#tomato', statusCode: 404, err: null, status: 'dead' },
380+
{ link: '#tomato_id', statusCode: 200, err: null, status: 'alive' },
381+
{ link: '#tomato_name', statusCode: 200, err: null, status: 'alive' },
382+
{ link: '#tomato_code', statusCode: 404, err: null, status: 'dead' },
383+
{ link: '#tomato_escaped_backticks', statusCode: 200, err: null, status: 'alive' },
384+
{ link: '#tomato_comment', statusCode: 404, err: null, status: 'dead' },
385+
{ link: '#onion', statusCode: 200, err: null, status: 'alive' },
386+
{ link: '#onion_outer', statusCode: 200, err: null, status: 'alive' },
387+
{ link: '#onion_inner', statusCode: 200, err: null, status: 'alive' },
381388
{ link: '#header-with-special-char-', statusCode: 404, err: null, status: 'dead' },
382389
]);
383390
done();

0 commit comments

Comments
 (0)