| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789 |
- /**
- * @import {CompileContext, Extension as FromMarkdownExtension, Handle as FromMarkdownHandle, OnEnterError, OnExitError, Token} from 'mdast-util-from-markdown'
- * @import {Handle as ToMarkdownHandle, Options as ToMarkdownExtension, State, Tracker} from 'mdast-util-to-markdown'
- * @import {Point} from 'unist'
- * @import {MdxJsxAttribute, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement} from '../index.js'
- */
- /**
- * @typedef Tag
- * Single tag.
- * @property {string | undefined} name
- * Name of tag, or `undefined` for fragment.
- *
- * > 👉 **Note**: `null` is used in the AST for fragments, as it serializes in
- * > JSON.
- * @property {Array<MdxJsxAttribute | MdxJsxExpressionAttribute>} attributes
- * Attributes.
- * @property {boolean} close
- * Whether the tag is closing (`</x>`).
- * @property {boolean} selfClosing
- * Whether the tag is self-closing (`<x/>`).
- * @property {Token['start']} start
- * Start point.
- * @property {Token['start']} end
- * End point.
- *
- * @typedef ToMarkdownOptions
- * Configuration.
- * @property {'"' | "'" | null | undefined} [quote='"']
- * Preferred quote to use around attribute values (default: `'"'`).
- * @property {boolean | null | undefined} [quoteSmart=false]
- * Use the other quote if that results in less bytes (default: `false`).
- * @property {boolean | null | undefined} [tightSelfClosing=false]
- * Do not use an extra space when closing self-closing elements: `<img/>`
- * instead of `<img />` (default: `false`).
- * @property {number | null | undefined} [printWidth=Infinity]
- * Try and wrap syntax at this width (default: `Infinity`).
- *
- * When set to a finite number (say, `80`), the formatter will print
- * attributes on separate lines when a tag doesn’t fit on one line.
- * The normal behavior is to print attributes with spaces between them
- * instead of line endings.
- */
- import {ccount} from 'ccount'
- import {ok as assert} from 'devlop'
- import {parseEntities} from 'parse-entities'
- import {stringifyEntitiesLight} from 'stringify-entities'
- import {stringifyPosition} from 'unist-util-stringify-position'
- import {VFileMessage} from 'vfile-message'
- const indent = ' '
- /**
- * Create an extension for `mdast-util-from-markdown` to enable MDX JSX.
- *
- * @returns {FromMarkdownExtension}
- * Extension for `mdast-util-from-markdown` to enable MDX JSX.
- *
- * When using the syntax extension with `addResult`, nodes will have a
- * `data.estree` field set to an ESTree `Program` node.
- */
- export function mdxJsxFromMarkdown() {
- return {
- canContainEols: ['mdxJsxTextElement'],
- enter: {
- mdxJsxFlowTag: enterMdxJsxTag,
- mdxJsxFlowTagClosingMarker: enterMdxJsxTagClosingMarker,
- mdxJsxFlowTagAttribute: enterMdxJsxTagAttribute,
- mdxJsxFlowTagExpressionAttribute: enterMdxJsxTagExpressionAttribute,
- mdxJsxFlowTagAttributeValueLiteral: buffer,
- mdxJsxFlowTagAttributeValueExpression: buffer,
- mdxJsxFlowTagSelfClosingMarker: enterMdxJsxTagSelfClosingMarker,
- mdxJsxTextTag: enterMdxJsxTag,
- mdxJsxTextTagClosingMarker: enterMdxJsxTagClosingMarker,
- mdxJsxTextTagAttribute: enterMdxJsxTagAttribute,
- mdxJsxTextTagExpressionAttribute: enterMdxJsxTagExpressionAttribute,
- mdxJsxTextTagAttributeValueLiteral: buffer,
- mdxJsxTextTagAttributeValueExpression: buffer,
- mdxJsxTextTagSelfClosingMarker: enterMdxJsxTagSelfClosingMarker
- },
- exit: {
- mdxJsxFlowTagClosingMarker: exitMdxJsxTagClosingMarker,
- mdxJsxFlowTagNamePrimary: exitMdxJsxTagNamePrimary,
- mdxJsxFlowTagNameMember: exitMdxJsxTagNameMember,
- mdxJsxFlowTagNameLocal: exitMdxJsxTagNameLocal,
- mdxJsxFlowTagExpressionAttribute: exitMdxJsxTagExpressionAttribute,
- mdxJsxFlowTagExpressionAttributeValue: data,
- mdxJsxFlowTagAttributeNamePrimary: exitMdxJsxTagAttributeNamePrimary,
- mdxJsxFlowTagAttributeNameLocal: exitMdxJsxTagAttributeNameLocal,
- mdxJsxFlowTagAttributeValueLiteral: exitMdxJsxTagAttributeValueLiteral,
- mdxJsxFlowTagAttributeValueLiteralValue: data,
- mdxJsxFlowTagAttributeValueExpression:
- exitMdxJsxTagAttributeValueExpression,
- mdxJsxFlowTagAttributeValueExpressionValue: data,
- mdxJsxFlowTagSelfClosingMarker: exitMdxJsxTagSelfClosingMarker,
- mdxJsxFlowTag: exitMdxJsxTag,
- mdxJsxTextTagClosingMarker: exitMdxJsxTagClosingMarker,
- mdxJsxTextTagNamePrimary: exitMdxJsxTagNamePrimary,
- mdxJsxTextTagNameMember: exitMdxJsxTagNameMember,
- mdxJsxTextTagNameLocal: exitMdxJsxTagNameLocal,
- mdxJsxTextTagExpressionAttribute: exitMdxJsxTagExpressionAttribute,
- mdxJsxTextTagExpressionAttributeValue: data,
- mdxJsxTextTagAttributeNamePrimary: exitMdxJsxTagAttributeNamePrimary,
- mdxJsxTextTagAttributeNameLocal: exitMdxJsxTagAttributeNameLocal,
- mdxJsxTextTagAttributeValueLiteral: exitMdxJsxTagAttributeValueLiteral,
- mdxJsxTextTagAttributeValueLiteralValue: data,
- mdxJsxTextTagAttributeValueExpression:
- exitMdxJsxTagAttributeValueExpression,
- mdxJsxTextTagAttributeValueExpressionValue: data,
- mdxJsxTextTagSelfClosingMarker: exitMdxJsxTagSelfClosingMarker,
- mdxJsxTextTag: exitMdxJsxTag
- }
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function buffer() {
- this.buffer()
- }
- /**
- * Copy a point-like value.
- *
- * @param {Point} d
- * Point-like value.
- * @returns {Point}
- * unist point.
- */
- function point(d) {
- return {line: d.line, column: d.column, offset: d.offset}
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function data(token) {
- this.config.enter.data.call(this, token)
- this.config.exit.data.call(this, token)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTag(token) {
- /** @type {Tag} */
- const tag = {
- name: undefined,
- attributes: [],
- close: false,
- selfClosing: false,
- start: token.start,
- end: token.end
- }
- if (!this.data.mdxJsxTagStack) this.data.mdxJsxTagStack = []
- this.data.mdxJsxTag = tag
- this.buffer()
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTagClosingMarker(token) {
- const stack = this.data.mdxJsxTagStack
- assert(stack, 'expected `mdxJsxTagStack`')
- if (stack.length === 0) {
- throw new VFileMessage(
- 'Unexpected closing slash `/` in tag, expected an open tag first',
- {start: token.start, end: token.end},
- 'mdast-util-mdx-jsx:unexpected-closing-slash'
- )
- }
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTagAnyAttribute(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- if (tag.close) {
- throw new VFileMessage(
- 'Unexpected attribute in closing tag, expected the end of the tag',
- {start: token.start, end: token.end},
- 'mdast-util-mdx-jsx:unexpected-attribute'
- )
- }
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTagSelfClosingMarker(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- if (tag.close) {
- throw new VFileMessage(
- 'Unexpected self-closing slash `/` in closing tag, expected the end of the tag',
- {start: token.start, end: token.end},
- 'mdast-util-mdx-jsx:unexpected-self-closing-slash'
- )
- }
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagClosingMarker() {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- tag.close = true
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagNamePrimary(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- tag.name = this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagNameMember(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- tag.name += '.' + this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagNameLocal(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- tag.name += ':' + this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTagAttribute(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- enterMdxJsxTagAnyAttribute.call(this, token)
- tag.attributes.push({
- type: 'mdxJsxAttribute',
- name: '',
- value: null,
- position: {
- start: point(token.start),
- // @ts-expect-error: `end` will be patched later.
- end: undefined
- }
- })
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function enterMdxJsxTagExpressionAttribute(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- enterMdxJsxTagAnyAttribute.call(this, token)
- tag.attributes.push({
- type: 'mdxJsxExpressionAttribute',
- value: '',
- position: {
- start: point(token.start),
- // @ts-expect-error: `end` will be patched later.
- end: undefined
- }
- })
- this.buffer()
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagExpressionAttribute(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const tail = tag.attributes[tag.attributes.length - 1]
- assert(tail.type === 'mdxJsxExpressionAttribute')
- const estree = token.estree
- tail.value = this.resume()
- assert(tail.position !== undefined)
- tail.position.end = point(token.end)
- if (estree) {
- tail.data = {estree}
- }
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagAttributeNamePrimary(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const node = tag.attributes[tag.attributes.length - 1]
- assert(node.type === 'mdxJsxAttribute')
- node.name = this.sliceSerialize(token)
- assert(node.position !== undefined)
- node.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagAttributeNameLocal(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const node = tag.attributes[tag.attributes.length - 1]
- assert(node.type === 'mdxJsxAttribute')
- node.name += ':' + this.sliceSerialize(token)
- assert(node.position !== undefined)
- node.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagAttributeValueLiteral(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const node = tag.attributes[tag.attributes.length - 1]
- node.value = parseEntities(this.resume(), {nonTerminated: false})
- assert(node.position !== undefined)
- node.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagAttributeValueExpression(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const tail = tag.attributes[tag.attributes.length - 1]
- assert(tail.type === 'mdxJsxAttribute')
- /** @type {MdxJsxAttributeValueExpression} */
- const node = {type: 'mdxJsxAttributeValueExpression', value: this.resume()}
- const estree = token.estree
- if (estree) {
- node.data = {estree}
- }
- tail.value = node
- assert(tail.position !== undefined)
- tail.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTagSelfClosingMarker() {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- tag.selfClosing = true
- }
- /**
- * @this {CompileContext}
- * @type {FromMarkdownHandle}
- */
- function exitMdxJsxTag(token) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- const stack = this.data.mdxJsxTagStack
- assert(stack, 'expected `mdxJsxTagStack`')
- const tail = stack[stack.length - 1]
- if (tag.close && tail.name !== tag.name) {
- throw new VFileMessage(
- 'Unexpected closing tag `' +
- serializeAbbreviatedTag(tag) +
- '`, expected corresponding closing tag for `' +
- serializeAbbreviatedTag(tail) +
- '` (' +
- stringifyPosition(tail) +
- ')',
- {start: token.start, end: token.end},
- 'mdast-util-mdx-jsx:end-tag-mismatch'
- )
- }
- // End of a tag, so drop the buffer.
- this.resume()
- if (tag.close) {
- stack.pop()
- } else {
- this.enter(
- {
- type:
- token.type === 'mdxJsxTextTag'
- ? 'mdxJsxTextElement'
- : 'mdxJsxFlowElement',
- name: tag.name || null,
- attributes: tag.attributes,
- children: []
- },
- token,
- onErrorRightIsTag
- )
- }
- if (tag.selfClosing || tag.close) {
- this.exit(token, onErrorLeftIsTag)
- } else {
- stack.push(tag)
- }
- }
- /**
- * @this {CompileContext}
- * @type {OnEnterError}
- */
- function onErrorRightIsTag(closing, open) {
- const stack = this.data.mdxJsxTagStack
- assert(stack, 'expected `mdxJsxTagStack`')
- const tag = stack[stack.length - 1]
- assert(tag, 'expected `mdxJsxTag`')
- const place = closing ? ' before the end of `' + closing.type + '`' : ''
- const position = closing
- ? {start: closing.start, end: closing.end}
- : undefined
- throw new VFileMessage(
- 'Expected a closing tag for `' +
- serializeAbbreviatedTag(tag) +
- '` (' +
- stringifyPosition({start: open.start, end: open.end}) +
- ')' +
- place,
- position,
- 'mdast-util-mdx-jsx:end-tag-mismatch'
- )
- }
- /**
- * @this {CompileContext}
- * @type {OnExitError}
- */
- function onErrorLeftIsTag(a, b) {
- const tag = this.data.mdxJsxTag
- assert(tag, 'expected `mdxJsxTag`')
- throw new VFileMessage(
- 'Expected the closing tag `' +
- serializeAbbreviatedTag(tag) +
- '` either after the end of `' +
- b.type +
- '` (' +
- stringifyPosition(b.end) +
- ') or another opening tag after the start of `' +
- b.type +
- '` (' +
- stringifyPosition(b.start) +
- ')',
- {start: a.start, end: a.end},
- 'mdast-util-mdx-jsx:end-tag-mismatch'
- )
- }
- /**
- * Serialize a tag, excluding attributes.
- * `self-closing` is not supported, because we don’t need it yet.
- *
- * @param {Tag} tag
- * @returns {string}
- */
- function serializeAbbreviatedTag(tag) {
- return '<' + (tag.close ? '/' : '') + (tag.name || '') + '>'
- }
- }
- /**
- * Create an extension for `mdast-util-to-markdown` to enable MDX JSX.
- *
- * This extension configures `mdast-util-to-markdown` with
- * `options.fences: true` and `options.resourceLink: true` too, do not
- * overwrite them!
- *
- * @param {ToMarkdownOptions | null | undefined} [options]
- * Configuration (optional).
- * @returns {ToMarkdownExtension}
- * Extension for `mdast-util-to-markdown` to enable MDX JSX.
- */
- export function mdxJsxToMarkdown(options) {
- const options_ = options || {}
- const quote = options_.quote || '"'
- const quoteSmart = options_.quoteSmart || false
- const tightSelfClosing = options_.tightSelfClosing || false
- const printWidth = options_.printWidth || Number.POSITIVE_INFINITY
- const alternative = quote === '"' ? "'" : '"'
- if (quote !== '"' && quote !== "'") {
- throw new Error(
- 'Cannot serialize attribute values with `' +
- quote +
- '` for `options.quote`, expected `"`, or `\'`'
- )
- }
- mdxElement.peek = peekElement
- return {
- handlers: {
- mdxJsxFlowElement: mdxElement,
- mdxJsxTextElement: mdxElement
- },
- unsafe: [
- {character: '<', inConstruct: ['phrasing']},
- {atBreak: true, character: '<'}
- ],
- // Always generate fenced code (never indented code).
- fences: true,
- // Always generate links with resources (never autolinks).
- resourceLink: true
- }
- /**
- * @type {ToMarkdownHandle}
- * @param {MdxJsxFlowElement | MdxJsxTextElement} node
- */
- // eslint-disable-next-line complexity
- function mdxElement(node, _, state, info) {
- const flow = node.type === 'mdxJsxFlowElement'
- const selfClosing = node.name
- ? !node.children || node.children.length === 0
- : false
- const depth = inferDepth(state)
- const currentIndent = createIndent(depth)
- const trackerOneLine = state.createTracker(info)
- const trackerMultiLine = state.createTracker(info)
- /** @type {Array<string>} */
- const serializedAttributes = []
- const prefix = (flow ? currentIndent : '') + '<' + (node.name || '')
- const exit = state.enter(node.type)
- trackerOneLine.move(prefix)
- trackerMultiLine.move(prefix)
- // None.
- if (node.attributes && node.attributes.length > 0) {
- if (!node.name) {
- throw new Error('Cannot serialize fragment w/ attributes')
- }
- let index = -1
- while (++index < node.attributes.length) {
- const attribute = node.attributes[index]
- /** @type {string} */
- let result
- if (attribute.type === 'mdxJsxExpressionAttribute') {
- result = '{' + (attribute.value || '') + '}'
- } else {
- if (!attribute.name) {
- throw new Error('Cannot serialize attribute w/o name')
- }
- const value = attribute.value
- const left = attribute.name
- /** @type {string} */
- let right = ''
- if (value === null || value === undefined) {
- // Empty.
- } else if (typeof value === 'object') {
- right = '{' + (value.value || '') + '}'
- } else {
- // If the alternative is less common than `quote`, switch.
- const appliedQuote =
- quoteSmart && ccount(value, quote) > ccount(value, alternative)
- ? alternative
- : quote
- right =
- appliedQuote +
- stringifyEntitiesLight(value, {subset: [appliedQuote]}) +
- appliedQuote
- }
- result = left + (right ? '=' : '') + right
- }
- serializedAttributes.push(result)
- }
- }
- let attributesOnTheirOwnLine = false
- const attributesOnOneLine = serializedAttributes.join(' ')
- if (
- // Block:
- flow &&
- // Including a line ending (expressions).
- (/\r?\n|\r/.test(attributesOnOneLine) ||
- // Current position (including `<tag`).
- trackerOneLine.current().now.column +
- // -1 because columns, +1 for ` ` before attributes.
- // Attributes joined by spaces.
- attributesOnOneLine.length +
- // ` />`.
- (selfClosing ? (tightSelfClosing ? 2 : 3) : 1) >
- printWidth)
- ) {
- attributesOnTheirOwnLine = true
- }
- let tracker = trackerOneLine
- let value = prefix
- if (attributesOnTheirOwnLine) {
- tracker = trackerMultiLine
- let index = -1
- while (++index < serializedAttributes.length) {
- // Only indent first line of of attributes, we can’t indent attribute
- // values.
- serializedAttributes[index] =
- currentIndent + indent + serializedAttributes[index]
- }
- value += tracker.move(
- '\n' + serializedAttributes.join('\n') + '\n' + currentIndent
- )
- } else if (attributesOnOneLine) {
- value += tracker.move(' ' + attributesOnOneLine)
- }
- if (selfClosing) {
- value += tracker.move(
- (tightSelfClosing || attributesOnTheirOwnLine ? '' : ' ') + '/'
- )
- }
- value += tracker.move('>')
- if (node.children && node.children.length > 0) {
- if (node.type === 'mdxJsxTextElement') {
- value += tracker.move(
- state.containerPhrasing(node, {
- ...tracker.current(),
- before: '>',
- after: '<'
- })
- )
- } else {
- tracker.shift(2)
- value += tracker.move('\n')
- value += tracker.move(containerFlow(node, state, tracker.current()))
- value += tracker.move('\n')
- }
- }
- if (!selfClosing) {
- value += tracker.move(
- (flow ? currentIndent : '') + '</' + (node.name || '') + '>'
- )
- }
- exit()
- return value
- }
- }
- // Modified copy of:
- // <https://github.com/syntax-tree/mdast-util-to-markdown/blob/a381cbc/lib/util/container-flow.js>.
- //
- // To do: add `indent` support to `mdast-util-to-markdown`.
- // As indents are only used for JSX, it’s fine for now, but perhaps better
- // there.
- /**
- * @param {MdxJsxFlowElement} parent
- * Parent of flow nodes.
- * @param {State} state
- * Info passed around about the current state.
- * @param {ReturnType<Tracker['current']>} info
- * Info on where we are in the document we are generating.
- * @returns {string}
- * Serialized children, joined by (blank) lines.
- */
- function containerFlow(parent, state, info) {
- const indexStack = state.indexStack
- const children = parent.children
- const tracker = state.createTracker(info)
- const currentIndent = createIndent(inferDepth(state))
- /** @type {Array<string>} */
- const results = []
- let index = -1
- indexStack.push(-1)
- while (++index < children.length) {
- const child = children[index]
- indexStack[indexStack.length - 1] = index
- const childInfo = {before: '\n', after: '\n', ...tracker.current()}
- const result = state.handle(child, parent, state, childInfo)
- const serializedChild =
- child.type === 'mdxJsxFlowElement'
- ? result
- : state.indentLines(result, function (line, _, blank) {
- return (blank ? '' : currentIndent) + line
- })
- results.push(tracker.move(serializedChild))
- if (child.type !== 'list') {
- state.bulletLastUsed = undefined
- }
- if (index < children.length - 1) {
- results.push(tracker.move('\n\n'))
- }
- }
- indexStack.pop()
- return results.join('')
- }
- /**
- * @param {State} state
- * @returns {number}
- */
- function inferDepth(state) {
- let depth = 0
- let index = state.stack.length
- while (--index > -1) {
- const name = state.stack[index]
- if (name === 'blockquote' || name === 'listItem') break
- if (name === 'mdxJsxFlowElement') depth++
- }
- return depth
- }
- /**
- * @param {number} depth
- * @returns {string}
- */
- function createIndent(depth) {
- return indent.repeat(depth)
- }
- /**
- * @type {ToMarkdownHandle}
- */
- function peekElement() {
- return '<'
- }
|