Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 817f24e

Browse files
committedJan 27, 2023
Refactor code-style
* Add more docs to JSDoc * Add support for `null` in input of API types
1 parent 4a1a05e commit 817f24e

File tree

2 files changed

+445
-254
lines changed

2 files changed

+445
-254
lines changed
 

‎dev/lib/index.js

Lines changed: 445 additions & 252 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
* @typedef {import('micromark-util-types').Token} Token
66
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
77
* @typedef {import('micromark-util-types').Value} Value
8+
*
89
* @typedef {import('unist').Parent} UnistParent
910
* @typedef {import('unist').Point} Point
11+
*
1012
* @typedef {import('mdast').PhrasingContent} PhrasingContent
13+
* @typedef {import('mdast').StaticPhrasingContent} StaticPhrasingContent
1114
* @typedef {import('mdast').Content} Content
12-
* @typedef {Root|Content} Node
13-
* @typedef {Extract<Node, UnistParent>} Parent
1415
* @typedef {import('mdast').Break} Break
1516
* @typedef {import('mdast').Blockquote} Blockquote
1617
* @typedef {import('mdast').Code} Code
@@ -30,58 +31,134 @@
3031
* @typedef {import('mdast').Strong} Strong
3132
* @typedef {import('mdast').Text} Text
3233
* @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
3340
*
34-
* @typedef {UnistParent & {type: 'fragment', children: Array<PhrasingContent>}} Fragment
41+
* @typedef {Omit<UnistParent, 'type' | 'children'> & {type: 'fragment', children: Array<PhrasingContent>}} Fragment
3542
*/
3643

3744
/**
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.
4661
*
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.
4877
*
49-
* @typedef {(tree: Root) => Root|void} Transform
50-
* @typedef {(this: CompileContext, token: Token) => void} Handle
5178
* @typedef {Record<string, Handle>} Handles
5279
* 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.
5680
*
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.
59126
*
60127
* @typedef CompileContext
61128
* mdast compiler context
62129
* @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
65134
* 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
67136
* Get data from the key-value store.
68137
* @property {(this: CompileContext) => void} buffer
69138
* Capture some of the output data.
70139
* @property {(this: CompileContext) => string} resume
71140
* 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
73142
* Enter a token.
74143
* @property {(this: CompileContext, token: Token, onError?: OnExitError) => Node} exit
75144
* Exit a token.
76145
* @property {TokenizeContext['sliceSerialize']} sliceSerialize
77146
* Get the string value of a token.
78-
* @property {NormalizedExtension} config
147+
* @property {Config} config
79148
* Configuration.
80149
*
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+
*
82155
* @typedef {ParseOptions & FromMarkdownOptions} Options
83156
*/
84157

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+
85162
import {ok as assert} from 'uvu/assert'
86163
import {toString} from 'mdast-util-to-string'
87164
import {parse} from 'micromark/lib/parse.js'
@@ -99,22 +176,27 @@ import {stringifyPosition} from 'unist-util-stringify-position'
99176
const own = {}.hasOwnProperty
100177

101178
/**
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.
105187
*/
106188
export const fromMarkdown =
107189
/**
108190
* @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)
111193
* )}
112194
*/
113195
(
114196
/**
115197
* @param {Value} value
116-
* @param {Encoding} [encoding]
117-
* @param {Options} [options]
198+
* @param {Encoding | Options | null | undefined} [encoding]
199+
* @param {Options | null | undefined} [options]
118200
* @returns {Root}
119201
*/
120202
function (value, encoding, options) {
@@ -125,6 +207,7 @@ export const fromMarkdown =
125207

126208
return compiler(options)(
127209
postprocess(
210+
// @ts-expect-error: micromark types need to accept `null`.
128211
parse(options).document().write(preprocess()(value, encoding, true))
129212
)
130213
)
@@ -134,140 +217,130 @@ export const fromMarkdown =
134217
/**
135218
* Note this compiler only understand complete buffering, not streaming.
136219
*
137-
* @param {Options} [options]
220+
* @param {Options | null | undefined} [options]
138221
*/
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)
245269
},
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 || [])
248323

249324
/** @type {CompileData} */
250325
const data = {}
251326

252327
return compile
253328

