|
|
@@ -1,552 +0,0 @@
|
|
|
-/**
|
|
|
- * @typedef {import('micromark-util-types').Extension} Extension
|
|
|
- * @typedef {import('micromark-util-types').Resolver} Resolver
|
|
|
- * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
|
|
|
- * @typedef {import('micromark-util-types').State} State
|
|
|
- * @typedef {import('micromark-util-types').Token} Token
|
|
|
- */
|
|
|
-
|
|
|
-/**
|
|
|
- * @typedef {'left'|'center'|'right'|'none'} Align
|
|
|
- */
|
|
|
-
|
|
|
-import { factorySpace } from 'micromark-factory-space';
|
|
|
-import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace } from 'micromark-util-character';
|
|
|
-/**
|
|
|
- * Syntax extension for micromark (passed in `extensions`).
|
|
|
- *
|
|
|
- * @type {Extension}
|
|
|
- */
|
|
|
-export const gfmTable = {
|
|
|
- flow: {
|
|
|
- null: {
|
|
|
- tokenize: tokenizeTable,
|
|
|
- resolve: resolveTable
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-const nextPrefixedOrBlank = {
|
|
|
- tokenize: tokenizeNextPrefixedOrBlank,
|
|
|
- partial: true
|
|
|
-};
|
|
|
-
|
|
|
-/** @type {Resolver} */
|
|
|
-// eslint-disable-next-line complexity
|
|
|
-function resolveTable(events, context) {
|
|
|
- let index = -1;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let inHead;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let inDelimiterRow;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let inRow;
|
|
|
- /** @type {number|undefined} */
|
|
|
- let contentStart;
|
|
|
- /** @type {number|undefined} */
|
|
|
- let contentEnd;
|
|
|
- /** @type {number|undefined} */
|
|
|
- let cellStart;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let seenCellInRow;
|
|
|
- while (++index < events.length) {
|
|
|
- const token = events[index][1];
|
|
|
- if (inRow) {
|
|
|
- if (token.type === 'temporaryTableCellContent') {
|
|
|
- contentStart = contentStart || index;
|
|
|
- contentEnd = index;
|
|
|
- }
|
|
|
- if (
|
|
|
- // Combine separate content parts into one.
|
|
|
- (token.type === 'tableCellDivider' || token.type === 'tableRow') && contentEnd) {
|
|
|
- const content = {
|
|
|
- type: 'tableContent',
|
|
|
- start: events[contentStart][1].start,
|
|
|
- end: events[contentEnd][1].end
|
|
|
- };
|
|
|
- /** @type {Token} */
|
|
|
- const text = {
|
|
|
- type: "chunkText",
|
|
|
- start: content.start,
|
|
|
- end: content.end,
|
|
|
- // @ts-expect-error It’s fine.
|
|
|
- contentType: "text"
|
|
|
- };
|
|
|
- events.splice(contentStart, contentEnd - contentStart + 1, ['enter', content, context], ['enter', text, context], ['exit', text, context], ['exit', content, context]);
|
|
|
- index -= contentEnd - contentStart - 3;
|
|
|
- contentStart = undefined;
|
|
|
- contentEnd = undefined;
|
|
|
- }
|
|
|
- }
|
|
|
- if (events[index][0] === 'exit' && cellStart !== undefined && cellStart + (seenCellInRow ? 0 : 1) < index && (token.type === 'tableCellDivider' || token.type === 'tableRow' && (cellStart + 3 < index || events[cellStart][1].type !== "whitespace"))) {
|
|
|
- const cell = {
|
|
|
- // eslint-disable-next-line no-nested-ternary
|
|
|
- type: inDelimiterRow ? 'tableDelimiter' : inHead ? 'tableHeader' : 'tableData',
|
|
|
- start: events[cellStart][1].start,
|
|
|
- end: events[index][1].end
|
|
|
- };
|
|
|
- events.splice(index + (token.type === 'tableCellDivider' ? 1 : 0), 0, ['exit', cell, context]);
|
|
|
- events.splice(cellStart, 0, ['enter', cell, context]);
|
|
|
- index += 2;
|
|
|
- cellStart = index + 1;
|
|
|
- seenCellInRow = true;
|
|
|
- }
|
|
|
- if (token.type === 'tableRow') {
|
|
|
- inRow = events[index][0] === 'enter';
|
|
|
- if (inRow) {
|
|
|
- cellStart = index + 1;
|
|
|
- seenCellInRow = false;
|
|
|
- }
|
|
|
- }
|
|
|
- if (token.type === 'tableDelimiterRow') {
|
|
|
- inDelimiterRow = events[index][0] === 'enter';
|
|
|
- if (inDelimiterRow) {
|
|
|
- cellStart = index + 1;
|
|
|
- seenCellInRow = false;
|
|
|
- }
|
|
|
- }
|
|
|
- if (token.type === 'tableHead') {
|
|
|
- inHead = events[index][0] === 'enter';
|
|
|
- }
|
|
|
- }
|
|
|
- return events;
|
|
|
-}
|
|
|
-
|
|
|
-/** @type {Tokenizer} */
|
|
|
-function tokenizeTable(effects, ok, nok) {
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
- const self = this;
|
|
|
- /** @type {Array<Align>} */
|
|
|
- const align = [];
|
|
|
- let tableHeaderCount = 0;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let seenDelimiter;
|
|
|
- /** @type {boolean|undefined} */
|
|
|
- let hasDash;
|
|
|
- return start;
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function start(code) {
|
|
|
- const {
|
|
|
- containerState
|
|
|
- } = self;
|
|
|
- const prevRowCount = containerState.rowCount ?? 0;
|
|
|
- const hasDelimiterRow = containerState.hasDelimiterRow ?? false;
|
|
|
-
|
|
|
- // @ts-expect-error Custom.
|
|
|
- effects.enter('table')._align = align;
|
|
|
- effects.enter('tableHead');
|
|
|
- effects.enter('tableRow');
|
|
|
-
|
|
|
- // increment row count
|
|
|
- const crrRowCount = prevRowCount + 1;
|
|
|
- containerState.rowCount = crrRowCount;
|
|
|
-
|
|
|
- // Max 2 rows processing before delimiter row
|
|
|
- if (hasDelimiterRow || crrRowCount > 2) {
|
|
|
- return nok(code);
|
|
|
- }
|
|
|
-
|
|
|
- // If we start with a pipe, we open a cell marker.
|
|
|
- if (code === 124) {
|
|
|
- return cellDividerHead(code);
|
|
|
- }
|
|
|
- tableHeaderCount++;
|
|
|
- effects.enter('temporaryTableCellContent');
|
|
|
- // Can’t be space or eols at the start of a construct, so we’re in a cell.
|
|
|
-
|
|
|
- return inCellContentHead(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function cellDividerHead(code) {
|
|
|
- effects.enter('tableCellDivider');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableCellDivider');
|
|
|
- seenDelimiter = true;
|
|
|
- return cellBreakHead;
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function cellBreakHead(code) {
|
|
|
- if (code === null || markdownLineEnding(code)) {
|
|
|
- return atRowEndHead(code);
|
|
|
- }
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.enter("whitespace");
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceHead;
|
|
|
- }
|
|
|
- if (seenDelimiter) {
|
|
|
- seenDelimiter = undefined;
|
|
|
- tableHeaderCount++;
|
|
|
- }
|
|
|
- if (code === 124) {
|
|
|
- return cellDividerHead(code);
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else is cell content.
|
|
|
- effects.enter('temporaryTableCellContent');
|
|
|
- return inCellContentHead(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inWhitespaceHead(code) {
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceHead;
|
|
|
- }
|
|
|
- effects.exit("whitespace");
|
|
|
- return cellBreakHead(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inCellContentHead(code) {
|
|
|
- // EOF, whitespace, pipe
|
|
|
- if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
|
|
|
- effects.exit('temporaryTableCellContent');
|
|
|
- return cellBreakHead(code);
|
|
|
- }
|
|
|
- effects.consume(code);
|
|
|
- return code === 92 ? inCellContentEscapeHead : inCellContentHead;
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inCellContentEscapeHead(code) {
|
|
|
- if (code === 92 || code === 124) {
|
|
|
- effects.consume(code);
|
|
|
- return inCellContentHead;
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else.
|
|
|
- return inCellContentHead(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function atRowEndHead(code) {
|
|
|
- // for debug -- 2023.05.06 Yuki Takei
|
|
|
- // const { containerState } = self;
|
|
|
- // let atRowEndHeadCount = containerState.atRowEndHeadCount ?? 0;
|
|
|
- // atRowEndHeadCount++;
|
|
|
- // containerState.atRowEndHeadCount = atRowEndHeadCount;
|
|
|
- // console.log({ atRowEndHeadCount });
|
|
|
-
|
|
|
- if (code === null) {
|
|
|
- return tableExit(code);
|
|
|
- }
|
|
|
- effects.exit('tableRow');
|
|
|
- effects.exit('tableHead');
|
|
|
- const originalInterrupt = self.interrupt;
|
|
|
- self.interrupt = true;
|
|
|
- return effects.attempt({
|
|
|
- tokenize: tokenizeRowEnd,
|
|
|
- partial: true
|
|
|
- }, code => {
|
|
|
- self.interrupt = originalInterrupt;
|
|
|
- effects.enter('tableDelimiterRow');
|
|
|
- return atDelimiterRowBreak(code);
|
|
|
- }, code => {
|
|
|
- self.interrupt = originalInterrupt;
|
|
|
- return tableExit(code);
|
|
|
- })(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function atDelimiterRowBreak(code) {
|
|
|
- // persist that the table has a delimiter row
|
|
|
- self.containerState.hasDelimiterRow = true;
|
|
|
- if (code === null || markdownLineEnding(code)) {
|
|
|
- return rowEndDelimiter(code);
|
|
|
- }
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.enter("whitespace");
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceDelimiter;
|
|
|
- }
|
|
|
- if (code === 45) {
|
|
|
- effects.enter('tableDelimiterFiller');
|
|
|
- effects.consume(code);
|
|
|
- hasDash = true;
|
|
|
- align.push('none');
|
|
|
- return inFillerDelimiter;
|
|
|
- }
|
|
|
- if (code === 58) {
|
|
|
- effects.enter('tableDelimiterAlignment');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableDelimiterAlignment');
|
|
|
- align.push('left');
|
|
|
- return afterLeftAlignment;
|
|
|
- }
|
|
|
-
|
|
|
- // If we start with a pipe, we open a cell marker.
|
|
|
- if (code === 124) {
|
|
|
- effects.enter('tableCellDivider');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableCellDivider');
|
|
|
- return atDelimiterRowBreak;
|
|
|
- }
|
|
|
- return tableExit(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inWhitespaceDelimiter(code) {
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceDelimiter;
|
|
|
- }
|
|
|
- effects.exit("whitespace");
|
|
|
- return atDelimiterRowBreak(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inFillerDelimiter(code) {
|
|
|
- if (code === 45) {
|
|
|
- effects.consume(code);
|
|
|
- return inFillerDelimiter;
|
|
|
- }
|
|
|
- effects.exit('tableDelimiterFiller');
|
|
|
- if (code === 58) {
|
|
|
- effects.enter('tableDelimiterAlignment');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableDelimiterAlignment');
|
|
|
- align[align.length - 1] = align[align.length - 1] === 'left' ? 'center' : 'right';
|
|
|
- return afterRightAlignment;
|
|
|
- }
|
|
|
- return atDelimiterRowBreak(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function afterLeftAlignment(code) {
|
|
|
- if (code === 45) {
|
|
|
- effects.enter('tableDelimiterFiller');
|
|
|
- effects.consume(code);
|
|
|
- hasDash = true;
|
|
|
- return inFillerDelimiter;
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else is not ok.
|
|
|
- return tableExit(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function afterRightAlignment(code) {
|
|
|
- if (code === null || markdownLineEnding(code)) {
|
|
|
- return rowEndDelimiter(code);
|
|
|
- }
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.enter("whitespace");
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceDelimiter;
|
|
|
- }
|
|
|
-
|
|
|
- // `|`
|
|
|
- if (code === 124) {
|
|
|
- effects.enter('tableCellDivider');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableCellDivider');
|
|
|
- return atDelimiterRowBreak;
|
|
|
- }
|
|
|
- return tableExit(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function rowEndDelimiter(code) {
|
|
|
- effects.exit('tableDelimiterRow');
|
|
|
-
|
|
|
- // Exit if there was no dash at all, or if the header cell count is not the
|
|
|
- // delimiter cell count.
|
|
|
- if (!hasDash || tableHeaderCount !== align.length) {
|
|
|
- return tableExit(code);
|
|
|
- }
|
|
|
- if (code === null) {
|
|
|
- return tableClose(code);
|
|
|
- }
|
|
|
- return effects.check(nextPrefixedOrBlank, tableClose, effects.attempt({
|
|
|
- tokenize: tokenizeRowEnd,
|
|
|
- partial: true
|
|
|
- }, factorySpace(effects, bodyStart, "linePrefix", 4), tableClose))(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function tableExit(code) {
|
|
|
- // delete persisted states
|
|
|
- delete self.containerState.rowCount;
|
|
|
- delete self.containerState.hasDelimiterRow;
|
|
|
- return nok(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function tableClose(code) {
|
|
|
- effects.exit('table');
|
|
|
-
|
|
|
- // delete persisted states
|
|
|
- delete self.containerState.rowCount;
|
|
|
- delete self.containerState.hasDelimiterRow;
|
|
|
- return ok(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function bodyStart(code) {
|
|
|
- effects.enter('tableBody');
|
|
|
- return rowStartBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function rowStartBody(code) {
|
|
|
- effects.enter('tableRow');
|
|
|
-
|
|
|
- // If we start with a pipe, we open a cell marker.
|
|
|
- if (code === 124) {
|
|
|
- return cellDividerBody(code);
|
|
|
- }
|
|
|
- effects.enter('temporaryTableCellContent');
|
|
|
- // Can’t be space or eols at the start of a construct, so we’re in a cell.
|
|
|
- return inCellContentBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function cellDividerBody(code) {
|
|
|
- effects.enter('tableCellDivider');
|
|
|
- effects.consume(code);
|
|
|
- effects.exit('tableCellDivider');
|
|
|
- return cellBreakBody;
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function cellBreakBody(code) {
|
|
|
- if (code === null || markdownLineEnding(code)) {
|
|
|
- return atRowEndBody(code);
|
|
|
- }
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.enter("whitespace");
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceBody;
|
|
|
- }
|
|
|
-
|
|
|
- // `|`
|
|
|
- if (code === 124) {
|
|
|
- return cellDividerBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else is cell content.
|
|
|
- effects.enter('temporaryTableCellContent');
|
|
|
- return inCellContentBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inWhitespaceBody(code) {
|
|
|
- if (markdownSpace(code)) {
|
|
|
- effects.consume(code);
|
|
|
- return inWhitespaceBody;
|
|
|
- }
|
|
|
- effects.exit("whitespace");
|
|
|
- return cellBreakBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inCellContentBody(code) {
|
|
|
- // EOF, whitespace, pipe
|
|
|
- if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
|
|
|
- effects.exit('temporaryTableCellContent');
|
|
|
- return cellBreakBody(code);
|
|
|
- }
|
|
|
- effects.consume(code);
|
|
|
- return code === 92 ? inCellContentEscapeBody : inCellContentBody;
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function inCellContentEscapeBody(code) {
|
|
|
- if (code === 92 || code === 124) {
|
|
|
- effects.consume(code);
|
|
|
- return inCellContentBody;
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else.
|
|
|
- return inCellContentBody(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function atRowEndBody(code) {
|
|
|
- effects.exit('tableRow');
|
|
|
- if (code === null) {
|
|
|
- return tableBodyClose(code);
|
|
|
- }
|
|
|
- return effects.check(nextPrefixedOrBlank, tableBodyClose, effects.attempt({
|
|
|
- tokenize: tokenizeRowEnd,
|
|
|
- partial: true
|
|
|
- }, factorySpace(effects, rowStartBody, "linePrefix", 4), tableBodyClose))(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function tableBodyClose(code) {
|
|
|
- effects.exit('tableBody');
|
|
|
- return tableClose(code);
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {Tokenizer} */
|
|
|
- function tokenizeRowEnd(effects, ok, nok) {
|
|
|
- return start;
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function start(code) {
|
|
|
- effects.enter("lineEnding");
|
|
|
- effects.consume(code);
|
|
|
- effects.exit("lineEnding");
|
|
|
- return factorySpace(effects, prefixed, "linePrefix");
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function prefixed(code) {
|
|
|
- // Blank or interrupting line.
|
|
|
- if (self.parser.lazy[self.now().line] || code === null || markdownLineEnding(code)) {
|
|
|
- return nok(code);
|
|
|
- }
|
|
|
- const tail = self.events[self.events.length - 1];
|
|
|
-
|
|
|
- // Indented code can interrupt delimiter and body rows.
|
|
|
- if (!self.parser.constructs.disable.null.includes('codeIndented') && tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4) {
|
|
|
- return nok(code);
|
|
|
- }
|
|
|
- self._gfmTableDynamicInterruptHack = true;
|
|
|
- return effects.check(self.parser.constructs.flow, code => {
|
|
|
- self._gfmTableDynamicInterruptHack = false;
|
|
|
- return nok(code);
|
|
|
- }, code => {
|
|
|
- self._gfmTableDynamicInterruptHack = false;
|
|
|
- return ok(code);
|
|
|
- })(code);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** @type {Tokenizer} */
|
|
|
-function tokenizeNextPrefixedOrBlank(effects, ok, nok) {
|
|
|
- let size = 0;
|
|
|
- return start;
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function start(code) {
|
|
|
- // This is a check, so we don’t care about tokens, but we open a bogus one
|
|
|
- // so we’re valid.
|
|
|
- effects.enter('check');
|
|
|
- // EOL.
|
|
|
- effects.consume(code);
|
|
|
- return whitespace;
|
|
|
- }
|
|
|
-
|
|
|
- /** @type {State} */
|
|
|
- function whitespace(code) {
|
|
|
- if (code === -1 || code === 32) {
|
|
|
- effects.consume(code);
|
|
|
- size++;
|
|
|
- return size === 4 ? ok : whitespace;
|
|
|
- }
|
|
|
-
|
|
|
- // EOF or whitespace
|
|
|
- if (code === null || markdownLineEndingOrSpace(code)) {
|
|
|
- return ok(code);
|
|
|
- }
|
|
|
-
|
|
|
- // Anything else.
|
|
|
- return nok(code);
|
|
|
- }
|
|
|
-}
|