5
5
* @typedef {import('micromark-util-types').Token } Token
6
6
* @typedef {import('micromark-util-types').TokenizeContext } TokenizeContext
7
7
* @typedef {import('micromark-util-types').Value } Value
8
+ *
8
9
* @typedef {import('unist').Parent } UnistParent
9
10
* @typedef {import('unist').Point } Point
11
+ *
10
12
* @typedef {import('mdast').PhrasingContent } PhrasingContent
13
+ * @typedef {import('mdast').StaticPhrasingContent } StaticPhrasingContent
11
14
* @typedef {import('mdast').Content } Content
12
- * @typedef {Root|Content } Node
13
- * @typedef {Extract<Node, UnistParent> } Parent
14
15
* @typedef {import('mdast').Break } Break
15
16
* @typedef {import('mdast').Blockquote } Blockquote
16
17
* @typedef {import('mdast').Code } Code
30
31
* @typedef {import('mdast').Strong } Strong
31
32
* @typedef {import('mdast').Text } Text
32
33
* @typedef {import('mdast').ThematicBreak } ThematicBreak
34
+ * @typedef {import('mdast').ReferenceType } ReferenceType
35
+ */
36
+
37
+ /**
38
+ * @typedef {Root | Content } Node
39
+ * @typedef {Extract<Node, UnistParent> } Parent
33
40
*
34
- * @typedef {UnistParent & {type: 'fragment', children: Array<PhrasingContent>} } Fragment
41
+ * @typedef {Omit< UnistParent, 'type' | 'children'> & {type: 'fragment', children: Array<PhrasingContent>} } Fragment
35
42
*/
36
43
37
44
/**
38
- * @typedef _CompileDataFields
39
- * @property {boolean|undefined } expectingFirstListItemValue
40
- * @property {boolean|undefined } flowCodeInside
41
- * @property {boolean|undefined } setextHeadingSlurpLineEnding
42
- * @property {boolean|undefined } atHardBreak
43
- * @property {'collapsed'|'full' } referenceType
44
- * @property {boolean|undefined } inReference
45
- * @property {'characterReferenceMarkerHexadecimal'|'characterReferenceMarkerNumeric' } characterReferenceType
45
+ * @typedef CompileData
46
+ * State.
47
+ * @property {boolean | undefined } [atHardBreak]
48
+ * Whether we’re inside a hard break.
49
+ * @property {'characterReferenceMarkerHexadecimal' | 'characterReferenceMarkerNumeric' | undefined } [characterReferenceType]
50
+ * Current character reference type.
51
+ * @property {boolean | undefined } [expectingFirstListItemValue]
52
+ * Whether a first list item value (`1` in `1. a`) is expected.
53
+ * @property {boolean | undefined } [flowCodeInside]
54
+ * Whether we’re in flow code.
55
+ * @property {boolean | undefined } [inReference]
56
+ * Whether we’re in a reference.
57
+ * @property {boolean | undefined } [setextHeadingSlurpLineEnding]
58
+ * Whether we’re expecting a line ending from a setext heading, which can be slurped.
59
+ * @property {'collapsed' | 'full' | undefined } [referenceType]
60
+ * Current reference.
46
61
*
47
- * @typedef {Record<string, unknown> & Partial<_CompileDataFields> } CompileData
62
+ * @callback Transform
63
+ * Extra transform, to change the AST afterwards.
64
+ * @param {Root } tree
65
+ * Tree to transform.
66
+ * @returns {Root | undefined | null | void }
67
+ * New tree (the default is to assume the tree is mutated).
68
+ *
69
+ * @callback Handle
70
+ * Handle a token.
71
+ * @param {CompileContext } this
72
+ * Context.
73
+ * @param {Token } token
74
+ * Current token.
75
+ * @returns {void }
76
+ * Nothing.
48
77
*
49
- * @typedef {(tree: Root) => Root|void } Transform
50
- * @typedef {(this: CompileContext, token: Token) => void } Handle
51
78
* @typedef {Record<string, Handle> } Handles
52
79
* Token types mapping to handles
53
- * @typedef {Record<string, Record<string, unknown>|Array<unknown>> & {canContainEols: Array<string>, transforms: Array<Transform>, enter: Handles, exit: Handles} } NormalizedExtension
54
- * @typedef {Partial<NormalizedExtension> } Extension
55
- * An mdast extension changes how markdown tokens are turned into mdast.
56
80
*
57
- * @typedef {(this: Omit<CompileContext, 'sliceSerialize'>, left: Token|undefined, right: Token) => void } OnEnterError
58
- * @typedef {(this: Omit<CompileContext, 'sliceSerialize'>, left: Token, right: Token) => void } OnExitError
81
+ * @callback OnEnterError
82
+ * Handle the case where the `right` token is open, but it is closed (by the
83
+ * `left` token) or because we reached the end of the document.
84
+ * @param {Omit<CompileContext, 'sliceSerialize'> } this
85
+ * Context.
86
+ * @param {Token | undefined } left
87
+ * Left token.
88
+ * @param {Token } right
89
+ * Right token.
90
+ * @returns {void }
91
+ * Nothing.
92
+ *
93
+ * @callback OnExitError
94
+ * Handle the case where the `right` token is open but it is closed by
95
+ * exitting the `left` token.
96
+ * @param {Omit<CompileContext, 'sliceSerialize'> } this
97
+ * Context.
98
+ * @param {Token } left
99
+ * Left token.
100
+ * @param {Token } right
101
+ * Right token.
102
+ * @returns {void }
103
+ * Nothing.
104
+ *
105
+ * @typedef {[Token, OnEnterError | undefined] } TokenTuple
106
+ * Open token on the stack, with an optional error handler for when
107
+ * that token isn’t closed properly.
108
+ */
109
+
110
+ /**
111
+ * @typedef Config
112
+ * Configuration.
113
+ *
114
+ * We have our defaults, but extensions will add more.
115
+ * @property {Array<string> } canContainEols
116
+ * Token types where line endings are used.
117
+ * @property {Handles } enter
118
+ * Opening handles.
119
+ * @property {Handles } exit
120
+ * Closing handles.
121
+ * @property {Array<Transform> } transforms
122
+ * Tree transforms.
123
+ *
124
+ * @typedef {Partial<Config> } Extension
125
+ * Change how markdown tokens from micromark are turned into mdast.
59
126
*
60
127
* @typedef CompileContext
61
128
* mdast compiler context
62
129
* @property {Array<Node | Fragment> } stack
63
- * @property {Array<[Token, OnEnterError|undefined]> } tokenStack
64
- * @property {(key: string, value?: unknown) => void } setData
130
+ * Stack of nodes.
131
+ * @property {Array<TokenTuple> } tokenStack
132
+ * Stack of tokens.
133
+ * @property {<Key extends keyof CompileData>(key: Key, value?: CompileData[Key]) => void } setData
65
134
* Set data into the key-value store.
66
- * @property {<K extends string >(key: K ) => CompileData[K ] } getData
135
+ * @property {<Key extends keyof CompileData >(key: Key ) => CompileData[Key ] } getData
67
136
* Get data from the key-value store.
68
137
* @property {(this: CompileContext) => void } buffer
69
138
* Capture some of the output data.
70
139
* @property {(this: CompileContext) => string } resume
71
140
* Stop capturing and access the output data.
72
- * @property {<N extends Node>(this: CompileContext, node: N , token: Token, onError?: OnEnterError) => N } enter
141
+ * @property {<Kind extends Node>(this: CompileContext, node: Kind , token: Token, onError?: OnEnterError) => Kind } enter
73
142
* Enter a token.
74
143
* @property {(this: CompileContext, token: Token, onError?: OnExitError) => Node } exit
75
144
* Exit a token.
76
145
* @property {TokenizeContext['sliceSerialize'] } sliceSerialize
77
146
* Get the string value of a token.
78
- * @property {NormalizedExtension } config
147
+ * @property {Config } config
79
148
* Configuration.
80
149
*
81
- * @typedef {{mdastExtensions?: Array<Extension|Array<Extension>>} } FromMarkdownOptions
150
+ * @typedef FromMarkdownOptions
151
+ * Configuration for how to build mdast.
152
+ * @property {Array<Extension | Array<Extension>> | null | undefined } [mdastExtensions]
153
+ * Extensions to change how tokens are turned into a tree.
154
+ *
82
155
* @typedef {ParseOptions & FromMarkdownOptions } Options
83
156
*/
84
157
158
+ // To do: micromark: create a registry of tokens?
159
+ // To do: next major: don’t return given `Node` from `enter`.
160
+ // To do: next major: remove setter/getter.
161
+
85
162
import { ok as assert } from 'uvu/assert'
86
163
import { toString } from 'mdast-util-to-string'
87
164
import { parse } from 'micromark/lib/parse.js'
@@ -99,22 +176,27 @@ import {stringifyPosition} from 'unist-util-stringify-position'
99
176
const own = { } . hasOwnProperty
100
177
101
178
/**
102
- * @param value Markdown to parse (`string` or `Buffer`).
103
- * @param [encoding] Character encoding to understand `value` as when it’s a `Buffer` (`string`, default: `'utf8'`).
104
- * @param [options] Configuration
179
+ * @param value
180
+ * Markdown to parse (`string` or `Buffer`).
181
+ * @param encoding
182
+ * Character encoding to understand `value` as when it’s a `Buffer` (`string`, default: `'utf8'`).
183
+ * @param options
184
+ * Configuration.
185
+ * @returns
186
+ * mdast tree.
105
187
*/
106
188
export const fromMarkdown =
107
189
/**
108
190
* @type {(
109
- * ((value: Value, encoding: Encoding, options?: Options) => Root) &
110
- * ((value: Value, options?: Options) => Root)
191
+ * ((value: Value, encoding: Encoding, options?: Options | null | undefined ) => Root) &
192
+ * ((value: Value, options?: Options | null | undefined ) => Root)
111
193
* )}
112
194
*/
113
195
(
114
196
/**
115
197
* @param {Value } value
116
- * @param {Encoding } [encoding]
117
- * @param {Options } [options]
198
+ * @param {Encoding | Options | null | undefined } [encoding]
199
+ * @param {Options | null | undefined } [options]
118
200
* @returns {Root }
119
201
*/
120
202
function ( value , encoding , options ) {
@@ -125,6 +207,7 @@ export const fromMarkdown =
125
207
126
208
return compiler ( options ) (
127
209
postprocess (
210
+ // @ts -expect-error: micromark types need to accept `null`.
128
211
parse ( options ) . document ( ) . write ( preprocess ( ) ( value , encoding , true ) )
129
212
)
130
213
)
@@ -134,140 +217,130 @@ export const fromMarkdown =
134
217
/**
135
218
* Note this compiler only understand complete buffering, not streaming.
136
219
*
137
- * @param {Options } [options]
220
+ * @param {Options | null | undefined } [options]
138
221
*/
139
- function compiler ( options = { } ) {
140
- /** @type {NormalizedExtension } */
141
- // @ts -expect-error: our base has all required fields, so the result will too.
142
- const config = configure (
143
- {
144
- transforms : [ ] ,
145
- canContainEols : [
146
- 'emphasis' ,
147
- 'fragment' ,
148
- 'heading' ,
149
- 'paragraph' ,
150
- 'strong'
151
- ] ,
152
- enter : {
153
- autolink : opener ( link ) ,
154
- autolinkProtocol : onenterdata ,
155
- autolinkEmail : onenterdata ,
156
- atxHeading : opener ( heading ) ,
157
- blockQuote : opener ( blockQuote ) ,
158
- characterEscape : onenterdata ,
159
- characterReference : onenterdata ,
160
- codeFenced : opener ( codeFlow ) ,
161
- codeFencedFenceInfo : buffer ,
162
- codeFencedFenceMeta : buffer ,
163
- codeIndented : opener ( codeFlow , buffer ) ,
164
- codeText : opener ( codeText , buffer ) ,
165
- codeTextData : onenterdata ,
166
- data : onenterdata ,
167
- codeFlowValue : onenterdata ,
168
- definition : opener ( definition ) ,
169
- definitionDestinationString : buffer ,
170
- definitionLabelString : buffer ,
171
- definitionTitleString : buffer ,
172
- emphasis : opener ( emphasis ) ,
173
- hardBreakEscape : opener ( hardBreak ) ,
174
- hardBreakTrailing : opener ( hardBreak ) ,
175
- htmlFlow : opener ( html , buffer ) ,
176
- htmlFlowData : onenterdata ,
177
- htmlText : opener ( html , buffer ) ,
178
- htmlTextData : onenterdata ,
179
- image : opener ( image ) ,
180
- label : buffer ,
181
- link : opener ( link ) ,
182
- listItem : opener ( listItem ) ,
183
- listItemValue : onenterlistitemvalue ,
184
- listOrdered : opener ( list , onenterlistordered ) ,
185
- listUnordered : opener ( list ) ,
186
- paragraph : opener ( paragraph ) ,
187
- reference : onenterreference ,
188
- referenceString : buffer ,
189
- resourceDestinationString : buffer ,
190
- resourceTitleString : buffer ,
191
- setextHeading : opener ( heading ) ,
192
- strong : opener ( strong ) ,
193
- thematicBreak : opener ( thematicBreak )
194
- } ,
195
- exit : {
196
- atxHeading : closer ( ) ,
197
- atxHeadingSequence : onexitatxheadingsequence ,
198
- autolink : closer ( ) ,
199
- autolinkEmail : onexitautolinkemail ,
200
- autolinkProtocol : onexitautolinkprotocol ,
201
- blockQuote : closer ( ) ,
202
- characterEscapeValue : onexitdata ,
203
- characterReferenceMarkerHexadecimal : onexitcharacterreferencemarker ,
204
- characterReferenceMarkerNumeric : onexitcharacterreferencemarker ,
205
- characterReferenceValue : onexitcharacterreferencevalue ,
206
- codeFenced : closer ( onexitcodefenced ) ,
207
- codeFencedFence : onexitcodefencedfence ,
208
- codeFencedFenceInfo : onexitcodefencedfenceinfo ,
209
- codeFencedFenceMeta : onexitcodefencedfencemeta ,
210
- codeFlowValue : onexitdata ,
211
- codeIndented : closer ( onexitcodeindented ) ,
212
- codeText : closer ( onexitcodetext ) ,
213
- codeTextData : onexitdata ,
214
- data : onexitdata ,
215
- definition : closer ( ) ,
216
- definitionDestinationString : onexitdefinitiondestinationstring ,
217
- definitionLabelString : onexitdefinitionlabelstring ,
218
- definitionTitleString : onexitdefinitiontitlestring ,
219
- emphasis : closer ( ) ,
220
- hardBreakEscape : closer ( onexithardbreak ) ,
221
- hardBreakTrailing : closer ( onexithardbreak ) ,
222
- htmlFlow : closer ( onexithtmlflow ) ,
223
- htmlFlowData : onexitdata ,
224
- htmlText : closer ( onexithtmltext ) ,
225
- htmlTextData : onexitdata ,
226
- image : closer ( onexitimage ) ,
227
- label : onexitlabel ,
228
- labelText : onexitlabeltext ,
229
- lineEnding : onexitlineending ,
230
- link : closer ( onexitlink ) ,
231
- listItem : closer ( ) ,
232
- listOrdered : closer ( ) ,
233
- listUnordered : closer ( ) ,
234
- paragraph : closer ( ) ,
235
- referenceString : onexitreferencestring ,
236
- resourceDestinationString : onexitresourcedestinationstring ,
237
- resourceTitleString : onexitresourcetitlestring ,
238
- resource : onexitresource ,
239
- setextHeading : closer ( onexitsetextheading ) ,
240
- setextHeadingLineSequence : onexitsetextheadinglinesequence ,
241
- setextHeadingText : onexitsetextheadingtext ,
242
- strong : closer ( ) ,
243
- thematicBreak : closer ( )
244
- }
222
+ function compiler ( options ) {
223
+ /** @type {Config } */
224
+ const config = {
225
+ transforms : [ ] ,
226
+ canContainEols : [ 'emphasis' , 'fragment' , 'heading' , 'paragraph' , 'strong' ] ,
227
+ enter : {
228
+ autolink : opener ( link ) ,
229
+ autolinkProtocol : onenterdata ,
230
+ autolinkEmail : onenterdata ,
231
+ atxHeading : opener ( heading ) ,
232
+ blockQuote : opener ( blockQuote ) ,
233
+ characterEscape : onenterdata ,
234
+ characterReference : onenterdata ,
235
+ codeFenced : opener ( codeFlow ) ,
236
+ codeFencedFenceInfo : buffer ,
237
+ codeFencedFenceMeta : buffer ,
238
+ codeIndented : opener ( codeFlow , buffer ) ,
239
+ codeText : opener ( codeText , buffer ) ,
240
+ codeTextData : onenterdata ,
241
+ data : onenterdata ,
242
+ codeFlowValue : onenterdata ,
243
+ definition : opener ( definition ) ,
244
+ definitionDestinationString : buffer ,
245
+ definitionLabelString : buffer ,
246
+ definitionTitleString : buffer ,
247
+ emphasis : opener ( emphasis ) ,
248
+ hardBreakEscape : opener ( hardBreak ) ,
249
+ hardBreakTrailing : opener ( hardBreak ) ,
250
+ htmlFlow : opener ( html , buffer ) ,
251
+ htmlFlowData : onenterdata ,
252
+ htmlText : opener ( html , buffer ) ,
253
+ htmlTextData : onenterdata ,
254
+ image : opener ( image ) ,
255
+ label : buffer ,
256
+ link : opener ( link ) ,
257
+ listItem : opener ( listItem ) ,
258
+ listItemValue : onenterlistitemvalue ,
259
+ listOrdered : opener ( list , onenterlistordered ) ,
260
+ listUnordered : opener ( list ) ,
261
+ paragraph : opener ( paragraph ) ,
262
+ reference : onenterreference ,
263
+ referenceString : buffer ,
264
+ resourceDestinationString : buffer ,
265
+ resourceTitleString : buffer ,
266
+ setextHeading : opener ( heading ) ,
267
+ strong : opener ( strong ) ,
268
+ thematicBreak : opener ( thematicBreak )
245
269
} ,
246
- options . mdastExtensions || [ ]
247
- )
270
+ exit : {
271
+ atxHeading : closer ( ) ,
272
+ atxHeadingSequence : onexitatxheadingsequence ,
273
+ autolink : closer ( ) ,
274
+ autolinkEmail : onexitautolinkemail ,
275
+ autolinkProtocol : onexitautolinkprotocol ,
276
+ blockQuote : closer ( ) ,
277
+ characterEscapeValue : onexitdata ,
278
+ characterReferenceMarkerHexadecimal : onexitcharacterreferencemarker ,
279
+ characterReferenceMarkerNumeric : onexitcharacterreferencemarker ,
280
+ characterReferenceValue : onexitcharacterreferencevalue ,
281
+ codeFenced : closer ( onexitcodefenced ) ,
282
+ codeFencedFence : onexitcodefencedfence ,
283
+ codeFencedFenceInfo : onexitcodefencedfenceinfo ,
284
+ codeFencedFenceMeta : onexitcodefencedfencemeta ,
285
+ codeFlowValue : onexitdata ,
286
+ codeIndented : closer ( onexitcodeindented ) ,
287
+ codeText : closer ( onexitcodetext ) ,
288
+ codeTextData : onexitdata ,
289
+ data : onexitdata ,
290
+ definition : closer ( ) ,
291
+ definitionDestinationString : onexitdefinitiondestinationstring ,
292
+ definitionLabelString : onexitdefinitionlabelstring ,
293
+ definitionTitleString : onexitdefinitiontitlestring ,
294
+ emphasis : closer ( ) ,
295
+ hardBreakEscape : closer ( onexithardbreak ) ,
296
+ hardBreakTrailing : closer ( onexithardbreak ) ,
297
+ htmlFlow : closer ( onexithtmlflow ) ,
298
+ htmlFlowData : onexitdata ,
299
+ htmlText : closer ( onexithtmltext ) ,
300
+ htmlTextData : onexitdata ,
301
+ image : closer ( onexitimage ) ,
302
+ label : onexitlabel ,
303
+ labelText : onexitlabeltext ,
304
+ lineEnding : onexitlineending ,
305
+ link : closer ( onexitlink ) ,
306
+ listItem : closer ( ) ,
307
+ listOrdered : closer ( ) ,
308
+ listUnordered : closer ( ) ,
309
+ paragraph : closer ( ) ,
310
+ referenceString : onexitreferencestring ,
311
+ resourceDestinationString : onexitresourcedestinationstring ,
312
+ resourceTitleString : onexitresourcetitlestring ,
313
+ resource : onexitresource ,
314
+ setextHeading : closer ( onexitsetextheading ) ,
315
+ setextHeadingLineSequence : onexitsetextheadinglinesequence ,
316
+ setextHeadingText : onexitsetextheadingtext ,
317
+ strong : closer ( ) ,
318
+ thematicBreak : closer ( )
319
+ }
320
+ }
321
+
322
+ configure ( config , ( options || { } ) . mdastExtensions || [ ] )
248
323
249
324
/** @type {CompileData } */
250
325
const data = { }
251
326
252
327
return compile
253
328
254
329
/**
330
+ * Turn micromark events into an mdast tree.
331
+ *
255
332
* @param {Array<Event> } events
333
+ * Events.
256
334
* @returns {Root }
335
+ * mdast tree.
257
336
*/
258
337
function compile ( events ) {
259
338
/** @type {Root } */
260
339
let tree = { type : 'root' , children : [ ] }
261
- /** @type {CompileContext['stack'] } */
262
- const stack = [ tree ]
263
- /** @type {CompileContext['tokenStack'] } */
264
- const tokenStack = [ ]
265
- /** @type {Array<number> } */
266
- const listStack = [ ]
267
340
/** @type {Omit<CompileContext, 'sliceSerialize'> } */
268
341
const context = {
269
- stack,
270
- tokenStack,
342
+ stack : [ tree ] ,
343
+ tokenStack : [ ] ,
271
344
config,
272
345
enter,
273
346
exit,
@@ -276,6 +349,8 @@ function compiler(options = {}) {
276
349
setData,
277
350
getData
278
351
}
352
+ /** @type {Array<number> } */
353
+ const listStack = [ ]
279
354
let index = - 1
280
355
281
356
while ( ++ index < events . length ) {
@@ -311,8 +386,9 @@ function compiler(options = {}) {
311
386
}
312
387
}
313
388
314
- if ( tokenStack . length > 0 ) {
315
- const tail = tokenStack [ tokenStack . length - 1 ]
389
+ // Handle tokens still being open.
390
+ if ( context . tokenStack . length > 0 ) {
391
+ const tail = context . tokenStack [ context . tokenStack . length - 1 ]
316
392
const handler = tail [ 1 ] || defaultOnError
317
393
handler . call ( context , undefined , tail [ 0 ] )
318
394
}
@@ -329,6 +405,7 @@ function compiler(options = {}) {
329
405
)
330
406
}
331
407
408
+ // Call transforms.
332
409
index = - 1
333
410
while ( ++ index < config . transforms . length ) {
334
411
tree = config . transforms [ index ] ( tree ) || tree
@@ -347,13 +424,13 @@ function compiler(options = {}) {
347
424
let index = start - 1
348
425
let containerBalance = - 1
349
426
let listSpread = false
350
- /** @type {Token| undefined } */
427
+ /** @type {Token | undefined } */
351
428
let listItem
352
- /** @type {number| undefined } */
429
+ /** @type {number | undefined } */
353
430
let lineIndex
354
- /** @type {number| undefined } */
431
+ /** @type {number | undefined } */
355
432
let firstBlankLineIndex
356
- /** @type {boolean| undefined } */
433
+ /** @type {boolean | undefined } */
357
434
let atMarker
358
435
359
436
while ( ++ index <= length ) {
@@ -481,35 +558,44 @@ function compiler(options = {}) {
481
558
}
482
559
483
560
/**
484
- * @type {CompileContext['setData'] }
485
- * @param [value]
561
+ * Set data.
562
+ *
563
+ * @template {keyof CompileData} Key
564
+ * Field type.
565
+ * @param {Key } key
566
+ * Key of field.
567
+ * @param {CompileData[Key] } [value]
568
+ * New value.
569
+ * @returns {void }
570
+ * Nothing.
486
571
*/
487
572
function setData ( key , value ) {
488
573
data [ key ] = value
489
574
}
490
575
491
576
/**
492
- * @type {CompileContext['getData'] }
493
- * @template {string} K
494
- * @param {K } key
495
- * @returns {CompileData[K] }
577
+ * Get data.
578
+ *
579
+ * @template {keyof CompileData} Key
580
+ * Field type.
581
+ * @param {Key } key
582
+ * Key of field.
583
+ * @returns {CompileData[Key] }
584
+ * Value.
496
585
*/
497
586
function getData ( key ) {
498
587
return data [ key ]
499
588
}
500
589
501
590
/**
502
- * @param {Point } d
503
- * @returns {Point }
504
- */
505
- function point ( d ) {
506
- return { line : d . line , column : d . column , offset : d . offset }
507
- }
508
-
509
- /**
591
+ * Create an opener handle.
592
+ *
510
593
* @param {(token: Token) => Node } create
594
+ * Create a node.
511
595
* @param {Handle } [and]
596
+ * Optional function to also run.
512
597
* @returns {Handle }
598
+ * Handle.
513
599
*/
514
600
function opener ( create , and ) {
515
601
return open
@@ -534,13 +620,18 @@ function compiler(options = {}) {
534
620
}
535
621
536
622
/**
537
- * @type { CompileContext['enter'] }
538
- * @template { Node} N
623
+ * @template {Node} Kind
624
+ * Node type.
539
625
* @this {CompileContext}
540
- * @param {N } node
626
+ * Context.
627
+ * @param {Kind } node
628
+ * Node to enter.
541
629
* @param {Token } token
542
- * @param {OnEnterError } [errorHandler]
543
- * @returns {N }
630
+ * Corresponding token.
631
+ * @param {OnEnterError | undefined } [errorHandler]
632
+ * Handle the case where this token is open, but it is closed by something else.
633
+ * @returns {Kind }
634
+ * The given node.
544
635
*/
545
636
function enter ( node , token , errorHandler ) {
546
637
const parent = this . stack [ this . stack . length - 1 ]
@@ -556,8 +647,12 @@ function compiler(options = {}) {
556
647
}
557
648
558
649
/**
650
+ * Create a closer handle.
651
+ *
559
652
* @param {Handle } [and]
653
+ * Optional function to also run.
560
654
* @returns {Handle }
655
+ * Handle.
561
656
*/
562
657
function closer ( and ) {
563
658
return close
@@ -574,11 +669,14 @@ function compiler(options = {}) {
574
669
}
575
670
576
671
/**
577
- * @type {CompileContext['exit'] }
578
672
* @this {CompileContext}
673
+ * Context.
579
674
* @param {Token } token
580
- * @param {OnExitError } [onExitError]
675
+ * Corresponding token.
676
+ * @param {OnExitError | undefined } [onExitError]
677
+ * Handle the case where another token is open.
581
678
* @returns {Node }
679
+ * The closed node.
582
680
*/
583
681
function exit ( token , onExitError ) {
584
682
const node = this . stack . pop ( )
@@ -634,7 +732,9 @@ function compiler(options = {}) {
634
732
*/
635
733
function onenterlistitemvalue ( token ) {
636
734
if ( getData ( 'expectingFirstListItemValue' ) ) {
637
- const ancestor = /** @type {List } */ ( this . stack [ this . stack . length - 2 ] )
735
+ const ancestor = this . stack [ this . stack . length - 2 ]
736
+ assert ( ancestor , 'expected nodes on stack' )
737
+ assert ( ancestor . type === 'list' , 'expected list on stack' )
638
738
ancestor . start = Number . parseInt (
639
739
this . sliceSerialize ( token ) ,
640
740
constants . numericBaseDecimal
@@ -649,7 +749,9 @@ function compiler(options = {}) {
649
749
*/
650
750
function onexitcodefencedfenceinfo ( ) {
651
751
const data = this . resume ( )
652
- const node = /** @type {Code } */ ( this . stack [ this . stack . length - 1 ] )
752
+ const node = this . stack [ this . stack . length - 1 ]
753
+ assert ( node , 'expected node on stack' )
754
+ assert ( node . type === 'code' , 'expected code on stack' )
653
755
node . lang = data
654
756
}
655
757
@@ -659,7 +761,9 @@ function compiler(options = {}) {
659
761
*/
660
762
function onexitcodefencedfencemeta ( ) {
661
763
const data = this . resume ( )
662
- const node = /** @type {Code } */ ( this . stack [ this . stack . length - 1 ] )
764
+ const node = this . stack [ this . stack . length - 1 ]
765
+ assert ( node , 'expected node on stack' )
766
+ assert ( node . type === 'code' , 'expected code on stack' )
663
767
node . meta = data
664
768
}
665
769
@@ -680,10 +784,11 @@ function compiler(options = {}) {
680
784
*/
681
785
function onexitcodefenced ( ) {
682
786
const data = this . resume ( )
683
- const node = /** @type {Code } */ ( this . stack [ this . stack . length - 1 ] )
787
+ const node = this . stack [ this . stack . length - 1 ]
788
+ assert ( node , 'expected node on stack' )
789
+ assert ( node . type === 'code' , 'expected code on stack' )
684
790
685
791
node . value = data . replace ( / ^ ( \r ? \n | \r ) | ( \r ? \n | \r ) $ / g, '' )
686
-
687
792
setData ( 'flowCodeInside' )
688
793
}
689
794
@@ -693,7 +798,9 @@ function compiler(options = {}) {
693
798
*/
694
799
function onexitcodeindented ( ) {
695
800
const data = this . resume ( )
696
- const node = /** @type {Code } */ ( this . stack [ this . stack . length - 1 ] )
801
+ const node = this . stack [ this . stack . length - 1 ]
802
+ assert ( node , 'expected node on stack' )
803
+ assert ( node . type === 'code' , 'expected code on stack' )
697
804
698
805
node . value = data . replace ( / ( \r ? \n | \r ) $ / g, '' )
699
806
}
@@ -703,9 +810,11 @@ function compiler(options = {}) {
703
810
* @type {Handle }
704
811
*/
705
812
function onexitdefinitionlabelstring ( token ) {
706
- // Discard label, use the source content instead.
707
813
const label = this . resume ( )
708
- const node = /** @type {Definition } */ ( this . stack [ this . stack . length - 1 ] )
814
+ const node = this . stack [ this . stack . length - 1 ]
815
+ assert ( node , 'expected node on stack' )
816
+ assert ( node . type === 'definition' , 'expected definition on stack' )
817
+
709
818
node . label = label
710
819
node . identifier = normalizeIdentifier (
711
820
this . sliceSerialize ( token )
@@ -718,7 +827,10 @@ function compiler(options = {}) {
718
827
*/
719
828
function onexitdefinitiontitlestring ( ) {
720
829
const data = this . resume ( )
721
- const node = /** @type {Definition } */ ( this . stack [ this . stack . length - 1 ] )
830
+ const node = this . stack [ this . stack . length - 1 ]
831
+ assert ( node , 'expected node on stack' )
832
+ assert ( node . type === 'definition' , 'expected definition on stack' )
833
+
722
834
node . title = data
723
835
}
724
836
@@ -728,7 +840,10 @@ function compiler(options = {}) {
728
840
*/
729
841
function onexitdefinitiondestinationstring ( ) {
730
842
const data = this . resume ( )
731
- const node = /** @type {Definition } */ ( this . stack [ this . stack . length - 1 ] )
843
+ const node = this . stack [ this . stack . length - 1 ]
844
+ assert ( node , 'expected node on stack' )
845
+ assert ( node . type === 'definition' , 'expected definition on stack' )
846
+
732
847
node . url = data
733
848
}
734
849
@@ -737,7 +852,10 @@ function compiler(options = {}) {
737
852
* @type {Handle }
738
853
*/
739
854
function onexitatxheadingsequence ( token ) {
740
- const node = /** @type {Heading } */ ( this . stack [ this . stack . length - 1 ] )
855
+ const node = this . stack [ this . stack . length - 1 ]
856
+ assert ( node , 'expected node on stack' )
857
+ assert ( node . type === 'heading' , 'expected heading on stack' )
858
+
741
859
if ( ! node . depth ) {
742
860
const depth = this . sliceSerialize ( token ) . length
743
861
@@ -768,7 +886,9 @@ function compiler(options = {}) {
768
886
* @type {Handle }
769
887
*/
770
888
function onexitsetextheadinglinesequence ( token ) {
771
- const node = /** @type {Heading } */ ( this . stack [ this . stack . length - 1 ] )
889
+ const node = this . stack [ this . stack . length - 1 ]
890
+ assert ( node , 'expected node on stack' )
891
+ assert ( node . type === 'heading' , 'expected heading on stack' )
772
892
773
893
node . depth =
774
894
this . sliceSerialize ( token ) . charCodeAt ( 0 ) === codes . equalsTo ? 1 : 2
@@ -788,17 +908,19 @@ function compiler(options = {}) {
788
908
*/
789
909
790
910
function onenterdata ( token ) {
791
- const parent = /** @type {Parent } */ ( this . stack [ this . stack . length - 1 ] )
792
- /** @type {Node } */
793
- let tail = parent . children [ parent . children . length - 1 ]
911
+ const node = this . stack [ this . stack . length - 1 ]
912
+ assert ( node , 'expected node on stack' )
913
+ assert ( 'children' in node , 'expected parent on stack' )
914
+
915
+ let tail = node . children [ node . children . length - 1 ]
794
916
795
917
if ( ! tail || tail . type !== 'text' ) {
796
918
// Add a new text node.
797
919
tail = text ( )
798
920
// @ts -expect-error: we’ll add `end` later.
799
921
tail . position = { start : point ( token . start ) }
800
922
// @ts -expect-error: Assume `parent` accepts `text`.
801
- parent . children . push ( tail )
923
+ node . children . push ( tail )
802
924
}
803
925
804
926
this . stack . push ( tail )
@@ -862,7 +984,10 @@ function compiler(options = {}) {
862
984
863
985
function onexithtmlflow ( ) {
864
986
const data = this . resume ( )
865
- const node = /** @type {HTML } */ ( this . stack [ this . stack . length - 1 ] )
987
+ const node = this . stack [ this . stack . length - 1 ]
988
+ assert ( node , 'expected node on stack' )
989
+ assert ( node . type === 'html' , 'expected html on stack' )
990
+
866
991
node . value = data
867
992
}
868
993
@@ -873,7 +998,10 @@ function compiler(options = {}) {
873
998
874
999
function onexithtmltext ( ) {
875
1000
const data = this . resume ( )
876
- const node = /** @type {HTML } */ ( this . stack [ this . stack . length - 1 ] )
1001
+ const node = this . stack [ this . stack . length - 1 ]
1002
+ assert ( node , 'expected node on stack' )
1003
+ assert ( node . type === 'html' , 'expected html on stack' )
1004
+
877
1005
node . value = data
878
1006
}
879
1007
@@ -884,7 +1012,10 @@ function compiler(options = {}) {
884
1012
885
1013
function onexitcodetext ( ) {
886
1014
const data = this . resume ( )
887
- const node = /** @type {InlineCode } */ ( this . stack [ this . stack . length - 1 ] )
1015
+ const node = this . stack [ this . stack . length - 1 ]
1016
+ assert ( node , 'expected node on stack' )
1017
+ assert ( node . type === 'inlineCode' , 'expected inline code on stack' )
1018
+
888
1019
node . value = data
889
1020
}
890
1021
@@ -894,23 +1025,29 @@ function compiler(options = {}) {
894
1025
*/
895
1026
896
1027
function onexitlink ( ) {
897
- const context = /** @type {Link & {identifier: string, label: string} } */ (
898
- this . stack [ this . stack . length - 1 ]
899
- )
1028
+ const node = this . stack [ this . stack . length - 1 ]
1029
+ assert ( node , 'expected node on stack' )
1030
+ assert ( node . type === 'link' , 'expected link on stack' )
1031
+
1032
+ // Note: there are also `identifier` and `label` fields on this link node!
1033
+ // These are used / cleaned here.
900
1034
901
1035
// To do: clean.
902
1036
if ( getData ( 'inReference' ) ) {
903
- context . type += 'Reference'
1037
+ /** @type {ReferenceType } */
1038
+ const referenceType = getData ( 'referenceType' ) || 'shortcut'
1039
+
1040
+ node . type += 'Reference'
904
1041
// @ts -expect-error: mutate.
905
- context . referenceType = getData ( ' referenceType' ) || 'shortcut'
1042
+ node . referenceType = referenceType
906
1043
// @ts -expect-error: mutate.
907
- delete context . url
908
- delete context . title
1044
+ delete node . url
1045
+ delete node . title
909
1046
} else {
910
1047
// @ts -expect-error: mutate.
911
- delete context . identifier
1048
+ delete node . identifier
912
1049
// @ts -expect-error: mutate.
913
- delete context . label
1050
+ delete node . label
914
1051
}
915
1052
916
1053
setData ( 'referenceType' )
@@ -922,23 +1059,29 @@ function compiler(options = {}) {
922
1059
*/
923
1060
924
1061
function onexitimage ( ) {
925
- const context = /** @type {Image & {identifier: string, label: string} } */ (
926
- this . stack [ this . stack . length - 1 ]
927
- )
1062
+ const node = this . stack [ this . stack . length - 1 ]
1063
+ assert ( node , 'expected node on stack' )
1064
+ assert ( node . type === 'image' , 'expected image on stack' )
1065
+
1066
+ // Note: there are also `identifier` and `label` fields on this link node!
1067
+ // These are used / cleaned here.
928
1068
929
1069
// To do: clean.
930
1070
if ( getData ( 'inReference' ) ) {
931
- context . type += 'Reference'
1071
+ /** @type {ReferenceType } */
1072
+ const referenceType = getData ( 'referenceType' ) || 'shortcut'
1073
+
1074
+ node . type += 'Reference'
932
1075
// @ts -expect-error: mutate.
933
- context . referenceType = getData ( ' referenceType' ) || 'shortcut'
1076
+ node . referenceType = referenceType
934
1077
// @ts -expect-error: mutate.
935
- delete context . url
936
- delete context . title
1078
+ delete node . url
1079
+ delete node . title
937
1080
} else {
938
1081
// @ts -expect-error: mutate.
939
- delete context . identifier
1082
+ delete node . identifier
940
1083
// @ts -expect-error: mutate.
941
- delete context . label
1084
+ delete node . label
942
1085
}
943
1086
944
1087
setData ( 'referenceType' )
@@ -950,13 +1093,18 @@ function compiler(options = {}) {
950
1093
*/
951
1094
952
1095
function onexitlabeltext ( token ) {
953
- const ancestor =
954
- /** @type {(Link|Image) & {identifier: string, label: string} } */ (
955
- this . stack [ this . stack . length - 2 ]
956
- )
957
1096
const string = this . sliceSerialize ( token )
1097
+ const ancestor = this . stack [ this . stack . length - 2 ]
1098
+ assert ( ancestor , 'expected ancestor on stack' )
1099
+ assert (
1100
+ ancestor . type === 'image' || ancestor . type === 'link' ,
1101
+ 'expected image or link on stack'
1102
+ )
958
1103
1104
+ // @ts -expect-error: stash this on the node, as it might become a reference
1105
+ // later.
959
1106
ancestor . label = decodeString ( string )
1107
+ // @ts -expect-error: same as above.
960
1108
ancestor . identifier = normalizeIdentifier ( string ) . toLowerCase ( )
961
1109
}
962
1110
@@ -966,19 +1114,26 @@ function compiler(options = {}) {
966
1114
*/
967
1115
968
1116
function onexitlabel ( ) {
969
- const fragment = /** @type {Fragment } */ ( this . stack [ this . stack . length - 1 ] )
1117
+ const fragment = this . stack [ this . stack . length - 1 ]
1118
+ assert ( fragment , 'expected node on stack' )
1119
+ assert ( fragment . type === 'fragment' , 'expected fragment on stack' )
970
1120
const value = this . resume ( )
971
- const node =
972
- /** @type {(Link|Image) & {identifier: string, label: string} } */ (
973
- this . stack [ this . stack . length - 1 ]
974
- )
1121
+ const node = this . stack [ this . stack . length - 1 ]
1122
+ assert ( node , 'expected node on stack' )
1123
+ assert (
1124
+ node . type === 'image' || node . type === 'link' ,
1125
+ 'expected image or link on stack'
1126
+ )
975
1127
976
1128
// Assume a reference.
977
1129
setData ( 'inReference' , true )
978
1130
979
1131
if ( node . type === 'link' ) {
1132
+ /** @type {Array<StaticPhrasingContent> } */
980
1133
// @ts -expect-error: Assume static phrasing content.
981
- node . children = fragment . children
1134
+ const children = fragment . children
1135
+
1136
+ node . children = children
982
1137
} else {
983
1138
node . alt = value
984
1139
}
@@ -991,7 +1146,12 @@ function compiler(options = {}) {
991
1146
992
1147
function onexitresourcedestinationstring ( ) {
993
1148
const data = this . resume ( )
994
- const node = /** @type {Link|Image } */ ( this . stack [ this . stack . length - 1 ] )
1149
+ const node = this . stack [ this . stack . length - 1 ]
1150
+ assert ( node , 'expected node on stack' )
1151
+ assert (
1152
+ node . type === 'image' || node . type === 'link' ,
1153
+ 'expected image or link on stack'
1154
+ )
995
1155
node . url = data
996
1156
}
997
1157
@@ -1002,7 +1162,12 @@ function compiler(options = {}) {
1002
1162
1003
1163
function onexitresourcetitlestring ( ) {
1004
1164
const data = this . resume ( )
1005
- const node = /** @type {Link|Image } */ ( this . stack [ this . stack . length - 1 ] )
1165
+ const node = this . stack [ this . stack . length - 1 ]
1166
+ assert ( node , 'expected node on stack' )
1167
+ assert (
1168
+ node . type === 'image' || node . type === 'link' ,
1169
+ 'expected image or link on stack'
1170
+ )
1006
1171
node . title = data
1007
1172
}
1008
1173
@@ -1031,10 +1196,17 @@ function compiler(options = {}) {
1031
1196
1032
1197
function onexitreferencestring ( token ) {
1033
1198
const label = this . resume ( )
1034
- const node = /** @type {LinkReference|ImageReference } */ (
1035
- this . stack [ this . stack . length - 1 ]
1199
+ const node = this . stack [ this . stack . length - 1 ]
1200
+ assert ( node , 'expected node on stack' )
1201
+ assert (
1202
+ node . type === 'image' || node . type === 'link' ,
1203
+ 'expected image reference or link reference on stack'
1036
1204
)
1205
+
1206
+ // @ts -expect-error: stash this on the node, as it might become a reference
1207
+ // later.
1037
1208
node . label = label
1209
+ // @ts -expect-error: same as above.
1038
1210
node . identifier = normalizeIdentifier (
1039
1211
this . sliceSerialize ( token )
1040
1212
) . toLowerCase ( )
@@ -1047,6 +1219,10 @@ function compiler(options = {}) {
1047
1219
*/
1048
1220
1049
1221
function onexitcharacterreferencemarker ( token ) {
1222
+ assert (
1223
+ token . type === 'characterReferenceMarkerNumeric' ||
1224
+ token . type === 'characterReferenceMarkerHexadecimal'
1225
+ )
1050
1226
setData ( 'characterReferenceType' , token . type )
1051
1227
}
1052
1228
@@ -1069,10 +1245,9 @@ function compiler(options = {}) {
1069
1245
)
1070
1246
setData ( 'characterReferenceType' )
1071
1247
} else {
1072
- // @ts -expect-error `decodeNamedCharacterReference` can return false for
1073
- // invalid named character references, but everything we’ve tokenized is
1074
- // valid.
1075
- value = decodeNamedCharacterReference ( data )
1248
+ const result = decodeNamedCharacterReference ( data )
1249
+ assert ( result !== false , 'expected reference to decode' )
1250
+ value = result
1076
1251
}
1077
1252
1078
1253
const tail = this . stack . pop ( )
@@ -1089,7 +1264,10 @@ function compiler(options = {}) {
1089
1264
*/
1090
1265
function onexitautolinkprotocol ( token ) {
1091
1266
onexitdata . call ( this , token )
1092
- const node = /** @type {Link } */ ( this . stack [ this . stack . length - 1 ] )
1267
+ const node = this . stack [ this . stack . length - 1 ]
1268
+ assert ( node , 'expected node on stack' )
1269
+ assert ( node . type === 'link' , 'expected link on stack' )
1270
+
1093
1271
node . url = this . sliceSerialize ( token )
1094
1272
}
1095
1273
@@ -1099,7 +1277,10 @@ function compiler(options = {}) {
1099
1277
*/
1100
1278
function onexitautolinkemail ( token ) {
1101
1279
onexitdata . call ( this , token )
1102
- const node = /** @type {Link } */ ( this . stack [ this . stack . length - 1 ] )
1280
+ const node = this . stack [ this . stack . length - 1 ]
1281
+ assert ( node , 'expected node on stack' )
1282
+ assert ( node . type === 'link' , 'expected link on stack' )
1283
+
1103
1284
node . url = 'mailto:' + this . sliceSerialize ( token )
1104
1285
}
1105
1286
@@ -1215,9 +1396,21 @@ function compiler(options = {}) {
1215
1396
}
1216
1397
1217
1398
/**
1218
- * @param {Extension } combined
1219
- * @param {Array<Extension|Array<Extension>> } extensions
1220
- * @returns {Extension }
1399
+ * Copy a point-like value.
1400
+ *
1401
+ * @param {Point } d
1402
+ * Point-like value.
1403
+ * @returns {Point }
1404
+ * unist point.
1405
+ */
1406
+ function point ( d ) {
1407
+ return { line : d . line , column : d . column , offset : d . offset }
1408
+ }
1409
+
1410
+ /**
1411
+ * @param {Config } combined
1412
+ * @param {Array<Extension | Array<Extension>> } extensions
1413
+ * @returns {void }
1221
1414
*/
1222
1415
function configure ( combined , extensions ) {
1223
1416
let index = - 1
@@ -1231,33 +1424,33 @@ function configure(combined, extensions) {
1231
1424
extension ( combined , value )
1232
1425
}
1233
1426
}
1234
-
1235
- return combined
1236
1427
}
1237
1428
1238
1429
/**
1239
- * @param {Extension } combined
1430
+ * @param {Config } combined
1240
1431
* @param {Extension } extension
1241
1432
* @returns {void }
1242
1433
*/
1243
1434
function extension ( combined , extension ) {
1244
- /** @type {string } */
1435
+ /** @type {keyof Extension } */
1245
1436
let key
1246
1437
1247
1438
for ( key in extension ) {
1248
1439
if ( own . call ( extension , key ) ) {
1249
- const list = key === 'canContainEols' || key === 'transforms'
1250
- const maybe = own . call ( combined , key ) ? combined [ key ] : undefined
1251
- /* c8 ignore next */
1252
- const left = maybe || ( combined [ key ] = list ? [ ] : { } )
1253
- const right = extension [ key ]
1254
-
1255
- if ( right ) {
1256
- if ( list ) {
1257
- // @ts -expect-error: `left` is an array.
1258
- combined [ key ] = [ ...left , ...right ]
1259
- } else {
1260
- Object . assign ( left , right )
1440
+ if ( key === 'canContainEols' ) {
1441
+ const right = extension [ key ]
1442
+ if ( right ) {
1443
+ combined [ key ] . push ( ...right )
1444
+ }
1445
+ } else if ( key === 'transforms' ) {
1446
+ const right = extension [ key ]
1447
+ if ( right ) {
1448
+ combined [ key ] . push ( ...right )
1449
+ }
1450
+ } else if ( key === 'enter' || key === 'exit' ) {
1451
+ const right = extension [ key ]
1452
+ if ( right ) {
1453
+ Object . assign ( combined [ key ] , right )
1261
1454
}
1262
1455
}
1263
1456
}
0 commit comments