254329
/**
330+
* Turn micromark events into an mdast tree.
331+
*
255332
* @param {Array<Event>} events
333+
* Events.
256334
* @returns {Root}
335+
* mdast tree.
257336
*/
258337
function compile(events) {
259338
/** @type {Root} */
260339
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 = []
267340
/** @type {Omit<CompileContext, 'sliceSerialize'>} */
268341
const context = {
269-
stack,
270-
tokenStack,
342+
stack: [tree],
343+
tokenStack: [],
271344
config,
272345
enter,
273346
exit,
@@ -276,6 +349,8 @@ function compiler(options = {}) {
276349
setData,
277350
getData
278351
}
352+
/** @type {Array<number>} */
353+
const listStack = []
279354
let index = -1
280355

281356
while (++index < events.length) {
@@ -311,8 +386,9 @@ function compiler(options = {}) {
311386
}
312387
}
313388

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]
316392
const handler = tail[1] || defaultOnError
317393
handler.call(context, undefined, tail[0])
318394
}
@@ -329,6 +405,7 @@ function compiler(options = {}) {
329405
)
330406
}
331407

408+
// Call transforms.
332409
index = -1
333410
while (++index < config.transforms.length) {
334411
tree = config.transforms[index](tree) || tree
@@ -347,13 +424,13 @@ function compiler(options = {}) {
347424
let index = start - 1
348425
let containerBalance = -1
349426
let listSpread = false
350-
/** @type {Token|undefined} */
427+
/** @type {Token | undefined} */
351428
let listItem
352-
/** @type {number|undefined} */
429+
/** @type {number | undefined} */
353430
let lineIndex
354-
/** @type {number|undefined} */
431+
/** @type {number | undefined} */
355432
let firstBlankLineIndex
356-
/** @type {boolean|undefined} */
433+
/** @type {boolean | undefined} */
357434
let atMarker
358435

359436
while (++index <= length) {
@@ -481,35 +558,44 @@ function compiler(options = {}) {
481558
}
482559

483560
/**
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.
486571
*/
487572
function setData(key, value) {
488573
data[key] = value
489574
}
490575

491576
/**
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.
496585
*/
497586
function getData(key) {
498587
return data[key]
499588
}
500589

501590
/**
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+
*
510593
* @param {(token: Token) => Node} create
594+
* Create a node.
511595
* @param {Handle} [and]
596+
* Optional function to also run.
512597
* @returns {Handle}
598+
* Handle.
513599
*/
514600
function opener(create, and) {
515601
return open
@@ -534,13 +620,18 @@ function compiler(options = {}) {
534620
}
535621

536622
/**
537-
* @type {CompileContext['enter']}
538-
* @template {Node} N
623+
* @template {Node} Kind
624+
* Node type.
539625
* @this {CompileContext}
540-
* @param {N} node
626+
* Context.
627+
* @param {Kind} node
628+
* Node to enter.
541629
* @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.
544635
*/
545636
function enter(node, token, errorHandler) {
546637
const parent = this.stack[this.stack.length - 1]
@@ -556,8 +647,12 @@ function compiler(options = {}) {
556647
}
557648

558649
/**
650+
* Create a closer handle.
651+
*
559652
* @param {Handle} [and]
653+
* Optional function to also run.
560654
* @returns {Handle}
655+
* Handle.
561656
*/
562657
function closer(and) {
563658
return close
@@ -574,11 +669,14 @@ function compiler(options = {}) {
574669
}
575670

576671
/**
577-
* @type {CompileContext['exit']}
578672
* @this {CompileContext}
673+
* Context.
579674
* @param {Token} token
580-
* @param {OnExitError} [onExitError]
675+
* Corresponding token.
676+
* @param {OnExitError | undefined} [onExitError]
677+
* Handle the case where another token is open.
581678
* @returns {Node}
679+
* The closed node.
582680
*/
583681
function exit(token, onExitError) {
584682
const node = this.stack.pop()
@@ -634,7 +732,9 @@ function compiler(options = {}) {
634732
*/
635733
function onenterlistitemvalue(token) {
636734
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')
638738
ancestor.start = Number.parseInt(
639739
this.sliceSerialize(token),
640740
constants.numericBaseDecimal
@@ -649,7 +749,9 @@ function compiler(options = {}) {
649749
*/
650750
function onexitcodefencedfenceinfo() {
651751
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')
653755
node.lang = data
654756
}
655757

@@ -659,7 +761,9 @@ function compiler(options = {}) {
659761
*/
660762
function onexitcodefencedfencemeta() {
661763
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')
663767
node.meta = data
664768
}
665769

@@ -680,10 +784,11 @@ function compiler(options = {}) {
680784
*/
681785
function onexitcodefenced() {
682786
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')
684790

685791
node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '')
686-
687792
setData('flowCodeInside')
688793
}
689794

@@ -693,7 +798,9 @@ function compiler(options = {}) {
693798
*/
694799
function onexitcodeindented() {
695800
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')
697804

698805
node.value = data.replace(/(\r?\n|\r)$/g, '')
699806
}
@@ -703,9 +810,11 @@ function compiler(options = {}) {
703810
* @type {Handle}
704811
*/
705812
function onexitdefinitionlabelstring(token) {
706-
// Discard label, use the source content instead.
707813
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+
709818
node.label = label
710819
node.identifier = normalizeIdentifier(
711820
this.sliceSerialize(token)
@@ -718,7 +827,10 @@ function compiler(options = {}) {
718827
*/
719828
function onexitdefinitiontitlestring() {
720829
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+
722834
node.title = data
723835
}
724836

@@ -728,7 +840,10 @@ function compiler(options = {}) {
728840
*/
729841
function onexitdefinitiondestinationstring() {
730842
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+
732847
node.url = data
733848
}
734849

@@ -737,7 +852,10 @@ function compiler(options = {}) {
737852
* @type {Handle}
738853
*/
739854
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+
741859
if (!node.depth) {
742860
const depth = this.sliceSerialize(token).length
743861

@@ -768,7 +886,9 @@ function compiler(options = {}) {
768886
* @type {Handle}
769887
*/
770888
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')
772892

773893
node.depth =
774894
this.sliceSerialize(token).charCodeAt(0) === codes.equalsTo ? 1 : 2
@@ -788,17 +908,19 @@ function compiler(options = {}) {
788908
*/
789909

790910
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]
794916

795917
if (!tail || tail.type !== 'text') {
796918
// Add a new text node.
797919
tail = text()
798920
// @ts-expect-error: we’ll add `end` later.
799921
tail.position = {start: point(token.start)}
800922
// @ts-expect-error: Assume `parent` accepts `text`.
801-
parent.children.push(tail)
923+
node.children.push(tail)
802924
}
803925

804926
this.stack.push(tail)
@@ -862,7 +984,10 @@ function compiler(options = {}) {
862984

863985
function onexithtmlflow() {
864986
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+
866991
node.value = data
867992
}
868993

@@ -873,7 +998,10 @@ function compiler(options = {}) {
873998

874999
function onexithtmltext() {
8751000
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+
8771005
node.value = data
8781006
}
8791007

@@ -884,7 +1012,10 @@ function compiler(options = {}) {
8841012

8851013
function onexitcodetext() {
8861014
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+
8881019
node.value = data
8891020
}
8901021

@@ -894,23 +1025,29 @@ function compiler(options = {}) {
8941025
*/
8951026

8961027
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.
9001034

9011035
// To do: clean.
9021036
if (getData('inReference')) {
903-
context.type += 'Reference'
1037+
/** @type {ReferenceType} */
1038+
const referenceType = getData('referenceType') || 'shortcut'
1039+
1040+
node.type += 'Reference'
9041041
// @ts-expect-error: mutate.
905-
context.referenceType = getData('referenceType') || 'shortcut'
1042+
node.referenceType = referenceType
9061043
// @ts-expect-error: mutate.
907-
delete context.url
908-
delete context.title
1044+
delete node.url
1045+
delete node.title
9091046
} else {
9101047
// @ts-expect-error: mutate.
911-
delete context.identifier
1048+
delete node.identifier
9121049
// @ts-expect-error: mutate.
913-
delete context.label
1050+
delete node.label
9141051
}
9151052

9161053
setData('referenceType')
@@ -922,23 +1059,29 @@ function compiler(options = {}) {
9221059
*/
9231060

9241061
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.
9281068

9291069
// To do: clean.
9301070
if (getData('inReference')) {
931-
context.type += 'Reference'
1071+
/** @type {ReferenceType} */
1072+
const referenceType = getData('referenceType') || 'shortcut'
1073+
1074+
node.type += 'Reference'
9321075
// @ts-expect-error: mutate.
933-
context.referenceType = getData('referenceType') || 'shortcut'
1076+
node.referenceType = referenceType
9341077
// @ts-expect-error: mutate.
935-
delete context.url
936-
delete context.title
1078+
delete node.url
1079+
delete node.title
9371080
} else {
9381081
// @ts-expect-error: mutate.
939-
delete context.identifier
1082+
delete node.identifier
9401083
// @ts-expect-error: mutate.
941-
delete context.label
1084+
delete node.label
9421085
}
9431086

9441087
setData('referenceType')
@@ -950,13 +1093,18 @@ function compiler(options = {}) {
9501093
*/
9511094

9521095
function onexitlabeltext(token) {
953-
const ancestor =
954-
/** @type {(Link|Image) & {identifier: string, label: string}} */ (
955-
this.stack[this.stack.length - 2]
956-
)
9571096
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+
)
9581103

1104+
// @ts-expect-error: stash this on the node, as it might become a reference
1105+
// later.
9591106
ancestor.label = decodeString(string)
1107+
// @ts-expect-error: same as above.
9601108
ancestor.identifier = normalizeIdentifier(string).toLowerCase()
9611109
}
9621110

@@ -966,19 +1114,26 @@ function compiler(options = {}) {
9661114
*/
9671115

9681116
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')
9701120
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+
)
9751127

9761128
// Assume a reference.
9771129
setData('inReference', true)
9781130

9791131
if (node.type === 'link') {
1132+
/** @type {Array<StaticPhrasingContent>} */
9801133
// @ts-expect-error: Assume static phrasing content.
981-
node.children = fragment.children
1134+
const children = fragment.children
1135+
1136+
node.children = children
9821137
} else {
9831138
node.alt = value
9841139
}
@@ -991,7 +1146,12 @@ function compiler(options = {}) {
9911146

9921147
function onexitresourcedestinationstring() {
9931148
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+
)
9951155
node.url = data
9961156
}
9971157

@@ -1002,7 +1162,12 @@ function compiler(options = {}) {
10021162

10031163
function onexitresourcetitlestring() {
10041164
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+
)
10061171
node.title = data
10071172
}
10081173

@@ -1031,10 +1196,17 @@ function compiler(options = {}) {
10311196

10321197
function onexitreferencestring(token) {
10331198
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'
10361204
)
1205+
1206+
// @ts-expect-error: stash this on the node, as it might become a reference
1207+
// later.
10371208
node.label = label
1209+
// @ts-expect-error: same as above.
10381210
node.identifier = normalizeIdentifier(
10391211
this.sliceSerialize(token)
10401212
).toLowerCase()
@@ -1047,6 +1219,10 @@ function compiler(options = {}) {
10471219
*/
10481220

10491221
function onexitcharacterreferencemarker(token) {
1222+
assert(
1223+
token.type === 'characterReferenceMarkerNumeric' ||
1224+
token.type === 'characterReferenceMarkerHexadecimal'
1225+
)
10501226
setData('characterReferenceType', token.type)
10511227
}
10521228

@@ -1069,10 +1245,9 @@ function compiler(options = {}) {
10691245
)
10701246
setData('characterReferenceType')
10711247
} 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
10761251
}
10771252

10781253
const tail = this.stack.pop()
@@ -1089,7 +1264,10 @@ function compiler(options = {}) {
10891264
*/
10901265
function onexitautolinkprotocol(token) {
10911266
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+
10931271
node.url = this.sliceSerialize(token)
10941272
}
10951273

@@ -1099,7 +1277,10 @@ function compiler(options = {}) {
10991277
*/
11001278
function onexitautolinkemail(token) {
11011279
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+
11031284
node.url = 'mailto:' + this.sliceSerialize(token)
11041285
}
11051286

@@ -1215,9 +1396,21 @@ function compiler(options = {}) {
12151396
}
12161397

12171398
/**
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}
12211414
*/
12221415
function configure(combined, extensions) {
12231416
let index = -1
@@ -1231,33 +1424,33 @@ function configure(combined, extensions) {
12311424
extension(combined, value)
12321425
}
12331426
}
1234-
1235-
return combined
12361427
}
12371428

12381429
/**
1239-
* @param {Extension} combined
1430+
* @param {Config} combined
12401431
* @param {Extension} extension
12411432
* @returns {void}
12421433
*/
12431434
function extension(combined, extension) {
1244-
/** @type {string} */
1435+
/** @type {keyof Extension} */
12451436
let key
12461437

12471438
for (key in extension) {
12481439
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)
12611454
}
12621455
}
12631456
}

‎test/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ test('mdast-util-from-markdown', (t) => {
7878
fromMarkdown('a\nb', {
7979
mdastExtensions: [
8080
{
81-
// Unknown objects are used, but have no effect.
82-
unknown: undefined,
8381
// `canContainEols` is an array.
8482
canContainEols: ['someType'],
8583
enter: {

0 commit comments

Comments
 (0)
Please sign in to comment.