feat: support line number checking in link fragment (e.g: '#L50')

Fixes #6
This commit is contained in:
2024-01-31 01:14:27 +01:00
parent e20ee54b05
commit f332c833ca
15 changed files with 154 additions and 2 deletions

View File

@ -8,6 +8,9 @@ const {
convertHeadingToHTMLFragment,
getMarkdownHeadings,
getMarkdownIdOrAnchorNameFragments,
isValidIntegerString,
getNumberOfLines,
getLineNumberStringFromFragment,
} = require("./utils.js")
/** @typedef {import('markdownlint').Rule} MarkdownLintRule */
@ -121,7 +124,35 @@ const customRule = {
fragmentsHTML.push(...idOrAnchorNameHTMLFragments)
if (!fragmentsHTML.includes(url.hash)) {
if (!fragmentsHTML.includes(url.hash.toLowerCase())) {
if (url.hash.startsWith("#L")) {
const lineNumberFragmentString = getLineNumberStringFromFragment(
url.hash,
)
const hasOnlyDigits = isValidIntegerString(lineNumberFragmentString)
if (!hasOnlyDigits) {
onError({
lineNumber,
detail: `${detail} should have a valid fragment identifier`,
})
continue
}
const lineNumberFragment = Number.parseInt(
lineNumberFragmentString,
10,
)
const numberOfLines = getNumberOfLines(fileContent)
if (lineNumberFragment > numberOfLines) {
onError({
lineNumber,
detail: `${detail} should have a valid fragment identifier, ${detail} should have at least ${lineNumberFragment} lines to be valid`,
})
continue
}
}
onError({
lineNumber,
detail: `${detail} should have a valid fragment identifier`,

View File

@ -114,8 +114,47 @@ const getMarkdownIdOrAnchorNameFragments = (content) => {
return result
}
/**
* Checks if a string is a valid integer.
*
* Using `Number.parseInt` combined with `Number.isNaN` will not be sufficient enough because `Number.parseInt("1abc", 10)` will return `1` (a valid number) instead of `NaN`.
*
* @param {string} value
* @returns {boolean}
* @example isValidIntegerString("1") // true
* @example isValidIntegerString("45") // true
* @example isValidIntegerString("1abc") // false
* @example isValidIntegerString("1.0") // false
*/
const isValidIntegerString = (value) => {
const regex = /^\d+$/
return regex.test(value)
}
/**
* Gets the number of lines in a string, based on the number of `\n` characters.
* @param {string} content
* @returns {number}
*/
const getNumberOfLines = (content) => {
return content.split("\n").length
}
/**
* Gets the line number string from a fragment.
* @param {string} fragment
* @returns {string}
* @example getLineNumberStringFromFragment("#L50") // 50
*/
const getLineNumberStringFromFragment = (fragment) => {
return fragment.slice(2)
}
module.exports = {
convertHeadingToHTMLFragment,
getMarkdownHeadings,
getMarkdownIdOrAnchorNameFragments,
isValidIntegerString,
getNumberOfLines,
getLineNumberStringFromFragment,
}