Files
documentation/node_modules/micromark-extension-mdx-expression/dev/lib/syntax.js
2024-03-22 03:47:51 +05:30

274 lines
7.0 KiB
JavaScript

/**
* @typedef {import('micromark-util-events-to-acorn').Acorn} Acorn
* @typedef {import('micromark-util-events-to-acorn').AcornOptions} AcornOptions
* @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
*/
/**
* @typedef Options
* Configuration (optional).
* @property {Acorn | null | undefined} [acorn]
* Acorn parser to use (optional).
* @property {AcornOptions | null | undefined} [acornOptions]
* Configuration for acorn (default: `{ecmaVersion: 2024, locations: true,
* sourceType: 'module'}`).
*
* All fields except `locations` can be set.
* @property {boolean | null | undefined} [addResult=false]
* Whether to add `estree` fields to tokens with results from acorn (default:
* `false`).
* @property {boolean | null | undefined} [spread=false]
* Undocumented option to parse only a spread (used by
* `micromark-extension-mdx-jsx` to parse spread attributes) (default:
* `false`).
* @property {boolean | null | undefined} [allowEmpty=true]
* Undocumented option to disallow empty attributes (used by
* `micromark-extension-mdx-jsx` to prohobit empty attribute values)
* (default: `false`).
*/
import {ok as assert} from 'devlop'
import {factoryMdxExpression} from 'micromark-factory-mdx-expression'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
import {codes, types} from 'micromark-util-symbol'
/**
* Create an extension for `micromark` to enable MDX expression syntax.
*
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions` to enable MDX
* expression syntax.
*/
export function mdxExpression(options) {
const options_ = options || {}
const addResult = options_.addResult
const acorn = options_.acorn
// Hidden: `micromark-extension-mdx-jsx` supports expressions in tags,
// and one of them is only “spread” elements.
// It also has expressions that are not allowed to be empty (`<x y={}/>`).
// Instead of duplicating code there, this are two small hidden feature here
// to test that behavior.
const spread = options_.spread
let allowEmpty = options_.allowEmpty
/** @type {AcornOptions} */
let acornOptions
if (allowEmpty === null || allowEmpty === undefined) {
allowEmpty = true
}
if (acorn) {
if (!acorn.parseExpressionAt) {
throw new Error(
'Expected a proper `acorn` instance passed in as `options.acorn`'
)
}
acornOptions = Object.assign(
{ecmaVersion: 2024, sourceType: 'module'},
options_.acornOptions
)
} else if (options_.acornOptions || options_.addResult) {
throw new Error('Expected an `acorn` instance passed in as `options.acorn`')
}
return {
flow: {
[codes.leftCurlyBrace]: {
name: 'mdxFlowExpression',
tokenize: tokenizeFlowExpression,
concrete: true
}
},
text: {
[codes.leftCurlyBrace]: {
name: 'mdxTextExpression',
tokenize: tokenizeTextExpression
}
}
}
/**
* MDX expression (flow).
*
* ```markdown
* > | {Math.PI}
* ^^^^^^^^^
* ```
*
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeFlowExpression(effects, ok, nok) {
const self = this
return start
/**
* Start of an MDX expression (flow).
*
* ```markdown
* > | {Math.PI}
* ^
* ```
*
* @type {State}
*/
function start(code) {
// To do: in `markdown-rs`, constructs need to parse the indent themselves.
// This should also be introduced in `micromark-js`.
assert(code === codes.leftCurlyBrace, 'expected `{`')
return before(code)
}
/**
* After optional whitespace, before expression.
*
* ```markdown
* > | {Math.PI}
* ^
* ```
*
* @type {State}
*/
function before(code) {
return factoryMdxExpression.call(
self,
effects,
after,
'mdxFlowExpression',
'mdxFlowExpressionMarker',
'mdxFlowExpressionChunk',
acorn,
acornOptions,
addResult,
spread,
allowEmpty
)(code)
}
/**
* After expression.
*
* ```markdown
* > | {Math.PI}
* ^
* ```
*
* @type {State}
*/
function after(code) {
return markdownSpace(code)
? factorySpace(effects, end, types.whitespace)(code)
: end(code)
}
/**
* After expression, after optional whitespace.
*
* ```markdown
* > | {Math.PI}␠␊
* ^
* ```
*
* @type {State}
*/
function end(code) {
// We want to allow tags directly after expressions.
//
// This case is useful:
//
// ```mdx
// <a>{b}</a>
// ```
//
// This case is not (very?) useful:
//
// ```mdx
// {a}<b/>
// ```
//
// …but it would be tougher than needed to disallow.
//
// To allow that, here we call the flow construct of
// `micromark-extension-mdx-jsx`, and there we call this one.
//
// It would introduce a cyclical interdependency if we test JSX and
// expressions here.
// Because the JSX extension already uses parts of this monorepo, we
// instead test it there.
const lessThanValue = self.parser.constructs.flow[codes.lessThan]
const constructs = Array.isArray(lessThanValue)
? lessThanValue
: /* c8 ignore next 3 -- always a list when normalized. */
lessThanValue
? [lessThanValue]
: []
const jsxTag = constructs.find(function (d) {
return d.name === 'mdxJsxFlowTag'
})
/* c8 ignore next 3 -- this is tested in `micromark-extension-mdx-jsx` */
if (code === codes.lessThan && jsxTag) {
return effects.attempt(jsxTag, end, nok)(code)
}
return code === codes.eof || markdownLineEnding(code)
? ok(code)
: nok(code)
}
}
/**
* MDX expression (text).
*
* ```markdown
* > | a {Math.PI} c.
* ^^^^^^^^^
* ```
*
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeTextExpression(effects, ok) {
const self = this
return start
/**
* Start of an MDX expression (text).
*
* ```markdown
* > | a {Math.PI} c.
* ^
* ```
*
*
* @type {State}
*/
function start(code) {
assert(code === codes.leftCurlyBrace, 'expected `{`')
return factoryMdxExpression.call(
self,
effects,
ok,
'mdxTextExpression',
'mdxTextExpressionMarker',
'mdxTextExpressionChunk',
acorn,
acornOptions,
addResult,
spread,
allowEmpty,
true
)(code)
}
}
}