25
25
* @property {boolean } [tightSelfClosing=false]
26
26
* Do not use an extra space when closing self-closing elements: `<img/>`
27
27
* 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.
28
35
*/
29
36
30
37
import { ccount } from 'ccount'
@@ -35,6 +42,7 @@ import {stringifyEntitiesLight} from 'stringify-entities'
35
42
import { containerFlow } from 'mdast-util-to-markdown/lib/util/container-flow.js'
36
43
import { containerPhrasing } from 'mdast-util-to-markdown/lib/util/container-phrasing.js'
37
44
import { indentLines } from 'mdast-util-to-markdown/lib/util/indent-lines.js'
45
+ import { track } from 'mdast-util-to-markdown/lib/util/track.js'
38
46
39
47
/** @return {FromMarkdownExtension } */
40
48
export function mdxJsxFromMarkdown ( ) {
@@ -366,7 +374,12 @@ export function mdxJsxFromMarkdown() {
366
374
* @returns {ToMarkdownExtension }
367
375
*/
368
376
export function mdxJsxToMarkdown ( options = { } ) {
369
- const { quote = '"' , quoteSmart, tightSelfClosing} = options
377
+ const {
378
+ quote = '"' ,
379
+ quoteSmart,
380
+ tightSelfClosing,
381
+ printWidth = Number . POSITIVE_INFINITY
382
+ } = options
370
383
const alternative = quote === '"' ? "'" : '"'
371
384
372
385
if ( quote !== '"' && quote !== "'" ) {
@@ -397,28 +410,26 @@ export function mdxJsxToMarkdown(options = {}) {
397
410
* @param {MdxJsxFlowElement|MdxJsxTextElement } node
398
411
*/
399
412
// eslint-disable-next-line complexity
400
- function mdxElement ( node , _ , context ) {
413
+ function mdxElement ( node , _ , context , safeOptions ) {
414
+ const tracker = track ( safeOptions )
401
415
const selfClosing =
402
416
node . name && ( ! node . children || node . children . length === 0 )
403
417
const exit = context . enter ( node . type )
404
- let attributeValue = ''
405
418
let index = - 1
406
419
/** @type {Array<string> } */
407
- const attributes = [ ]
408
- /** @type {string } */
409
- let result
420
+ const serializedAttributes = [ ]
421
+ let value = tracker . move ( '<' + ( node . name || '' ) )
410
422
411
423
// None.
412
424
if ( node . attributes && node . attributes . length > 0 ) {
413
425
if ( ! node . name ) {
414
426
throw new Error ( 'Cannot serialize fragment w/ attributes' )
415
427
}
416
428
417
- const isMultiFlow =
418
- node . type === 'mdxJsxFlowElement' && node . attributes . length > 1
419
-
420
429
while ( ++ index < node . attributes . length ) {
421
430
const attribute = node . attributes [ index ]
431
+ /** @type {string } */
432
+ let result
422
433
423
434
if ( attribute . type === 'mdxJsxExpressionAttribute' ) {
424
435
result = '{' + ( attribute . value || '' ) + '}'
@@ -428,71 +439,109 @@ export function mdxJsxToMarkdown(options = {}) {
428
439
}
429
440
430
441
const value = attribute . value
431
- let initializer = ''
442
+ const left = attribute . name
443
+ /** @type {string } */
444
+ let right = ''
432
445
433
446
if ( value === undefined || value === null ) {
434
447
// Empty.
435
448
} else if ( typeof value === 'object' ) {
436
- initializer = '= {' + ( value . value || '' ) + '}'
449
+ right = '{' + ( value . value || '' ) + '}'
437
450
} else {
438
451
// If the alternative is less common than `quote`, switch.
439
452
const appliedQuote =
440
453
quoteSmart && ccount ( value , quote ) > ccount ( value , alternative )
441
454
? alternative
442
455
: quote
443
-
444
- initializer +=
445
- '=' +
456
+ right =
446
457
appliedQuote +
447
458
stringifyEntitiesLight ( value , { subset : [ appliedQuote ] } ) +
448
459
appliedQuote
449
460
}
450
461
451
- result = attribute . name + initializer
462
+ result = left + ( right ? '=' : '' ) + right
452
463
}
453
464
454
- attributes . push ( ( isMultiFlow ? '\n ' : ' ' ) + result )
465
+ serializedAttributes . push ( result )
455
466
}
467
+ }
456
468
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
458
487
}
459
488
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
+ }
474
531
475
532
exit ( )
476
533
return value
477
534
}
478
535
536
+ /** @type {ToMarkdownMap } */
537
+ function map ( line , _ , blank ) {
538
+ return ( blank ? '' : ' ' ) + line
539
+ }
540
+
479
541
/**
480
542
* @type {ToMarkdownHandle }
481
543
*/
482
544
function peekElement ( ) {
483
545
return '<'
484
546
}
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
- }
498
547
}
0 commit comments