88 * @typedef {import('mdast-util-from-markdown').OnEnterError } OnEnterError
99 * @typedef {import('mdast-util-from-markdown').OnExitError } OnExitError
1010 *
11- * @typedef {import('mdast-util-to-markdown').Options } ToMarkdownExtension
1211 * @typedef {import('mdast-util-to-markdown').Handle } ToMarkdownHandle
13- * @typedef {import('mdast-util-to-markdown').Map } ToMarkdownMap
12+ * @typedef {import('mdast-util-to-markdown').Options } ToMarkdownExtension
13+ * @typedef {import('mdast-util-to-markdown').State } State
14+ * @typedef {import('mdast-util-to-markdown').Tracker } Tracker
1415 *
1516 * @typedef {import('../index.js').MdxJsxAttributeValueExpression } MdxJsxAttributeValueExpression
1617 * @typedef {import('../index.js').MdxJsxAttribute } MdxJsxAttribute
@@ -61,13 +62,14 @@ import {parseEntities} from 'parse-entities'
6162import { stringifyPosition } from 'unist-util-stringify-position'
6263import { VFileMessage } from 'vfile-message'
6364import { stringifyEntitiesLight } from 'stringify-entities'
64- import { containerFlow } from 'mdast-util-to-markdown/lib/util/container-flow.js'
6565import { containerPhrasing } from 'mdast-util-to-markdown/lib/util/container-phrasing.js'
6666import { indentLines } from 'mdast-util-to-markdown/lib/util/indent-lines.js'
6767import { track } from 'mdast-util-to-markdown/lib/util/track.js'
6868
6969// To do: next major: use `state`, use utilities from state, rename `safeOptions` to `info`.
7070
71+ const indent = ' '
72+
7173/**
7274 * Create an extension for `mdast-util-from-markdown` to enable MDX JSX.
7375 *
@@ -521,21 +523,28 @@ export function mdxJsxToMarkdown(options) {
521523 */
522524 // eslint-disable-next-line complexity
523525 function mdxElement ( node , _ , context , safeOptions ) {
524- const tracker = track ( safeOptions )
525- const selfClosing =
526- node . name && ( ! node . children || node . children . length === 0 )
527- const exit = context . enter ( node . type )
528- let index = - 1
526+ const selfClosing = node . name
527+ ? ! node . children || node . children . length === 0
528+ : false
529+ const depth = inferDepth ( context )
530+ const currentIndent = createIndent ( depth )
531+ const trackerOneLine = track ( safeOptions )
532+ const trackerMultiLine = track ( safeOptions )
529533 /** @type {Array<string> } */
530534 const serializedAttributes = [ ]
531- let value = tracker . move ( '<' + ( node . name || '' ) )
535+ const prefix = currentIndent + '<' + ( node . name || '' )
536+ const exit = context . enter ( node . type )
537+
538+ trackerOneLine . move ( prefix )
539+ trackerMultiLine . move ( prefix )
532540
533541 // None.
534542 if ( node . attributes && node . attributes . length > 0 ) {
535543 if ( ! node . name ) {
536544 throw new Error ( 'Cannot serialize fragment w/ attributes' )
537545 }
538546
547+ let index = - 1
539548 while ( ++ index < node . attributes . length ) {
540549 const attribute = node . attributes [ index ]
541550 /** @type {string } */
@@ -585,7 +594,7 @@ export function mdxJsxToMarkdown(options) {
585594 // Including a line ending (expressions).
586595 ( / \r ? \n | \r / . test ( attributesOnOneLine ) ||
587596 // Current position (including `<tag`).
588- tracker . current ( ) . now . column +
597+ trackerOneLine . current ( ) . now . column +
589598 // -1 because columns, +1 for ` ` before attributes.
590599 // Attributes joined by spaces.
591600 attributesOnOneLine . length +
@@ -596,18 +605,28 @@ export function mdxJsxToMarkdown(options) {
596605 attributesOnTheirOwnLine = true
597606 }
598607
608+ let tracker = trackerOneLine
609+ let value = prefix
610+
599611 if ( attributesOnTheirOwnLine ) {
612+ tracker = trackerMultiLine
613+
614+ let index = - 1
615+
616+ while ( ++ index < serializedAttributes . length ) {
617+ // Only indent first line of of attributes, we can’t indent attribute
618+ // values.
619+ serializedAttributes [ index ] =
620+ currentIndent + indent + serializedAttributes [ index ]
621+ }
622+
600623 value += tracker . move (
601- '\n' + indentLines ( serializedAttributes . join ( '\n' ) , map )
624+ '\n' + serializedAttributes . join ( '\n' ) + '\n' + currentIndent
602625 )
603626 } else if ( attributesOnOneLine ) {
604627 value += tracker . move ( ' ' + attributesOnOneLine )
605628 }
606629
607- if ( attributesOnTheirOwnLine ) {
608- value += tracker . move ( '\n' )
609- }
610-
611630 if ( selfClosing ) {
612631 value += tracker . move (
613632 ( tightSelfClosing || attributesOnTheirOwnLine ? '' : ' ' ) + '/'
@@ -617,41 +636,118 @@ export function mdxJsxToMarkdown(options) {
617636 value += tracker . move ( '>' )
618637
619638 if ( node . children && node . children . length > 0 ) {
620- if ( node . type === 'mdxJsxFlowElement' ) {
621- tracker . shift ( 2 )
622- value += tracker . move ( '\n' )
623- value += tracker . move (
624- indentLines ( containerFlow ( node , context , tracker . current ( ) ) , map )
625- )
626- value += tracker . move ( '\n' )
627- } else {
639+ if ( node . type === 'mdxJsxTextElement' ) {
628640 value += tracker . move (
629641 containerPhrasing ( node , context , {
630642 ...tracker . current ( ) ,
631- before : '< ' ,
632- after : '> '
643+ before : '> ' ,
644+ after : '< '
633645 } )
634646 )
647+ } else {
648+ tracker . shift ( 2 )
649+ value += tracker . move ( '\n' )
650+ value += tracker . move ( containerFlow ( node , context , tracker . current ( ) ) )
651+ value += tracker . move ( '\n' )
635652 }
636653 }
637654
638655 if ( ! selfClosing ) {
639- value += tracker . move ( '</' + ( node . name || '' ) + '>' )
656+ value += tracker . move ( currentIndent + '</' + ( node . name || '' ) + '>' )
640657 }
641658
642659 exit ( )
643660 return value
644661 }
662+ }
663+
664+ // Modified copy of:
665+ // <https://github.com/syntax-tree/mdast-util-to-markdown/blob/a381cbc/lib/util/container-flow.js>.
666+ //
667+ // To do: add `indent` support to `mdast-util-to-markdown`.
668+ // As indents are only used for JSX, it’s fine for now, but perhaps better
669+ // there.
670+ /**
671+ * @param {MdxJsxFlowElement } parent
672+ * Parent of flow nodes.
673+ * @param {State } state
674+ * Info passed around about the current state.
675+ * @param {ReturnType<Tracker['current']> } info
676+ * Info on where we are in the document we are generating.
677+ * @returns {string }
678+ * Serialized children, joined by (blank) lines.
679+ */
680+ function containerFlow ( parent , state , info ) {
681+ const indexStack = state . indexStack
682+ const children = parent . children
683+ const tracker = state . createTracker ( info )
684+ const currentIndent = createIndent ( inferDepth ( state ) )
685+ /** @type {Array<string> } */
686+ const results = [ ]
687+ let index = - 1
688+
689+ indexStack . push ( - 1 )
690+
691+ while ( ++ index < children . length ) {
692+ const child = children [ index ]
645693
646- /** @type {ToMarkdownMap } */
647- function map ( line , _ , blank ) {
648- return ( blank ? '' : ' ' ) + line
694+ indexStack [ indexStack . length - 1 ] = index
695+
696+ const childInfo = { before : '\n' , after : '\n' , ...tracker . current ( ) }
697+
698+ const result = state . handle ( child , parent , state , childInfo )
699+
700+ const serializedChild =
701+ child . type === 'mdxJsxFlowElement'
702+ ? result
703+ : indentLines ( result , function ( line , _ , blank ) {
704+ return ( blank ? '' : currentIndent ) + line
705+ } )
706+
707+ results . push ( tracker . move ( serializedChild ) )
708+
709+ if ( child . type !== 'list' ) {
710+ state . bulletLastUsed = undefined
711+ }
712+
713+ if ( index < children . length - 1 ) {
714+ results . push ( tracker . move ( '\n\n' ) )
715+ }
649716 }
650717
651- /**
652- * @type {ToMarkdownHandle }
653- */
654- function peekElement ( ) {
655- return '<'
718+ indexStack . pop ( )
719+
720+ return results . join ( '' )
721+ }
722+
723+ /**
724+ *
725+ * @param {State } context
726+ * @returns {number }
727+ */
728+ function inferDepth ( context ) {
729+ let depth = 0
730+
731+ for ( const x of context . stack ) {
732+ if ( x === 'mdxJsxFlowElement' ) {
733+ depth ++
734+ }
656735 }
736+
737+ return depth
738+ }
739+
740+ /**
741+ * @param {number } depth
742+ * @returns {string }
743+ */
744+ function createIndent ( depth ) {
745+ return indent . repeat ( depth )
746+ }
747+
748+ /**
749+ * @type {ToMarkdownHandle }
750+ */
751+ function peekElement ( ) {
752+ return '<'
657753}
0 commit comments