Skip to content

Commit b14ec32

Browse files
committed
Add bulletOrdered, bulletOrderedOther options
Related to GH-18.
1 parent 0148aa6 commit b14ec32

File tree

7 files changed

+279
-20
lines changed

7 files changed

+279
-20
lines changed

lib/handle/list.js

+23-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import {containerFlow} from '../util/container-flow.js'
77
import {checkBullet} from '../util/check-bullet.js'
88
import {checkBulletOther} from '../util/check-bullet-other.js'
9+
import {checkBulletOrdered} from '../util/check-bullet-ordered.js'
10+
import {checkBulletOrderedOther} from '../util/check-bullet-ordered-other.js'
911
import {checkRule} from '../util/check-rule.js'
1012

1113
/**
@@ -16,25 +18,28 @@ export function list(node, parent, context) {
1618
const exit = context.enter('list')
1719
const bulletCurrent = context.bulletCurrent
1820
/** @type {string} */
19-
let bullet = checkBullet(context)
21+
let bullet = node.ordered ? checkBulletOrdered(context) : checkBullet(context)
2022
/** @type {string} */
21-
const bulletOther = checkBulletOther(context)
23+
const bulletOther = node.ordered
24+
? checkBulletOrderedOther(context)
25+
: checkBulletOther(context)
2226
const bulletLastUsed = context.bulletLastUsed
27+
let useDifferentMarker = false
2328

24-
if (node.ordered) {
25-
bullet = '.'
26-
} else {
27-
const firstListItem = node.children ? node.children[0] : undefined
28-
let useDifferentMarker = false
29+
if (
30+
parent &&
31+
// Explicit `other` set.
32+
(node.ordered
33+
? context.options.bulletOrderedOther
34+
: context.options.bulletOther) &&
35+
bulletLastUsed &&
36+
bullet === bulletLastUsed
37+
) {
38+
useDifferentMarker = true
39+
}
2940

30-
if (
31-
parent &&
32-
context.options.bulletOther &&
33-
bulletLastUsed &&
34-
bullet === bulletLastUsed
35-
) {
36-
useDifferentMarker = true
37-
}
41+
if (!node.ordered) {
42+
const firstListItem = node.children ? node.children[0] : undefined
3843

3944
// If there’s an empty first list item directly in two list items,
4045
// we have to use a different bullet:
@@ -90,10 +95,10 @@ export function list(node, parent, context) {
9095
}
9196
}
9297
}
98+
}
9399

94-
if (useDifferentMarker) {
95-
bullet = bulletOther
96-
}
100+
if (useDifferentMarker) {
101+
bullet = bulletOther
97102
}
98103

99104
context.bulletCurrent = bullet

