2525 * @property {boolean } [tightSelfClosing=false]
2626 * Do not use an extra space when closing self-closing elements: `<img/>`
2727 * instead of `<img />`.
28+ * @property {number } [printWidth=Infinity]
29+ * Specify the line length that the printer will wrap on.
30+ * This is not a hard maximum width: things will be printed longer and
31+ * shorter.
32+ *
33+ * Note: this option is only used for JSX tags currently, and might be moved
34+ * to `mdast-util-to-markdown` in the future.
2835 */
2936
3037import { ccount } from 'ccount'
@@ -35,6 +42,7 @@ import {stringifyEntitiesLight} from 'stringify-entities'
3542import { containerFlow } from 'mdast-util-to-markdown/lib/util/container-flow.js'
3643import { containerPhrasing } from 'mdast-util-to-markdown/lib/util/container-phrasing.js'
3744import { indentLines } from 'mdast-util-to-markdown/lib/util/indent-lines.js'
45+ import { track } from 'mdast-util-to-markdown/lib/util/track.js'
3846
3947/** @return {FromMarkdownExtension } */
4048export function mdxJsxFromMarkdown ( ) {
@@ -366,7 +374,12 @@ export function mdxJsxFromMarkdown() {
366374 * @returns {ToMarkdownExtension }
367375 */
368376export function mdxJsxToMarkdown ( options = { } ) {
369- const { quote = '"' , quoteSmart, tightSelfClosing} = options
377+ const {
378+ quote = '"' ,
379+ quoteSmart,
380+ tightSelfClosing,
381+ printWidth = Number . POSITIVE_INFINITY
382+ } = options
370383 const alternative = quote === '"' ? "'" : '"'
371384
372385 if ( quote !== '"' && quote !== "'" ) {
@@ -397,28 +410,26 @@ export function mdxJsxToMarkdown(options = {}) {
397410 * @param {MdxJsxFlowElement|MdxJsxTextElement } node
398411 */
399412 // eslint-disable-next-line complexity
400- function mdxElement ( node , _ , context ) {
413+ function mdxElement ( node , _ , context , safeOptions ) {
414+ const tracker = track ( safeOptions )
401415 const selfClosing =
402416 node . name && ( ! node . children || node . children . length === 0 )
403417 const exit = context . enter ( node . type )
404- let attributeValue = ''
405418 let index = - 1
406419 /** @type {Array<string> } */
407- const attributes = [ ]
408- /** @type {string } */
409- let result
420+ const serializedAttributes = [ ]
421+ let value = tracker . move ( '<' + ( node . name || '' ) )
410422
411423 // None.
412424 if ( node . attributes && node . attributes . length > 0 ) {
413425 if ( ! node . name ) {
414426 throw new Error ( 'Cannot serialize fragment w/ attributes' )
415427 }
416428
417- const isMultiFlow =
418- node . type === 'mdxJsxFlowElement' && node . attributes . length > 1
419-
420429 while ( ++ index < node . attributes . length ) {
421430 const attribute = node . attributes [ index ]
431+ /** @type {string } */
432+ let result
422433
423434 if ( attribute . type === 'mdxJsxExpressionAttribute' ) {
424435 result = '{' + ( attribute . value || '' ) + '}'
@@ -428,71 +439,109 @@ export function mdxJsxToMarkdown(options = {}) {
428439 }
429440
430441 const value = attribute . value
431- let initializer = ''
442+ const left = attribute . name
443+ /** @type {string } */
444+ let right = ''
432445
433446 if ( value === undefined || value === null ) {
434447 // Empty.
435448 } else if ( typeof value === 'object' ) {
436- initializer = '= {' + ( value . value || '' ) + '}'
449+ right = '{' + ( value . value || '' ) + '}'
437450 } else {
438451 // If the alternative is less common than `quote`, switch.
439452 const appliedQuote =
440453 quoteSmart && ccount ( value , quote ) > ccount ( value , alternative )
441454 ? alternative
442455 : quote
443-
444- initializer +=
445- '=' +
456+ right =
446457 appliedQuote +
447458 stringifyEntitiesLight ( value , { subset : [ appliedQuote ] } ) +
448459 appliedQuote
449460 }
450461
451- result = attribute . name + initializer
462+ result = left + ( right ? '=' : '' ) + right
452463 }
453464
454- attributes . push ( ( isMultiFlow ? '\n ' : ' ' ) + result )
465+ serializedAttributes . push ( result )
455466 }
467+ }
456468
457- attributeValue = attributes . join ( '' ) + ( isMultiFlow ? '\n' : '' )
469+ let attributesOnTheirOwnLine = false
470+ const attributesOnOneLine = serializedAttributes . join ( ' ' )
471+
472+ if (
473+ // Block:
474+ node . type === 'mdxJsxFlowElement' &&
475+ // Including a line ending (expressions).
476+ ( / \r ? \n | \r / . test ( attributesOnOneLine ) ||
477+ // Current position (including `<tag`).
478+ tracker . current ( ) . now . column +
479+ // -1 because columns, +1 for ` ` before attributes.
480+ // Attributes joined by spaces.
481+ attributesOnOneLine . length +
482+ // ` />`.
483+ ( selfClosing ? ( tightSelfClosing ? 2 : 3 ) : 1 ) >
484+ printWidth )
485+ ) {
486+ attributesOnTheirOwnLine = true
458487 }
459488
460- const value =
461- '<' +
462- ( node . name || '' ) +
463- attributeValue +
464- ( selfClosing
465- ? ( tightSelfClosing || / [ \n ] $ / . test ( attributeValue ) ? '' : ' ' ) + '/'
466- : '' ) +
467- '>' +
468- ( node . children && node . children . length > 0
469- ? node . type === 'mdxJsxFlowElement'
470- ? '\n' + indent ( containerFlow ( node , context ) ) + '\n'
471- : containerPhrasing ( node , context , { before : '<' , after : '>' } )
472- : '' ) +
473- ( selfClosing ? '' : '</' + ( node . name || '' ) + '>' )
489+ if ( attributesOnTheirOwnLine ) {
490+ value += tracker . move (
491+ '\n' + indentLines ( serializedAttributes . join ( '\n' ) , map )
492+ )
493+ } else if ( attributesOnOneLine ) {
494+ value += tracker . move ( ' ' + attributesOnOneLine )
495+ }
496+
497+ if ( attributesOnTheirOwnLine ) {
498+ value += tracker . move ( '\n' )
499+ }
500+
501+ if ( selfClosing ) {
502+ value += tracker . move (
503+ ( tightSelfClosing || attributesOnTheirOwnLine ? '' : ' ' ) + '/'
504+ )
505+ }
506+
507+ value += tracker . move ( '>' )
508+
509+ if ( node . children && node . children . length > 0 ) {
510+ if ( node . type === 'mdxJsxFlowElement' ) {
511+ tracker . shift ( 2 )
512+ value += tracker . move ( '\n' )
513+ value += tracker . move (
514+ indentLines ( containerFlow ( node , context , tracker . current ( ) ) , map )
515+ )
516+ value += tracker . move ( '\n' )
517+ } else {
518+ value += tracker . move (
519+ containerPhrasing ( node , context , {
520+ ...tracker . current ( ) ,
521+ before : '<' ,
522+ after : '>'
523+ } )
524+ )
525+ }
526+ }
527+
528+ if ( ! selfClosing ) {
529+ value += tracker . move ( '</' + ( node . name || '' ) + '>' )
530+ }
474531
475532 exit ( )
476533 return value
477534 }
478535
536+ /** @type {ToMarkdownMap } */
537+ function map ( line , _ , blank ) {
538+ return ( blank ? '' : ' ' ) + line
539+ }
540+
479541 /**
480542 * @type {ToMarkdownHandle }
481543 */
482544 function peekElement ( ) {
483545 return '<'
484546 }
485-
486- /**
487- * @param {string } value
488- * @returns {string }
489- */
490- function indent ( value ) {
491- return indentLines ( value , map )
492-
493- /** @type {ToMarkdownMap } */
494- function map ( line , _ , blank ) {
495- return ( blank ? '' : ' ' ) + line
496- }
497- }
498547}
0 commit comments