mirror of
https://github.com/theoludwig/markdownlint-rule-relative-links.git
synced 2026-06-09 23:25:33 +02:00
feat: validate relative links fragments
Similar to https://github.com/DavidAnson/markdownlint/blob/main/doc/md051.md Fixes #2 BREAKING CHANGE: Validate links fragments in relative links
This commit is contained in:
+35
-40
@@ -3,42 +3,12 @@
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const fs = require('node:fs')
|
||||
|
||||
/**
|
||||
* Calls the provided function for each matching token.
|
||||
*
|
||||
* @param {Object} params RuleParams instance.
|
||||
* @param {string} type Token type identifier.
|
||||
* @param {Function} handler Callback function.
|
||||
* @returns {void}
|
||||
*/
|
||||
const filterTokens = (params, type, handler) => {
|
||||
for (const token of params.tokens) {
|
||||
if (token.type === type) {
|
||||
handler(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a generic error object via the onError callback.
|
||||
*
|
||||
* @param {Object} onError RuleOnError instance.
|
||||
* @param {number} lineNumber Line number.
|
||||
* @param {string} [detail] Error details.
|
||||
* @param {string} [context] Error context.
|
||||
* @param {number[]} [range] Column and length of error.
|
||||
* @param {Object} [fixInfo] RuleOnErrorFixInfo instance.
|
||||
* @returns {void}
|
||||
*/
|
||||
const addError = (onError, lineNumber, detail, context, range, fixInfo) => {
|
||||
onError({
|
||||
lineNumber,
|
||||
detail,
|
||||
context,
|
||||
range,
|
||||
fixInfo
|
||||
})
|
||||
}
|
||||
const {
|
||||
filterTokens,
|
||||
addError,
|
||||
convertHeadingToHTMLFragment,
|
||||
getMarkdownHeadings
|
||||
} = require('./utils.js')
|
||||
|
||||
const customRule = {
|
||||
names: ['relative-links'],
|
||||
@@ -70,12 +40,37 @@ const customRule = {
|
||||
|
||||
if (hrefSrc != null) {
|
||||
const url = new URL(hrefSrc, pathToFileURL(params.name))
|
||||
url.hash = ''
|
||||
const isRelative =
|
||||
url.protocol === 'file:' && !hrefSrc.startsWith('/')
|
||||
if (isRelative && !fs.existsSync(url)) {
|
||||
const detail = `Link "${hrefSrc}" is dead`
|
||||
addError(onError, lineNumber, detail)
|
||||
if (isRelative) {
|
||||
const detail = `Link "${hrefSrc}" is not valid`
|
||||
|
||||
if (!fs.existsSync(url)) {
|
||||
addError(onError, lineNumber, detail)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user