site/node_modules/micromark-extension-gfm-task-list-item/dev/lib/syntax.js

170 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

2024-10-14 06:09:33 +00:00
/**
* @typedef {import('micromark-util-types').Extension} Extension
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {
markdownLineEnding,
markdownLineEndingOrSpace,
markdownSpace
} from 'micromark-util-character'
import {codes, types} from 'micromark-util-symbol'
const tasklistCheck = {tokenize: tokenizeTasklistCheck}
/**
* Create an HTML extension for `micromark` to support GFM task list items
* syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GFM task list items when serializing to HTML.
*/
export function gfmTaskListItem() {
return {
text: {[codes.leftSquareBracket]: tasklistCheck}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeTasklistCheck(effects, ok, nok) {
const self = this
return open
/**
* At start of task list item check.
*
* ```markdown
* > | * [x] y.
* ^
* ```
*
* @type {State}
*/
function open(code) {
assert(code === codes.leftSquareBracket, 'expected `[`')
if (
// Exit if theres stuff before.
self.previous !== codes.eof ||
// Exit if not in the first content that is the first child of a list
// item.
!self._gfmTasklistFirstContentOfListItem
) {
return nok(code)
}
effects.enter('taskListCheck')
effects.enter('taskListCheckMarker')
effects.consume(code)
effects.exit('taskListCheckMarker')
return inside
}
/**
* In task list item check.
*
* ```markdown
* > | * [x] y.
* ^
* ```
*
* @type {State}
*/
function inside(code) {
// Currently we match how GH works in files.
// To match how GH works in comments, use `markdownSpace` (`[\t ]`) instead
// of `markdownLineEndingOrSpace` (`[\t\n\r ]`).
if (markdownLineEndingOrSpace(code)) {
effects.enter('taskListCheckValueUnchecked')
effects.consume(code)
effects.exit('taskListCheckValueUnchecked')
return close
}
if (code === codes.uppercaseX || code === codes.lowercaseX) {
effects.enter('taskListCheckValueChecked')
effects.consume(code)
effects.exit('taskListCheckValueChecked')
return close
}
return nok(code)
}
/**
* At close of task list item check.
*
* ```markdown
* > | * [x] y.
* ^
* ```
*
* @type {State}
*/
function close(code) {
if (code === codes.rightSquareBracket) {
effects.enter('taskListCheckMarker')
effects.consume(code)
effects.exit('taskListCheckMarker')
effects.exit('taskListCheck')
return after
}
return nok(code)
}
/**
* @type {State}
*/
function after(code) {
// EOL in paragraph means there must be something else after it.
if (markdownLineEnding(code)) {
return ok(code)
}
// Space or tab?
// Check what comes after.
if (markdownSpace(code)) {
return effects.check({tokenize: spaceThenNonSpace}, ok, nok)(code)
}
// EOF, or non-whitespace, both wrong.
return nok(code)
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function spaceThenNonSpace(effects, ok, nok) {
return factorySpace(effects, after, types.whitespace)
/**
* After whitespace, after task list item check.
*
* ```markdown
* > | * [x] y.
* ^
* ```
*
* @type {State}
*/
function after(code) {
// EOF means there was nothing, so bad.
// EOL means theres content after it, so good.
// Impossible to have more spaces.
// Anything else is good.
return code === codes.eof ? nok(code) : ok(code)
}
}