/** | |
* @typedef {import('micromark-util-types').Effects} Effects | |
* @typedef {import('micromark-util-types').State} State | |
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | |
* @typedef {import('micromark-util-types').TokenType} TokenType | |
*/ | |
import {markdownLineEnding, markdownSpace} from 'micromark-util-character' | |
/** | |
* Parse labels. | |
* | |
* > 👉 **Note**: labels in markdown are capped at 999 characters in the string. | |
* | |
* ###### Examples | |
* | |
* ```markdown | |
* [a] | |
* [a | |
* b] | |
* [a\]b] | |
* ``` | |
* | |
* @this {TokenizeContext} | |
* Tokenize context. | |
* @param {Effects} effects | |
* Context. | |
* @param {State} ok | |
* State switched to when successful. | |
* @param {State} nok | |
* State switched to when unsuccessful. | |
* @param {TokenType} type | |
* Type of the whole label (`[a]`). | |
* @param {TokenType} markerType | |
* Type for the markers (`[` and `]`). | |
* @param {TokenType} stringType | |
* Type for the identifier (`a`). | |
* @returns {State} | |
* Start state. | |
*/ // eslint-disable-next-line max-params | |
export function factoryLabel(effects, ok, nok, type, markerType, stringType) { | |
const self = this | |
let size = 0 | |
/** @type {boolean} */ | |
let seen | |
return start | |
/** | |
* Start of label. | |
* | |
* ```markdown | |
* > | [a] | |
* ^ | |
* ``` | |
* | |
* @type {State} | |
*/ | |
function start(code) { | |
effects.enter(type) | |
effects.enter(markerType) | |
effects.consume(code) | |
effects.exit(markerType) | |
effects.enter(stringType) | |
return atBreak | |
} | |
/** | |
* In label, at something, before something else. | |
* | |
* ```markdown | |
* > | [a] | |
* ^ | |
* ``` | |
* | |
* @type {State} | |
*/ | |
function atBreak(code) { | |
if ( | |
size > 999 || | |
code === null || | |
code === 91 || | |
(code === 93 && !seen) || | |
// To do: remove in the future once we’ve switched from | |
// `micromark-extension-footnote` to `micromark-extension-gfm-footnote`, | |
// which doesn’t need this. | |
// Hidden footnotes hook. | |
/* c8 ignore next 3 */ | |
(code === 94 && | |
!size && | |
'_hiddenFootnoteSupport' in self.parser.constructs) | |
) { | |
return nok(code) | |
} | |
if (code === 93) { | |
effects.exit(stringType) | |
effects.enter(markerType) | |
effects.consume(code) | |
effects.exit(markerType) | |
effects.exit(type) | |
return ok | |
} | |
// To do: indent? Link chunks and EOLs together? | |
if (markdownLineEnding(code)) { | |
effects.enter('lineEnding') | |
effects.consume(code) | |
effects.exit('lineEnding') | |
return atBreak | |
} | |
effects.enter('chunkString', { | |
contentType: 'string' | |
}) | |
return labelInside(code) | |
} | |
/** | |
* In label, in text. | |
* | |
* ```markdown | |
* > | [a] | |
* ^ | |
* ``` | |
* | |
* @type {State} | |
*/ | |
function labelInside(code) { | |
if ( | |
code === null || | |
code === 91 || | |
code === 93 || | |
markdownLineEnding(code) || | |
size++ > 999 | |
) { | |
effects.exit('chunkString') | |
return atBreak(code) | |
} | |
effects.consume(code) | |
if (!seen) seen = !markdownSpace(code) | |
return code === 92 ? labelEscape : labelInside | |
} | |
/** | |
* After `\`, at a special character. | |
* | |
* ```markdown | |
* > | [a\*a] | |
* ^ | |
* ``` | |
* | |
* @type {State} | |
*/ | |
function labelEscape(code) { | |
if (code === 91 || code === 92 || code === 93) { | |
effects.consume(code) | |
size++ | |
return labelInside | |
} | |
return labelInside(code) | |
} | |
} | |