92 lines
2.4 KiB
JavaScript
Raw Normal View History

2023-01-02 15:23:16 +01:00
'use strict'
2023-01-02 15:23:47 +01:00
const { pathToFileURL } = require('node:url')
const fs = require('node:fs')
const {
filterTokens,
addError,
convertHeadingToHTMLFragment,
getMarkdownHeadings
} = require('./utils.js')
2023-01-02 15:23:47 +01:00
2023-01-02 15:23:16 +01:00
const customRule = {
names: ['relative-links'],
description: 'Relative links should be valid',
tags: ['links'],
2023-01-02 15:23:47 +01:00
function: (params, onError) => {
filterTokens(params, 'inline', (token) => {
2023-07-18 23:18:39 +02:00
for (const child of token.children) {
2023-01-02 15:23:47 +01:00
const { lineNumber, type, attrs } = child
2023-01-02 19:45:46 +01:00
/** @type {string | null} */
let hrefSrc = null
2023-01-02 15:23:47 +01:00
if (type === 'link_open') {
2023-07-18 23:18:39 +02:00
for (const attr of attrs) {
2023-01-02 15:23:47 +01:00
if (attr[0] === 'href') {
2023-01-02 19:45:46 +01:00
hrefSrc = attr[1]
2023-07-18 23:18:39 +02:00
break
2023-01-02 15:23:47 +01:00
}
2023-07-18 23:18:39 +02:00
}
2023-01-02 15:23:47 +01:00
}
2023-01-02 19:45:46 +01:00
if (type === 'image') {
2023-07-18 23:18:39 +02:00
for (const attr of attrs) {
2023-01-02 19:45:46 +01:00
if (attr[0] === 'src') {
hrefSrc = attr[1]
2023-07-18 23:18:39 +02:00
break
2023-01-02 19:45:46 +01:00
}
2023-07-18 23:18:39 +02:00
}
2023-01-02 19:45:46 +01:00
}
if (hrefSrc != null) {
const url = new URL(hrefSrc, pathToFileURL(params.name))
const isRelative =
url.protocol === 'file:' && !hrefSrc.startsWith('/')
if (isRelative) {
const detail = `Link "${hrefSrc}"`
if (!fs.existsSync(url)) {
addError(
onError,
lineNumber,
`${detail} should exist in the file system`
)
2023-07-18 23:18:39 +02:00
continue
}
if (type === 'link_open' && url.hash !== '') {
const fileContent = fs.readFileSync(url, { encoding: 'utf8' })
const headings = getMarkdownHeadings(fileContent)
/** @type {Map<string, number>} */
const fragments = new Map()
const headingsHTMLFragments = headings.map((heading) => {
const fragment = convertHeadingToHTMLFragment(heading)
const count = fragments.get(fragment) ?? 0
fragments.set(fragment, count + 1)
if (count !== 0) {
return `${fragment}-${count}`
}
return fragment
})
if (!headingsHTMLFragments.includes(url.hash)) {
addError(
onError,
lineNumber,
`${detail} should have a valid fragment`
)
}
}
2023-01-02 19:45:46 +01:00
}
}
2023-07-18 23:18:39 +02:00
}
2023-01-02 15:23:47 +01:00
})
}
2023-01-02 15:23:16 +01:00
}
module.exports = customRule