micromark-factory-mdx-expression
Advanced tools
Comparing version 1.0.7 to 1.0.8
376
dev/index.js
/** | ||
* @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').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Effects} Effects | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-events-to-acorn').Acorn} Acorn | ||
* @typedef {import('micromark-util-events-to-acorn').AcornOptions} AcornOptions | ||
*/ | ||
import {ok as assert} from 'uvu/assert' | ||
/** | ||
* @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 {constants} from 'micromark-util-symbol/constants.js' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import {positionFromEstree} from 'unist-util-position-from-estree' | ||
import {ok as assert} from 'uvu/assert' | ||
import {VFileMessage} from 'vfile-message' | ||
import {eventsToAcorn} from 'micromark-util-events-to-acorn' | ||
/** | ||
* @this {TokenizeContext} | ||
* Context. | ||
* @param {Effects} effects | ||
* Context. | ||
* @param {State} ok | ||
* State switched to when successful | ||
* @param {string} type | ||
* Token type for whole (`{}`). | ||
* @param {string} markerType | ||
* Token type for the markers (`{`, `}`). | ||
* @param {string} 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] | ||
* @param {number | null | undefined} [startColumn=0] | ||
* Support lazy continuation of an expression. | ||
* @returns {State} | ||
@@ -48,16 +76,9 @@ */ | ||
allowEmpty, | ||
allowLazy, | ||
startColumn | ||
allowLazy | ||
) { | ||
const self = this | ||
const eventStart = this.events.length + 3 // Add main and marker token | ||
const tail = this.events[this.events.length - 1] | ||
const initialPrefix = | ||
tail && tail[1].type === types.linePrefix | ||
? tail[2].sliceSerialize(tail[1], true).length | ||
: 0 | ||
const prefixExpressionIndent = initialPrefix ? initialPrefix + 1 : 0 | ||
let balance = 1 | ||
let size = 0 | ||
/** @type {Point} */ | ||
let startPosition | ||
let pointStart | ||
/** @type {Error} */ | ||
@@ -68,3 +89,12 @@ let lastCrash | ||
/** @type {State} */ | ||
/** | ||
* Start of an MDX expression. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
@@ -76,8 +106,17 @@ assert(code === codes.leftCurlyBrace, 'expected `{`') | ||
effects.exit(markerType) | ||
startPosition = self.now() | ||
return atBreak | ||
pointStart = self.now() | ||
return before | ||
} | ||
/** @type {State} */ | ||
function atBreak(code) { | ||
/** | ||
* Before data. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function before(code) { | ||
if (code === codes.eof) { | ||
@@ -94,6 +133,2 @@ throw ( | ||
if (code === codes.rightCurlyBrace) { | ||
return atClosingBrace(code) | ||
} | ||
if (markdownLineEnding(code)) { | ||
@@ -103,30 +138,36 @@ effects.enter(types.lineEnding) | ||
effects.exit(types.lineEnding) | ||
// `startColumn` is used by the JSX extensions that also wraps this | ||
// factory. | ||
// JSX can be indented arbitrarily, but expressions can’t exdent | ||
// arbitrarily, due to that they might contain template strings | ||
// (backticked strings). | ||
// We’ll eat up to where that tag starts (`startColumn`), and a tab size. | ||
/* c8 ignore next 3 */ | ||
const prefixTagIndent = startColumn | ||
? startColumn + constants.tabSize - self.now().column | ||
: 0 | ||
const indent = Math.max(prefixExpressionIndent, prefixTagIndent) | ||
return indent | ||
? factorySpace(effects, atBreak, types.linePrefix, indent) | ||
: atBreak | ||
return eolAfter | ||
} | ||
const now = self.now() | ||
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 ( | ||
now.line !== startPosition.line && | ||
!allowLazy && | ||
self.parser.lazy[now.line] | ||
) { | ||
throw new VFileMessage( | ||
'Unexpected end of file in expression, expected a corresponding closing brace for `{`', | ||
self.now(), | ||
'micromark-extension-mdx-expression:unexpected-eof' | ||
) | ||
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 | ||
} | ||
@@ -138,17 +179,27 @@ | ||
/** @type {State} */ | ||
/** | ||
* In data. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function inside(code) { | ||
if ( | ||
(code === codes.rightCurlyBrace && size === 0) || | ||
code === codes.eof || | ||
code === codes.rightCurlyBrace || | ||
markdownLineEnding(code) | ||
) { | ||
effects.exit(chunkType) | ||
return atBreak(code) | ||
return before(code) | ||
} | ||
// Don’t count if gnostic. | ||
if (code === codes.leftCurlyBrace && !acorn) { | ||
effects.consume(code) | ||
balance++ | ||
return inside | ||
size += 1 | ||
} else if (code === codes.rightCurlyBrace) { | ||
size -= 1 | ||
} | ||
@@ -160,82 +211,135 @@ | ||
/** @type {State} */ | ||
function atClosingBrace(code) { | ||
balance-- | ||
/** | ||
* After eol. | ||
* | ||
* ```markdown | ||
* | a {b + | ||
* > | c} d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function eolAfter(code) { | ||
const now = self.now() | ||
// Agnostic mode: count balanced braces. | ||
if (!acorn) { | ||
if (balance) { | ||
effects.enter(chunkType) | ||
effects.consume(code) | ||
return inside | ||
} | ||
effects.enter(markerType) | ||
effects.consume(code) | ||
effects.exit(markerType) | ||
effects.exit(type) | ||
return ok | ||
// 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' | ||
) | ||
} | ||
// Gnostic mode: parse w/ acorn. | ||
const result = eventsToAcorn(self.events.slice(eventStart), { | ||
acorn, | ||
acornOptions, | ||
start: startPosition, | ||
expression: true, | ||
allowEmpty, | ||
prefix: spread ? '({' : '', | ||
suffix: spread ? '})' : '' | ||
}) | ||
const estree = result.estree | ||
// Idea: investigate if we’d 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 doesn’t. | ||
// The intent above is *perhaps* for the split to be as `>␠␠|␠␠␠␠|d`, | ||
// Currently, we *don’t* do anything at all, it’s `>␠|␠␠␠␠␠|d` instead. | ||
// | ||
// Note: we used to have some handling here, and `markdown-rs` still does, | ||
// which should be removed. | ||
return before(code) | ||
} | ||
} | ||
// 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') | ||
/** | ||
* 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 | ||
// 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' | ||
) | ||
} | ||
// 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) { | ||
lastCrash = new VFileMessage( | ||
if (result.error) { | ||
return { | ||
type: 'nok', | ||
message: new VFileMessage( | ||
'Could not parse expression with acorn: ' + result.error.message, | ||
{ | ||
// @ts-expect-error: fine. | ||
line: result.error.loc.line, | ||
// @ts-expect-error: fine. | ||
column: result.error.loc.column + 1, | ||
// @ts-expect-error: fine. | ||
offset: result.error.pos | ||
@@ -245,18 +349,6 @@ }, | ||
) | ||
if (code !== codes.eof && result.swallow) { | ||
effects.enter(chunkType) | ||
effects.consume(code) | ||
return inside | ||
} | ||
throw lastCrash | ||
} | ||
} | ||
effects.enter(markerType) | ||
effects.consume(code) | ||
effects.exit(markerType) | ||
Object.assign(effects.exit(type), addResult ? {estree} : undefined) | ||
return ok | ||
} | ||
return {type: 'ok', estree} | ||
} |
/** | ||
* @this {TokenizeContext} | ||
* Context. | ||
* @param {Effects} effects | ||
* Context. | ||
* @param {State} ok | ||
* State switched to when successful | ||
* @param {string} type | ||
* Token type for whole (`{}`). | ||
* @param {string} markerType | ||
* Token type for the markers (`{`, `}`). | ||
* @param {string} 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] | ||
* @param {number | null | undefined} [startColumn=0] | ||
* Support lazy continuation of an expression. | ||
* @returns {State} | ||
@@ -29,10 +40,38 @@ */ | ||
allowEmpty?: boolean | null | undefined, | ||
allowLazy?: boolean | null | undefined, | ||
startColumn?: number | null | undefined | ||
allowLazy?: boolean | null | undefined | ||
): State | ||
export type Program = import('estree').Program | ||
export type Acorn = import('micromark-util-events-to-acorn').Acorn | ||
export type AcornOptions = import('micromark-util-events-to-acorn').AcornOptions | ||
export type Effects = import('micromark-util-types').Effects | ||
export type Point = import('micromark-util-types').Point | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Effects = import('micromark-util-types').Effects | ||
export type State = import('micromark-util-types').State | ||
export type Acorn = import('micromark-util-events-to-acorn').Acorn | ||
export type AcornOptions = import('micromark-util-events-to-acorn').AcornOptions | ||
/** | ||
* Good result. | ||
*/ | ||
export type MdxSignalOk = { | ||
/** | ||
* Type. | ||
*/ | ||
type: 'ok' | ||
/** | ||
* Value. | ||
*/ | ||
estree: Program | undefined | ||
} | ||
/** | ||
* Bad result. | ||
*/ | ||
export type MdxSignalNok = { | ||
/** | ||
* Type. | ||
*/ | ||
type: 'nok' | ||
/** | ||
* Value. | ||
*/ | ||
message: VFileMessage | ||
} | ||
export type MdxSignal = MdxSignalOk | MdxSignalNok | ||
import {VFileMessage} from 'vfile-message' |
380
index.js
/** | ||
* @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').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Effects} Effects | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-events-to-acorn').Acorn} Acorn | ||
* @typedef {import('micromark-util-events-to-acorn').AcornOptions} AcornOptions | ||
*/ | ||
/** | ||
* @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 {factorySpace} from 'micromark-factory-space' | ||
import {eventsToAcorn} from 'micromark-util-events-to-acorn' | ||
import {positionFromEstree} from 'unist-util-position-from-estree' | ||
import {VFileMessage} from 'vfile-message' | ||
import {eventsToAcorn} from 'micromark-util-events-to-acorn' | ||
/** | ||
* @this {TokenizeContext} | ||
* Context. | ||
* @param {Effects} effects | ||
* Context. | ||
* @param {State} ok | ||
* State switched to when successful | ||
* @param {string} type | ||
* Token type for whole (`{}`). | ||
* @param {string} markerType | ||
* Token type for the markers (`{`, `}`). | ||
* @param {string} 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] | ||
* @param {number | null | undefined} [startColumn=0] | ||
* Support lazy continuation of an expression. | ||
* @returns {State} | ||
@@ -44,16 +73,9 @@ */ | ||
allowEmpty, | ||
allowLazy, | ||
startColumn | ||
allowLazy | ||
) { | ||
const self = this | ||
const eventStart = this.events.length + 3 // Add main and marker token | ||
const tail = this.events[this.events.length - 1] | ||
const initialPrefix = | ||
tail && tail[1].type === 'linePrefix' | ||
? tail[2].sliceSerialize(tail[1], true).length | ||
: 0 | ||
const prefixExpressionIndent = initialPrefix ? initialPrefix + 1 : 0 | ||
let balance = 1 | ||
let size = 0 | ||
/** @type {Point} */ | ||
let startPosition | ||
let pointStart | ||
/** @type {Error} */ | ||
@@ -63,3 +85,12 @@ let lastCrash | ||
/** @type {State} */ | ||
/** | ||
* Start of an MDX expression. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
@@ -70,8 +101,17 @@ effects.enter(type) | ||
effects.exit(markerType) | ||
startPosition = self.now() | ||
return atBreak | ||
pointStart = self.now() | ||
return before | ||
} | ||
/** @type {State} */ | ||
function atBreak(code) { | ||
/** | ||
* Before data. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function before(code) { | ||
if (code === null) { | ||
@@ -87,5 +127,2 @@ throw ( | ||
} | ||
if (code === 125) { | ||
return atClosingBrace(code) | ||
} | ||
if (markdownLineEnding(code)) { | ||
@@ -95,28 +132,36 @@ effects.enter('lineEnding') | ||
effects.exit('lineEnding') | ||
// `startColumn` is used by the JSX extensions that also wraps this | ||
// factory. | ||
// JSX can be indented arbitrarily, but expressions can’t exdent | ||
// arbitrarily, due to that they might contain template strings | ||
// (backticked strings). | ||
// We’ll eat up to where that tag starts (`startColumn`), and a tab size. | ||
/* c8 ignore next 3 */ | ||
const prefixTagIndent = startColumn | ||
? startColumn + 4 - self.now().column | ||
: 0 | ||
const indent = Math.max(prefixExpressionIndent, prefixTagIndent) | ||
return indent | ||
? factorySpace(effects, atBreak, 'linePrefix', indent) | ||
: atBreak | ||
return eolAfter | ||
} | ||
const now = self.now() | ||
if ( | ||
now.line !== startPosition.line && | ||
!allowLazy && | ||
self.parser.lazy[now.line] | ||
) { | ||
throw new VFileMessage( | ||
'Unexpected end of file in expression, expected a corresponding closing brace for `{`', | ||
self.now(), | ||
'micromark-extension-mdx-expression:unexpected-eof' | ||
) | ||
if (code === 125 && 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 | ||
} | ||
@@ -127,12 +172,27 @@ effects.enter(chunkType) | ||
/** @type {State} */ | ||
/** | ||
* In data. | ||
* | ||
* ```markdown | ||
* > | a {Math.PI} c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function inside(code) { | ||
if (code === null || code === 125 || markdownLineEnding(code)) { | ||
if ( | ||
(code === 125 && size === 0) || | ||
code === null || | ||
markdownLineEnding(code) | ||
) { | ||
effects.exit(chunkType) | ||
return atBreak(code) | ||
return before(code) | ||
} | ||
// Don’t count if gnostic. | ||
if (code === 123 && !acorn) { | ||
effects.consume(code) | ||
balance++ | ||
return inside | ||
size += 1 | ||
} else if (code === 125) { | ||
size -= 1 | ||
} | ||
@@ -143,78 +203,132 @@ effects.consume(code) | ||
/** @type {State} */ | ||
function atClosingBrace(code) { | ||
balance-- | ||
/** | ||
* After eol. | ||
* | ||
* ```markdown | ||
* | a {b + | ||
* > | c} d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function eolAfter(code) { | ||
const now = self.now() | ||
// Agnostic mode: count balanced braces. | ||
if (!acorn) { | ||
if (balance) { | ||
effects.enter(chunkType) | ||
effects.consume(code) | ||
return inside | ||
} | ||
effects.enter(markerType) | ||
effects.consume(code) | ||
effects.exit(markerType) | ||
effects.exit(type) | ||
return ok | ||
// 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' | ||
) | ||
} | ||
// Gnostic mode: parse w/ acorn. | ||
const result = eventsToAcorn(self.events.slice(eventStart), { | ||
acorn, | ||
acornOptions, | ||
start: startPosition, | ||
expression: true, | ||
allowEmpty, | ||
prefix: spread ? '({' : '', | ||
suffix: spread ? '})' : '' | ||
}) | ||
const estree = result.estree | ||
// Idea: investigate if we’d 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 doesn’t. | ||
// The intent above is *perhaps* for the split to be as `>␠␠|␠␠␠␠|d`, | ||
// Currently, we *don’t* do anything at all, it’s `>␠|␠␠␠␠␠|d` instead. | ||
// | ||
// Note: we used to have some handling here, and `markdown-rs` still does, | ||
// which should be removed. | ||
return before(code) | ||
} | ||
} | ||
// Get the spread value. | ||
if (spread && estree) { | ||
// Should always be the case as we wrap in `d={}` | ||
/** | ||
* 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 | ||
const head = estree.body[0] | ||
// 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' | ||
) | ||
} | ||
// Get the spread value. | ||
if (spread && estree) { | ||
// Should always be the case as we wrap in `d={}` | ||
const head = estree.body[0] | ||
// 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) { | ||
lastCrash = new VFileMessage( | ||
} | ||
if (result.error) { | ||
return { | ||
type: 'nok', | ||
message: new VFileMessage( | ||
'Could not parse expression with acorn: ' + result.error.message, | ||
{ | ||
// @ts-expect-error: fine. | ||
line: result.error.loc.line, | ||
// @ts-expect-error: fine. | ||
column: result.error.loc.column + 1, | ||
// @ts-expect-error: fine. | ||
offset: result.error.pos | ||
@@ -224,22 +338,8 @@ }, | ||
) | ||
if (code !== null && result.swallow) { | ||
effects.enter(chunkType) | ||
effects.consume(code) | ||
return inside | ||
} | ||
throw lastCrash | ||
} | ||
effects.enter(markerType) | ||
effects.consume(code) | ||
effects.exit(markerType) | ||
Object.assign( | ||
effects.exit(type), | ||
addResult | ||
? { | ||
estree | ||
} | ||
: undefined | ||
) | ||
return ok | ||
} | ||
return { | ||
type: 'ok', | ||
estree | ||
} | ||
} |
{ | ||
"name": "micromark-factory-mdx-expression", | ||
"version": "1.0.7", | ||
"version": "1.0.8", | ||
"description": "micromark factory to parse MDX expressions (found in JSX attributes, flow, text)", | ||
@@ -42,3 +42,3 @@ "license": "MIT", | ||
"dependencies": { | ||
"micromark-factory-space": "^1.0.0", | ||
"@types/estree": "^1.0.0", | ||
"micromark-util-character": "^1.0.0", | ||
@@ -53,3 +53,3 @@ "micromark-util-events-to-acorn": "^1.0.0", | ||
"scripts": { | ||
"build": "tsc --build --clean && tsc && type-coverage && micromark-build" | ||
"build": "micromark-build" | ||
}, | ||
@@ -56,0 +56,0 @@ "xo": false, |
@@ -11,3 +11,3 @@ # micromark-factory-mdx-expression | ||
micromark factory to parse MDX expressions (found in JSX attributes, flow, | ||
[micromark][] factory to parse MDX expressions (found in JSX attributes, flow, | ||
text). | ||
@@ -22,2 +22,3 @@ | ||
* [Types](#types) | ||
* [Compatibility](#compatibility) | ||
* [Security](#security) | ||
@@ -30,3 +31,3 @@ * [Contribute](#contribute) | ||
This package is [ESM only][esm]. | ||
In Node.js (version 12.20+, 14.14+, 16.0+, or 18.0+), install with [npm][]: | ||
In Node.js (version 16+), install with [npm][]: | ||
@@ -40,3 +41,3 @@ ```sh | ||
```js | ||
import {mdxExpression} from 'https://esm.sh/micromark-factory-mdx-expression@1' | ||
import {factoryMdxExpression} from 'https://esm.sh/micromark-factory-mdx-expression@1' | ||
``` | ||
@@ -48,3 +49,3 @@ | ||
<script type="module"> | ||
import {mdxExpression} from 'https://esm.sh/micromark-factory-mdx-expression@1?bundle' | ||
import {factoryMdxExpression} from 'https://esm.sh/micromark-factory-mdx-expression@1?bundle' | ||
</script> | ||
@@ -91,6 +92,7 @@ ``` | ||
This module exports the identifiers `factoryMdxExpression`. | ||
This module exports the identifier | ||
[`factoryMdxExpression`][api-factory-mdx-expression]. | ||
There is no default export. | ||
The export map supports the endorsed [`development` condition][condition]. | ||
The export map supports the [`development` condition][development]. | ||
Run `node --conditions development module.js` to get instrumented dev code. | ||
@@ -103,16 +105,24 @@ Without this condition, production code is loaded. | ||
* `effects` (`Effects`) — Context | ||
* `ok` (`State`) — State switched to when successful | ||
* `type` (`string`) — Token type for whole (`{}`) | ||
* `markerType` (`string`) — Token type for the markers (`{`, `}`) | ||
* `chunkType` (`string`) — Token type for the value (`1`) | ||
* `acorn` (`Acorn`) — Object with `acorn.parse` and `acorn.parseExpressionAt` | ||
* `acornOptions` ([`AcornOptions`][acorn-options]) — Configuration for acorn | ||
* `boolean` (`addResult`, default: `false`) — Add `estree` to token | ||
* `boolean` (`spread`, default: `false`) — Support a spread (`{...a}`) only | ||
* `boolean` (`allowEmpty`, default: `false`) — Support an empty expression | ||
* `boolean` (`allowLazy`, default: `false`) — Support lazy continuation of an | ||
expression | ||
* `number` (`startColumn`, default: `0`) — Treat whitespace up to this number | ||
and a tab size as indent | ||
* `effects` (`Effects`) | ||
— context | ||
* `ok` (`State`) | ||
— state switched to when successful | ||
* `type` (`string`) | ||
— token type for whole (`{}`) | ||
* `markerType` (`string`) | ||
— token type for the markers (`{`, `}`) | ||
* `chunkType` (`string`) | ||
— token type for the value (`1`) | ||
* `acorn` (`Acorn`) | ||
— object with `acorn.parse` and `acorn.parseExpressionAt` | ||
* `acornOptions` ([`AcornOptions`][acorn-options]) | ||
— configuration for acorn | ||
* `boolean` (`addResult`, default: `false`) | ||
— add `estree` to token | ||
* `boolean` (`spread`, default: `false`) | ||
— support a spread (`{...a}`) only | ||
* `boolean` (`allowEmpty`, default: `false`) | ||
— support an empty expression | ||
* `boolean` (`allowLazy`, default: `false`) | ||
— support lazy continuation of an expression | ||
@@ -123,15 +133,20 @@ ###### Returns | ||
###### Examples | ||
See [`micromark-extension-mdx-expression`][extension] | ||
## Types | ||
This package is fully typed with [TypeScript][]. | ||
It exports the additional types `Acorn` and `AcornOptions`. | ||
It exports the additional types [`Acorn`][acorn] and | ||
[`AcornOptions`][acorn-options]. | ||
## Compatibility | ||
Projects maintained by the unified collective are compatible with all maintained | ||
versions of Node.js. | ||
As of now, that is Node.js 16+. | ||
Our projects sometimes work with older versions, but this is not guaranteed. | ||
These extensions work with `micromark` version 3+. | ||
## Security | ||
See [`security.md`][securitymd] in [`micromark/.github`][health] for how to | ||
submit a security report. | ||
This package is safe. | ||
@@ -190,10 +205,8 @@ ## Contribute | ||
[securitymd]: https://github.com/micromark/.github/blob/HEAD/security.md | ||
[contributing]: https://github.com/micromark/.github/blob/main/contributing.md | ||
[contributing]: https://github.com/micromark/.github/blob/HEAD/contributing.md | ||
[support]: https://github.com/micromark/.github/blob/main/support.md | ||
[support]: https://github.com/micromark/.github/blob/HEAD/support.md | ||
[coc]: https://github.com/micromark/.github/blob/main/code-of-conduct.md | ||
[coc]: https://github.com/micromark/.github/blob/HEAD/code-of-conduct.md | ||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
@@ -203,6 +216,10 @@ | ||
[condition]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions | ||
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions | ||
[acorn-options]: https://github.com/acornjs/acorn/tree/master/acorn#interface | ||
[acorn]: https://github.com/acornjs/acorn | ||
[extension]: https://github.com/micromark/micromark-extension-mdx-expression | ||
[acorn-options]: https://github.com/acornjs/acorn/blob/96c721dbf89d0ccc3a8c7f39e69ef2a6a3c04dfa/acorn/dist/acorn.d.ts#L16 | ||
[micromark]: https://github.com/micromark/micromark | ||
[api-factory-mdx-expression]: #micromark-factory-mdx-expression |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29652
6
798
216
1
+ Added@types/estree@^1.0.0
- Removedmicromark-factory-space@^1.0.0
- Removedmicromark-factory-space@1.1.0(transitive)