Skip to content

Commit e9f71aa

Browse files
committed
Add safe helper on state
1 parent 19301e7 commit e9f71aa

File tree

11 files changed

+130
-30
lines changed

11 files changed

+130
-30
lines changed

lib/handle/code.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import {longestStreak} from 'longest-streak'
1010
import {formatCodeAsIndented} from '../util/format-code-as-indented.js'
1111
import {checkFence} from '../util/check-fence.js'
12-
import {safe} from '../util/safe.js'
1312
import {track} from '../util/track.js'
1413

1514
/**
@@ -39,7 +38,7 @@ export function code(node, _, state, info) {
3938
if (node.lang) {
4039
const subexit = state.enter(`codeFencedLang${suffix}`)
4140
value += tracker.move(
42-
safe(state, node.lang, {
41+
state.safe(node.lang, {
4342
before: value,
4443
after: ' ',
4544
encode: ['`'],
@@ -53,7 +52,7 @@ export function code(node, _, state, info) {
5352
const subexit = state.enter(`codeFencedMeta${suffix}`)
5453
value += tracker.move(' ')
5554
value += tracker.move(
56-
safe(state, node.meta, {
55+
state.safe(node.meta, {
5756
before: value,
5857
after: '\n',
5958
encode: ['`'],

lib/handle/definition.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import {association} from '../util/association.js'
99
import {checkQuote} from '../util/check-quote.js'
10-
import {safe} from '../util/safe.js'
1110
import {track} from '../util/track.js'
1211

1312
/**
@@ -25,7 +24,7 @@ export function definition(node, _, state, info) {
2524
const tracker = track(info)
2625
let value = tracker.move('[')
2726
value += tracker.move(
28-
safe(state, association(node), {
27+
state.safe(association(node), {
2928
before: value,
3029
after: ']',
3130
...tracker.current()
@@ -44,14 +43,14 @@ export function definition(node, _, state, info) {
4443
subexit = state.enter('destinationLiteral')
4544
value += tracker.move('<')
4645
value += tracker.move(
47-
safe(state, node.url, {before: value, after: '>', ...tracker.current()})
46+
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
4847
)
4948
value += tracker.move('>')
5049
} else {
5150
// No whitespace, raw is prettier.
5251
subexit = state.enter('destinationRaw')
5352
value += tracker.move(
54-
safe(state, node.url, {
53+
state.safe(node.url, {
5554
before: value,
5655
after: node.title ? ' ' : '\n',
5756
...tracker.current()
@@ -65,7 +64,7 @@ export function definition(node, _, state, info) {
6564
subexit = state.enter(`title${suffix}`)
6665
value += tracker.move(' ' + quote)
6766
value += tracker.move(
68-
safe(state, node.title, {
67+
state.safe(node.title, {
6968
before: value,
7069
after: quote,
7170
...tracker.current()

lib/handle/image-reference.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import {association} from '../util/association.js'
9-
import {safe} from '../util/safe.js'
109
import {track} from '../util/track.js'
1110

1211
imageReference.peek = imageReferencePeek
@@ -24,7 +23,7 @@ export function imageReference(node, _, state, info) {
2423
let subexit = state.enter('label')
2524
const tracker = track(info)
2625
let value = tracker.move('![')
27-
const alt = safe(state, node.alt, {
26+
const alt = state.safe(node.alt, {
2827
before: value,
2928
after: ']',
3029
...tracker.current()
@@ -40,7 +39,7 @@ export function imageReference(node, _, state, info) {
4039
// up making a `shortcut` reference, because then there is no brace output.
4140
// Practically, in that case, there is no content, so it doesn’t matter that
4241
// we’ve tracked one too many characters.
43-
const reference = safe(state, association(node), {
42+
const reference = state.safe(association(node), {
4443
before: value,
4544
after: ']',
4645
...tracker.current()

lib/handle/image.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import {checkQuote} from '../util/check-quote.js'
9-
import {safe} from '../util/safe.js'
109
import {track} from '../util/track.js'
1110

1211
image.peek = imagePeek
@@ -26,7 +25,7 @@ export function image(node, _, state, info) {
2625
const tracker = track(info)
2726
let value = tracker.move('![')
2827
value += tracker.move(
29-
safe(state, node.alt, {before: value, after: ']', ...tracker.current()})
28+
state.safe(node.alt, {before: value, after: ']', ...tracker.current()})
3029
)
3130
value += tracker.move('](')
3231

@@ -41,14 +40,14 @@ export function image(node, _, state, info) {
4140
subexit = state.enter('destinationLiteral')
4241
value += tracker.move('<')
4342
value += tracker.move(
44-
safe(state, node.url, {before: value, after: '>', ...tracker.current()})
43+
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
4544
)
4645
value += tracker.move('>')
4746
} else {
4847
// No whitespace, raw is prettier.
4948
subexit = state.enter('destinationRaw')
5049
value += tracker.move(
51-
safe(state, node.url, {
50+
state.safe(node.url, {
5251
before: value,
5352
after: node.title ? ' ' : ')',
5453
...tracker.current()
@@ -62,7 +61,7 @@ export function image(node, _, state, info) {
6261
subexit = state.enter(`title${suffix}`)
6362
value += tracker.move(' ' + quote)
6463
value += tracker.move(
65-
safe(state, node.title, {
64+
state.safe(node.title, {
6665
before: value,
6766
after: quote,
6867
...tracker.current()

lib/handle/link-reference.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import {association} from '../util/association.js'
9-
import {safe} from '../util/safe.js'
109
import {track} from '../util/track.js'
1110

1211
linkReference.peek = linkReferencePeek
@@ -40,7 +39,7 @@ export function linkReference(node, _, state, info) {
4039
// up making a `shortcut` reference, because then there is no brace output.
4140
// Practically, in that case, there is no content, so it doesn’t matter that
4241
// we’ve tracked one too many characters.
43-
const reference = safe(state, association(node), {
42+
const reference = state.safe(association(node), {
4443
before: value,
4544
after: ']',
4645
...tracker.current()

lib/handle/link.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import {checkQuote} from '../util/check-quote.js'
1010
import {formatLinkAsAutolink} from '../util/format-link-as-autolink.js'
11-
import {safe} from '../util/safe.js'
1211
import {track} from '../util/track.js'
1312

1413
link.peek = linkPeek
@@ -70,14 +69,14 @@ export function link(node, _, state, info) {
7069
subexit = state.enter('destinationLiteral')
7170
value += tracker.move('<')
7271
value += tracker.move(
73-
safe(state, node.url, {before: value, after: '>', ...tracker.current()})
72+
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
7473
)
7574
value += tracker.move('>')
7675
} else {
7776
// No whitespace, raw is prettier.
7877
subexit = state.enter('destinationRaw')
7978
value += tracker.move(
80-
safe(state, node.url, {
79+
state.safe(node.url, {
8180
before: value,
8281
after: node.title ? ' ' : ')',
8382
...tracker.current()
@@ -91,7 +90,7 @@ export function link(node, _, state, info) {
9190
subexit = state.enter(`title${suffix}`)
9291
value += tracker.move(' ' + quote)
9392
value += tracker.move(
94-
safe(state, node.title, {
93+
state.safe(node.title, {
9594
before: value,
9695
after: quote,
9796
...tracker.current()

lib/handle/text.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
* @typedef {import('../types.js').Info} Info
66
*/
77

