2023-06-24 11:42:09 +02:00
|
|
|
const MarkdownIt = require('markdown-it')
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls the provided function for each matching token.
|
|
|
|
*
|
2023-07-18 23:18:39 +02:00
|
|
|
* @param {object} params RuleParams instance.
|
2023-06-24 11:42:09 +02:00
|
|
|
* @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.
|
|
|
|
*
|
2023-07-18 23:18:39 +02:00
|
|
|
* @param {object} onError RuleOnError instance.
|
2023-06-24 11:42:09 +02:00
|
|
|
* @param {number} lineNumber Line number.
|
|
|
|
* @param {string} [detail] Error details.
|
|
|
|
* @param {string} [context] Error context.
|
|
|
|
* @param {number[]} [range] Column and length of error.
|
2023-07-18 23:18:39 +02:00
|
|
|
* @param {object} [fixInfo] RuleOnErrorFixInfo instance.
|
2023-06-24 11:42:09 +02:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
const addError = (onError, lineNumber, detail, context, range, fixInfo) => {
|
|
|
|
onError({
|
|
|
|
lineNumber,
|
|
|
|
detail,
|
|
|
|
context,
|
|
|
|
range,
|
|
|
|
fixInfo
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a Markdown heading into an HTML fragment according to the rules
|
|
|
|
* used by GitHub.
|
|
|
|
*
|
2023-06-27 13:15:03 +02:00
|
|
|
* @see https://github.com/DavidAnson/markdownlint/blob/d01180ec5a014083ee9d574b693a8d7fbc1e566d/lib/md051.js#L1
|
2023-06-24 11:42:09 +02:00
|
|
|
* @param {string} inlineText Inline token for heading.
|
|
|
|
* @returns {string} Fragment string for heading.
|
|
|
|
*/
|
|
|
|
const convertHeadingToHTMLFragment = (inlineText) => {
|
|
|
|
return (
|
|
|
|
'#' +
|
|
|
|
encodeURIComponent(
|
|
|
|
inlineText
|
|
|
|
.toLowerCase()
|
|
|
|
// RegExp source with Ruby's \p{Word} expanded into its General Categories
|
|
|
|
// https://github.com/gjtorikian/html-pipeline/blob/main/lib/html/pipeline/toc_filter.rb
|
|
|
|
// https://ruby-doc.org/core-3.0.2/Regexp.html
|
|
|
|
.replace(
|
|
|
|
/[^\p{Letter}\p{Mark}\p{Number}\p{Connector_Punctuation}\- ]/gu,
|
|
|
|
''
|
|
|
|
)
|
|
|
|
.replace(/ /gu, '-')
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const headingTags = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
|
|
|
|
const ignoredTokens = new Set(['heading_open', 'heading_close'])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the headings from a Markdown string.
|
|
|
|
* @param {string} content
|
|
|
|
* @returns {string[]}
|
|
|
|
*/
|
|
|
|
const getMarkdownHeadings = (content) => {
|
|
|
|
const markdownIt = new MarkdownIt({ html: true })
|
|
|
|
const tokens = markdownIt.parse(content, {})
|
|
|
|
|
|
|
|
/** @type {string[]} */
|
|
|
|
const headings = []
|
|
|
|
|
|
|
|
/** @type {string | null} */
|
|
|
|
let headingToken = null
|
|
|
|
|
|
|
|
for (const token of tokens) {
|
|
|
|
if (headingTags.has(token.tag)) {
|
|
|
|
if (token.type === 'heading_open') {
|
|
|
|
headingToken = token.markup
|
|
|
|
} else if (token.type === 'heading_close') {
|
|
|
|
headingToken = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ignoredTokens.has(token.type)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headingToken === null) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
headings.push(
|
|
|
|
`${token.children
|
|
|
|
.map((token) => {
|
|
|
|
return token.content
|
|
|
|
})
|
|
|
|
.join('')}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return headings
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
filterTokens,
|
|
|
|
addError,
|
|
|
|
convertHeadingToHTMLFragment,
|
|
|
|
getMarkdownHeadings
|
|
|
|
}
|