lib/join.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ function joinDefaults(left, right, parent, context) {
2424
if (
2525
left.type === 'list' &&
2626
left.type === right.type &&
27-
((left.ordered && right.ordered) ||
28-
(!left.ordered && !right.ordered && !context.options.bulletOther))
27+
Boolean(left.ordered) === Boolean(right.ordered) &&
28+
!(left.ordered
29+
? context.options.bulletOrderedOther
30+
: context.options.bulletOther)
2931
) {
3032
return false
3133
}

lib/types.js

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
* @typedef Options
8080
* @property {'-'|'*'|'+'} [bullet]
8181
* @property {'-'|'*'|'+'} [bulletOther]
82+
* @property {'.'|')'} [bulletOrdered]
83+
* @property {'.'|')'} [bulletOrderedOther]
8284
* @property {boolean} [closeAtx]
8385
* @property {'_'|'*'} [emphasis]
8486
* @property {'~'|'`'} [fence]
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @typedef {import('../types.js').Context} Context
3+
* @typedef {import('../types.js').Options} Options
4+
*/
5+
6+
import {checkBulletOrdered} from './check-bullet-ordered.js'
7+
8+
/**
9+
* @param {Context} context
10+
* @returns {Exclude<Options['bulletOrdered'], undefined>}
11+
*/
12+
export function checkBulletOrderedOther(context) {
13+
const bulletOrdered = checkBulletOrdered(context)
14+
const bulletOrderedOther = context.options.bulletOrderedOther
15+
16+
if (!bulletOrderedOther) {
17+
return bulletOrdered === '.' ? ')' : '.'
18+
}
19+
20+
if (bulletOrderedOther !== '.' && bulletOrderedOther !== ')') {
21+
throw new Error(
22+
'Cannot serialize items with `' +
23+
bulletOrderedOther +
24+
'` for `options.bulletOrderedOther`, expected `*`, `+`, or `-`'
25+
)
26+
}
27+
28+
if (bulletOrderedOther === bulletOrdered) {
29+
throw new Error(
30+
'Expected `bulletOrdered` (`' +
31+
bulletOrdered +
32+
'`) and `bulletOrderedOther` (`' +
33+
bulletOrderedOther +
34+
'`) to be different'
35+
)
36+
}
37+
38+
return bulletOrderedOther
39+
}

lib/util/check-bullet-ordered.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @typedef {import('../types.js').Context} Context
3+
* @typedef {import('../types.js').Options} Options
4+
*/
5+
6+
/**
7+
* @param {Context} context
8+
* @returns {Exclude<Options['bulletOrdered'], undefined>}
9+
*/
10+
export function checkBulletOrdered(context) {
11+
const marker = context.options.bulletOrdered || '.'
12+
13+
if (marker !== '.' && marker !== ')') {
14+
throw new Error(
15+
'Cannot serialize items with `' +
16+
marker +
17+
'` for `options.bulletOrdered`, expected `.` or `)`'
18+
)
19+
}
20+
21+
return marker
22+
}

readme.md

+19
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,25 @@ There are three cases where the primary bullet can’t be used:
112112
`* a\n<!---->\n* b`, but if `bulletOther` is given explicitly, it will be
113113
used instead
114114

115+
###### `options.bulletOrdered`
116+
117+
Marker to use for bullets of items in ordered lists (`'.'` or `')'`, default:
118+
`'.'`).
119+
120+
###### `options.bulletOrderedOther`
121+
122+
Marker to use in certain cases where the primary bullet for ordered items
123+
doesn’t work (`'.'` or `')'`, default: none).
124+
125+
There is one case where the primary bullet for ordered items can’t be used:
126+
127+
* When two ordered lists appear next to each other: `1. a\n2) b`.
128+
CommonMark added support for `)` as a marker, but other markdown parsers
129+
do not support it.
130+
To solve for both, we instead inject an empty comment between the two lists:
131+
`1. a\n<!---->\n1. b`, but if `bulletOrderedOther` is given explicitly, it
132+
will be used instead
133+
115134
###### `options.closeAtx`
116135

117136
Whether to add the same number of number signs (`#`) at the end of an ATX

test/index.js

+170
Original file line numberDiff line numberDiff line change
@@ -2627,6 +2627,99 @@ test('listItem', (t) => {
26272627
'should not use a different bullet for an empty list item at non-head in two lists'
26282628
)
26292629

2630+
t.equal(
2631+
to(
2632+
{
2633+
type: 'list',
2634+
ordered: true,
2635+
children: [{type: 'listItem', children: []}]
2636+
},
2637+
{bulletOrdered: ')'}
2638+
),
2639+
'1)\n',
2640+
'should support `bulletOrdered`'
2641+
)
2642+
2643+
t.throws(
2644+
() => {
2645+
to(
2646+
{
2647+
type: 'list',
2648+
ordered: true,
2649+
children: [{type: 'listItem', children: []}]
2650+
},
2651+
// @ts-expect-error: runtime.
2652+
{bulletOrdered: '~'}
2653+
)
2654+
},
2655+
/Cannot serialize items with `~` for `options.bulletOrdered`/,
2656+
'should throw on a `bulletOrdered` that is invalid'
2657+
)
2658+
2659+
t.equal(
2660+
to(
2661+
{
2662+
type: 'root',
2663+
children: [
2664+
{
2665+
type: 'list',
2666+
ordered: true,
2667+
children: [{type: 'listItem', children: []}]
2668+
},
2669+
{
2670+
type: 'list',
2671+
ordered: true,
2672+
children: [{type: 'listItem', children: []}]
2673+
}
2674+
]
2675+
},
2676+
{bulletOrdered: '.', bulletOrderedOther: ')'}
2677+
),
2678+
'1.\n\n1)\n',
2679+
'should support `bulletOrderedOther`'
2680+
)
2681+
2682+
t.throws(
2683+
() => {
2684+
to(
2685+
{
2686+
type: 'list',
2687+
ordered: true,
2688+
children: [{type: 'listItem', children: []}]
2689+
},
2690+
// @ts-expect-error: runtime.
2691+
{bulletOrderedOther: '~'}
2692+
)
2693+
},
2694+
/Cannot serialize items with `~` for `options.bulletOrderedOther`/,
2695+
'should throw on a `bulletOrderedOther` that is invalid'
2696+
)
2697+
2698+
t.throws(
2699+
() => {
2700+
to(
2701+
{
2702+
type: 'root',
2703+
children: [
2704+
{
2705+
type: 'list',
2706+
ordered: true,
2707+
children: [{type: 'listItem', children: []}]
2708+
},
2709+
{
2710+
type: 'list',
2711+
ordered: true,
2712+
children: [{type: 'listItem', children: []}]
2713+
}
2714+
]
2715+
},
2716+
{bulletOrdered: '.', bulletOrderedOther: '.'}
2717+
)
2718+
},
2719+
/Expected `bulletOrdered` \(`.`\) and `bulletOrderedOther` \(`.`\) to be different/,
2720+
'should throw on a `bulletOrderedOther` that matches `bulletOrdered`'
2721+
)
2722+
26302723
t.end()
26312724
})
26322725

