micromark-extension-mdx-jsx
Advanced tools
Comparing version 1.0.3 to 1.0.4
@@ -6,6 +6,6 @@ /** | ||
* @param {State} nok | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* @param {boolean|undefined} allowLazy | ||
* @param {Acorn | undefined} acorn | ||
* @param {AcornOptions | undefined} acornOptions | ||
* @param {boolean | undefined} addResult | ||
* @param {boolean | undefined} allowLazy | ||
* @param {string} tagType | ||
@@ -38,2 +38,3 @@ * @param {string} tagMarkerType | ||
export function factoryTag( | ||
this: import('micromark-util-types').TokenizeContext, | ||
effects: Effects, | ||
@@ -74,10 +75,8 @@ ok: State, | ||
) => void | import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type Effects = import('micromark-util-types').Effects | ||
export type State = import('micromark-util-types').State | ||
export type Code = import('micromark-util-types').Code | ||
export type Point = import('micromark-util-types').Point | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Code = import('micromark-util-types').Code | ||
export type Effects = import('micromark-util-types').Effects | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext |
/** | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Code} Code | ||
* @typedef {import('micromark-util-types').Effects} Effects | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').Code} Code | ||
* @typedef {import('micromark-util-types').Point} Point | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
*/ | ||
import {ok as assert} from 'uvu/assert' | ||
import {start as idStart, cont as idCont} from 'estree-util-is-identifier-name' | ||
import {factoryMdxExpression} from 'micromark-factory-mdx-expression' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import { | ||
@@ -25,6 +21,5 @@ markdownLineEnding, | ||
import {types} from 'micromark-util-symbol/types.js' | ||
import {ok as assert} from 'uvu/assert' | ||
import {VFileMessage} from 'vfile-message' | ||
const lazyLineEnd = {tokenize: tokenizeLazyLineEnd, partial: true} | ||
/** | ||
@@ -35,6 +30,6 @@ * @this {TokenizeContext} | ||
* @param {State} nok | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* @param {boolean|undefined} allowLazy | ||
* @param {Acorn | undefined} acorn | ||
* @param {AcornOptions | undefined} acornOptions | ||
* @param {boolean | undefined} addResult | ||
* @param {boolean | undefined} allowLazy | ||
* @param {string} tagType | ||
@@ -104,13 +99,19 @@ * @param {string} tagMarkerType | ||
let returnState | ||
/** @type {NonNullable<Code>|undefined} */ | ||
/** @type {NonNullable<Code> | undefined} */ | ||
let marker | ||
/** @type {Point|undefined} */ | ||
let startPoint | ||
return start | ||
/** @type {State} */ | ||
/** | ||
* Start of MDX: JSX. | ||
* | ||
* ```markdown | ||
* > | a <B /> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
assert(code === codes.lessThan, 'expected `<`') | ||
startPoint = self.now() | ||
effects.enter(tagType) | ||
@@ -120,10 +121,19 @@ effects.enter(tagMarkerType) | ||
effects.exit(tagMarkerType) | ||
return afterStart | ||
return startAfter | ||
} | ||
/** @type {State} */ | ||
function afterStart(code) { | ||
/** | ||
* After `<`. | ||
* | ||
* ```markdown | ||
* > | a <B /> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function startAfter(code) { | ||
// Deviate from JSX, which allows arbitrary whitespace. | ||
// See: <https://github.com/micromark/micromark-extension-mdx-jsx/issues/7>. | ||
if (markdownLineEnding(code) || markdownSpace(code)) { | ||
if (markdownLineEndingOrSpace(code)) { | ||
return nok(code) | ||
@@ -133,9 +143,21 @@ } | ||
// Any other ES whitespace does not get this treatment. | ||
returnState = beforeName | ||
return optionalEsWhitespace(code) | ||
returnState = nameBefore | ||
return esWhitespaceStart(code) | ||
} | ||
// Right after `<`, before an optional name. | ||
/** @type {State} */ | ||
function beforeName(code) { | ||
/** | ||
* Before name, self slash, or end of tag for fragments. | ||
* | ||
* ```markdown | ||
* > | a <B> c | ||
* ^ | ||
* > | a </B> c | ||
* ^ | ||
* > | a <> b | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function nameBefore(code) { | ||
// Closing tag. | ||
@@ -146,4 +168,4 @@ if (code === codes.slash) { | ||
effects.exit(tagClosingMarkerType) | ||
returnState = beforeClosingTagName | ||
return optionalEsWhitespace | ||
returnState = closingTagNameBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -174,5 +196,15 @@ | ||
// At the start of a closing tag, right after `</`. | ||
/** @type {State} */ | ||
function beforeClosingTagName(code) { | ||
/** | ||
* Before name of closing tag or end of closing fragment tag. | ||
* | ||
* ```markdown | ||
* > | a </> b | ||
* ^ | ||
* > | a </B> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closingTagNameBefore(code) { | ||
// Fragment closing tag. | ||
@@ -201,4 +233,12 @@ if (code === codes.greaterThan) { | ||
// Inside the primary name. | ||
/** @type {State} */ | ||
/** | ||
* In primary name. | ||
* | ||
* ```markdown | ||
* > | a <Bc> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function primaryName(code) { | ||
@@ -222,4 +262,4 @@ // Continuation of name: remain. | ||
effects.exit(tagNamePrimaryType) | ||
returnState = afterPrimaryName | ||
return optionalEsWhitespace(code) | ||
returnState = primaryNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -237,5 +277,15 @@ | ||
// After a name. | ||
/** @type {State} */ | ||
function afterPrimaryName(code) { | ||
/** | ||
* After primary name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b:c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function primaryNameAfter(code) { | ||
// Start of a member name. | ||
@@ -246,4 +296,4 @@ if (code === codes.dot) { | ||
effects.exit(tagNameMemberMarkerType) | ||
returnState = beforeMemberName | ||
return optionalEsWhitespace | ||
returnState = memberNameBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -256,4 +306,4 @@ | ||
effects.exit(tagNamePrefixMarkerType) | ||
returnState = beforeLocalName | ||
return optionalEsWhitespace | ||
returnState = localNameBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -269,3 +319,3 @@ | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
@@ -280,5 +330,13 @@ | ||
// We’ve seen a `.` and are expecting a member name. | ||
/** @type {State} */ | ||
function beforeMemberName(code) { | ||
/** | ||
* Before member name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberNameBefore(code) { | ||
// Start of a member name. | ||
@@ -298,6 +356,14 @@ if (code !== codes.eof && idStart(code)) { | ||
// Inside the member name. | ||
/** @type {State} */ | ||
/** | ||
* In member name. | ||
* | ||
* ```markdown | ||
* > | a <b.cd> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberName(code) { | ||
// Continuation of member name: stay in state | ||
// Continuation of name: remain. | ||
if (code === codes.dash || (code !== codes.eof && idCont(code))) { | ||
@@ -308,3 +374,4 @@ effects.consume(code) | ||
// End of member name (note that namespaces and members can’t be combined). | ||
// End of name. | ||
// Note: no `:` allowed here. | ||
if ( | ||
@@ -319,4 +386,4 @@ code === codes.dot || | ||
effects.exit(tagNameMemberType) | ||
returnState = afterMemberName | ||
return optionalEsWhitespace(code) | ||
returnState = memberNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -334,6 +401,15 @@ | ||
// After a member name: this is the same as `afterPrimaryName` but we don’t | ||
// expect colons. | ||
/** @type {State} */ | ||
function afterMemberName(code) { | ||
/** | ||
* After member name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b.c.d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberNameAfter(code) { | ||
// Start another member name. | ||
@@ -344,4 +420,4 @@ if (code === codes.dot) { | ||
effects.exit(tagNameMemberMarkerType) | ||
returnState = beforeMemberName | ||
return optionalEsWhitespace | ||
returnState = memberNameBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -357,3 +433,3 @@ | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
@@ -368,5 +444,13 @@ | ||
// We’ve seen a `:`, and are expecting a local name. | ||
/** @type {State} */ | ||
function beforeLocalName(code) { | ||
/** | ||
* Local member name. | ||
* | ||
* ```markdown | ||
* > | a <b:c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localNameBefore(code) { | ||
// Start of a local name. | ||
@@ -392,6 +476,14 @@ if (code !== codes.eof && idStart(code)) { | ||
// Inside the local name. | ||
/** @type {State} */ | ||
/** | ||
* In local name. | ||
* | ||
* ```markdown | ||
* > | a <b:cd> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localName(code) { | ||
// Continuation of local name: stay in state | ||
// Continuation of name: remain. | ||
if (code === codes.dash || (code !== codes.eof && idCont(code))) { | ||
@@ -411,4 +503,4 @@ effects.consume(code) | ||
effects.exit(tagNameLocalType) | ||
returnState = afterLocalName | ||
return optionalEsWhitespace(code) | ||
returnState = localNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -423,6 +515,18 @@ | ||
// After a local name: this is the same as `afterPrimaryName` but we don’t | ||
// expect colons or periods. | ||
/** @type {State} */ | ||
function afterLocalName(code) { | ||
/** | ||
* After local name. | ||
* | ||
* This is like as `primary_name_after`, but we don’t expect colons or | ||
* periods. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b.c.d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localNameAfter(code) { | ||
// End of name. | ||
@@ -436,3 +540,3 @@ if ( | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
@@ -447,5 +551,20 @@ | ||
/** @type {State} */ | ||
function beforeAttribute(code) { | ||
// Mark as self-closing. | ||
/** | ||
* Before attribute. | ||
* | ||
* ```markdown | ||
* > | a <b /> c | ||
* ^ | ||
* > | a <b > c | ||
* ^ | ||
* > | a <b {...c}> d | ||
* ^ | ||
* > | a <b c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeBefore(code) { | ||
// Self-closing. | ||
if (code === codes.slash) { | ||
@@ -456,3 +575,3 @@ effects.enter(tagSelfClosingMarker) | ||
returnState = selfClosing | ||
return optionalEsWhitespace | ||
return esWhitespaceStart | ||
} | ||
@@ -467,7 +586,6 @@ | ||
if (code === codes.leftCurlyBrace) { | ||
assert(startPoint, 'expected `startPoint` to be defined') | ||
return factoryMdxExpression.call( | ||
self, | ||
effects, | ||
afterAttributeExpression, | ||
attributeExpressionAfter, | ||
tagExpressionAttributeType, | ||
@@ -481,4 +599,3 @@ tagExpressionAttributeMarkerType, | ||
false, | ||
allowLazy, | ||
startPoint.column | ||
allowLazy | ||
)(code) | ||
@@ -503,13 +620,33 @@ } | ||
// At the start of an attribute expression. | ||
/** @type {State} */ | ||
function afterAttributeExpression(code) { | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
/** | ||
* After attribute expression. | ||
* | ||
* ```markdown | ||
* > | a <b {c} d/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeExpressionAfter(code) { | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
// In the attribute name. | ||
/** @type {State} */ | ||
/** | ||
* In primary attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b cd/> e | ||
* ^ | ||
* > | a <b c:d> e | ||
* ^ | ||
* > | a <b c=d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributePrimaryName(code) { | ||
// Continuation of the attribute name. | ||
// Continuation of name: remain. | ||
if (code === codes.dash || (code !== codes.eof && idCont(code))) { | ||
@@ -531,4 +668,4 @@ effects.consume(code) | ||
effects.exit(tagAttributeNamePrimaryType) | ||
returnState = afterAttributePrimaryName | ||
return optionalEsWhitespace(code) | ||
returnState = attributePrimaryNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -543,5 +680,17 @@ | ||
// After an attribute name, probably finding an equals. | ||
/** @type {State} */ | ||
function afterAttributePrimaryName(code) { | ||
/** | ||
* After primary attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c/> d | ||
* ^ | ||
* > | a <b c:d> e | ||
* ^ | ||
* > | a <b c=d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributePrimaryNameAfter(code) { | ||
// Start of a local name. | ||
@@ -552,7 +701,7 @@ if (code === codes.colon) { | ||
effects.exit(tagAttributeNamePrefixMarkerType) | ||
returnState = beforeAttributeLocalName | ||
return optionalEsWhitespace | ||
returnState = attributeLocalNameBefore | ||
return esWhitespaceStart | ||
} | ||
// Start of an attribute value. | ||
// Initializer: start of an attribute value. | ||
if (code === codes.equalsTo) { | ||
@@ -563,4 +712,4 @@ effects.exit(tagAttributeNameType) | ||
effects.exit(tagAttributeInitializerMarkerType) | ||
returnState = beforeAttributeValue | ||
return optionalEsWhitespace | ||
returnState = attributeValueBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -579,4 +728,4 @@ | ||
effects.exit(tagAttributeType) | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -591,5 +740,13 @@ | ||
// We’ve seen a `:`, and are expecting a local name. | ||
/** @type {State} */ | ||
function beforeAttributeLocalName(code) { | ||
/** | ||
* Before local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:d/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalNameBefore(code) { | ||
// Start of a local name. | ||
@@ -609,6 +766,16 @@ if (code !== codes.eof && idStart(code)) { | ||
// In the local attribute name. | ||
/** @type {State} */ | ||
/** | ||
* In local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:de/> f | ||
* ^ | ||
* > | a <b c:d=e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalName(code) { | ||
// Continuation of the local attribute name. | ||
// Continuation of name: remain. | ||
if (code === codes.dash || (code !== codes.eof && idCont(code))) { | ||
@@ -619,3 +786,3 @@ effects.consume(code) | ||
// End of tag / attribute name. | ||
// End of local name (note that we don’t expect another colon). | ||
if ( | ||
@@ -631,4 +798,4 @@ code === codes.slash || | ||
effects.exit(tagAttributeNameType) | ||
returnState = afterAttributeLocalName | ||
return optionalEsWhitespace(code) | ||
returnState = attributeLocalNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -643,5 +810,15 @@ | ||
// After a local attribute name, expecting an equals. | ||
/** @type {State} */ | ||
function afterAttributeLocalName(code) { | ||
/** | ||
* After local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:d/> f | ||
* ^ | ||
* > | a <b c:d=e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalNameAfter(code) { | ||
// Start of an attribute value. | ||
@@ -652,7 +829,7 @@ if (code === codes.equalsTo) { | ||
effects.exit(tagAttributeInitializerMarkerType) | ||
returnState = beforeAttributeValue | ||
return optionalEsWhitespace | ||
returnState = attributeValueBefore | ||
return esWhitespaceStart | ||
} | ||
// End of tag / new attribute. | ||
// End of name. | ||
if ( | ||
@@ -665,3 +842,3 @@ code === codes.slash || | ||
effects.exit(tagAttributeType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
@@ -676,5 +853,15 @@ | ||
// After an attribute value initializer, expecting quotes and such. | ||
/** @type {State} */ | ||
function beforeAttributeValue(code) { | ||
/** | ||
* After `=`, before value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* > | a <b c={d}/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueBefore(code) { | ||
// Start of double- or single quoted value. | ||
@@ -690,9 +877,8 @@ if (code === codes.quotationMark || code === codes.apostrophe) { | ||
// Start of an assignment expression. | ||
// Attribute value expression. | ||
if (code === codes.leftCurlyBrace) { | ||
assert(startPoint, 'expected `startPoint` to be defined') | ||
return factoryMdxExpression.call( | ||
self, | ||
effects, | ||
afterAttributeValueExpression, | ||
attributeValueExpressionAfter, | ||
tagAttributeValueExpressionType, | ||
@@ -706,4 +892,3 @@ tagAttributeValueExpressionMarkerType, | ||
false, | ||
allowLazy, | ||
startPoint.column | ||
allowLazy | ||
)(code) | ||
@@ -722,11 +907,28 @@ } | ||
/** @type {State} */ | ||
function afterAttributeValueExpression(code) { | ||
/** | ||
* After attribute value expression. | ||
* | ||
* ```markdown | ||
* > | a <b c={d} e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueExpressionAfter(code) { | ||
effects.exit(tagAttributeType) | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
// At the start of a quoted attribute value. | ||
/** @type {State} */ | ||
/** | ||
* Before quoted literal attribute value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueQuotedStart(code) { | ||
@@ -739,3 +941,3 @@ assert(marker !== undefined, 'expected `marker` to be defined') | ||
'in attribute value', | ||
'a corresponding closing quote `' + String.fromCharCode(marker) + '`' | ||
'a corresponding closing quote `' + String.fromCodePoint(marker) + '`' | ||
) | ||
@@ -751,4 +953,4 @@ } | ||
marker = undefined | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace | ||
returnState = attributeBefore | ||
return esWhitespaceStart | ||
} | ||
@@ -758,3 +960,3 @@ | ||
returnState = attributeValueQuotedStart | ||
return optionalEsWhitespace(code) | ||
return esWhitespaceStart(code) | ||
} | ||
@@ -766,4 +968,12 @@ | ||
// In a quoted attribute value. | ||
/** @type {State} */ | ||
/** | ||
* In quoted literal attribute value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueQuoted(code) { | ||
@@ -775,3 +985,2 @@ if (code === codes.eof || code === marker || markdownLineEnding(code)) { | ||
// Continuation. | ||
effects.consume(code) | ||
@@ -781,6 +990,13 @@ return attributeValueQuoted | ||
// Right after the slash on a tag, e.g., `<asd /`. | ||
/** @type {State} */ | ||
/** | ||
* After self-closing slash. | ||
* | ||
* ```markdown | ||
* > | a <b/> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function selfClosing(code) { | ||
// End of tag. | ||
if (code === codes.greaterThan) { | ||
@@ -800,4 +1016,12 @@ return tagEnd(code) | ||
// At a `>`. | ||
/** @type {State} */ | ||
/** | ||
* At final `>`. | ||
* | ||
* ```markdown | ||
* > | a <b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function tagEnd(code) { | ||
@@ -812,28 +1036,18 @@ assert(code === codes.greaterThan, 'expected `>`') | ||
// Optionally start whitespace. | ||
/** @type {State} */ | ||
function optionalEsWhitespace(code) { | ||
/** | ||
* Before optional ECMAScript whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceStart(code) { | ||
if (markdownLineEnding(code)) { | ||
if (allowLazy) { | ||
effects.enter(types.lineEnding) | ||
effects.consume(code) | ||
effects.exit(types.lineEnding) | ||
return factorySpace( | ||
effects, | ||
optionalEsWhitespace, | ||
types.linePrefix, | ||
constants.tabSize | ||
) | ||
} | ||
return effects.attempt( | ||
lazyLineEnd, | ||
factorySpace( | ||
effects, | ||
optionalEsWhitespace, | ||
types.linePrefix, | ||
constants.tabSize | ||
), | ||
crashEol | ||
)(code) | ||
effects.enter(types.lineEnding) | ||
effects.consume(code) | ||
effects.exit(types.lineEnding) | ||
return esWhitespaceEolAfter | ||
} | ||
@@ -843,3 +1057,3 @@ | ||
effects.enter('esWhitespace') | ||
return optionalEsWhitespaceContinue(code) | ||
return esWhitespaceInside(code) | ||
} | ||
@@ -850,28 +1064,53 @@ | ||
// Continue optional whitespace. | ||
/** @type {State} */ | ||
function optionalEsWhitespaceContinue(code) { | ||
if ( | ||
markdownLineEnding(code) || | ||
!(markdownSpace(code) || unicodeWhitespace(code)) | ||
) { | ||
/** | ||
* In ECMAScript whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceInside(code) { | ||
if (markdownLineEnding(code)) { | ||
effects.exit('esWhitespace') | ||
return optionalEsWhitespace(code) | ||
return esWhitespaceStart(code) | ||
} | ||
effects.consume(code) | ||
return optionalEsWhitespaceContinue | ||
if (markdownSpace(code) || unicodeWhitespace(code)) { | ||
effects.consume(code) | ||
return esWhitespaceInside | ||
} | ||
effects.exit('esWhitespace') | ||
return returnState(code) | ||
} | ||
/** @type {State} */ | ||
function crashEol() { | ||
throw new VFileMessage( | ||
'Unexpected lazy line in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc', | ||
self.now(), | ||
'micromark-extension-mdx-jsx:unexpected-eof' | ||
) | ||
/** | ||
* After eol in whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a\nb> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceEolAfter(code) { | ||
// Lazy continuation in a flow tag is a syntax error. | ||
if (!allowLazy && self.parser.lazy[self.now().line]) { | ||
throw new VFileMessage( | ||
'Unexpected lazy line in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc', | ||
self.now(), | ||
'micromark-extension-mdx-jsx:unexpected-eof' | ||
) | ||
} | ||
return esWhitespaceStart(code) | ||
} | ||
// Crash at a nonconforming character. | ||
/** | ||
* Crash at a nonconforming character. | ||
* | ||
* @param {Code} code | ||
@@ -887,3 +1126,5 @@ * @param {string} at | ||
: 'character `' + | ||
(code === codes.graveAccent ? '` ` `' : String.fromCharCode(code)) + | ||
(code === codes.graveAccent | ||
? '` ` `' | ||
: String.fromCodePoint(code)) + | ||
'` (' + | ||
@@ -903,23 +1144,2 @@ serializeCharCode(code) + | ||
/** @type {Tokenizer} */ | ||
function tokenizeLazyLineEnd(effects, ok, nok) { | ||
const self = this | ||
return start | ||
/** @type {State} */ | ||
function start(code) { | ||
assert(markdownLineEnding(code), 'expected eol') | ||
effects.enter(types.lineEnding) | ||
effects.consume(code) | ||
effects.exit(types.lineEnding) | ||
return lineStart | ||
} | ||
/** @type {State} */ | ||
function lineStart(code) { | ||
return self.parser.lazy[self.now().line] ? nok(code) : ok(code) | ||
} | ||
} | ||
/** | ||
@@ -926,0 +1146,0 @@ * @param {NonNullable<Code>} code |
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (flow). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -12,7 +18,8 @@ export function jsxFlow( | ||
): Construct | ||
export type Construct = import('micromark-util-types').Construct | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type State = import('micromark-util-types').State | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Construct = import('micromark-util-types').Construct | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer |
/** | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
*/ | ||
import {ok as assert} from 'uvu/assert' | ||
import {markdownLineEnding, markdownSpace} from 'micromark-util-character' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import {markdownLineEnding} from 'micromark-util-character' | ||
import {codes} from 'micromark-util-symbol/codes.js' | ||
import {types} from 'micromark-util-symbol/types.js' | ||
import {ok as assert} from 'uvu/assert' | ||
import {factoryTag} from './factory-tag.js' | ||
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (flow). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -25,3 +32,13 @@ export function jsxFlow(acorn, acornOptions, addResult) { | ||
/** @type {Tokenizer} */ | ||
/** | ||
* MDX JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^^^^^ | ||
* ``` | ||
* | ||
* @this {TokenizeContext} | ||
* @type {Tokenizer} | ||
*/ | ||
function tokenizeJsxFlow(effects, ok, nok) { | ||
@@ -32,9 +49,34 @@ const self = this | ||
/** @type {State} */ | ||
/** | ||
* Start of MDX: JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^ | ||
* ``` | ||
* | ||
* @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.lessThan, 'expected `<`') | ||
return before(code) | ||
} | ||
/** | ||
* After optional whitespace, before of MDX JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function before(code) { | ||
return factoryTag.call( | ||
self, | ||
effects, | ||
factorySpace(effects, after, types.whitespace), | ||
after, | ||
nok, | ||
@@ -73,4 +115,29 @@ acorn, | ||
/** @type {State} */ | ||
/** | ||
* After an MDX JSX (flow) tag. | ||
* | ||
* ```markdown | ||
* > | <A> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function after(code) { | ||
return markdownSpace(code) | ||
? factorySpace(effects, end, types.whitespace)(code) | ||
: end(code) | ||
} | ||
/** | ||
* After an MDX JSX (flow) tag, after optional whitespace. | ||
* | ||
* ```markdown | ||
* > | <A> <B> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function end(code) { | ||
// Another tag. | ||
@@ -77,0 +144,0 @@ return code === codes.lessThan |
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (text). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -12,6 +18,7 @@ export function jsxText( | ||
): Construct | ||
export type Construct = import('micromark-util-types').Construct | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Construct = import('micromark-util-types').Construct | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer |
/** | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
*/ | ||
@@ -11,6 +12,12 @@ | ||
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (text). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -20,3 +27,13 @@ export function jsxText(acorn, acornOptions, addResult) { | ||
/** @type {Tokenizer} */ | ||
/** | ||
* MDX JSX (text). | ||
* | ||
* ```markdown | ||
* > | a <b />. | ||
* ^^^^^ | ||
* ``` | ||
* | ||
* @this {TokenizeContext} | ||
* @type {Tokenizer} | ||
*/ | ||
function tokenizeJsxText(effects, ok, nok) { | ||
@@ -23,0 +40,0 @@ return factoryTag.call( |
/** | ||
* @param {Options} [options] | ||
* Create an extension for `micromark` to enable MDX JSX syntax. | ||
* | ||
* @param {Options | null | undefined} [options] | ||
* Configuration (optional). | ||
* @returns {Extension} | ||
* Extension for `micromark` that can be passed in `extensions` to enable MDX | ||
* JSX syntax. | ||
*/ | ||
export function mdxJsx(options?: Options | undefined): Extension | ||
export function mdxJsx(options?: Options | null | undefined): Extension | ||
export type Extension = import('micromark-util-types').Extension | ||
@@ -10,6 +15,21 @@ export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
/** | ||
* Configuration (optional). | ||
*/ | ||
export type Options = { | ||
addResult?: boolean | undefined | ||
acorn?: import('micromark-util-events-to-acorn').Acorn | undefined | ||
acornOptions?: import('acorn').Options | undefined | ||
/** | ||
* Acorn parser to use (optional). | ||
*/ | ||
acorn?: Acorn | null | undefined | ||
/** | ||
* Configuration for acorn (default: `{ecmaVersion: 2020, locations: true, | ||
* sourceType: 'module'}`). | ||
* | ||
* All fields except `locations` can be set. | ||
*/ | ||
acornOptions?: AcornOptions | null | undefined | ||
/** | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
*/ | ||
addResult?: boolean | null | undefined | ||
} |
@@ -9,5 +9,12 @@ /** | ||
* @typedef Options | ||
* @property {boolean} [addResult=false] | ||
* @property {Acorn} [acorn] | ||
* @property {AcornOptions} [acornOptions] | ||
* Configuration (optional). | ||
* @property {Acorn | null | undefined} [acorn] | ||
* Acorn parser to use (optional). | ||
* @property {AcornOptions | null | undefined} [acornOptions] | ||
* Configuration for acorn (default: `{ecmaVersion: 2020, 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. | ||
*/ | ||
@@ -20,8 +27,14 @@ | ||
/** | ||
* @param {Options} [options] | ||
* Create an extension for `micromark` to enable MDX JSX syntax. | ||
* | ||
* @param {Options | null | undefined} [options] | ||
* Configuration (optional). | ||
* @returns {Extension} | ||
* Extension for `micromark` that can be passed in `extensions` to enable MDX | ||
* JSX syntax. | ||
*/ | ||
export function mdxJsx(options = {}) { | ||
const acorn = options.acorn | ||
/** @type {AcornOptions|undefined} */ | ||
export function mdxJsx(options) { | ||
const settings = options || {} | ||
const acorn = settings.acorn | ||
/** @type {AcornOptions | undefined} */ | ||
let acornOptions | ||
@@ -38,6 +51,6 @@ | ||
{ecmaVersion: 2020, sourceType: 'module'}, | ||
options.acornOptions, | ||
settings.acornOptions, | ||
{locations: true} | ||
) | ||
} else if (options.acornOptions || options.addResult) { | ||
} else if (settings.acornOptions || settings.addResult) { | ||
throw new Error('Expected an `acorn` instance passed in as `options.acorn`') | ||
@@ -47,5 +60,17 @@ } | ||
return { | ||
flow: {[codes.lessThan]: jsxFlow(acorn, acornOptions, options.addResult)}, | ||
text: {[codes.lessThan]: jsxText(acorn, acornOptions, options.addResult)} | ||
flow: { | ||
[codes.lessThan]: jsxFlow( | ||
acorn || undefined, | ||
acornOptions, | ||
settings.addResult || false | ||
) | ||
}, | ||
text: { | ||
[codes.lessThan]: jsxText( | ||
acorn || undefined, | ||
acornOptions, | ||
settings.addResult || false | ||
) | ||
} | ||
} | ||
} |
/** | ||
* @typedef {import('./lib/syntax.js').Options} Options | ||
*/ | ||
export {mdxJsx} from './lib/syntax.js' |
@@ -6,6 +6,6 @@ /** | ||
* @param {State} nok | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* @param {boolean|undefined} allowLazy | ||
* @param {Acorn | undefined} acorn | ||
* @param {AcornOptions | undefined} acornOptions | ||
* @param {boolean | undefined} addResult | ||
* @param {boolean | undefined} allowLazy | ||
* @param {string} tagType | ||
@@ -38,2 +38,3 @@ * @param {string} tagMarkerType | ||
export function factoryTag( | ||
this: import('micromark-util-types').TokenizeContext, | ||
effects: Effects, | ||
@@ -74,10 +75,8 @@ ok: State, | ||
) => void | import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type Effects = import('micromark-util-types').Effects | ||
export type State = import('micromark-util-types').State | ||
export type Code = import('micromark-util-types').Code | ||
export type Point = import('micromark-util-types').Point | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Code = import('micromark-util-types').Code | ||
export type Effects = import('micromark-util-types').Effects | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext |
/** | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Code} Code | ||
* @typedef {import('micromark-util-types').Effects} Effects | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').Code} Code | ||
* @typedef {import('micromark-util-types').Point} Point | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
*/ | ||
import {start as idStart, cont as idCont} from 'estree-util-is-identifier-name' | ||
import {factoryMdxExpression} from 'micromark-factory-mdx-expression' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import { | ||
@@ -21,6 +19,3 @@ markdownLineEnding, | ||
import {VFileMessage} from 'vfile-message' | ||
const lazyLineEnd = { | ||
tokenize: tokenizeLazyLineEnd, | ||
partial: true | ||
} | ||
/** | ||
@@ -31,6 +26,6 @@ * @this {TokenizeContext} | ||
* @param {State} nok | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* @param {boolean|undefined} allowLazy | ||
* @param {Acorn | undefined} acorn | ||
* @param {AcornOptions | undefined} acornOptions | ||
* @param {boolean | undefined} addResult | ||
* @param {boolean | undefined} allowLazy | ||
* @param {string} tagType | ||
@@ -63,3 +58,2 @@ * @param {string} tagMarkerType | ||
// eslint-disable-next-line max-params | ||
export function factoryTag( | ||
@@ -101,15 +95,18 @@ effects, | ||
/** @type {State} */ | ||
let returnState | ||
/** @type {NonNullable<Code>|undefined} */ | ||
/** @type {NonNullable<Code> | undefined} */ | ||
let marker | ||
/** @type {Point|undefined} */ | ||
let startPoint | ||
return start | ||
/** @type {State} */ | ||
/** | ||
* Start of MDX: JSX. | ||
* | ||
* ```markdown | ||
* > | a <B /> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
startPoint = self.now() | ||
effects.enter(tagType) | ||
@@ -119,20 +116,42 @@ effects.enter(tagMarkerType) | ||
effects.exit(tagMarkerType) | ||
return afterStart | ||
return startAfter | ||
} | ||
/** @type {State} */ | ||
function afterStart(code) { | ||
/** | ||
* After `<`. | ||
* | ||
* ```markdown | ||
* > | a <B /> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function startAfter(code) { | ||
// Deviate from JSX, which allows arbitrary whitespace. | ||
// See: <https://github.com/micromark/micromark-extension-mdx-jsx/issues/7>. | ||
if (markdownLineEnding(code) || markdownSpace(code)) { | ||
if (markdownLineEndingOrSpace(code)) { | ||
return nok(code) | ||
} // Any other ES whitespace does not get this treatment. | ||
} | ||
returnState = beforeName | ||
return optionalEsWhitespace(code) | ||
} // Right after `<`, before an optional name. | ||
// Any other ES whitespace does not get this treatment. | ||
returnState = nameBefore | ||
return esWhitespaceStart(code) | ||
} | ||
/** @type {State} */ | ||
function beforeName(code) { | ||
/** | ||
* Before name, self slash, or end of tag for fragments. | ||
* | ||
* ```markdown | ||
* > | a <B> c | ||
* ^ | ||
* > | a </B> c | ||
* ^ | ||
* > | a <> b | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function nameBefore(code) { | ||
// Closing tag. | ||
@@ -143,10 +162,12 @@ if (code === 47) { | ||
effects.exit(tagClosingMarkerType) | ||
returnState = beforeClosingTagName | ||
return optionalEsWhitespace | ||
} // Fragment opening tag. | ||
returnState = closingTagNameBefore | ||
return esWhitespaceStart | ||
} | ||
// Fragment opening tag. | ||
if (code === 62) { | ||
return tagEnd(code) | ||
} // Start of a name. | ||
} | ||
// Start of a name. | ||
if (code !== null && idStart(code)) { | ||
@@ -158,3 +179,2 @@ effects.enter(tagNameType) | ||
} | ||
crash( | ||
@@ -168,12 +188,23 @@ code, | ||
) | ||
} // At the start of a closing tag, right after `</`. | ||
} | ||
/** @type {State} */ | ||
function beforeClosingTagName(code) { | ||
/** | ||
* Before name of closing tag or end of closing fragment tag. | ||
* | ||
* ```markdown | ||
* > | a </> b | ||
* ^ | ||
* > | a </B> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closingTagNameBefore(code) { | ||
// Fragment closing tag. | ||
if (code === 62) { | ||
return tagEnd(code) | ||
} // Start of a closing tag name. | ||
} | ||
// Start of a closing tag name. | ||
if (code !== null && idStart(code)) { | ||
@@ -185,3 +216,2 @@ effects.enter(tagNameType) | ||
} | ||
crash( | ||
@@ -195,6 +225,14 @@ code, | ||
) | ||
} // Inside the primary name. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* In primary name. | ||
* | ||
* ```markdown | ||
* > | a <Bc> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function primaryName(code) { | ||
@@ -205,4 +243,5 @@ // Continuation of name: remain. | ||
return primaryName | ||
} // End of name. | ||
} | ||
// End of name. | ||
if ( | ||
@@ -218,6 +257,5 @@ code === 46 || | ||
effects.exit(tagNamePrimaryType) | ||
returnState = afterPrimaryName | ||
return optionalEsWhitespace(code) | ||
returnState = primaryNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -231,7 +269,17 @@ code, | ||
) | ||
} // After a name. | ||
} | ||
/** @type {State} */ | ||
function afterPrimaryName(code) { | ||
/** | ||
* After primary name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b:c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function primaryNameAfter(code) { | ||
// Start of a member name. | ||
@@ -242,6 +290,7 @@ if (code === 46) { | ||
effects.exit(tagNameMemberMarkerType) | ||
returnState = beforeMemberName | ||
return optionalEsWhitespace | ||
} // Start of a local name. | ||
returnState = memberNameBefore | ||
return esWhitespaceStart | ||
} | ||
// Start of a local name. | ||
if (code === 58) { | ||
@@ -251,6 +300,7 @@ effects.enter(tagNamePrefixMarkerType) | ||
effects.exit(tagNamePrefixMarkerType) | ||
returnState = beforeLocalName | ||
return optionalEsWhitespace | ||
} // End of name. | ||
returnState = localNameBefore | ||
return esWhitespaceStart | ||
} | ||
// End of name. | ||
if ( | ||
@@ -263,5 +313,4 @@ code === 47 || | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
crash( | ||
@@ -272,7 +321,15 @@ code, | ||
) | ||
} // We’ve seen a `.` and are expecting a member name. | ||
} | ||
/** @type {State} */ | ||
function beforeMemberName(code) { | ||
/** | ||
* Before member name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberNameBefore(code) { | ||
// Start of a member name. | ||
@@ -284,3 +341,2 @@ if (code !== null && idStart(code)) { | ||
} | ||
crash( | ||
@@ -291,13 +347,23 @@ code, | ||
) | ||
} // Inside the member name. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* In member name. | ||
* | ||
* ```markdown | ||
* > | a <b.cd> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberName(code) { | ||
// Continuation of member name: stay in state | ||
// Continuation of name: remain. | ||
if (code === 45 || (code !== null && idCont(code))) { | ||
effects.consume(code) | ||
return memberName | ||
} // End of member name (note that namespaces and members can’t be combined). | ||
} | ||
// End of name. | ||
// Note: no `:` allowed here. | ||
if ( | ||
@@ -312,6 +378,5 @@ code === 46 || | ||
effects.exit(tagNameMemberType) | ||
returnState = afterMemberName | ||
return optionalEsWhitespace(code) | ||
returnState = memberNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -325,8 +390,17 @@ code, | ||
) | ||
} // After a member name: this is the same as `afterPrimaryName` but we don’t | ||
// expect colons. | ||
} | ||
/** @type {State} */ | ||
function afterMemberName(code) { | ||
/** | ||
* After member name. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b.c.d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function memberNameAfter(code) { | ||
// Start another member name. | ||
@@ -337,6 +411,7 @@ if (code === 46) { | ||
effects.exit(tagNameMemberMarkerType) | ||
returnState = beforeMemberName | ||
return optionalEsWhitespace | ||
} // End of name. | ||
returnState = memberNameBefore | ||
return esWhitespaceStart | ||
} | ||
// End of name. | ||
if ( | ||
@@ -349,5 +424,4 @@ code === 47 || | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
crash( | ||
@@ -358,7 +432,15 @@ code, | ||
) | ||
} // We’ve seen a `:`, and are expecting a local name. | ||
} | ||
/** @type {State} */ | ||
function beforeLocalName(code) { | ||
/** | ||
* Local member name. | ||
* | ||
* ```markdown | ||
* > | a <b:c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localNameBefore(code) { | ||
// Start of a local name. | ||
@@ -370,3 +452,2 @@ if (code !== null && idStart(code)) { | ||
} | ||
crash( | ||
@@ -376,18 +457,27 @@ code, | ||
'a character that can start a name, such as a letter, `$`, or `_`' + | ||
(code === 43 || (code !== null && code > 46 && code < 58) | ||
? /* `/` - `9` */ | ||
' (note: to create a link in MDX, use `[text](url)`)' | ||
(code === 43 || | ||
(code !== null && code > 46 && code < 58) /* `/` - `9` */ | ||
? ' (note: to create a link in MDX, use `[text](url)`)' | ||
: '') | ||
) | ||
} // Inside the local name. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* In local name. | ||
* | ||
* ```markdown | ||
* > | a <b:cd> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localName(code) { | ||
// Continuation of local name: stay in state | ||
// Continuation of name: remain. | ||
if (code === 45 || (code !== null && idCont(code))) { | ||
effects.consume(code) | ||
return localName | ||
} // End of local name (note that we don’t expect another colon, or a member). | ||
} | ||
// End of local name (note that we don’t expect another colon, or a member). | ||
if ( | ||
@@ -401,6 +491,5 @@ code === 47 || | ||
effects.exit(tagNameLocalType) | ||
returnState = afterLocalName | ||
return optionalEsWhitespace(code) | ||
returnState = localNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -411,8 +500,20 @@ code, | ||
) | ||
} // After a local name: this is the same as `afterPrimaryName` but we don’t | ||
// expect colons or periods. | ||
} | ||
/** @type {State} */ | ||
function afterLocalName(code) { | ||
/** | ||
* After local name. | ||
* | ||
* This is like as `primary_name_after`, but we don’t expect colons or | ||
* periods. | ||
* | ||
* ```markdown | ||
* > | a <b.c> d | ||
* ^ | ||
* > | a <b.c.d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function localNameAfter(code) { | ||
// End of name. | ||
@@ -426,5 +527,4 @@ if ( | ||
effects.exit(tagNameType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
crash( | ||
@@ -436,6 +536,21 @@ code, | ||
} | ||
/** @type {State} */ | ||
function beforeAttribute(code) { | ||
// Mark as self-closing. | ||
/** | ||
* Before attribute. | ||
* | ||
* ```markdown | ||
* > | a <b /> c | ||
* ^ | ||
* > | a <b > c | ||
* ^ | ||
* > | a <b {...c}> d | ||
* ^ | ||
* > | a <b c> d | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeBefore(code) { | ||
// Self-closing. | ||
if (code === 47) { | ||
@@ -446,9 +561,11 @@ effects.enter(tagSelfClosingMarker) | ||
returnState = selfClosing | ||
return optionalEsWhitespace | ||
} // End of tag. | ||
return esWhitespaceStart | ||
} | ||
// End of tag. | ||
if (code === 62) { | ||
return tagEnd(code) | ||
} // Attribute expression. | ||
} | ||
// Attribute expression. | ||
if (code === 123) { | ||
@@ -458,3 +575,3 @@ return factoryMdxExpression.call( | ||
effects, | ||
afterAttributeExpression, | ||
attributeExpressionAfter, | ||
tagExpressionAttributeType, | ||
@@ -468,7 +585,7 @@ tagExpressionAttributeMarkerType, | ||
false, | ||
allowLazy, | ||
startPoint.column | ||
allowLazy | ||
)(code) | ||
} // Start of an attribute name. | ||
} | ||
// Start of an attribute name. | ||
if (code !== null && idStart(code)) { | ||
@@ -481,3 +598,2 @@ effects.enter(tagAttributeType) | ||
} | ||
crash( | ||
@@ -488,20 +604,41 @@ code, | ||
) | ||
} // At the start of an attribute expression. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* After attribute expression. | ||
* | ||
* ```markdown | ||
* > | a <b {c} d/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeExpressionAfter(code) { | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
function afterAttributeExpression(code) { | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
} // In the attribute name. | ||
/** @type {State} */ | ||
/** | ||
* In primary attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b cd/> e | ||
* ^ | ||
* > | a <b c:d> e | ||
* ^ | ||
* > | a <b c=d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributePrimaryName(code) { | ||
// Continuation of the attribute name. | ||
// Continuation of name: remain. | ||
if (code === 45 || (code !== null && idCont(code))) { | ||
effects.consume(code) | ||
return attributePrimaryName | ||
} // End of attribute name or tag. | ||
} | ||
// End of attribute name or tag. | ||
if ( | ||
@@ -517,6 +654,5 @@ code === 47 || | ||
effects.exit(tagAttributeNamePrimaryType) | ||
returnState = afterAttributePrimaryName | ||
return optionalEsWhitespace(code) | ||
returnState = attributePrimaryNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -527,7 +663,19 @@ code, | ||
) | ||
} // After an attribute name, probably finding an equals. | ||
} | ||
/** @type {State} */ | ||
function afterAttributePrimaryName(code) { | ||
/** | ||
* After primary attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c/> d | ||
* ^ | ||
* > | a <b c:d> e | ||
* ^ | ||
* > | a <b c=d> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributePrimaryNameAfter(code) { | ||
// Start of a local name. | ||
@@ -538,6 +686,7 @@ if (code === 58) { | ||
effects.exit(tagAttributeNamePrefixMarkerType) | ||
returnState = beforeAttributeLocalName | ||
return optionalEsWhitespace | ||
} // Start of an attribute value. | ||
returnState = attributeLocalNameBefore | ||
return esWhitespaceStart | ||
} | ||
// Initializer: start of an attribute value. | ||
if (code === 61) { | ||
@@ -548,6 +697,7 @@ effects.exit(tagAttributeNameType) | ||
effects.exit(tagAttributeInitializerMarkerType) | ||
returnState = beforeAttributeValue | ||
return optionalEsWhitespace | ||
} // End of tag / new attribute. | ||
returnState = attributeValueBefore | ||
return esWhitespaceStart | ||
} | ||
// End of tag / new attribute. | ||
if ( | ||
@@ -563,6 +713,5 @@ code === 47 || | ||
effects.exit(tagAttributeType) | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -573,7 +722,15 @@ code, | ||
) | ||
} // We’ve seen a `:`, and are expecting a local name. | ||
} | ||
/** @type {State} */ | ||
function beforeAttributeLocalName(code) { | ||
/** | ||
* Before local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:d/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalNameBefore(code) { | ||
// Start of a local name. | ||
@@ -585,3 +742,2 @@ if (code !== null && idStart(code)) { | ||
} | ||
crash( | ||
@@ -592,13 +748,24 @@ code, | ||
) | ||
} // In the local attribute name. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* In local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:de/> f | ||
* ^ | ||
* > | a <b c:d=e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalName(code) { | ||
// Continuation of the local attribute name. | ||
// Continuation of name: remain. | ||
if (code === 45 || (code !== null && idCont(code))) { | ||
effects.consume(code) | ||
return attributeLocalName | ||
} // End of tag / attribute name. | ||
} | ||
// End of local name (note that we don’t expect another colon). | ||
if ( | ||
@@ -614,6 +781,5 @@ code === 47 || | ||
effects.exit(tagAttributeNameType) | ||
returnState = afterAttributeLocalName | ||
return optionalEsWhitespace(code) | ||
returnState = attributeLocalNameAfter | ||
return esWhitespaceStart(code) | ||
} | ||
crash( | ||
@@ -624,7 +790,17 @@ code, | ||
) | ||
} // After a local attribute name, expecting an equals. | ||
} | ||
/** @type {State} */ | ||
function afterAttributeLocalName(code) { | ||
/** | ||
* After local attribute name. | ||
* | ||
* ```markdown | ||
* > | a <b c:d/> f | ||
* ^ | ||
* > | a <b c:d=e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeLocalNameAfter(code) { | ||
// Start of an attribute value. | ||
@@ -635,6 +811,7 @@ if (code === 61) { | ||
effects.exit(tagAttributeInitializerMarkerType) | ||
returnState = beforeAttributeValue | ||
return optionalEsWhitespace | ||
} // End of tag / new attribute. | ||
returnState = attributeValueBefore | ||
return esWhitespaceStart | ||
} | ||
// End of name. | ||
if ( | ||
@@ -647,5 +824,4 @@ code === 47 || | ||
effects.exit(tagAttributeType) | ||
return beforeAttribute(code) | ||
return attributeBefore(code) | ||
} | ||
crash( | ||
@@ -656,7 +832,17 @@ code, | ||
) | ||
} // After an attribute value initializer, expecting quotes and such. | ||
} | ||
/** @type {State} */ | ||
function beforeAttributeValue(code) { | ||
/** | ||
* After `=`, before value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* > | a <b c={d}/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueBefore(code) { | ||
// Start of double- or single quoted value. | ||
@@ -670,4 +856,5 @@ if (code === 34 || code === 39) { | ||
return attributeValueQuotedStart | ||
} // Start of an assignment expression. | ||
} | ||
// Attribute value expression. | ||
if (code === 123) { | ||
@@ -677,3 +864,3 @@ return factoryMdxExpression.call( | ||
effects, | ||
afterAttributeValueExpression, | ||
attributeValueExpressionAfter, | ||
tagAttributeValueExpressionType, | ||
@@ -687,7 +874,5 @@ tagAttributeValueExpressionMarkerType, | ||
false, | ||
allowLazy, | ||
startPoint.column | ||
allowLazy | ||
)(code) | ||
} | ||
crash( | ||
@@ -702,12 +887,29 @@ code, | ||
} | ||
/** @type {State} */ | ||
function afterAttributeValueExpression(code) { | ||
/** | ||
* After attribute value expression. | ||
* | ||
* ```markdown | ||
* > | a <b c={d} e/> f | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueExpressionAfter(code) { | ||
effects.exit(tagAttributeType) | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace(code) | ||
} // At the start of a quoted attribute value. | ||
returnState = attributeBefore | ||
return esWhitespaceStart(code) | ||
} | ||
/** @type {State} */ | ||
/** | ||
* Before quoted literal attribute value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueQuotedStart(code) { | ||
@@ -718,6 +920,5 @@ if (code === null) { | ||
'in attribute value', | ||
'a corresponding closing quote `' + String.fromCharCode(marker) + '`' | ||
'a corresponding closing quote `' + String.fromCodePoint(marker) + '`' | ||
) | ||
} | ||
if (code === marker) { | ||
@@ -730,17 +931,23 @@ effects.enter(tagAttributeValueLiteralMarkerType) | ||
marker = undefined | ||
returnState = beforeAttribute | ||
return optionalEsWhitespace | ||
returnState = attributeBefore | ||
return esWhitespaceStart | ||
} | ||
if (markdownLineEnding(code)) { | ||
returnState = attributeValueQuotedStart | ||
return optionalEsWhitespace(code) | ||
return esWhitespaceStart(code) | ||
} | ||
effects.enter(tagAttributeValueLiteralValueType) | ||
return attributeValueQuoted(code) | ||
} // In a quoted attribute value. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* In quoted literal attribute value. | ||
* | ||
* ```markdown | ||
* > | a <b c="d"/> e | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function attributeValueQuoted(code) { | ||
@@ -750,16 +957,21 @@ if (code === null || code === marker || markdownLineEnding(code)) { | ||
return attributeValueQuotedStart(code) | ||
} // Continuation. | ||
} | ||
effects.consume(code) | ||
return attributeValueQuoted | ||
} // Right after the slash on a tag, e.g., `<asd /`. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* After self-closing slash. | ||
* | ||
* ```markdown | ||
* > | a <b/> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function selfClosing(code) { | ||
// End of tag. | ||
if (code === 62) { | ||
return tagEnd(code) | ||
} | ||
crash( | ||
@@ -773,6 +985,14 @@ code, | ||
) | ||
} // At a `>`. | ||
} | ||
/** @type {State} */ | ||
/** | ||
* At final `>`. | ||
* | ||
* ```markdown | ||
* > | a <b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function tagEnd(code) { | ||
@@ -784,55 +1004,76 @@ effects.enter(tagMarkerType) | ||
return ok | ||
} // Optionally start whitespace. | ||
} | ||
/** @type {State} */ | ||
function optionalEsWhitespace(code) { | ||
/** | ||
* Before optional ECMAScript whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceStart(code) { | ||
if (markdownLineEnding(code)) { | ||
if (allowLazy) { | ||
effects.enter('lineEnding') | ||
effects.consume(code) | ||
effects.exit('lineEnding') | ||
return factorySpace(effects, optionalEsWhitespace, 'linePrefix', 4) | ||
} | ||
return effects.attempt( | ||
lazyLineEnd, | ||
factorySpace(effects, optionalEsWhitespace, 'linePrefix', 4), | ||
crashEol | ||
)(code) | ||
effects.enter('lineEnding') | ||
effects.consume(code) | ||
effects.exit('lineEnding') | ||
return esWhitespaceEolAfter | ||
} | ||
if (markdownSpace(code) || unicodeWhitespace(code)) { | ||
effects.enter('esWhitespace') | ||
return optionalEsWhitespaceContinue(code) | ||
return esWhitespaceInside(code) | ||
} | ||
return returnState(code) | ||
} // Continue optional whitespace. | ||
} | ||
/** @type {State} */ | ||
function optionalEsWhitespaceContinue(code) { | ||
if ( | ||
markdownLineEnding(code) || | ||
!(markdownSpace(code) || unicodeWhitespace(code)) | ||
) { | ||
/** | ||
* In ECMAScript whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a b> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceInside(code) { | ||
if (markdownLineEnding(code)) { | ||
effects.exit('esWhitespace') | ||
return optionalEsWhitespace(code) | ||
return esWhitespaceStart(code) | ||
} | ||
if (markdownSpace(code) || unicodeWhitespace(code)) { | ||
effects.consume(code) | ||
return esWhitespaceInside | ||
} | ||
effects.exit('esWhitespace') | ||
return returnState(code) | ||
} | ||
effects.consume(code) | ||
return optionalEsWhitespaceContinue | ||
/** | ||
* After eol in whitespace. | ||
* | ||
* ```markdown | ||
* > | a <a\nb> c | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function esWhitespaceEolAfter(code) { | ||
// Lazy continuation in a flow tag is a syntax error. | ||
if (!allowLazy && self.parser.lazy[self.now().line]) { | ||
throw new VFileMessage( | ||
'Unexpected lazy line in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc', | ||
self.now(), | ||
'micromark-extension-mdx-jsx:unexpected-eof' | ||
) | ||
} | ||
return esWhitespaceStart(code) | ||
} | ||
/** @type {State} */ | ||
function crashEol() { | ||
throw new VFileMessage( | ||
'Unexpected lazy line in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc', | ||
self.now(), | ||
'micromark-extension-mdx-jsx:unexpected-eof' | ||
) | ||
} // Crash at a nonconforming character. | ||
/** | ||
* Crash at a nonconforming character. | ||
* | ||
* @param {Code} code | ||
@@ -842,3 +1083,2 @@ * @param {string} at | ||
*/ | ||
function crash(code, at, expect) { | ||
@@ -850,3 +1090,3 @@ throw new VFileMessage( | ||
: 'character `' + | ||
(code === 96 ? '` ` `' : String.fromCharCode(code)) + | ||
(code === 96 ? '` ` `' : String.fromCodePoint(code)) + | ||
'` (' + | ||
@@ -865,21 +1105,3 @@ serializeCharCode(code) + | ||
} | ||
/** @type {Tokenizer} */ | ||
function tokenizeLazyLineEnd(effects, ok, nok) { | ||
const self = this | ||
return start | ||
/** @type {State} */ | ||
function start(code) { | ||
effects.enter('lineEnding') | ||
effects.consume(code) | ||
effects.exit('lineEnding') | ||
return lineStart | ||
} | ||
/** @type {State} */ | ||
function lineStart(code) { | ||
return self.parser.lazy[self.now().line] ? nok(code) : ok(code) | ||
} | ||
} | ||
/** | ||
@@ -889,5 +1111,4 @@ * @param {NonNullable<Code>} code | ||
*/ | ||
function serializeCharCode(code) { | ||
return 'U+' + code.toString(16).toUpperCase().padStart(4, '0') | ||
} |
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (flow). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -12,7 +18,8 @@ export function jsxFlow( | ||
): Construct | ||
export type Construct = import('micromark-util-types').Construct | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type State = import('micromark-util-types').State | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Construct = import('micromark-util-types').Construct | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer |
/** | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
*/ | ||
import {markdownLineEnding, markdownSpace} from 'micromark-util-character' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import {markdownLineEnding} from 'micromark-util-character' | ||
import {factoryTag} from './factory-tag.js' | ||
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (flow). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
export function jsxFlow(acorn, acornOptions, addResult) { | ||
@@ -23,14 +31,50 @@ return { | ||
} | ||
/** @type {Tokenizer} */ | ||
/** | ||
* MDX JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^^^^^ | ||
* ``` | ||
* | ||
* @this {TokenizeContext} | ||
* @type {Tokenizer} | ||
*/ | ||
function tokenizeJsxFlow(effects, ok, nok) { | ||
const self = this | ||
return start | ||
/** @type {State} */ | ||
/** | ||
* Start of MDX: JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^ | ||
* ``` | ||
* | ||
* @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`. | ||
return before(code) | ||
} | ||
/** | ||
* After optional whitespace, before of MDX JSX (flow). | ||
* | ||
* ```markdown | ||
* > | <A /> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function before(code) { | ||
return factoryTag.call( | ||
self, | ||
effects, | ||
factorySpace(effects, after, 'whitespace'), | ||
after, | ||
nok, | ||
@@ -68,5 +112,30 @@ acorn, | ||
} | ||
/** @type {State} */ | ||
/** | ||
* After an MDX JSX (flow) tag. | ||
* | ||
* ```markdown | ||
* > | <A> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function after(code) { | ||
return markdownSpace(code) | ||
? factorySpace(effects, end, 'whitespace')(code) | ||
: end(code) | ||
} | ||
/** | ||
* After an MDX JSX (flow) tag, after optional whitespace. | ||
* | ||
* ```markdown | ||
* > | <A> <B> | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function end(code) { | ||
// Another tag. | ||
@@ -73,0 +142,0 @@ return code === 60 |
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (text). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
@@ -12,6 +18,7 @@ export function jsxText( | ||
): Construct | ||
export type Construct = import('micromark-util-types').Construct | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
export type AcornOptions = | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
export type Construct = import('micromark-util-types').Construct | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer |
/** | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-factory-mdx-expression').Acorn} Acorn | ||
* @typedef {import('micromark-factory-mdx-expression').AcornOptions} AcornOptions | ||
*/ | ||
import {factoryTag} from './factory-tag.js' | ||
/** | ||
* @param {Acorn|undefined} acorn | ||
* @param {AcornOptions|undefined} acornOptions | ||
* @param {boolean|undefined} addResult | ||
* Parse JSX (text). | ||
* | ||
* @param {Acorn | undefined} acorn | ||
* Acorn parser to use (optional). | ||
* @param {AcornOptions | undefined} acornOptions | ||
* Configuration for acorn. | ||
* @param {boolean | undefined} addResult | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
* @returns {Construct} | ||
* Construct. | ||
*/ | ||
export function jsxText(acorn, acornOptions, addResult) { | ||
@@ -19,4 +27,14 @@ return { | ||
} | ||
/** @type {Tokenizer} */ | ||
/** | ||
* MDX JSX (text). | ||
* | ||
* ```markdown | ||
* > | a <b />. | ||
* ^^^^^ | ||
* ``` | ||
* | ||
* @this {TokenizeContext} | ||
* @type {Tokenizer} | ||
*/ | ||
function tokenizeJsxText(effects, ok, nok) { | ||
@@ -23,0 +41,0 @@ return factoryTag.call( |
/** | ||
* @param {Options} [options] | ||
* Create an extension for `micromark` to enable MDX JSX syntax. | ||
* | ||
* @param {Options | null | undefined} [options] | ||
* Configuration (optional). | ||
* @returns {Extension} | ||
* Extension for `micromark` that can be passed in `extensions` to enable MDX | ||
* JSX syntax. | ||
*/ | ||
export function mdxJsx(options?: Options | undefined): Extension | ||
export function mdxJsx(options?: Options | null | undefined): Extension | ||
export type Extension = import('micromark-util-types').Extension | ||
@@ -10,6 +15,21 @@ export type Acorn = import('micromark-factory-mdx-expression').Acorn | ||
import('micromark-factory-mdx-expression').AcornOptions | ||
/** | ||
* Configuration (optional). | ||
*/ | ||
export type Options = { | ||
addResult?: boolean | undefined | ||
acorn?: import('micromark-util-events-to-acorn').Acorn | undefined | ||
acornOptions?: import('acorn').Options | undefined | ||
/** | ||
* Acorn parser to use (optional). | ||
*/ | ||
acorn?: Acorn | null | undefined | ||
/** | ||
* Configuration for acorn (default: `{ecmaVersion: 2020, locations: true, | ||
* sourceType: 'module'}`). | ||
* | ||
* All fields except `locations` can be set. | ||
*/ | ||
acornOptions?: AcornOptions | null | undefined | ||
/** | ||
* Whether to add `estree` fields to tokens with results from acorn. | ||
*/ | ||
addResult?: boolean | null | undefined | ||
} |
@@ -9,19 +9,31 @@ /** | ||
* @typedef Options | ||
* @property {boolean} [addResult=false] | ||
* @property {Acorn} [acorn] | ||
* @property {AcornOptions} [acornOptions] | ||
* Configuration (optional). | ||
* @property {Acorn | null | undefined} [acorn] | ||
* Acorn parser to use (optional). | ||
* @property {AcornOptions | null | undefined} [acornOptions] | ||
* Configuration for acorn (default: `{ecmaVersion: 2020, 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. | ||
*/ | ||
import {jsxText} from './jsx-text.js' | ||
import {jsxFlow} from './jsx-flow.js' | ||
/** | ||
* @param {Options} [options] | ||
* Create an extension for `micromark` to enable MDX JSX syntax. | ||
* | ||
* @param {Options | null | undefined} [options] | ||
* Configuration (optional). | ||
* @returns {Extension} | ||
* Extension for `micromark` that can be passed in `extensions` to enable MDX | ||
* JSX syntax. | ||
*/ | ||
export function mdxJsx(options = {}) { | ||
const acorn = options.acorn | ||
/** @type {AcornOptions|undefined} */ | ||
export function mdxJsx(options) { | ||
const settings = options || {} | ||
const acorn = settings.acorn | ||
/** @type {AcornOptions | undefined} */ | ||
let acornOptions | ||
if (acorn) { | ||
@@ -33,3 +45,2 @@ if (!acorn.parse || !acorn.parseExpressionAt) { | ||
} | ||
acornOptions = Object.assign( | ||
@@ -40,3 +51,3 @@ { | ||
}, | ||
options.acornOptions, | ||
settings.acornOptions, | ||
{ | ||
@@ -46,14 +57,21 @@ locations: true | ||
) | ||
} else if (options.acornOptions || options.addResult) { | ||
} else if (settings.acornOptions || settings.addResult) { | ||
throw new Error('Expected an `acorn` instance passed in as `options.acorn`') | ||
} | ||
return { | ||
flow: { | ||
[60]: jsxFlow(acorn, acornOptions, options.addResult) | ||
[60]: jsxFlow( | ||
acorn || undefined, | ||
acornOptions, | ||
settings.addResult || false | ||
) | ||
}, | ||
text: { | ||
[60]: jsxText(acorn, acornOptions, options.addResult) | ||
[60]: jsxText( | ||
acorn || undefined, | ||
acornOptions, | ||
settings.addResult || false | ||
) | ||
} | ||
} | ||
} |
{ | ||
"name": "micromark-extension-mdx-jsx", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "micromark extension to support MDX or MDX.js JSX", | ||
@@ -55,7 +55,10 @@ "license": "MIT", | ||
"devDependencies": { | ||
"@types/estree": "^1.0.0", | ||
"@types/estree-jsx": "^1.0.0", | ||
"@types/mdast": "^3.0.0", | ||
"@types/tape": "^4.0.0", | ||
"@types/node": "^20.0.0", | ||
"acorn": "^8.0.0", | ||
"acorn-jsx": "^5.0.0", | ||
"c8": "^7.0.0", | ||
"estree-util-visit": "^1.0.0", | ||
"mdast-zone": "^5.0.0", | ||
@@ -65,15 +68,16 @@ "micromark": "^3.0.0", | ||
"prettier": "^2.0.0", | ||
"remark-cli": "^10.0.0", | ||
"remark-cli": "^11.0.0", | ||
"remark-preset-wooorm": "^9.0.0", | ||
"rimraf": "^3.0.0", | ||
"tape": "^5.0.0", | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
"xo": "^0.47.0" | ||
"typescript": "^5.0.0", | ||
"xo": "^0.54.0" | ||
}, | ||
"scripts": { | ||
"build": "rimraf \"dev/**/*.d.ts\" \"script/**/*.d.ts\" \"test/**/*.d.ts\" && tsc && type-coverage && micromark-build", | ||
"prepack": "npm run build && npm run format", | ||
"build": "tsc --build --clean && tsc --build && type-coverage && micromark-build", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"test-api": "node --conditions development test/index.js", | ||
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js", | ||
"test-api-prod": "node --conditions production test/index.js", | ||
"test-api-dev": "node --conditions development test/index.js", | ||
"test-api": "npm run test-api-dev && npm run test-api-prod", | ||
"test-coverage": "c8 --100 --reporter lcov npm run test-api", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
@@ -92,5 +96,4 @@ }, | ||
"rules": { | ||
"node/file-extension-in-import": "off", | ||
"unicorn/no-this-assignment": "off", | ||
"unicorn/prefer-code-point": "off" | ||
"n/file-extension-in-import": "off", | ||
"unicorn/no-this-assignment": "off" | ||
} | ||
@@ -97,0 +100,0 @@ }, |
373
readme.md
@@ -11,25 +11,58 @@ # micromark-extension-mdx-jsx | ||
**[micromark][]** extension to support [MDX][mdx-js] (or MDX.js) JSX. | ||
[micromark][] extension to support [MDX][mdxjs] JSX (`<Component />`). | ||
This package provides the low-level modules for integrating with the micromark | ||
tokenizer but has no handling of compiling to HTML: go to a syntax tree instead. | ||
## Contents | ||
* [What is this?](#what-is-this) | ||
* [When to use this](#when-to-use-this) | ||
* [Install](#install) | ||
* [Use](#use) | ||
* [API](#api) | ||
* [`mdxJsx(options?)`](#mdxjsxoptions) | ||
* [`Options`](#options) | ||
* [Authoring](#authoring) | ||
* [Syntax](#syntax) | ||
* [Errors](#errors) | ||
* [Unexpected end of file $at, expected $expect](#unexpected-end-of-file-at-expected-expect) | ||
* [Unexpected character $at, expected $expect](#unexpected-character-at-expected-expect) | ||
* [Tokens](#tokens) | ||
* [Types](#types) | ||
* [Compatibility](#compatibility) | ||
* [Security](#security) | ||
* [Related](#related) | ||
* [Contribute](#contribute) | ||
* [License](#license) | ||
## What is this? | ||
This package contains an extension that adds support for the JSX syntax enabled | ||
by [MDX][mdxjs] to [`micromark`][micromark]. | ||
These extensions are used inside MDX. | ||
It mostly matches how JSX works in most places that support it (TypeScript, | ||
Babel, esbuild, SWC, etc). | ||
This package can be made aware or unaware of JavaScript syntax. | ||
When unaware, expressions could include Rust or variables or whatnot. | ||
## When to use this | ||
This package is already included in [xdm][] and [`mdx-js/mdx` (next)][mdx-js]. | ||
This project is useful when you want to support JSX in markdown. | ||
You should probably use [`micromark-extension-mdx`][mdx] or | ||
[`micromark-extension-mdxjs`][mdxjs] instead, which combine this package with | ||
other MDX features. | ||
Alternatively, if you’re using [`micromark`][micromark] or | ||
[`mdast-util-from-markdown`][from-markdown] and you don’t want all of MDX, use | ||
this package. | ||
You can use this extension when you are working with [`micromark`][micromark]. | ||
To support all MDX features, use | ||
[`micromark-extension-mdxjs`][micromark-extension-mdxjs] instead. | ||
When you need a syntax tree, combine this package with | ||
[`mdast-util-mdx-jsx`][mdast-util-mdx-jsx]. | ||
All these packages are used in [`remark-mdx`][remark-mdx], which focusses on | ||
making it easier to transform content by abstracting these internals away. | ||
When you are using [`mdx-js/mdx`][mdxjs], all of this is already included. | ||
## Install | ||
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): | ||
Node 12+ is needed to use it and it must be `import`ed instead of `require`d. | ||
This package is [ESM only][esm]. | ||
In Node.js (version 16+), install with [npm][]: | ||
[npm][]: | ||
```sh | ||
@@ -39,2 +72,16 @@ npm install micromark-extension-mdx-jsx | ||
In Deno with [`esm.sh`][esmsh]: | ||
```js | ||
import {mdxJsx} from 'https://esm.sh/micromark-extension-mdx-jsx@1' | ||
``` | ||
In browsers with [`esm.sh`][esmsh]: | ||
```html | ||
<script type="module"> | ||
import {mdxJsx} from 'https://esm.sh/micromark-extension-mdx-jsx@1?bundle' | ||
</script> | ||
``` | ||
## Use | ||
@@ -57,13 +104,12 @@ | ||
…which is rather useless: go to a syntax tree with | ||
[`mdast-util-from-markdown`][from-markdown] and | ||
[`mdast-util-mdx-expression`][util] instead. | ||
…which is useless: go to a syntax tree with | ||
[`mdast-util-from-markdown`][mdast-util-from-markdown] and | ||
[`mdast-util-mdx-jsx`][mdast-util-mdx-jsx] instead. | ||
## API | ||
This package exports the following identifiers: `mdxJsx`. | ||
This package exports the identifier [`mdxJsx`][api-mdx-jsx]. | ||
There is no default export. | ||
The export map supports the endorsed | ||
[`development` condition](https://nodejs.org/api/packages.html#packages_resolving_user_conditions). | ||
The export map supports the [`development` condition][development]. | ||
Run `node --conditions development module.js` to get instrumented dev code. | ||
@@ -74,88 +120,120 @@ Without this condition, production code is loaded. | ||
A function that can be called with options that returns an extension for | ||
micromark to parse JSX (can be passed in `extensions`). | ||
Create an extension for `micromark` to enable MDX JSX syntax. | ||
##### `options` | ||
###### Parameters | ||
###### `options.acorn` | ||
* `options` ([`Options`][api-options], optional) | ||
— configuration | ||
Acorn parser to use ([`Acorn`][acorn], optional). | ||
###### Returns | ||
###### `options.acornOptions` | ||
Extension for `micromark` that can be passed in `extensions` to enable MDX | ||
JSX syntax ([`Extension`][micromark-extension]). | ||
Options to pass to acorn (`Object`, default: `{ecmaVersion: 2020, sourceType: | ||
'module'}`). | ||
All fields can be set. | ||
Positional info (`loc`, `range`) is set on ES nodes regardless of acorn options. | ||
### `Options` | ||
###### `options.addResult` | ||
Configuration (TypeScript type). | ||
Whether to add an `estree` field to the `mdxTextJsx` and `mdxFlowJsx` tokens | ||
with the results from acorn (`boolean`, default: `false`). | ||
###### Fields | ||
## Syntax | ||
* `acorn` ([`Acorn`][acorn], optional) | ||
— acorn parser to use | ||
* `acornOptions` ([`AcornOptions`][acorn-options], default: | ||
`{ecmaVersion: 2020, locations: true, sourceType: 'module'}`) | ||
— configuration for acorn; all fields except `locations` can be set | ||
* `addResult` (`boolean`, default: `false`) | ||
— whether to add `estree` fields to tokens with results from acorn | ||
This extensions support both MDX and MDX.js. | ||
The first is agnostic to the programming language (it could contain attribute | ||
expressions and attribute value expressions with Rust or so), the last is | ||
specific to JavaScript (in which case attribute expressions must be spread | ||
expressions). | ||
To turn on gnostic mode, pass `acorn`. | ||
## Authoring | ||
The syntax of JSX supported here is described in [W3C Backus–Naur form][w3c-bnf] | ||
with the following additions: | ||
When authoring markdown with JSX, keep in mind that MDX is a whitespace | ||
sensitive and line-based language, while JavaScript is insensitive to | ||
whitespace. | ||
This affects how markdown and JSX interleave with eachother in MDX. | ||
For more info on how it works, see [§ Interleaving][mdxjs-interleaving] on the | ||
MDX site. | ||
1. **`A - B`** — matches any string that matches `A` but does not match `B`. | ||
2. **`'string'`** — same as **`"string"`** but with single quotes. | ||
3. **`BREAK`** — lookahead match for a block break opportunity (either | ||
EOF (end of file), U+000A LINE FEED (LF), U+000D CARRIAGE RETURN (CR), or | ||
another JSX tag) | ||
###### Comments inside tags | ||
The syntax is defined as follows, however, do note that interleaving (mixing) | ||
of markdown and MDX is defined elsewhere, and that the constraints are imposed | ||
in [`mdast-util-mdx-jsx`][util]. | ||
JavaScript comments in JSX are not supported. | ||
<!--grammar start--> | ||
Incorrect: | ||
<pre><code>; Entries | ||
<a id=x-mdx-flow href=#x-mdx-flow>mdxFlow</a> ::= *<a href=#x-space-or-tab>spaceOrTab</a> <a href=#x-element>element</a> *<a href=#x-space-or-tab>spaceOrTab</a> BREAK | ||
<a id=x-mdx-text href=#x-mdx-text>mdxText</a> ::= <a href=#x-element>element</a> | ||
```jsx | ||
<hi/*comment!*//> | ||
<hello// comment! | ||
/> | ||
``` | ||
<a id=x-element href=#x-element>element</a> ::= <a href=#x-self-closing>selfClosing</a> | <a href=#x-closed>closed</a> | ||
<a id=x-self-closing href=#x-self-closing>selfClosing</a> ::= | ||
; constraint: tag MUST be named, MUST NOT be closing, and MUST be self-closing | ||
<a href=#x-tag>tag</a> | ||
<a id=x-closed href=#x-closed>closed</a> ::= | ||
; constraint: tag MUST NOT be closing and MUST NOT be self-closing | ||
<a href=#x-tag>tag</a> | ||
*<a href=#x-data>data</a> | ||
; constraint: tag MUST be closing, MUST NOT be self-closing, MUST NOT have | ||
; attributes, and either both tags MUST have the same name or both tags MUST | ||
; be nameless | ||
<a href=#x-tag>tag</a> | ||
Correct: | ||
<a id=x-data href=#x-data>data</a> ::= <a href=#x-element>element</a> | <a href=#x-text>text</a> | ||
```jsx | ||
<hi/> | ||
<hello | ||
/> | ||
``` | ||
; constraint: markdown whitespace (<a href=#x-space-or-tab>spaceOrTab</a> | '\r' | '\n') is NOT | ||
A PR that adds support for them would be accepted. | ||
###### Element or fragment attribute values | ||
JSX elements or JSX fragments as attribute values are not supported. | ||
The reason for this change is that it would be confusing whether markdown | ||
would work. | ||
Incorrect: | ||
```jsx | ||
<welcome name=<>Venus</> /> | ||
<welcome name=<span>Pluto</span> /> | ||
``` | ||
Correct: | ||
```jsx | ||
<welcome name='Mars' /> | ||
<welcome name={<span>Jupiter</span>} /> | ||
``` | ||
###### Greater than (`>`) and right curly brace (`}`) | ||
JSX does not allow U+003E GREATER THAN (`>`) or U+007D RIGHT CURLY BRACE | ||
(`}`) literally in text, they need to be encoded as character references | ||
(or expressions). | ||
There is no good reason for this (some JSX parsers agree with us and don’t | ||
crash either). | ||
Therefore, in MDX, U+003E GREATER THAN (`>`) and U+007D RIGHT CURLY BRACE | ||
(`}`) are fine literally and don’t need to be encoded. | ||
## Syntax | ||
JSX forms with the following BNF: | ||
<!--grammar start--> | ||
<pre><code><a id=x-mdx-jsx-flow href=#x-mdx-jsx-flow>mdx_jsx_flow</a> ::= <a href=#x-mdx-jsx>mdx_jsx</a> *<a href=#x-space-or-tab>space_or_tab</a> [<a href=#x-mdx-jsx>mdx_jsx</a> *<a href=#x-space-or-tab>space_or_tab</a>] | ||
<a id=x-mdx-jsx-text href=#x-mdx-jsx-text>mdx_jsx_text</a> ::= <a href=#x-mdx-jsx>mdx_jsx</a> | ||
; constraint: markdown whitespace (`<a href=#x-space-or-tab>space_or_tab</a> | <a href=#x-eol>eol</a>`) is NOT | ||
; allowed directly after `<` in order to allow `1 < 3` in markdown. | ||
<a id=x-tag href=#x-tag>tag</a> ::= | ||
'<' *1<a href=#x-closing>closing</a> | ||
*1(*<a href=#x-whitespace>whitespace</a> <a href=#x-name>name</a> *1<a href=#x-attributes-after-identifier>attributesAfterIdentifier</a> *1<a href=#x-closing>closing</a>) | ||
<a id=x-mdx-jsx href=#x-mdx-jsx>mdx_jsx</a> ::= | ||
'<' [<a href=#x-closing>closing</a>] | ||
[*<a href=#x-whitespace>whitespace</a> <a href=#x-name>name</a> [<a href=#x-attributes-after-identifier>attributes_after_identifier</a>] [<a href=#x-closing>closing</a>]] | ||
*<a href=#x-whitespace>whitespace</a> '>' | ||
<a id=x-attributes-after-identifier href=#x-attributes-after-identifier>attributesAfterIdentifier</a> ::= | ||
1*<a href=#x-whitespace>whitespace</a> (<a href=#x-attributes-boolean>attributesBoolean</a> | <a href=#x-attributes-value>attributesValue</a>) | | ||
*<a href=#x-whitespace>whitespace</a> <a href=#x-attributes-expression>attributesExpression</a> | | ||
<a id=x-attributes-after-value href=#x-attributes-after-value>attributesAfterValue</a> ::= | ||
*<a href=#x-whitespace>whitespace</a> (<a href=#x-attributes-boolean>attributesBoolean</a> | <a href=#x-attributes-expression>attributesExpression</a> | <a href=#x-attributes-value>attributesValue</a>) | ||
<a name=attributes-boolean href=#x-attributes-boolean>attributesBoolean</a> ::= <a href=#x-key>key</a> *1<a href=#x-attributes-after-identifier>attributesAfterIdentifier</a> | ||
<a id=x-attributes-after-identifier href=#x-attributes-after-identifier>attributes_after_identifier</a> ::= | ||
1*<a href=#x-whitespace>whitespace</a> (<a href=#x-attributes-boolean>attributes_boolean</a> | <a href=#x-attributes-value>attributes_value</a>) | | ||
*<a href=#x-whitespace>whitespace</a> <a href=#x-attributes-expression>attributes_expression</a> | | ||
<a id=x-attributes-after-value href=#x-attributes-after-value>attributes_after_value</a> ::= | ||
*<a href=#x-whitespace>whitespace</a> (<a href=#x-attributes-boolean>attributes_boolean</a> | <a href=#x-attributes-expression>attributes_expression</a> | <a href=#x-attributes-value>attributes_value</a>) | ||
<a id=x-attributes-boolean href=#x-attributes-boolean>attributes_boolean</a> ::= <a href=#x-key>key</a> [<a href=#x-attributes-after-identifier>attributes_after_identifier</a>] | ||
; Note: in gnostic mode the value of the expression must instead be a single valid ES spread | ||
; expression | ||
<a name=attributes-expression href=#x-attributes-expression>attributesExpression</a> ::= <a href=#x-expression>expression</a> *1<a href=#x-attributes-after-value>attributesAfterValue</a> | ||
<a name=attributes-value href=#x-attributes-value>attributesValue</a> ::= <a href=#x-key>key</a> <a href=#x-initializer>initializer</a> *1<a href=#x-attributes-after-value>attributesAfterValue</a> | ||
<a id=x-attributes-expression href=#x-attributes-expression>attributes_expression</a> ::= <a href=#x-expression>expression</a> [<a href=#x-attributes-after-value>attributes_after_value</a>] | ||
<a id=x-attributes-value href=#x-attributes-value>attributes_value</a> ::= <a href=#x-key>key</a> <a href=#x-initializer>initializer</a> [<a href=#x-attributes-after-value>attributes_after_value</a>] | ||
<a id=x-closing href=#x-closing>closing</a> ::= *<a href=#x-whitespace>whitespace</a> '/' | ||
<a id=x-name href=#x-name>name</a> ::= <a href=#x-identifier>identifier</a> *1(<a href=#x-local>local</a> | <a href=#x-members>members</a>) | ||
<a id=x-key href=#x-key>key</a> ::= <a href=#x-identifier>identifier</a> *1<a href=#x-local>local</a> | ||
<a id=x-name href=#x-name>name</a> ::= <a href=#x-identifier>identifier</a> [<a href=#x-local>local</a> | <a href=#x-members>members</a>] | ||
<a id=x-key href=#x-key>key</a> ::= <a href=#x-identifier>identifier</a> [<a href=#x-local>local</a>] | ||
<a id=x-local href=#x-local>local</a> ::= *<a href=#x-whitespace>whitespace</a> ':' *<a href=#x-whitespace>whitespace</a> <a href=#x-identifier>identifier</a> | ||
@@ -165,31 +243,28 @@ <a id=x-members href=#x-members>members</a> ::= <a href=#x-member>member</a> *<a href=#x-member>member</a> | ||
<a id=x-identifier href=#x-identifier>identifier</a> ::= <a href=#x-identifier-start>identifierStart</a> *<a href=#x-identifier-part>identifierPart</a> | ||
<a id=x-identifier href=#x-identifier>identifier</a> ::= <a href=#x-identifier-start>identifier_start</a> *<a href=#x-identifier-part>identifier_part</a> | ||
<a id=x-initializer href=#x-initializer>initializer</a> ::= *<a href=#x-whitespace>whitespace</a> '=' *<a href=#x-whitespace>whitespace</a> <a href=#x-value>value</a> | ||
<a id=x-value href=#x-value>value</a> ::= <a href=#x-double-quoted>doubleQuoted</a> | <a href=#x-single-quoted>singleQuoted</a> | <a href=#x-expression>expression</a> | ||
<a id=x-value href=#x-value>value</a> ::= <a href=#x-double-quoted>double_quoted</a> | <a href=#x-single-quoted>single_quoted</a> | <a href=#x-expression>expression</a> | ||
; Note: in gnostic mode the value must instead be a single valid ES expression | ||
<a id=x-expression href=#x-expression>expression</a> ::= '{' *(<a href=#x-expression-text>expressionText</a> | <a href=#x-expression>expression</a>) '}' | ||
<a id=x-expression href=#x-expression>expression</a> ::= '{' *(<a href=#x-expression-text>expression_text</a> | <a href=#x-expression>expression</a>) '}' | ||
<a id=x-double-quoted href=#x-double-quoted>doubleQuoted</a> ::= '"' *<a href=#x-double-quoted-text>doubleQuotedText</a> '"' | ||
<a id=x-single-quoted href=#x-single-quoted>singleQuoted</a> ::= "'" *<a href=#x-single-quoted-text>singleQuotedText</a> "'" | ||
<a id=x-double-quoted href=#x-double-quoted>double_quoted</a> ::= '"' *<a href=#x-double-quoted-text>double_quoted_text</a> '"' | ||
<a id=x-single-quoted href=#x-single-quoted>single_quoted</a> ::= "'" *<a href=#x-single-quoted-text>single_quoted_text</a> "'" | ||
<a id=x-space-or-tab href=#x-space-or-tab>spaceOrTab</a> ::= ' ' | '\t' | ||
<a id=x-text href=#x-text>text</a> ::= <a href=#x-character>character</a> - '<' - '{' | ||
<a id=x-whitespace href=#x-whitespace>whitespace</a> ::= <a href=#x-es-whitespace>esWhitespace</a> | ||
<a id=x-double-quoted-text href=#x-double-quoted-text>doubleQuotedText</a> ::= <a href=#x-character>character</a> - '"' | ||
<a id=x-single-quoted-text href=#x-single-quoted-text>singleQuotedText</a> ::= <a href=#x-character>character</a> - "'" | ||
<a id=x-expression-text href=#x-expression-text>expressionText</a> ::= <a href=#x-character>character</a> - '{' - '}' | ||
<a id=x-identifier-start href=#x-identifier-start>identifierStart</a> ::= <a href=#x-es-identifier-start>esIdentifierStart</a> | ||
<a id=x-identifier-part href=#x-identifier-part>identifierPart</a> ::= <a href=#x-es-identifier-part>esIdentifierPart</a> | '-' | ||
<a id=x-whitespace href=#x-whitespace>whitespace</a> ::= <a href=#x-es-whitespace>es_whitespace</a> | ||
<a id=x-double-quoted-text href=#x-double-quoted-text>double_quoted_text</a> ::= char - '"' | ||
<a id=x-single-quoted-text href=#x-single-quoted-text>single_quoted_text</a> ::= char - "'" | ||
<a id=x-expression-text href=#x-expression-text>expression_text</a> ::= char - '{' - '}' | ||
<a id=x-identifier-start href=#x-identifier-start>identifier_start</a> ::= <a href=#x-es-identifier-start>es_identifier_start</a> | ||
<a id=x-identifier-part href=#x-identifier-part>identifier_part</a> ::= <a href=#x-es-identifier-part>es_identifier_part</a> | '-' | ||
; Unicode | ||
; Any unicode code point | ||
<a id=x-character href=#x-character>character</a> ::= | ||
<a id=x-space-or-tab href=#x-space-or-tab>space_or_tab</a> ::= '\t' | ' ' | ||
<a id=x-eol href=#x-eol>eol</a> ::= '\n' | '\r' | '\r\n' | ||
; ECMAScript | ||
; See “IdentifierStart”: <<a href=https://tc39.es/ecma262/#prod-IdentifierStart>https://tc39.es/ecma262/#prod-IdentifierStart</a>> | ||
<a id=x-es-identifier-start href=#x-es-identifier-start>esIdentifierStart</a> ::= | ||
<a id=x-es-identifier-start href=#x-es-identifier-start>es_identifier_start</a> ::= ? | ||
; See “IdentifierPart”: <<a href=https://tc39.es/ecma262/#prod-IdentifierPart>https://tc39.es/ecma262/#prod-IdentifierPart</a>> | ||
<a id=x-es-identifier-part href=#x-es-identifier-part>esIdentifierPart</a> ::= | ||
<a id=x-es-identifier-part href=#x-es-identifier-part>es_identifier_part</a> ::= ? | ||
; See “Whitespace”: <<a href=https://tc39.es/ecma262/#prod-WhiteSpace>https://tc39.es/ecma262/#prod-WhiteSpace</a>> | ||
<a id=x-es-whitespace href=#x-es-whitespace>esWhitespace</a> ::= | ||
<a id=x-es-whitespace href=#x-es-whitespace>es_whitespace</a> ::= ? | ||
</code></pre> | ||
@@ -199,7 +274,27 @@ | ||
As the flow construct occurs in flow, like all flow constructs, it must be | ||
followed by an eol (line ending) or eof (end of file). | ||
The grammar for JSX in markdown is much stricter than that of HTML in | ||
markdown. | ||
The primary benefit of this is that tags are parsed into tokens, and thus | ||
can be processed. | ||
Another, arguable, benefit of this is that it comes with syntax errors: if | ||
an author types something that is nonsensical, an error is thrown with | ||
information about where it happened, what occurred, and what was expected | ||
instead. | ||
This extension supports expressions both aware and unaware to JavaScript | ||
(respectively gnostic and agnostic). | ||
Depending on whether acorn is passed, either valid JavaScript must be used in | ||
expressions, or arbitrary text (such as Rust code or so) can be used. | ||
More on this can be found in | ||
[§ Syntax of `micromark-extension-mdx-expression`][expression-syntax]. | ||
## Errors | ||
In gnostic mode, expressions are parsed with | ||
[`micromark-extension-mdx-expression`][mdx-expression], which also throws | ||
certain errors. | ||
In aware (gnostic) mode, expressions are parsed with | ||
[`micromark-extension-mdx-expression`][micromark-extension-mdx-expression], | ||
which throws some more errors. | ||
@@ -318,18 +413,28 @@ ### Unexpected end of file $at, expected $expect | ||
## Types | ||
This package is fully typed with [TypeScript][]. | ||
It exports the additional type [`Options`][api-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 | ||
This package is safe. | ||
## Related | ||
* [`micromark/micromark`][micromark] | ||
— the smallest commonmark-compliant markdown parser that exists | ||
* [`micromark/micromark-extension-mdx`][mdx] | ||
— micromark extension to support MDX | ||
* [`micromark/micromark-extension-mdxjs`][mdxjs] | ||
— micromark extension to support MDX.js | ||
* [`micromark/micromark-extension-mdx-expression`][mdx-expression] | ||
— micromark extension to support MDX (or MDX.js) expressions | ||
* [`micromark/micromark-extension-mdx-md`][mdx-md] | ||
— micromark extension to support misc MDX changes | ||
* [`micromark/micromark-extension-mdxjs-esm`][mdxjs-esm] | ||
— micromark extension to support MDX.js import/exports | ||
* [`syntax-tree/mdast-util-mdx`][mdast-util-mdx] | ||
— mdast utility to support MDX (or MDX.js) | ||
* [`micromark-extension-mdxjs`][micromark-extension-mdxjs] | ||
— support all MDX syntax | ||
* [`mdast-util-mdx-jsx`][mdast-util-mdx-jsx] | ||
— support MDX JSX in mdast | ||
* [`remark-mdx`][remark-mdx] | ||
— support all MDX syntax in remark | ||
@@ -380,2 +485,4 @@ ## Contribute | ||
[esmsh]: https://esm.sh | ||
[license]: license | ||
@@ -391,26 +498,34 @@ | ||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[typescript]: https://www.typescriptlang.org | ||
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions | ||
[micromark]: https://github.com/micromark/micromark | ||
[xdm]: https://github.com/wooorm/xdm | ||
[micromark-extension]: https://github.com/micromark/micromark#syntaxextension | ||
[mdx-js]: https://github.com/mdx-js/mdx | ||
[micromark-extension-mdxjs]: https://github.com/micromark/micromark-extension-mdxjs | ||
[mdx-expression]: https://github.com/micromark/micromark-extension-mdx-expression | ||
[micromark-extension-mdx-expression]: https://github.com/micromark/micromark-extension-mdx-expression | ||
[mdx-md]: https://github.com/micromark/micromark-extension-mdx-md | ||
[expression-syntax]: https://github.com/micromark/micromark-extension-mdx-expression/blob/main/packages/micromark-extension-mdx-expression/readme.md#syntax | ||
[mdxjs-esm]: https://github.com/micromark/micromark-extension-mdxjs-esm | ||
[mdast-util-mdx-jsx]: https://github.com/syntax-tree/mdast-util-mdx-jsx | ||
[mdx]: https://github.com/micromark/micromark-extension-mdx | ||
[mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown | ||
[mdxjs]: https://github.com/micromark/micromark-extension-mdxjs | ||
[remark-mdx]: https://mdxjs.com/packages/remark-mdx/ | ||
[util]: https://github.com/syntax-tree/mdast-util-mdx-jsx | ||
[mdxjs]: https://mdxjs.com | ||
[mdast-util-mdx]: https://github.com/syntax-tree/mdast-util-mdx | ||
[mdxjs-interleaving]: https://mdxjs.com/docs/what-is-mdx/#interleaving | ||
[w3c-bnf]: https://www.w3.org/Notation.html | ||
[acorn]: https://github.com/acornjs/acorn | ||
[from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown | ||
[acorn-options]: https://github.com/acornjs/acorn/blob/96c721dbf89d0ccc3a8c7f39e69ef2a6a3c04dfa/acorn/dist/acorn.d.ts#L16 | ||
[api-mdx-jsx]: #mdxjsxoptions | ||
[api-options]: #options |
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
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
102823
2912
523
17