Skip to content

Commit 6b8b0c2

Browse files
committed
add an internal link check for markdownlint-cli2
1 parent 459e145 commit 6b8b0c2

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

.markdownlint-cli2.jsonc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/refs/heads/main/schema/markdownlint-cli2-config-schema.json",
3+
"customRules": ["scripts/checkInternalLink.js"],
4+
}

.markdownlint.jsonc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@
1818
],
1919
},
2020
"ul-style": false,
21+
"check-internal-link": {
22+
"warn_only": true,
23+
},
2124
}

scripts/checkInternalLink.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// the slugify function is used in vitepress to generate title id
2+
const rControl = /[\u0000-\u001f]/g
3+
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g
4+
const rCombining = /[\u0300-\u036F]/g
5+
const slugify = str =>
6+
str
7+
.normalize('NFKD')
8+
.replace(rCombining, '')
9+
.replace(rControl, '')
10+
.replace(rSpecial, '-')
11+
.replace(/-{2,}/g, '-')
12+
.replace(/^-+|-+$/g, '')
13+
.replace(/^(\d)/, '_$1')
14+
.toLowerCase()
15+
16+
import path, { relative } from 'node:path'
17+
import { env } from 'node:process'
18+
let is_in_github_action = env.GITHUB_ACTIONS == 'true'
19+
20+
export default {
21+
names: ['check-internal-link'],
22+
description: 'check the internal title is referenced currectly in wiki',
23+
// information: undefined,
24+
tags: ['i18n'],
25+
function: function rule(params, onError) {
26+
// warn_only will just generate a `console.warn` message and will not block CI build
27+
// config it at `.markdownlint.jsonc`
28+
let warn_only = params.config && params.config.warn_only
29+
30+
let found_title = new Set()
31+
32+
let title_level = 0
33+
let pending_title = undefined
34+
35+
function find_titles(token) {
36+
if (token.children) {
37+
for (let t of token.children) {
38+
find_titles(t)
39+
}
40+
return
41+
}
42+
43+
switch (token.type) {
44+
case 'heading_open':
45+
pending_title = ''
46+
title_level++
47+
break
48+
case 'text':
49+
if (title_level > 0) pending_title += token.content
50+
break
51+
case 'heading_close':
52+
title_level--
53+
found_title.add(slugify(pending_title))
54+
break
55+
}
56+
}
57+
for (let token of params.parsers.markdownit.tokens) {
58+
find_titles(token)
59+
}
60+
if (title_level != 0) {
61+
console.warn(
62+
"warning: checkInternalLink.js can't find title currectly, it woll not check the file " +
63+
params.name,
64+
)
65+
return
66+
}
67+
68+
//console.log("found title in file", found_title)
69+
70+
function handle(token) {
71+
if (token.type == 'link_open') {
72+
let href = token.attrs[0][1]
73+
if (href.startsWith('#')) {
74+
let title = href.substr(1)
75+
if (!found_title.has(decodeURI(title))) {
76+
if (warn_only) {
77+
let file_name = relative(
78+
path.join(import.meta.dirname, '..'),
79+
params.name,
80+
)
81+
let lineno = token.lineNumber + params.frontMatterLines.length
82+
83+
if (is_in_github_action) {
84+
// github has 10 limit of annotation, it's not visible in the logs
85+
// so output the file_name and lineno in the next line
86+
console.log(
87+
`::warning file=${file_name},line=${lineno},title=Title not found::${href}\nat ${file_name}:${lineno}`,
88+
)
89+
} else {
90+
console.warn(
91+
'warning: ' +
92+
file_name +
93+
':' +
94+
lineno +
95+
': title not found: ' +
96+
href,
97+
)
98+
}
99+
} else {
100+
onError({
101+
lineNumber: token.lineNumber,
102+
detail: 'title not found: ' + href,
103+
// context: token.line
104+
})
105+
}
106+
}
107+
}
108+
} else {
109+
if (token.children) {
110+
for (let t of token.children) {
111+
handle(t)
112+
}
113+
}
114+
}
115+
}
116+
117+
for (let token of params.parsers.markdownit.tokens) {
118+
handle(token)
119+
}
120+
},
121+
}

0 commit comments

Comments
 (0)