|
54 | 54 | * @typedef {Partial<NormalizedExtension>} Extension
|
55 | 55 | * An mdast extension changes how markdown tokens are turned into mdast.
|
56 | 56 | *
|
| 57 | + * @typedef {(this: Omit<CompileContext, 'sliceSerialize'>, left: Token|undefined, right: Token) => void} OnError |
| 58 | + * |
57 | 59 | * @typedef CompileContext
|
58 | 60 | * mdast compiler context
|
59 | 61 | * @property {Array<Node | Fragment>} stack
|
60 |
| - * @property {Array<Token>} tokenStack |
| 62 | + * @property {Array<[Token, OnError|undefined]>} tokenStack |
61 | 63 | * @property {(key: string, value?: unknown) => void} setData
|
62 | 64 | * Set data into the key-value store.
|
63 | 65 | * @property {<K extends string>(key: K) => CompileData[K]} getData
|
|
66 | 68 | * Capture some of the output data.
|
67 | 69 | * @property {(this: CompileContext) => string} resume
|
68 | 70 | * Stop capturing and access the output data.
|
69 |
| - * @property {<N extends Node>(this: CompileContext, node: N, token: Token) => N} enter |
| 71 | + * @property {<N extends Node>(this: CompileContext, node: N, token: Token, onError?: OnError) => N} enter |
70 | 72 | * Enter a token.
|
71 | 73 | * @property {(this: CompileContext, token: Token) => Node} exit
|
72 | 74 | * Exit a token.
|
@@ -309,16 +311,9 @@ function compiler(options = {}) {
|
309 | 311 | }
|
310 | 312 |
|
311 | 313 | if (tokenStack.length > 0) {
|
312 |
| - throw new Error( |
313 |
| - 'Cannot close document, a token (`' + |
314 |
| - tokenStack[tokenStack.length - 1].type + |
315 |
| - '`, ' + |
316 |
| - stringifyPosition({ |
317 |
| - start: tokenStack[tokenStack.length - 1].start, |
318 |
| - end: tokenStack[tokenStack.length - 1].end |
319 |
| - }) + |
320 |
| - ') is still open' |
321 |
| - ) |
| 314 | + const tail = tokenStack[tokenStack.length - 1] |
| 315 | + const handler = tail[1] || defaultOnError |
| 316 | + handler.call(context, undefined, tail[0]) |
322 | 317 | }
|
323 | 318 |
|
324 | 319 | // Figure out `root` position.
|
@@ -540,16 +535,17 @@ function compiler(options = {}) {
|
540 | 535 | * @this {CompileContext}
|
541 | 536 | * @param {N} node
|
542 | 537 | * @param {Token} token
|
| 538 | + * @param {OnError} [errorHandler] |
543 | 539 | * @returns {N}
|
544 | 540 | */
|
545 |
| - function enter(node, token) { |
| 541 | + function enter(node, token, errorHandler) { |
546 | 542 | const parent = this.stack[this.stack.length - 1]
|
547 | 543 | assert(parent, 'expected `parent`')
|
548 | 544 | assert('children' in parent, 'expected `parent`')
|
549 | 545 | // @ts-expect-error: Assume `Node` can exist as a child of `parent`.
|
550 | 546 | parent.children.push(node)
|
551 | 547 | this.stack.push(node)
|
552 |
| - this.tokenStack.push(token) |
| 548 | + this.tokenStack.push([token, errorHandler]) |
553 | 549 | // @ts-expect-error: `end` will be patched later.
|
554 | 550 | node.position = {start: point(token.start)}
|
555 | 551 | return node
|
@@ -587,18 +583,9 @@ function compiler(options = {}) {
|
587 | 583 | stringifyPosition({start: token.start, end: token.end}) +
|
588 | 584 | '): it’s not open'
|
589 | 585 | )
|
590 |
| - } else if (open.type !== token.type) { |
591 |
| - throw new Error( |
592 |
| - 'Cannot close `' + |
593 |
| - token.type + |
594 |
| - '` (' + |
595 |
| - stringifyPosition({start: token.start, end: token.end}) + |
596 |
| - '): a different token (`' + |
597 |
| - open.type + |
598 |
| - '`, ' + |
599 |
| - stringifyPosition({start: open.start, end: open.end}) + |
600 |
| - ') is open' |
601 |
| - ) |
| 586 | + } else if (open[0].type !== token.type) { |
| 587 | + const handler = open[1] || defaultOnError |
| 588 | + handler.call(this, token, open[0]) |
602 | 589 | }
|
603 | 590 |
|
604 | 591 | assert(node.type !== 'fragment', 'unexpected fragment `exit`ed')
|
@@ -1142,3 +1129,28 @@ function extension(combined, extension) {
|
1142 | 1129 | }
|
1143 | 1130 | }
|
1144 | 1131 | }
|
| 1132 | + |
| 1133 | +/** @type {OnError} */ |
| 1134 | +function defaultOnError(left, right) { |
| 1135 | + if (left) { |
| 1136 | + throw new Error( |
| 1137 | + 'Cannot close `' + |
| 1138 | + left.type + |
| 1139 | + '` (' + |
| 1140 | + stringifyPosition({start: left.start, end: left.end}) + |
| 1141 | + '): a different token (`' + |
| 1142 | + right.type + |
| 1143 | + '`, ' + |
| 1144 | + stringifyPosition({start: right.start, end: right.end}) + |
| 1145 | + ') is open' |
| 1146 | + ) |
| 1147 | + } else { |
| 1148 | + throw new Error( |
| 1149 | + 'Cannot close document, a token (`' + |
| 1150 | + right.type + |
| 1151 | + '`, ' + |
| 1152 | + stringifyPosition({start: right.start, end: right.end}) + |
| 1153 | + ') is still open' |
| 1154 | + ) |
| 1155 | + } |
| 1156 | +} |
0 commit comments