2024-09-14 17:33:46 -07:00
|
|
|
// @ts-check
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const { newLineRe } = require("../helpers");
|
|
|
|
|
2024-12-25 20:42:32 -08:00
|
|
|
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
2024-12-27 21:22:14 -08:00
|
|
|
/** @typedef {import("markdownlint").MarkdownIt} MarkdownIt */
|
2024-12-03 19:58:28 -08:00
|
|
|
/** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */
|
|
|
|
/** @typedef {import("markdownlint").Plugin} Plugin */
|
2024-11-28 20:36:44 -08:00
|
|
|
|
2024-09-14 17:33:46 -07:00
|
|
|
/**
|
|
|
|
* @callback InlineCodeSpanCallback
|
|
|
|
* @param {string} code Code content.
|
|
|
|
* @param {number} lineIndex Line index (0-based).
|
|
|
|
* @param {number} columnIndex Column index (0-based).
|
|
|
|
* @param {number} ticks Count of backticks.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls the provided function for each inline code span's content.
|
|
|
|
*
|
|
|
|
* @param {string} input Markdown content.
|
|
|
|
* @param {InlineCodeSpanCallback} handler Callback function taking (code,
|
|
|
|
* lineIndex, columnIndex, ticks).
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function forEachInlineCodeSpan(input, handler) {
|
|
|
|
const backtickRe = /`+/g;
|
|
|
|
let match = null;
|
|
|
|
const backticksLengthAndIndex = [];
|
|
|
|
while ((match = backtickRe.exec(input)) !== null) {
|
|
|
|
backticksLengthAndIndex.push([ match[0].length, match.index ]);
|
|
|
|
}
|
|
|
|
const newLinesIndex = [];
|
|
|
|
while ((match = newLineRe.exec(input)) !== null) {
|
|
|
|
newLinesIndex.push(match.index);
|
|
|
|
}
|
|
|
|
let lineIndex = 0;
|
|
|
|
let lineStartIndex = 0;
|
|
|
|
let k = 0;
|
|
|
|
for (let i = 0; i < backticksLengthAndIndex.length - 1; i++) {
|
|
|
|
const [ startLength, startIndex ] = backticksLengthAndIndex[i];
|
|
|
|
if ((startIndex === 0) || (input[startIndex - 1] !== "\\")) {
|
|
|
|
for (let j = i + 1; j < backticksLengthAndIndex.length; j++) {
|
|
|
|
const [ endLength, endIndex ] = backticksLengthAndIndex[j];
|
|
|
|
if (startLength === endLength) {
|
|
|
|
for (; k < newLinesIndex.length; k++) {
|
|
|
|
const newLineIndex = newLinesIndex[k];
|
|
|
|
if (startIndex < newLineIndex) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
lineIndex++;
|
|
|
|
lineStartIndex = newLineIndex + 1;
|
|
|
|
}
|
|
|
|
const columnIndex = startIndex - lineStartIndex + startLength;
|
|
|
|
handler(
|
|
|
|
input.slice(startIndex + startLength, endIndex),
|
|
|
|
lineIndex,
|
|
|
|
columnIndex,
|
|
|
|
startLength
|
|
|
|
);
|
|
|
|
i = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Freeze all freeze-able members of a token and its children.
|
|
|
|
*
|
2024-11-28 20:36:44 -08:00
|
|
|
* @param {MarkdownItToken} token A markdown-it token.
|
2024-09-14 17:33:46 -07:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function freezeToken(token) {
|
|
|
|
if (token.attrs) {
|
|
|
|
for (const attr of token.attrs) {
|
|
|
|
Object.freeze(attr);
|
|
|
|
}
|
|
|
|
Object.freeze(token.attrs);
|
|
|
|
}
|
|
|
|
if (token.children) {
|
|
|
|
for (const child of token.children) {
|
|
|
|
freezeToken(child);
|
|
|
|
}
|
|
|
|
Object.freeze(token.children);
|
|
|
|
}
|
|
|
|
if (token.map) {
|
|
|
|
Object.freeze(token.map);
|
|
|
|
}
|
|
|
|
Object.freeze(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Annotate tokens with line/lineNumber and freeze them.
|
|
|
|
*
|
2024-12-25 20:42:32 -08:00
|
|
|
* @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens.
|
2024-09-14 17:33:46 -07:00
|
|
|
* @param {string[]} lines Lines of Markdown content.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function annotateAndFreezeTokens(tokens, lines) {
|
|
|
|
let trMap = null;
|
2024-11-28 20:36:44 -08:00
|
|
|
/** @type {MarkdownItToken[]} */
|
2024-09-14 17:33:46 -07:00
|
|
|
// @ts-ignore
|
|
|
|
const markdownItTokens = tokens;
|
|
|
|
for (const token of markdownItTokens) {
|
|
|
|
// Provide missing maps for table content
|
|
|
|
if (token.type === "tr_open") {
|
|
|
|
trMap = token.map;
|
|
|
|
} else if (token.type === "tr_close") {
|
|
|
|
trMap = null;
|
|
|
|
}
|
|
|
|
if (!token.map && trMap) {
|
|
|
|
token.map = [ ...trMap ];
|
|
|
|
}
|
|
|
|
// Update token metadata
|
|
|
|
if (token.map) {
|
|
|
|
token.line = lines[token.map[0]];
|
|
|
|
token.lineNumber = token.map[0] + 1;
|
|
|
|
// Trim bottom of token to exclude whitespace lines
|
|
|
|
while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
|
|
|
|
token.map[1]--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Annotate children with lineNumber
|
|
|
|
if (token.children) {
|
|
|
|
const codeSpanExtraLines = [];
|
|
|
|
if (token.children.some((child) => child.type === "code_inline")) {
|
|
|
|
forEachInlineCodeSpan(token.content, (code) => {
|
|
|
|
codeSpanExtraLines.push(code.split(newLineRe).length - 1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
let lineNumber = token.lineNumber;
|
|
|
|
for (const child of token.children) {
|
|
|
|
child.lineNumber = lineNumber;
|
|
|
|
child.line = lines[lineNumber - 1];
|
|
|
|
if ((child.type === "softbreak") || (child.type === "hardbreak")) {
|
|
|
|
lineNumber++;
|
|
|
|
} else if (child.type === "code_inline") {
|
|
|
|
lineNumber += codeSpanExtraLines.shift();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
freezeToken(token);
|
|
|
|
}
|
|
|
|
Object.freeze(tokens);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets an array of markdown-it tokens for the input.
|
|
|
|
*
|
2024-12-27 21:22:14 -08:00
|
|
|
* @param {MarkdownIt} markdownIt Instance of the markdown-it parser.
|
2024-09-14 17:33:46 -07:00
|
|
|
* @param {string} content Markdown content.
|
|
|
|
* @param {string[]} lines Lines of Markdown content.
|
2024-12-25 20:42:32 -08:00
|
|
|
* @returns {MarkdownItToken[]} Array of markdown-it tokens.
|
2024-09-14 17:33:46 -07:00
|
|
|
*/
|
2024-12-27 21:22:14 -08:00
|
|
|
function getMarkdownItTokens(markdownIt, content, lines) {
|
2024-12-25 20:42:32 -08:00
|
|
|
const tokens = markdownIt.parse(content, {});
|
2024-09-14 17:33:46 -07:00
|
|
|
annotateAndFreezeTokens(tokens, lines);
|
|
|
|
return tokens;
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
forEachInlineCodeSpan,
|
|
|
|
getMarkdownItTokens
|
|
|
|
};
|