8-
import {safe} from '../util/safe.js'
9-
108
/**
119
* @param {Text} node
1210
* @param {Parent | undefined} _
@@ -15,5 +13,5 @@ import {safe} from '../util/safe.js'
1513
* @returns {string}
1614
*/
1715
export function text(node, _, state, info) {
18-
return safe(state, node.value, info)
16+
return state.safe(node.value, info)
1917
}

lib/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @typedef {import('./types.js').Options} Options
88
* @typedef {import('./types.js').Parent} Parent
99
* @typedef {import('./types.js').PhrasingContent} PhrasingContent
10+
* @typedef {import('./types.js').SafeConfig} SafeConfig
1011
* @typedef {import('./types.js').State} State
1112
* @typedef {import('./types.js').TrackFields} TrackFields
1213
*/
@@ -19,6 +20,7 @@ import {unsafe} from './unsafe.js'
1920
import {containerPhrasing} from './util/container-phrasing.js'
2021
import {containerFlow} from './util/container-flow.js'
2122
import {indentLines} from './util/indent-lines.js'
23+
import {safe} from './util/safe.js'
2224

2325
/**
2426
* Turn an mdast syntax tree into markdown.
@@ -37,6 +39,7 @@ export function toMarkdown(tree, options = {}) {
3739
indentLines,
3840
containerPhrasing: containerPhrasingBound,
3941
containerFlow: containerFlowBound,
42+
safe: safeBound,
4043
stack: [],
4144
unsafe: [],
4245
join: [],
@@ -150,3 +153,32 @@ function containerPhrasingBound(parent, info) {
150153
function containerFlowBound(parent, info) {
151154
return containerFlow(parent, this, info)
152155
}
156+
157+
/**
158+
* Make a string safe for embedding in markdown constructs.
159+
*
160+
* In markdown, almost all punctuation characters can, in certain cases,
161+
* result in something.
162+
* Whether they do is highly subjective to where they happen and in what
163+
* they happen.
164+
*
165+
* To solve this, `mdast-util-to-markdown` tracks:
166+
*
167+
* * Characters before and after something;
168+
* * What “constructs” we are in.
169+
*
170+
* This information is then used by this function to escape or encode
171+
* special characters.
172+
*
173+
* @this {State}
174+
* Info passed around about the current state.
175+
* @param {string | null | undefined} value
176+
* Raw value to make safe.
177+
* @param {SafeConfig} config
178+
* Configuration.
179+
* @returns {string}
180+
* Serialized markdown safe for embedding.
181+
*/
182+
function safeBound(value, config) {
183+
return safe(this, value, config)
184+
}