@@ -3318,5 +3411,82 @@ test('roundtrip', (t) => {
33183411
'should roundtrip different lists w/ `bulletOther` and lists that could turn into thematic breaks (6)'
33193412
)
33203413

3414+
tree = from('1. a\n1) b')
3415+
3416+
t.deepEqual(
3417+
removePosition(tree, true),
3418+
removePosition(
3419+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3420+
true
3421+
),
3422+
'should roundtrip different lists w/ `bulletOrderedOther`'
3423+
)
3424+
3425+
tree = from('1. ---\n1) 1. 1)\n1. b')
3426+
3427+
t.deepEqual(
3428+
removePosition(tree, true),
3429+
removePosition(
3430+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3431+
true
3432+
),
3433+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (1)'
3434+
)
3435+
3436+
tree = from('1. 1. 1)\n1) ---\n1. b')
3437+
3438+
t.deepEqual(
3439+
removePosition(tree, true),
3440+
removePosition(
3441+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3442+
true
3443+
),
3444+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (2)'
3445+
)
3446+
3447+
tree = from('1. 1. 1)\n1. 1.')
3448+
3449+
t.deepEqual(
3450+
removePosition(tree, true),
3451+
removePosition(
3452+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3453+
true
3454+
),
3455+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (3)'
3456+
)
3457+
3458+
tree = from('1. 1) 1.\n 1.\n 1)\n 1.')
3459+
3460+
t.deepEqual(
3461+
removePosition(tree, true),
3462+
removePosition(
3463+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3464+
true
3465+
),
3466+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (4)'
3467+
)
3468+
3469+
tree = from('1. 1) 1.\n 1) 1.\n 1)\n 1.')
3470+
3471+
t.deepEqual(
3472+
removePosition(tree, true),
3473+
removePosition(
3474+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3475+
true
3476+
),
3477+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (5)'
3478+
)
3479+
3480+
tree = from('1. 1)\n1. 1.\n 1)\n 1.')
3481+
3482+
t.deepEqual(
3483+
removePosition(tree, true),
3484+
removePosition(
3485+
from(to(tree, {bulletOrdered: '.', bulletOrderedOther: ')'})),
3486+
true
3487+
),
3488+
'should roundtrip different lists w/ `bulletOrderedOther` and lists that could turn into thematic breaks (6)'
3489+
)
3490+
33213491
t.end()
33223492
})

0 commit comments

Comments
 (0)