site/node_modules/mdast-util-gfm-table/lib/index.js
2024-10-14 08:09:33 +02:00

301 lines
8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @typedef {import('mdast').InlineCode} InlineCode
* @typedef {import('mdast').Table} Table
* @typedef {import('mdast').TableCell} TableCell
* @typedef {import('mdast').TableRow} TableRow
*
* @typedef {import('markdown-table').Options} MarkdownTableOptions
*
* @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
* @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
* @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
*
* @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
* @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
* @typedef {import('mdast-util-to-markdown').State} State
* @typedef {import('mdast-util-to-markdown').Info} Info
*/
/**
* @typedef Options
* Configuration.
* @property {boolean | null | undefined} [tableCellPadding=true]
* Whether to add a space of padding between delimiters and cells (default:
* `true`).
* @property {boolean | null | undefined} [tablePipeAlign=true]
* Whether to align the delimiters (default: `true`).
* @property {MarkdownTableOptions['stringLength'] | null | undefined} [stringLength]
* Function to detect the length of table cell content, used when aligning
* the delimiters between cells (optional).
*/
import {ok as assert} from 'devlop'
import {markdownTable} from 'markdown-table'
import {defaultHandlers} from 'mdast-util-to-markdown'
/**
* Create an extension for `mdast-util-from-markdown` to enable GFM tables in
* markdown.
*
* @returns {FromMarkdownExtension}
* Extension for `mdast-util-from-markdown` to enable GFM tables.
*/
export function gfmTableFromMarkdown() {
return {
enter: {
table: enterTable,
tableData: enterCell,
tableHeader: enterCell,
tableRow: enterRow
},
exit: {
codeText: exitCodeText,
table: exitTable,
tableData: exit,
tableHeader: exit,
tableRow: exit
}
}
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterTable(token) {
const align = token._align
assert(align, 'expected `_align` on table')
this.enter(
{
type: 'table',
align: align.map(function (d) {
return d === 'none' ? null : d
}),
children: []
},
token
)
this.data.inTable = true
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitTable(token) {
this.exit(token)
this.data.inTable = undefined
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterRow(token) {
this.enter({type: 'tableRow', children: []}, token)
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exit(token) {
this.exit(token)
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterCell(token) {
this.enter({type: 'tableCell', children: []}, token)
}
// Overwrite the default code text data handler to unescape escaped pipes when
// they are in tables.
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitCodeText(token) {
let value = this.resume()
if (this.data.inTable) {
value = value.replace(/\\([\\|])/g, replace)
}
const node = this.stack[this.stack.length - 1]
assert(node.type === 'inlineCode')
node.value = value
this.exit(token)
}
/**
* @param {string} $0
* @param {string} $1
* @returns {string}
*/
function replace($0, $1) {
// Pipes work, backslashes dont (but cant escape pipes).
return $1 === '|' ? $1 : $0
}
/**
* Create an extension for `mdast-util-to-markdown` to enable GFM tables in
* markdown.
*
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {ToMarkdownExtension}
* Extension for `mdast-util-to-markdown` to enable GFM tables.
*/
export function gfmTableToMarkdown(options) {
const settings = options || {}
const padding = settings.tableCellPadding
const alignDelimiters = settings.tablePipeAlign
const stringLength = settings.stringLength
const around = padding ? ' ' : '|'
return {
unsafe: [
{character: '\r', inConstruct: 'tableCell'},
{character: '\n', inConstruct: 'tableCell'},
// A pipe, when followed by a tab or space (padding), or a dash or colon
// (unpadded delimiter row), could result in a table.
{atBreak: true, character: '|', after: '[\t :-]'},
// A pipe in a cell must be encoded.
{character: '|', inConstruct: 'tableCell'},
// A colon must be followed by a dash, in which case it could start a
// delimiter row.
{atBreak: true, character: ':', after: '-'},
// A delimiter row can also start with a dash, when followed by more
// dashes, a colon, or a pipe.
// This is a stricter version than the built in check for lists, thematic
// breaks, and setex heading underlines though:
// <https://github.com/syntax-tree/mdast-util-to-markdown/blob/51a2038/lib/unsafe.js#L57>
{atBreak: true, character: '-', after: '[:|-]'}
],
handlers: {
inlineCode: inlineCodeWithTable,
table: handleTable,
tableCell: handleTableCell,
tableRow: handleTableRow
}
}
/**
* @type {ToMarkdownHandle}
* @param {Table} node
*/
function handleTable(node, _, state, info) {
return serializeData(handleTableAsData(node, state, info), node.align)
}
/**
* This function isnt really used normally, because we handle rows at the
* table level.
* But, if someone passes in a table row, this ensures we make somewhat sense.
*
* @type {ToMarkdownHandle}
* @param {TableRow} node
*/
function handleTableRow(node, _, state, info) {
const row = handleTableRowAsData(node, state, info)
const value = serializeData([row])
// `markdown-table` will always add an align row
return value.slice(0, value.indexOf('\n'))
}
/**
* @type {ToMarkdownHandle}
* @param {TableCell} node
*/
function handleTableCell(node, _, state, info) {
const exit = state.enter('tableCell')
const subexit = state.enter('phrasing')
const value = state.containerPhrasing(node, {
...info,
before: around,
after: around
})
subexit()
exit()
return value
}
/**
* @param {Array<Array<string>>} matrix
* @param {Array<string | null | undefined> | null | undefined} [align]
*/
function serializeData(matrix, align) {
return markdownTable(matrix, {
align,
// @ts-expect-error: `markdown-table` types should support `null`.
alignDelimiters,
// @ts-expect-error: `markdown-table` types should support `null`.
padding,
// @ts-expect-error: `markdown-table` types should support `null`.
stringLength
})
}
/**
* @param {Table} node
* @param {State} state
* @param {Info} info
*/
function handleTableAsData(node, state, info) {
const children = node.children
let index = -1
/** @type {Array<Array<string>>} */
const result = []
const subexit = state.enter('table')
while (++index < children.length) {
result[index] = handleTableRowAsData(children[index], state, info)
}
subexit()
return result
}
/**
* @param {TableRow} node
* @param {State} state
* @param {Info} info
*/
function handleTableRowAsData(node, state, info) {
const children = node.children
let index = -1
/** @type {Array<string>} */
const result = []
const subexit = state.enter('tableRow')
while (++index < children.length) {
// Note: the positional info as used here is incorrect.
// Making it correct would be impossible due to aligning cells?
// And it would need copy/pasting `markdown-table` into this project.
result[index] = handleTableCell(children[index], node, state, info)
}
subexit()
return result
}
/**
* @type {ToMarkdownHandle}
* @param {InlineCode} node
*/
function inlineCodeWithTable(node, parent, state) {
let value = defaultHandlers.inlineCode(node, parent, state)
if (state.stack.includes('tableCell')) {
value = value.replace(/\|/g, '\\$&')
}
return value
}
}