lib/types.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,39 @@
7575
* @returns {string}
7676
* Serialized children, joined by (blank) lines.
7777
*
78+
* @typedef SafeEncodeFields
79+
* Extra configuration for `safe`
80+
* @property {Array<string> | null | undefined} [encode]
81+
* Extra characters that *must* be encoded (as character references) instead
82+
* of escaped (character escapes).
83+
*
84+
* Only ASCII punctuation will use character escapes, so you never need to
85+
* pass non-ASCII-punctuation here.
86+
*
87+
* @typedef {SafeFields & SafeEncodeFields} SafeConfig
88+
*
89+
* @callback Safe
90+
* Make a string safe for embedding in markdown constructs.
91+
*
92+
* In markdown, almost all punctuation characters can, in certain cases,
93+
* result in something.
94+
* Whether they do is highly subjective to where they happen and in what
95+
* they happen.
96+
*
97+
* To solve this, `mdast-util-to-markdown` tracks:
98+
*
99+
* * Characters before and after something;
100+
* * What “constructs” we are in.
101+
*
102+
* This information is then used by this function to escape or encode
103+
* special characters.
104+
* @param {string | null | undefined} input
105+
* Raw value to make safe.
106+
* @param {SafeConfig} config
107+
* Configuration.
108+
* @returns {string}
109+
* Serialized markdown safe for embedding.
110+
*
78111
* @callback Enter
79112
* Enter something.
80113
* @param {ConstructName} name
@@ -99,6 +132,8 @@
99132
* Serialize the children of a parent that contains phrasing children.
100133
* @property {ContainerFlow} containerFlow
101134
* Serialize the children of a parent that contains flow children.
135+
* @property {Safe} safe
136+
* Serialize the children of a parent that contains flow children.
102137
* @property {Enter} enter
103138
* Enter a construct (returns a corresponding exit function).
104139
* @property {Options} options

lib/util/safe.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
/**
22
* @typedef {import('../types.js').State} State
3-
* @typedef {import('../types.js').SafeFields} SafeFields
3+
* @typedef {import('../types.js').SafeConfig} SafeConfig
44
*/
55

66
import {patternCompile} from './pattern-compile.js'
77
import {patternInScope} from './pattern-in-scope.js'
88

99
/**
10+
* Make a string safe for embedding in markdown constructs.
11+
*
12+
* In markdown, almost all punctuation characters can, in certain cases,
13+
* result in something.
14+
* Whether they do is highly subjective to where they happen and in what
15+
* they happen.
16+
*
17+
* To solve this, `mdast-util-to-markdown` tracks:
18+
*
19+
* * Characters before and after something;
20+
* * What “constructs” we are in.
21+
*
22+
* This information is then used by this function to escape or encode
23+
* special characters.
24+
*
1025
* @param {State} state
26+
* Info passed around about the current state.
1127
* @param {string | null | undefined} input
12-
* @param {SafeFields & {encode?: Array<string>}} config
28+
* Raw value to make safe.
29+
* @param {SafeConfig} config
30+
* Configuration.
1331
* @returns {string}
32+
* Serialized markdown safe for embedding.
1433
*/
1534
export function safe(state, input, config) {
1635
const value = (config.before || '') + (input || '') + (config.after || '')

0 commit comments

Comments
 (0)