kjelsrud.dev/node_modules/micromark-factory-mdx-expression/dev/index.js
2023-07-19 21:31:30 +02:00

347 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @typedef {import('estree').Program} Program
* @typedef {import('micromark-util-events-to-acorn').Acorn} Acorn
* @typedef {import('micromark-util-events-to-acorn').AcornOptions} AcornOptions
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').Point} Point
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').TokenType} TokenType
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
*/
/**
* @typedef MdxSignalOk
* Good result.
* @property {'ok'} type
* Type.
* @property {Program | undefined} estree
* Value.
*
* @typedef MdxSignalNok
* Bad result.
* @property {'nok'} type
* Type.
* @property {VFileMessage} message
* Value.
*
* @typedef {MdxSignalOk | MdxSignalNok} MdxSignal
*/
import {markdownLineEnding} from 'micromark-util-character'
import {eventsToAcorn} from 'micromark-util-events-to-acorn'
import {codes} from 'micromark-util-symbol/codes.js'
import {types} from 'micromark-util-symbol/types.js'
import {positionFromEstree} from 'unist-util-position-from-estree'
import {ok as assert} from 'uvu/assert'
import {VFileMessage} from 'vfile-message'
/**
* @this {TokenizeContext}
* Context.
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful
* @param {TokenType} type
* Token type for whole (`{}`).
* @param {TokenType} markerType
* Token type for the markers (`{`, `}`).
* @param {TokenType} chunkType
* Token type for the value (`1`).
* @param {Acorn | null | undefined} [acorn]
* Object with `acorn.parse` and `acorn.parseExpressionAt`.
* @param {AcornOptions | null | undefined} [acornOptions]
* Configuration for acorn.
* @param {boolean | null | undefined} [addResult=false]
* Add `estree` to token.
* @param {boolean | null | undefined} [spread=false]
* Support a spread (`{...a}`) only.
* @param {boolean | null | undefined} [allowEmpty=false]
* Support an empty expression.
* @param {boolean | null | undefined} [allowLazy=false]
* Support lazy continuation of an expression.
* @returns {State}
*/
// eslint-disable-next-line max-params
export function factoryMdxExpression(
effects,
ok,
type,
markerType,
chunkType,
acorn,
acornOptions,
addResult,
spread,
allowEmpty,
allowLazy
) {
const self = this
const eventStart = this.events.length + 3 // Add main and marker token
let size = 0
/** @type {Point} */
let pointStart
/** @type {Error} */
let lastCrash
return start
/**
* Start of an MDX expression.
*
* ```markdown
* > | a {Math.PI} c
* ^
* ```
*
* @type {State}
*/
function start(code) {
assert(code === codes.leftCurlyBrace, 'expected `{`')
effects.enter(type)
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
pointStart = self.now()
return before
}
/**
* Before data.
*
* ```markdown
* > | a {Math.PI} c
* ^
* ```
*
* @type {State}
*/
function before(code) {
if (code === codes.eof) {
throw (
lastCrash ||
new VFileMessage(
'Unexpected end of file in expression, expected a corresponding closing brace for `{`',
self.now(),
'micromark-extension-mdx-expression:unexpected-eof'
)
)
}
if (markdownLineEnding(code)) {
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return eolAfter
}
if (code === codes.rightCurlyBrace && size === 0) {
/** @type {MdxSignal} */
const next = acorn
? mdxExpressionParse.call(
self,
acorn,
acornOptions,
eventStart,
pointStart,
allowEmpty || false,
spread || false
)
: {type: 'ok', estree: undefined}
if (next.type === 'ok') {
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
const token = effects.exit(type)
if (addResult && next.estree) {
Object.assign(token, {estree: next.estree})
}
return ok
}
lastCrash = next.message
effects.enter(chunkType)
effects.consume(code)
return inside
}
effects.enter(chunkType)
return inside(code)
}
/**
* In data.
*
* ```markdown
* > | a {Math.PI} c
* ^
* ```
*
* @type {State}
*/
function inside(code) {
if (
(code === codes.rightCurlyBrace && size === 0) ||
code === codes.eof ||
markdownLineEnding(code)
) {
effects.exit(chunkType)
return before(code)
}
// Dont count if gnostic.
if (code === codes.leftCurlyBrace && !acorn) {
size += 1
} else if (code === codes.rightCurlyBrace) {
size -= 1
}
effects.consume(code)
return inside
}
/**
* After eol.
*
* ```markdown
* | a {b +
* > | c} d
* ^
* ```
*
* @type {State}
*/
function eolAfter(code) {
const now = self.now()
// Lazy continuation in a flow expression (or flow tag) is a syntax error.
if (
now.line !== pointStart.line &&
!allowLazy &&
self.parser.lazy[now.line]
) {
// `markdown-rs` uses:
// ``Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc``.
throw new VFileMessage(
'Unexpected end of file in expression, expected a corresponding closing brace for `{`',
self.now(),
'micromark-extension-mdx-expression:unexpected-eof'
)
}
// Idea: investigate if wed need to use more complex stripping.
// Take this example:
//
// ```markdown
// > aaa <b c={`
// > d
// > `} /> eee
// ```
//
// The block quote takes one space from each line, the paragraph doesnt.
// The intent above is *perhaps* for the split to be as `>␠␠|␠␠␠␠|d`,
// Currently, we *dont* do anything at all, its `>␠|␠␠␠␠␠|d` instead.
//
// Note: we used to have some handling here, and `markdown-rs` still does,
// which should be removed.
return before(code)
}
}
/**
* Mix of `markdown-rs`s `parse_expression` and `MdxExpressionParse`
* functionality, to wrap our `eventsToAcorn`.
*
* In the future, the plan is to realise the rust way, which allows arbitrary
* parsers.
*
* @this {TokenizeContext}
* @param {Acorn} acorn
* @param {AcornOptions | null | undefined} acornOptions
* @param {number} eventStart
* @param {Point} pointStart
* @param {boolean} allowEmpty
* @param {boolean} spread
* @returns {MdxSignal}
*/
// eslint-disable-next-line max-params
function mdxExpressionParse(
acorn,
acornOptions,
eventStart,
pointStart,
allowEmpty,
spread
) {
// Gnostic mode: parse w/ acorn.
const result = eventsToAcorn(this.events.slice(eventStart), {
acorn,
acornOptions,
start: pointStart,
expression: true,
allowEmpty,
prefix: spread ? '({' : '',
suffix: spread ? '})' : ''
})
const estree = result.estree
// Get the spread value.
if (spread && estree) {
// Should always be the case as we wrap in `d={}`
assert(estree.type === 'Program', 'expected program')
const head = estree.body[0]
assert(head, 'expected body')
// Can occur in some complex attributes.
/* c8 ignore next 11 */
if (
head.type !== 'ExpressionStatement' ||
head.expression.type !== 'ObjectExpression'
) {
throw new VFileMessage(
'Unexpected `' +
head.type +
'` in code: expected an object spread (`{...spread}`)',
positionFromEstree(head).start,
'micromark-extension-mdx-expression:non-spread'
)
} else if (head.expression.properties[1]) {
throw new VFileMessage(
'Unexpected extra content in spread: only a single spread is supported',
positionFromEstree(head.expression.properties[1]).start,
'micromark-extension-mdx-expression:spread-extra'
)
} else if (
head.expression.properties[0] &&
head.expression.properties[0].type !== 'SpreadElement'
) {
throw new VFileMessage(
'Unexpected `' +
head.expression.properties[0].type +
'` in code: only spread elements are supported',
positionFromEstree(head.expression.properties[0]).start,
'micromark-extension-mdx-expression:non-spread'
)
}
}
if (result.error) {
return {
type: 'nok',
message: new VFileMessage(
'Could not parse expression with acorn: ' + result.error.message,
{
line: result.error.loc.line,
column: result.error.loc.column + 1,
offset: result.error.pos
},
'micromark-extension-mdx-expression:acorn'
)
}
}
return {type: 'ok', estree}
}