2023-01-12 21:20:54 -08:00
|
|
|
// @ts-check
|
|
|
|
|
2023-01-14 15:30:47 -08:00
|
|
|
"use strict";
|
2023-01-14 15:05:04 -08:00
|
|
|
|
|
|
|
// @ts-ignore
|
2023-02-23 22:14:44 -08:00
|
|
|
const {
|
|
|
|
gfmAutolinkLiteral, gfmFootnote, gfmTable, parse, postprocess, preprocess
|
2023-02-08 20:50:28 -08:00
|
|
|
// @ts-ignore
|
2023-02-23 22:14:44 -08:00
|
|
|
} = require("markdownlint-micromark");
|
2023-01-09 21:59:54 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Markdown token.
|
|
|
|
*
|
|
|
|
* @typedef {Object} Token
|
|
|
|
* @property {string} type Token type.
|
|
|
|
* @property {number} startLine Start line (1-based).
|
|
|
|
* @property {number} startColumn Start column (1-based).
|
|
|
|
* @property {number} endLine End line (1-based).
|
|
|
|
* @property {number} endColumn End column (1-based).
|
|
|
|
* @property {string} text Token text.
|
2023-02-25 15:42:28 -08:00
|
|
|
* @property {Token[]} children Child tokens.
|
2023-01-09 21:59:54 -08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2023-02-10 20:37:32 -08:00
|
|
|
* Parses a Markdown document and returns Micromark events.
|
2023-01-09 21:59:54 -08:00
|
|
|
*
|
|
|
|
* @param {string} markdown Markdown document.
|
2023-01-21 15:41:03 -08:00
|
|
|
* @param {Object} [options] Options for micromark.
|
2023-02-10 20:37:32 -08:00
|
|
|
* @returns {Object[]} Micromark events.
|
2023-01-09 21:59:54 -08:00
|
|
|
*/
|
2023-02-10 20:37:32 -08:00
|
|
|
function getMicromarkEvents(markdown, options = {}) {
|
2023-01-29 20:36:53 -08:00
|
|
|
|
|
|
|
// Customize options object to add useful extensions
|
|
|
|
options.extensions ||= [];
|
2023-02-23 22:14:44 -08:00
|
|
|
options.extensions.push(gfmAutolinkLiteral, gfmFootnote(), gfmTable);
|
2023-01-09 21:59:54 -08:00
|
|
|
|
|
|
|
// Use micromark to parse document into Events
|
|
|
|
const encoding = undefined;
|
|
|
|
const eol = true;
|
2023-01-29 20:36:53 -08:00
|
|
|
const parseContext = parse(options);
|
|
|
|
// Customize ParseContext to treat all references as defined
|
|
|
|
parseContext.defined.includes = (searchElement) => searchElement.length > 0;
|
2023-01-09 21:59:54 -08:00
|
|
|
const chunks = preprocess()(markdown, encoding, eol);
|
2023-01-29 20:36:53 -08:00
|
|
|
const events = postprocess(parseContext.document().write(chunks));
|
2023-02-10 20:37:32 -08:00
|
|
|
return events;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a Markdown document and returns (frozen) tokens.
|
|
|
|
*
|
|
|
|
* @param {string} markdown Markdown document.
|
|
|
|
* @param {Object} [options] Options for micromark.
|
|
|
|
* @returns {Token[]} Micromark tokens (frozen).
|
|
|
|
*/
|
|
|
|
function micromarkParse(markdown, options = {}) {
|
|
|
|
|
|
|
|
// Use micromark to parse document into Events
|
|
|
|
const events = getMicromarkEvents(markdown, options);
|
2023-01-09 21:59:54 -08:00
|
|
|
|
|
|
|
// Create Token objects
|
|
|
|
const document = [];
|
|
|
|
let current = {
|
2023-02-25 15:42:28 -08:00
|
|
|
"children": document
|
2023-01-09 21:59:54 -08:00
|
|
|
};
|
|
|
|
const history = [ current ];
|
|
|
|
for (const event of events) {
|
|
|
|
const [ kind, token, context ] = event;
|
2023-01-13 19:37:17 -08:00
|
|
|
const { type, start, end } = token;
|
2023-01-09 21:59:54 -08:00
|
|
|
const { "column": startColumn, "line": startLine } = start;
|
|
|
|
const { "column": endColumn, "line": endLine } = end;
|
2023-01-13 19:37:17 -08:00
|
|
|
let text = null;
|
|
|
|
try {
|
|
|
|
text = context.sliceSerialize(token);
|
|
|
|
} catch {
|
|
|
|
// https://github.com/micromark/micromark/issues/131
|
|
|
|
}
|
2023-01-09 21:59:54 -08:00
|
|
|
if (kind === "enter") {
|
|
|
|
const previous = current;
|
|
|
|
history.push(previous);
|
|
|
|
current = {
|
|
|
|
type,
|
|
|
|
startLine,
|
|
|
|
startColumn,
|
|
|
|
endLine,
|
|
|
|
endColumn,
|
|
|
|
text,
|
2023-02-25 15:42:28 -08:00
|
|
|
"children": []
|
2023-01-09 21:59:54 -08:00
|
|
|
};
|
2023-02-25 15:42:28 -08:00
|
|
|
previous.children.push(current);
|
2023-01-09 21:59:54 -08:00
|
|
|
} else if (kind === "exit") {
|
2023-02-25 15:42:28 -08:00
|
|
|
Object.freeze(current.children);
|
2023-01-15 21:41:22 -08:00
|
|
|
Object.freeze(current);
|
2023-01-12 21:20:54 -08:00
|
|
|
// @ts-ignore
|
2023-01-09 21:59:54 -08:00
|
|
|
current = history.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return document
|
2023-01-15 21:41:22 -08:00
|
|
|
Object.freeze(document);
|
2023-01-09 21:59:54 -08:00
|
|
|
return document;
|
|
|
|
}
|
|
|
|
|
2023-01-21 15:41:03 -08:00
|
|
|
/**
|
|
|
|
* Filter a list of Micromark tokens by predicate.
|
|
|
|
*
|
|
|
|
* @param {Token[]} tokens Micromark tokens.
|
2023-02-05 16:58:06 -08:00
|
|
|
* @param {Function} allowed Allowed token predicate.
|
|
|
|
* @param {Function} [transform] Transform token list predicate.
|
2023-01-21 15:41:03 -08:00
|
|
|
* @returns {Token[]} Filtered tokens.
|
|
|
|
*/
|
2023-02-05 16:58:06 -08:00
|
|
|
function filterByPredicate(tokens, allowed, transform) {
|
2023-01-21 15:41:03 -08:00
|
|
|
const result = [];
|
|
|
|
const pending = [ ...tokens ];
|
|
|
|
let token = null;
|
|
|
|
while ((token = pending.shift())) {
|
2023-02-05 16:58:06 -08:00
|
|
|
if (allowed(token)) {
|
2023-01-21 15:41:03 -08:00
|
|
|
result.push(token);
|
|
|
|
}
|
2023-02-25 15:42:28 -08:00
|
|
|
const transformed = transform ? transform(token.children) : token.children;
|
2023-02-05 16:58:06 -08:00
|
|
|
pending.unshift(...transformed);
|
2023-01-21 15:41:03 -08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filter a list of Micromark tokens by type.
|
|
|
|
*
|
|
|
|
* @param {Token[]} tokens Micromark tokens.
|
2023-02-05 16:58:06 -08:00
|
|
|
* @param {string[]} allowed Types to allow.
|
2023-01-21 15:41:03 -08:00
|
|
|
* @returns {Token[]} Filtered tokens.
|
|
|
|
*/
|
2023-02-05 16:58:06 -08:00
|
|
|
function filterByTypes(tokens, allowed) {
|
|
|
|
return filterByPredicate(
|
|
|
|
tokens,
|
|
|
|
(token) => allowed.includes(token.type)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets information about the tag in an HTML token.
|
|
|
|
*
|
|
|
|
* @param {Token} token Micromark token.
|
|
|
|
* @returns {Object | null} HTML tag information.
|
|
|
|
*/
|
|
|
|
function getHtmlTagInfo(token) {
|
|
|
|
const htmlTagNameRe = /^<([^!>][^/\s>]*)/;
|
|
|
|
if (token.type === "htmlText") {
|
|
|
|
const match = htmlTagNameRe.exec(token.text);
|
|
|
|
if (match) {
|
|
|
|
const name = match[1];
|
|
|
|
const close = name.startsWith("/");
|
|
|
|
return {
|
|
|
|
close,
|
|
|
|
"name": close ? name.slice(1) : name
|
2023-02-23 22:14:44 -08:00
|
|
|
};
|
2023-02-05 16:58:06 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2023-01-21 15:41:03 -08:00
|
|
|
}
|
|
|
|
|
2023-01-29 20:36:53 -08:00
|
|
|
/**
|
|
|
|
* Get the text of a single token from a list of Micromark tokens by type.
|
|
|
|
*
|
|
|
|
* @param {Token[]} tokens Micromark tokens.
|
|
|
|
* @param {string} type Types to match.
|
|
|
|
* @returns {string | null} Text of token.
|
|
|
|
*/
|
|
|
|
function getTokenTextByType(tokens, type) {
|
|
|
|
const filtered = tokens.filter((token) => token.type === type);
|
|
|
|
return (filtered.length === 1) ? filtered[0].text : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines a list of Micromark tokens matches and returns a subset.
|
|
|
|
*
|
|
|
|
* @param {Token[]} tokens Micromark tokens.
|
|
|
|
* @param {string[]} matchTypes Types to match.
|
|
|
|
* @param {string[]} [resultTypes] Types to return.
|
2023-02-23 22:14:44 -08:00
|
|
|
* @returns {Token[] | null} Matching tokens.
|
2023-01-29 20:36:53 -08:00
|
|
|
*/
|
|
|
|
function matchAndGetTokensByType(tokens, matchTypes, resultTypes) {
|
|
|
|
if (tokens.length !== matchTypes.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
resultTypes ||= matchTypes;
|
2023-02-23 22:14:44 -08:00
|
|
|
const result = [];
|
|
|
|
// eslint-disable-next-line unicorn/no-for-loop
|
2023-01-29 20:36:53 -08:00
|
|
|
for (let i = 0; i < matchTypes.length; i++) {
|
|
|
|
if (tokens[i].type !== matchTypes[i]) {
|
|
|
|
return null;
|
|
|
|
} else if (resultTypes.includes(matchTypes[i])) {
|
2023-02-23 22:14:44 -08:00
|
|
|
result.push(tokens[i]);
|
2023-01-29 20:36:53 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-01-14 15:30:47 -08:00
|
|
|
module.exports = {
|
2023-02-05 16:58:06 -08:00
|
|
|
"parse": micromarkParse,
|
2023-01-21 15:41:03 -08:00
|
|
|
filterByPredicate,
|
|
|
|
filterByTypes,
|
2023-02-05 16:58:06 -08:00
|
|
|
getHtmlTagInfo,
|
2023-02-10 20:37:32 -08:00
|
|
|
getMicromarkEvents,
|
2023-01-29 20:36:53 -08:00
|
|
|
getTokenTextByType,
|
2023-02-05 16:58:06 -08:00
|
|
|
matchAndGetTokensByType
|
2023-01-09 21:59:54 -08:00
|
|
|
};
|