From 19301e72b915d021e25e1f2f185d7ecaea25a771 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 4 Jan 2023 10:12:59 +0100 Subject: [PATCH] Add `containerPhrasing`, `containerFlow` helpers on `state` --- lib/handle/blockquote.js | 3 +- lib/handle/emphasis.js | 3 +- lib/handle/heading.js | 5 ++-- lib/handle/link-reference.js | 3 +- lib/handle/link.js | 5 ++-- lib/handle/list-item.js | 3 +- lib/handle/list.js | 3 +- lib/handle/paragraph.js | 4 +-- lib/handle/root.js | 6 ++-- lib/handle/strong.js | 3 +- lib/index.js | 50 ++++++++++++++++++++++++++++++++-- lib/types.js | 28 ++++++++++++++++++- lib/util/container-flow.js | 50 ++++++++++++++++++++-------------- lib/util/container-phrasing.js | 14 ++++++++-- readme.md | 4 +++ 15 files changed, 132 insertions(+), 52 deletions(-) diff --git a/lib/handle/blockquote.js b/lib/handle/blockquote.js index fb1f427..797b489 100644 --- a/lib/handle/blockquote.js +++ b/lib/handle/blockquote.js @@ -6,7 +6,6 @@ * @typedef {import('../types.js').Map} Map */ -import {containerFlow} from '../util/container-flow.js' import {track} from '../util/track.js' /** @@ -22,7 +21,7 @@ export function blockquote(node, _, state, info) { tracker.move('> ') tracker.shift(2) const value = state.indentLines( - containerFlow(node, state, tracker.current()), + state.containerFlow(node, tracker.current()), map ) exit() diff --git a/lib/handle/emphasis.js b/lib/handle/emphasis.js index 643abfa..20bb90d 100644 --- a/lib/handle/emphasis.js +++ b/lib/handle/emphasis.js @@ -6,7 +6,6 @@ */ import {checkEmphasis} from '../util/check-emphasis.js' -import {containerPhrasing} from '../util/container-phrasing.js' import {track} from '../util/track.js' emphasis.peek = emphasisPeek @@ -28,7 +27,7 @@ export function emphasis(node, _, state, info) { const tracker = track(info) let value = tracker.move(marker) value += tracker.move( - containerPhrasing(node, state, { + state.containerPhrasing(node, { before: value, after: marker, ...tracker.current() diff --git a/lib/handle/heading.js b/lib/handle/heading.js index 1b27c62..072bdac 100644 --- a/lib/handle/heading.js +++ b/lib/handle/heading.js @@ -6,7 +6,6 @@ */ import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js' -import {containerPhrasing} from '../util/container-phrasing.js' import {track} from '../util/track.js' /** @@ -23,7 +22,7 @@ export function heading(node, _, state, info) { if (formatHeadingAsSetext(node, state)) { const exit = state.enter('headingSetext') const subexit = state.enter('phrasing') - const value = containerPhrasing(node, state, { + const value = state.containerPhrasing(node, { ...tracker.current(), before: '\n', after: '\n' @@ -54,7 +53,7 @@ export function heading(node, _, state, info) { // we’ve tracked one too many characters. tracker.move(sequence + ' ') - let value = containerPhrasing(node, state, { + let value = state.containerPhrasing(node, { before: '# ', after: '\n', ...tracker.current() diff --git a/lib/handle/link-reference.js b/lib/handle/link-reference.js index e4474a1..8779c57 100644 --- a/lib/handle/link-reference.js +++ b/lib/handle/link-reference.js @@ -6,7 +6,6 @@ */ import {association} from '../util/association.js' -import {containerPhrasing} from '../util/container-phrasing.js' import {safe} from '../util/safe.js' import {track} from '../util/track.js' @@ -25,7 +24,7 @@ export function linkReference(node, _, state, info) { let subexit = state.enter('label') const tracker = track(info) let value = tracker.move('[') - const text = containerPhrasing(node, state, { + const text = state.containerPhrasing(node, { before: value, after: ']', ...tracker.current() diff --git a/lib/handle/link.js b/lib/handle/link.js index eec2310..89c1a78 100644 --- a/lib/handle/link.js +++ b/lib/handle/link.js @@ -8,7 +8,6 @@ import {checkQuote} from '../util/check-quote.js' import {formatLinkAsAutolink} from '../util/format-link-as-autolink.js' -import {containerPhrasing} from '../util/container-phrasing.js' import {safe} from '../util/safe.js' import {track} from '../util/track.js' @@ -37,7 +36,7 @@ export function link(node, _, state, info) { exit = state.enter('autolink') let value = tracker.move('<') value += tracker.move( - containerPhrasing(node, state, { + state.containerPhrasing(node, { before: value, after: '>', ...tracker.current() @@ -53,7 +52,7 @@ export function link(node, _, state, info) { subexit = state.enter('label') let value = tracker.move('[') value += tracker.move( - containerPhrasing(node, state, { + state.containerPhrasing(node, { before: value, after: '](', ...tracker.current() diff --git a/lib/handle/list-item.js b/lib/handle/list-item.js index 0c677f7..2fdf47f 100644 --- a/lib/handle/list-item.js +++ b/lib/handle/list-item.js @@ -8,7 +8,6 @@ import {checkBullet} from '../util/check-bullet.js' import {checkListItemIndent} from '../util/check-list-item-indent.js' -import {containerFlow} from '../util/container-flow.js' import {track} from '../util/track.js' /** @@ -49,7 +48,7 @@ export function listItem(node, parent, state, info) { tracker.shift(size) const exit = state.enter('listItem') const value = state.indentLines( - containerFlow(node, state, tracker.current()), + state.containerFlow(node, tracker.current()), map ) exit() diff --git a/lib/handle/list.js b/lib/handle/list.js index 244431f..f3d1e0f 100644 --- a/lib/handle/list.js +++ b/lib/handle/list.js @@ -5,7 +5,6 @@ * @typedef {import('../types.js').Info} Info */ -import {containerFlow} from '../util/container-flow.js' import {checkBullet} from '../util/check-bullet.js' import {checkBulletOther} from '../util/check-bullet-other.js' import {checkBulletOrdered} from '../util/check-bullet-ordered.js' @@ -106,7 +105,7 @@ export function list(node, parent, state, info) { } state.bulletCurrent = bullet - const value = containerFlow(node, state, info) + const value = state.containerFlow(node, info) state.bulletLastUsed = bullet state.bulletCurrent = bulletCurrent exit() diff --git a/lib/handle/paragraph.js b/lib/handle/paragraph.js index 92e8281..5412ec1 100644 --- a/lib/handle/paragraph.js +++ b/lib/handle/paragraph.js @@ -5,8 +5,6 @@ * @typedef {import('../types.js').Info} Info */ -import {containerPhrasing} from '../util/container-phrasing.js' - /** * @param {Paragraph} node * @param {Parent | undefined} _ @@ -17,7 +15,7 @@ import {containerPhrasing} from '../util/container-phrasing.js' export function paragraph(node, _, state, info) { const exit = state.enter('paragraph') const subexit = state.enter('phrasing') - const value = containerPhrasing(node, state, info) + const value = state.containerPhrasing(node, info) subexit() exit() return value diff --git a/lib/handle/root.js b/lib/handle/root.js index 471befb..2583934 100644 --- a/lib/handle/root.js +++ b/lib/handle/root.js @@ -6,8 +6,6 @@ */ import {phrasing} from 'mdast-util-phrasing' -import {containerFlow} from '../util/container-flow.js' -import {containerPhrasing} from '../util/container-phrasing.js' /** * @param {Root} node @@ -19,7 +17,7 @@ import {containerPhrasing} from '../util/container-phrasing.js' export function root(node, _, state, info) { // Note: `html` nodes are ambiguous. const hasPhrasing = node.children.some((d) => phrasing(d)) - const fn = hasPhrasing ? containerPhrasing : containerFlow + const fn = hasPhrasing ? state.containerPhrasing : state.containerFlow // @ts-expect-error: `root`s are supposed to have one type of content - return fn(node, state, info) + return fn.call(state, node, info) } diff --git a/lib/handle/strong.js b/lib/handle/strong.js index 9ea09a4..0283345 100644 --- a/lib/handle/strong.js +++ b/lib/handle/strong.js @@ -6,7 +6,6 @@ */ import {checkStrong} from '../util/check-strong.js' -import {containerPhrasing} from '../util/container-phrasing.js' import {track} from '../util/track.js' strong.peek = strongPeek @@ -28,7 +27,7 @@ export function strong(node, _, state, info) { const tracker = track(info) let value = tracker.move(marker + marker) value += tracker.move( - containerPhrasing(node, state, { + state.containerPhrasing(node, { before: value, after: marker, ...tracker.current() diff --git a/lib/index.js b/lib/index.js index 7f64a0e..7799ba3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,9 +1,14 @@ /** + * @typedef {import('./types.js').Enter} Enter + * @typedef {import('./types.js').Info} Info + * @typedef {import('./types.js').Join} Join + * @typedef {import('./types.js').FlowContent} FlowContent * @typedef {import('./types.js').Node} Node * @typedef {import('./types.js').Options} Options + * @typedef {import('./types.js').Parent} Parent + * @typedef {import('./types.js').PhrasingContent} PhrasingContent * @typedef {import('./types.js').State} State - * @typedef {import('./types.js').Join} Join - * @typedef {import('./types.js').Enter} Enter + * @typedef {import('./types.js').TrackFields} TrackFields */ import {zwitch} from 'zwitch' @@ -11,6 +16,8 @@ import {configure} from './configure.js' import {handle as handlers} from './handle/index.js' import {join} from './join.js' import {unsafe} from './unsafe.js' +import {containerPhrasing} from './util/container-phrasing.js' +import {containerFlow} from './util/container-flow.js' import {indentLines} from './util/indent-lines.js' /** @@ -28,6 +35,8 @@ export function toMarkdown(tree, options = {}) { const state = { enter, indentLines, + containerPhrasing: containerPhrasingBound, + containerFlow: containerFlowBound, stack: [], unsafe: [], join: [], @@ -104,3 +113,40 @@ function joinDefinition(left, right) { return 0 } } + +/** + * Serialize the children of a parent that contains phrasing children. + * + * These children will be joined flush together. + * + * @this {State} + * Info passed around about the current state. + * @param {Parent & {children: Array}} parent + * Parent of flow nodes. + * @param {Info} info + * Info on where we are in the document we are generating. + * @returns {string} + * Serialized children, joined together. + */ +function containerPhrasingBound(parent, info) { + return containerPhrasing(parent, this, info) +} + +/** + * Serialize the children of a parent that contains flow children. + * + * These children will typically be joined by blank lines. + * What they are joined by exactly is defined by `Join` functions. + * + * @this {State} + * Info passed around about the current state. + * @param {Parent & {children: Array}} parent + * Parent of flow nodes. + * @param {TrackFields} info + * Info on where we are in the document we are generating. + * @returns {string} + * Serialized children, joined by (blank) lines. + */ +function containerFlowBound(parent, info) { + return containerFlow(parent, this, info) +} diff --git a/lib/types.js b/lib/types.js index 4146349..d1b5f9c 100644 --- a/lib/types.js +++ b/lib/types.js @@ -43,7 +43,6 @@ * @returns {string} * Padded line. * - * @callback IndentLines * Pad serialized markdown. * @param {string} value @@ -53,6 +52,29 @@ * @returns {string} * Padded value. * + * @callback ContainerPhrasing + * Serialize the children of a parent that contains phrasing children. + * + * These children will be joined flush together. + * @param {Parent & {children: Array}} parent + * Parent of flow nodes. + * @param {Info} info + * Info on where we are in the document we are generating. + * @returns {string} + * Serialized children, joined together. + * + * @callback ContainerFlow + * Serialize the children of a parent that contains flow children. + * + * These children will typically be joined by blank lines. + * What they are joined by exactly is defined by `Join` functions. + * @param {Parent & {children: Array}} parent + * Parent of flow nodes. + * @param {TrackFields} info + * Info on where we are in the document we are generating. + * @returns {string} + * Serialized children, joined by (blank) lines. + * * @callback Enter * Enter something. * @param {ConstructName} name @@ -73,6 +95,10 @@ * Positions of child nodes in their parents. * @property {IndentLines} indentLines * Pad serialized markdown. + * @property {ContainerPhrasing} containerPhrasing + * Serialize the children of a parent that contains phrasing children. + * @property {ContainerFlow} containerFlow + * Serialize the children of a parent that contains flow children. * @property {Enter} enter * Enter a construct (returns a corresponding exit function). * @property {Options} options diff --git a/lib/util/container-flow.js b/lib/util/container-flow.js index 2289225..0838f0c 100644 --- a/lib/util/container-flow.js +++ b/lib/util/container-flow.js @@ -10,9 +10,13 @@ import {track} from './track.js' /** * @param {Parent & {children: Array}} parent + * Parent of flow nodes. * @param {State} state + * Info passed around about the current state. * @param {TrackFields} info + * Info on where we are in the document we are generating. * @returns {string} + * Serialized children, joined by (blank) lines. */ export function containerFlow(parent, state, info) { const indexStack = state.indexStack @@ -44,38 +48,42 @@ export function containerFlow(parent, state, info) { } if (index < children.length - 1) { - results.push(tracker.move(between(child, children[index + 1]))) + results.push( + tracker.move(between(child, children[index + 1], parent, state)) + ) } } indexStack.pop() return results.join('') +} - /** - * @param {Node} left - * @param {Node} right - * @returns {string} - */ - function between(left, right) { - let index = state.join.length - - while (index--) { - const result = state.join[index](left, right, parent, state) +/** + * @param {Node} left + * @param {Node} right + * @param {Parent} parent + * @param {State} state + * @returns {string} + */ +function between(left, right, parent, state) { + let index = state.join.length - if (result === true || result === 1) { - break - } + while (index--) { + const result = state.join[index](left, right, parent, state) - if (typeof result === 'number') { - return '\n'.repeat(1 + result) - } + if (result === true || result === 1) { + break + } - if (result === false) { - return '\n\n\n\n' - } + if (typeof result === 'number') { + return '\n'.repeat(1 + result) } - return '\n\n' + if (result === false) { + return '\n\n\n\n' + } } + + return '\n\n' } diff --git a/lib/util/container-phrasing.js b/lib/util/container-phrasing.js index 47df016..2b875c2 100644 --- a/lib/util/container-phrasing.js +++ b/lib/util/container-phrasing.js @@ -1,18 +1,26 @@ /** - * @typedef {import('../types.js').PhrasingContent} PhrasingContent - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').Info} Info * @typedef {import('../types.js').Handle} Handle + * @typedef {import('../types.js').Info} Info + * @typedef {import('../types.js').Parent} Parent + * @typedef {import('../types.js').PhrasingContent} PhrasingContent * @typedef {import('../types.js').State} State */ import {track} from './track.js' /** + * Serialize the children of a parent that contains phrasing children. + * + * These children will be joined flush together. + * * @param {Parent & {children: Array}} parent + * Parent of flow nodes. * @param {State} state + * Info passed around about the current state. * @param {Info} info + * Info on where we are in the document we are generating. * @returns {string} + * Serialized children, joined together. */ export function containerPhrasing(parent, state, info) { const indexStack = state.indexStack diff --git a/readme.md b/readme.md index 7807f18..8ba6650 100644 --- a/readme.md +++ b/readme.md @@ -465,6 +465,10 @@ Info passed around about the current state (TypeScript type). (see [`ConstructName`][constructname]) * `indentLines` (`(value: string, map: Map) => string`) — pad serialized markdown (see [`Map`][map]) +* `containerFlow` (`(parent: Node, info: Info) => string`) + — serialize flow children +* `containerPhrasing` (`(parent: Node, info: Info) => string`) + — serialize phrasing children * `options` ([`Options`][options]) — applied user configuration * `unsafe` ([`Array`][unsafe])