Skip to content

Commit 2a5ab92

Browse files
committedDec 6, 2020
.
0 parents  commit 2a5ab92

12 files changed

+2370
-0
lines changed
 

‎.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

‎.github/workflows/main.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: main
2+
on:
3+
- pull_request
4+
- push
5+
jobs:
6+
main:
7+
name: ${{matrix.node}}
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v2
11+
- uses: dcodeIO/setup-node-nvm@master
12+
with:
13+
node-version: ${{matrix.node}}
14+
- run: npm install
15+
- run: npm test
16+
- uses: codecov/codecov-action@v1
17+
strategy:
18+
matrix:
19+
node:
20+
- lts/dubnium
21+
- node

‎.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.nyc_output/
2+
coverage/
3+
node_modules/
4+
*.log
5+
.DS_Store
6+
yarn.lock

‎.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

‎.prettierignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
coverage/
2+
*.json
3+
*.md

‎from-markdown.js

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
exports.canContainEols = ['mdxJsxTextElement']
2+
exports.enter = {
3+
mdxJsxFlowTag: enterMdxJsxTag,
4+
mdxJsxFlowTagClosingMarker: enterMdxJsxTagClosingMarker,
5+
mdxJsxFlowTagAttribute: enterMdxJsxTagAttribute,
6+
mdxJsxFlowTagExpressionAttribute: enterMdxJsxTagExpressionAttribute,
7+
mdxJsxFlowTagAttributeValueLiteral: buffer,
8+
mdxJsxFlowTagAttributeValueExpression: buffer,
9+
mdxJsxFlowTagSelfClosingMarker: enterMdxJsxTagSelfClosingMarker,
10+
11+
mdxJsxTextTag: enterMdxJsxTag,
12+
mdxJsxTextTagClosingMarker: enterMdxJsxTagClosingMarker,
13+
mdxJsxTextTagAttribute: enterMdxJsxTagAttribute,
14+
mdxJsxTextTagExpressionAttribute: enterMdxJsxTagExpressionAttribute,
15+
mdxJsxTextTagAttributeValueLiteral: buffer,
16+
mdxJsxTextTagAttributeValueExpression: buffer,
17+
mdxJsxTextTagSelfClosingMarker: enterMdxJsxTagSelfClosingMarker
18+
}
19+
exports.exit = {
20+
mdxJsxFlowTagClosingMarker: exitMdxJsxTagClosingMarker,
21+
mdxJsxFlowTagNamePrimary: exitMdxJsxTagNamePrimary,
22+
mdxJsxFlowTagNameMember: exitMdxJsxTagNameMember,
23+
mdxJsxFlowTagNameLocal: exitMdxJsxTagNameLocal,
24+
mdxJsxFlowTagExpressionAttribute: exitMdxJsxTagExpressionAttribute,
25+
mdxJsxFlowTagExpressionAttributeValue: data,
26+
mdxJsxFlowTagAttributeNamePrimary: exitMdxJsxTagAttributeNamePrimary,
27+
mdxJsxFlowTagAttributeNameLocal: exitMdxJsxTagAttributeNameLocal,
28+
mdxJsxFlowTagAttributeValueLiteral: exitMdxJsxTagAttributeValueLiteral,
29+
mdxJsxFlowTagAttributeValueLiteralValue: data,
30+
mdxJsxFlowTagAttributeValueExpression: exitMdxJsxTagAttributeValueExpression,
31+
mdxJsxFlowTagAttributeValueExpressionValue: data,
32+
mdxJsxFlowTagSelfClosingMarker: exitMdxJsxTagSelfClosingMarker,
33+
mdxJsxFlowTag: exitMdxJsxTag,
34+
35+
mdxJsxTextTagClosingMarker: exitMdxJsxTagClosingMarker,
36+
mdxJsxTextTagNamePrimary: exitMdxJsxTagNamePrimary,
37+
mdxJsxTextTagNameMember: exitMdxJsxTagNameMember,
38+
mdxJsxTextTagNameLocal: exitMdxJsxTagNameLocal,
39+
mdxJsxTextTagExpressionAttribute: exitMdxJsxTagExpressionAttribute,
40+
mdxJsxTextTagExpressionAttributeValue: data,
41+
mdxJsxTextTagAttributeNamePrimary: exitMdxJsxTagAttributeNamePrimary,
42+
mdxJsxTextTagAttributeNameLocal: exitMdxJsxTagAttributeNameLocal,
43+
mdxJsxTextTagAttributeValueLiteral: exitMdxJsxTagAttributeValueLiteral,
44+
mdxJsxTextTagAttributeValueLiteralValue: data,
45+
mdxJsxTextTagAttributeValueExpression: exitMdxJsxTagAttributeValueExpression,
46+
mdxJsxTextTagAttributeValueExpressionValue: data,
47+
mdxJsxTextTagSelfClosingMarker: exitMdxJsxTagSelfClosingMarker,
48+
mdxJsxTextTag: exitMdxJsxTag
49+
}
50+
51+
var parseEntities = require('parse-entities')
52+
var stringifyPosition = require('unist-util-stringify-position')
53+
var VMessage = require('vfile-message')
54+
55+
function buffer() {
56+
this.buffer()
57+
}
58+
59+
function data(token) {
60+
this.config.enter.data.call(this, token)
61+
this.config.exit.data.call(this, token)
62+
}
63+
64+
function enterMdxJsxTag(token) {
65+
if (!this.getData('mdxJsxTagStack')) this.setData('mdxJsxTagStack', [])
66+
67+
this.setData('mdxJsxTag', {
68+
name: null,
69+
attributes: [],
70+
start: token.start,
71+
end: token.end
72+
})
73+
74+
this.buffer()
75+
}
76+
77+
function enterMdxJsxTagClosingMarker(token) {
78+
if (!this.getData('mdxJsxTagStack').length) {
79+
throw new VMessage(
80+
'Unexpected closing slash `/` in tag, expected an open tag first',
81+
{start: token.start, end: token.end},
82+
'mdast-util-mdx-jsx:unexpected-closing-slash'
83+
)
84+
}
85+
}
86+
87+
function enterMdxJsxTagAnyAttribute(token) {
88+
if (this.getData('mdxJsxTag').close) {
89+
throw new VMessage(
90+
'Unexpected attribute in closing tag, expected the end of the tag',
91+
{start: token.start, end: token.end},
92+
'mdast-util-mdx-jsx:unexpected-attribute'
93+
)
94+
}
95+
}
96+
97+
function enterMdxJsxTagSelfClosingMarker(token) {
98+
if (this.getData('mdxJsxTag').close) {
99+
throw new VMessage(
100+
'Unexpected self-closing slash `/` in closing tag, expected the end of the tag',
101+
{start: token.start, end: token.end},
102+
'mdast-util-mdx-jsx:unexpected-self-closing-slash'
103+
)
104+
}
105+
}
106+
107+
function exitMdxJsxTagClosingMarker() {
108+
this.getData('mdxJsxTag').close = true
109+
}
110+
111+
function exitMdxJsxTagNamePrimary(token) {
112+
this.getData('mdxJsxTag').name = this.sliceSerialize(token)
113+
}
114+
115+
function exitMdxJsxTagNameMember(token) {
116+
this.getData('mdxJsxTag').name += '.' + this.sliceSerialize(token)
117+
}
118+
119+
function exitMdxJsxTagNameLocal(token) {
120+
this.getData('mdxJsxTag').name += ':' + this.sliceSerialize(token)
121+
}
122+
123+
function enterMdxJsxTagAttribute(token) {
124+
enterMdxJsxTagAnyAttribute.call(this, token)
125+
this.getData('mdxJsxTag').attributes.push({
126+
type: 'mdxJsxAttribute',
127+
name: null,
128+
value: null
129+
})
130+
}
131+
132+
function enterMdxJsxTagExpressionAttribute(token) {
133+
enterMdxJsxTagAnyAttribute.call(this, token)
134+
this.getData('mdxJsxTag').attributes.push({
135+
type: 'mdxJsxExpressionAttribute',
136+
value: null
137+
})
138+
this.buffer()
139+
}
140+
141+
function exitMdxJsxTagExpressionAttribute(token) {
142+
var attributes = this.getData('mdxJsxTag').attributes
143+
attributes[attributes.length - 1].value = this.resume()
144+
145+
if (token.estree) {
146+
attributes[attributes.length - 1].data = {estree: token.estree}
147+
}
148+
}
149+
150+
function exitMdxJsxTagAttributeNamePrimary(token) {
151+
var attributes = this.getData('mdxJsxTag').attributes
152+
attributes[attributes.length - 1].name = this.sliceSerialize(token)
153+
}
154+
155+
function exitMdxJsxTagAttributeNameLocal(token) {
156+
var attributes = this.getData('mdxJsxTag').attributes
157+
attributes[attributes.length - 1].name += ':' + this.sliceSerialize(token)
158+
}
159+
160+
function exitMdxJsxTagAttributeValueLiteral() {
161+
var attributes = this.getData('mdxJsxTag').attributes
162+
attributes[attributes.length - 1].value = parseEntities(this.resume(), {
163+
nonTerminated: false
164+
})
165+
}
166+
167+
function exitMdxJsxTagAttributeValueExpression(token) {
168+
var attributes = this.getData('mdxJsxTag').attributes
169+
170+
attributes[attributes.length - 1].value = {
171+
type: 'mdxJsxAttributeValueExpression',
172+
value: this.resume()
173+
}
174+
175+
if (token.estree) {
176+
attributes[attributes.length - 1].value.data = {estree: token.estree}
177+
}
178+
}
179+
180+
function exitMdxJsxTagSelfClosingMarker() {
181+
this.getData('mdxJsxTag').selfClosing = true
182+
}
183+
184+
function exitMdxJsxTag(token) {
185+
var tag = this.getData('mdxJsxTag')
186+
var stack = this.getData('mdxJsxTagStack')
187+
var tail = stack[stack.length - 1]
188+
189+
if (tag.close && tail.name !== tag.name) {
190+
throw new VMessage(
191+
'Unexpected closing tag `' +
192+
serializeAbbreviatedTag(tag) +
193+
'`, expected corresponding closing tag for `' +
194+
serializeAbbreviatedTag(tail) +
195+
'` (' +
196+
stringifyPosition(tail) +
197+
')',
198+
{start: token.start, end: token.end},
199+
'mdast-util-mdx-jsx:end-tag-mismatch'
200+
)
201+
}
202+
203+
// End of a tag, so drop the buffer.
204+
this.resume()
205+
206+
if (tag.close) {
207+
stack.pop()
208+
} else {
209+
this.enter(
210+
{
211+
type:
212+
token.type === 'mdxJsxTextTag'
213+
? 'mdxJsxTextElement'
214+
: 'mdxJsxFlowElement',
215+
name: tag.name,
216+
attributes: tag.attributes,
217+
children: []
218+
},
219+
token
220+
)
221+
}
222+
223+
if (tag.selfClosing || tag.close) {
224+
this.exit(token)
225+
} else {
226+
stack.push(tag)
227+
}
228+
}
229+
230+
// Serialize a tag, excluding attributes.
231+
// `self-closing` is not supported, because we don’t need it yet.
232+
function serializeAbbreviatedTag(tag) {
233+
return '<' + (tag.close ? '/' : '') + (tag.name || '') + '>'
234+
}

‎index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
exports.fromMarkdown = require('./from-markdown')
2+
exports.toMarkdown = require('./to-markdown')

‎license

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(The MIT License)
2+
3+
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
'Software'), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

‎package.json

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"name": "mdast-util-mdx-jsx",
3+
"version": "0.0.0",
4+
"description": "mdast extension to parse and serialize MDX or MDX.js JSX",
5+
"license": "MIT",
6+
"keywords": [
7+
"unist",
8+
"mdast",
9+
"mdast-util",
10+
"util",
11+
"utility",
12+
"markdown",
13+
"markup",
14+
"mdx",
15+
"mdxjs",
16+
"jsx",
17+
"extension"
18+
],
19+
"repository": "syntax-tree/mdast-util-mdx-jsx",
20+
"bugs": "https://github.com/syntax-tree/mdast-util-mdx-jsx/issues",
21+
"funding": {
22+
"type": "opencollective",
23+
"url": "https://opencollective.com/unified"
24+
},
25+
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
26+
"contributors": [
27+
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
28+
],
29+
"files": [
30+
"from-markdown.js",
31+
"index.js",
32+
"to-markdown.js"
33+
],
34+
"dependencies": {
35+
"mdast-util-to-markdown": "^0.5.0",
36+
"parse-entities": "^2.0.0",
37+
"stringify-entities": "^3.1.0",
38+
"unist-util-remove-position": "^3.0.0",
39+
"vfile-message": "^2.0.0"
40+
},
41+
"devDependencies": {
42+
"acorn": "^8.0.0",
43+
"mdast-util-from-markdown": "^0.8.0",
44+
"micromark-extension-mdx-jsx": "^0.1.0",
45+
"nyc": "^15.0.0",
46+
"prettier": "^2.0.0",
47+
"remark-cli": "^9.0.0",
48+
"remark-preset-wooorm": "^8.0.0",
49+
"tape": "^5.0.0",
50+
"xo": "^0.35.0"
51+
},
52+
"scripts": {
53+
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
54+
"test-api": "node test",
55+
"test-coverage": "nyc --reporter lcov tape test.js",
56+
"test": "npm run format && npm run test-coverage"
57+
},
58+
"nyc": {
59+
"check-coverage": true,
60+
"lines": 100,
61+
"functions": 100,
62+
"branches": 100
63+
},
64+
"prettier": {
65+
"tabWidth": 2,
66+
"useTabs": false,
67+
"singleQuote": true,
68+
"bracketSpacing": false,
69+
"semi": false,
70+
"trailingComma": "none"
71+
},
72+
"xo": {
73+
"prettier": true,
74+
"esnext": false,
75+
"rules": {
76+
"eqeqeq": "off",
77+
"no-eq-null": "off",
78+
"unicorn/explicit-length-check": "off"
79+
}
80+
},
81+
"remarkConfig": {
82+
"plugins": [
83+
"preset-wooorm"
84+
]
85+
}
86+
}

‎readme.md

+397
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
# mdast-util-mdx-jsx
2+
3+
[![Build][build-badge]][build]
4+
[![Coverage][coverage-badge]][coverage]
5+
[![Downloads][downloads-badge]][downloads]
6+
[![Size][size-badge]][size]
7+
[![Sponsors][sponsors-badge]][collective]
8+
[![Backers][backers-badge]][collective]
9+
[![Chat][chat-badge]][chat]
10+
11+
Extension for [`mdast-util-from-markdown`][from-markdown] and/or
12+
[`mdast-util-to-markdown`][to-markdown] to support MDX (or MDX.js) JSX.
13+
When parsing (`from-markdown`), must be combined with
14+
[`micromark-extension-mdx-jsx`][extension].
15+
16+
This utility handles parsing and serializing.
17+
See [`micromark-extension-mdx-jsx`][extension] for how the syntax works.
18+
19+
You probably should use either `micromark-extension-mdx` with `mdast-util-mdx`
20+
or `micromark-extension-mdxjs` with `mdast-util-mdxjs` (which both include this
21+
package) to support all of MDX or MDX.js.
22+
Or use it all through `remark-mdx` or `remark-mdxjs` (**[remark][]**).
23+
24+
## Install
25+
26+
[npm][]:
27+
28+
```sh
29+
npm install mdast-util-mdx-jsx
30+
```
31+
32+
## Use
33+
34+
Say we have an MDX.js file, `example.mdx`:
35+
36+
```mdx
37+
<Box>
38+
- a list
39+
</Box>
40+
41+
<MyComponent {...props} />
42+
43+
<abbr title="Hypertext Markup Language">HTML</abbr> is a lovely language.
44+
```
45+
46+
And our script, `example.js`, looks as follows:
47+
48+
```js
49+
var fs = require('fs')
50+
var acorn = require('acorn')
51+
var syntax = require('micromark-extension-mdx-jsx')
52+
var fromMarkdown = require('mdast-util-from-markdown')
53+
var toMarkdown = require('mdast-util-to-markdown')
54+
var mdxJsx = require('mdast-util-mdx-jsx')
55+
56+
var doc = fs.readFileSync('example.mdx')
57+
58+
var tree = fromMarkdown(doc, {
59+
extensions: [syntax({acorn: acorn, addResult: true})],
60+
mdastExtensions: [mdxJsx.fromMarkdown]
61+
})
62+
63+
console.log(tree)
64+
65+
var out = toMarkdown(tree, {extensions: [mdxJsx.toMarkdown]})
66+
67+
console.log(out)
68+
```
69+
70+
Now, running `node example` yields (positional info removed for brevity):
71+
72+
```js
73+
{
74+
type: 'root',
75+
children: [
76+
{
77+
type: 'mdxJsxFlowElement',
78+
name: 'Box',
79+
attributes: [],
80+
children: [
81+
{
82+
type: 'list',
83+
ordered: false,
84+
start: null,
85+
spread: false,
86+
children: [
87+
{
88+
type: 'listItem',
89+
spread: false,
90+
checked: null,
91+
children: [
92+
{
93+
type: 'paragraph',
94+
children: [{type: 'text', value: 'a list'}]
95+
}
96+
]
97+
}
98+
]
99+
}
100+
]
101+
},
102+
{
103+
type: 'mdxJsxFlowElement',
104+
name: 'MyComponent',
105+
attributes: [
106+
{
107+
type: 'mdxJsxExpressionAttribute',
108+
value: '...props',
109+
data: {
110+
estree: {
111+
type: 'SpreadElement',
112+
argument: {type: 'Identifier', name: 'props'}
113+
}
114+
}
115+
}
116+
],
117+
children: []
118+
},
119+
{
120+
type: 'paragraph',
121+
children: [
122+
{
123+
type: 'mdxJsxTextElement',
124+
name: 'abbr',
125+
attributes: [
126+
{
127+
type: 'mdxJsxAttribute',
128+
name: 'title',
129+
value: 'Hypertext Markup Language'
130+
}
131+
],
132+
children: [{type: 'text', value: 'HTML'}]
133+
},
134+
{type: 'text', value: ' is a lovely language.'}
135+
]
136+
}
137+
]
138+
}
139+
```
140+
141+
```markdown
142+
<Box>
143+
* a list
144+
</Box>
145+
146+
<MyComponent {...props}/>
147+
148+
<abbr title="Hypertext Markup Language">HTML</abbr> is a lovely language.
149+
```
150+
151+
## API
152+
153+
### `mdxJsx.fromMarkdown`
154+
155+
### `mdxJsx.toMarkdown`
156+
157+
> Note: the separate extensions are also available at
158+
> `mdast-util-mdx-jsx/from-markdown` and
159+
> `mdast-util-mdx-jsx/to-markdown`.
160+
161+
Support MDX (or MDX.js) JSX.
162+
The exports are extensions, respectively for
163+
[`mdast-util-from-markdown`][from-markdown] and
164+
[`mdast-util-to-markdown`][to-markdown].
165+
166+
When using the [syntax extension][extension] with `addResult`, nodes will have a
167+
`data.estree` field set to an [ESTree][].
168+
169+
There are no options, but passing [`options.quote`][quote] to
170+
`mdast-util-to-markdown` is honored for attributes.
171+
172+
## Syntax tree
173+
174+
The following interfaces are added to **[mdast][]** by this utility.
175+
176+
### Nodes
177+
178+
#### `MDXJsxFlowElement`
179+
180+
```idl
181+
interface MDXJsxFlowElement <: Parent {
182+
type: "mdxJsxFlowElement"
183+
}
184+
185+
MDXJsxFlowElement includes MDXJsxElement
186+
```
187+
188+
**MDXJsxFlowElement** (**[Parent][dfn-parent]**) represents JSX in flow (block).
189+
It can be used where **[flow][dfn-content-flow]** content is expected.
190+
It includes the mixin **[MDXJsxElement][dfn-mixin-mdx-jsx-element]**.
191+
192+
For example, the following markdown:
193+
194+
```markdown
195+
<w x="y">
196+
z
197+
</w>
198+
```
199+
200+
Yields:
201+
202+
```js
203+
{
204+
type: 'mdxJsxFlowElement',
205+
name: 'w',
206+
attributes: [{type: 'mdxJsxAttribute', name: 'x', value: 'y'}],
207+
children: [{type: 'paragraph', children: [{type: 'text', value: 'z'}]}]
208+
}
209+
```
210+
211+
#### `MDXJsxTextElement`
212+
213+
```idl
214+
interface MDXJsxTextElement <: Parent {
215+
type: "mdxJsxTextElement"
216+
}
217+
218+
MDXJsxTextElement includes MDXJsxElement
219+
```
220+
221+
**MDXJsxTextElement** (**[Parent][dfn-parent]**) represents JSX in text (span,
222+
inline).
223+
It can be used where **[phrasing][dfn-content-phrasing]** content is
224+
expected.
225+
It includes the mixin **[MDXJsxElement][dfn-mixin-mdx-jsx-element]**.
226+
227+
For example, the following markdown:
228+
229+
```markdown
230+
a <b c>d</b> e.
231+
```
232+
233+
Yields:
234+
235+
```js
236+
{
237+
type: 'mdxJsxTextElement',
238+
name: 'b',
239+
attributes: [{type: 'mdxJsxAttribute', name: 'c', value: null}],
240+
children: [{type: 'text', value: 'd'}]
241+
}
242+
```
243+
244+
### Mixin
245+
246+
### `MDXJsxElement`
247+
248+
```idl
249+
interface mixin MDXJsxElement {
250+
name: string?
251+
attributes: [MDXJsxExpressionAttribute | MDXJsxAttribute]
252+
}
253+
254+
interface MDXJsxExpressionAttribute <: Literal {
255+
type: "mdxJsxExpressionAttribute"
256+
}
257+
258+
interface MDXJsxAttribute <: Node {
259+
type: "mdxJsxAttribute"
260+
name: string
261+
value: MDXJsxAttributeValueExpression | string?
262+
}
263+
264+
interface MDXJsxAttributeValueExpression <: Literal {
265+
type: "mdxJsxAttributeValueExpression"
266+
}
267+
```
268+
269+
**MDXJsxElement** represents a JSX element.
270+
271+
The `name` field can be present and represents an identifier.
272+
Without `name`, the element represents a fragment, in which case no attributes
273+
must be present.
274+
275+
The `attributes` field represents information associated with the node.
276+
The value of the `attributes` field is a list of **MDXJsxExpressionAttribute**
277+
and **MDXJsxAttribute** nodes.
278+
279+
**MDXJsxExpressionAttribute** represents an expression (typically in a
280+
programming language) that when evaluated results in multiple attributes.
281+
282+
**MDXJsxAttribute** represents a single attribute.
283+
The `name` field must be present.
284+
The `value` field can be present, in which case it is either a string (a static
285+
value) or an expression (typically in a programming language) that when
286+
evaluated results in an attribute value.
287+
288+
### Content model
289+
290+
#### `FlowContent` (MDX JSX)
291+
292+
```idl
293+
type MDXJsxFlowContent = MDXJsxFlowElement | FlowContent
294+
```
295+
296+
#### `PhrasingContent` (MDX JSX)
297+
298+
```idl
299+
type MDXJsxPhrasingContent = MDXJsxTextElement | PhrasingContent
300+
```
301+
302+
## Related
303+
304+
* [`remarkjs/remark`][remark]
305+
— markdown processor powered by plugins
306+
* `remarkjs/remark-mdx`
307+
— remark plugin to support MDX
308+
* `remarkjs/remark-mdxjs`
309+
— remark plugin to support MDX.js
310+
* [`syntax-tree/mdast-util-from-markdown`][from-markdown]
311+
— mdast parser using `micromark` to create mdast from markdown
312+
* [`syntax-tree/mdast-util-to-markdown`][to-markdown]
313+
— mdast serializer to create markdown from mdast
314+
* `syntax-tree/mdast-util-mdx`
315+
— mdast utility to support all of MDX
316+
* `syntax-tree/mdast-util-mdxjs`
317+
— mdast utility to support all of MDX.js
318+
* [`micromark/micromark`][micromark]
319+
— the smallest commonmark-compliant markdown parser that exists
320+
* [`micromark/micromark-extension-mdx-jsx`][extension]
321+
— micromark extension to parse JSX
322+
323+
## Contribute
324+
325+
See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get
326+
started.
327+
See [`support.md`][support] for ways to get help.
328+
329+
This project has a [code of conduct][coc].
330+
By interacting with this repository, organization, or community you agree to
331+
abide by its terms.
332+
333+
## License
334+
335+
[MIT][license] © [Titus Wormer][author]
336+
337+
[build-badge]: https://github.com/syntax-tree/mdast-util-mdx-jsx/workflows/main/badge.svg
338+
339+
[build]: https://github.com/syntax-tree/mdast-util-mdx-jsx/actions
340+
341+
[coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/mdast-util-mdx-jsx.svg
342+
343+
[coverage]: https://codecov.io/github/syntax-tree/mdast-util-mdx-jsx
344+
345+
[downloads-badge]: https://img.shields.io/npm/dm/mdast-util-mdx-jsx.svg
346+
347+
[downloads]: https://www.npmjs.com/package/mdast-util-mdx-jsx
348+
349+
[size-badge]: https://img.shields.io/bundlephobia/minzip/mdast-util-mdx-jsx.svg
350+
351+
[size]: https://bundlephobia.com/result?p=mdast-util-mdx-jsx
352+
353+
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
354+
355+
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
356+
357+
[collective]: https://opencollective.com/unified
358+
359+
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
360+
361+
[chat]: https://github.com/syntax-tree/unist/discussions
362+
363+
[npm]: https://docs.npmjs.com/cli/install
364+
365+
[license]: license
366+
367+
[author]: https://wooorm.com
368+
369+
[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md
370+
371+
[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md
372+
373+
[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md
374+
375+
[mdast]: https://github.com/syntax-tree/mdast
376+
377+
[remark]: https://github.com/remarkjs/remark
378+
379+
[from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown
380+
381+
[to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown
382+
383+
[micromark]: https://github.com/micromark/micromark
384+
385+
[extension]: https://github.com/micromark/micromark-extension-mdxjs-esm
386+
387+
[quote]: https://github.com/syntax-tree/mdast-util-to-markdown#optionsquote
388+
389+
[estree]: https://github.com/estree/estree
390+
391+
[dfn-parent]: https://github.com/syntax-tree/mdast#parent
392+
393+
[dfn-content-flow]: #flowcontent-mdx-jsx
394+
395+
[dfn-content-phrasing]: #phrasingcontent-mdx-jsx
396+
397+
[dfn-mixin-mdx-jsx-element]: #mdxjsxelement

‎test.js

+1,485
Large diffs are not rendered by default.

‎to-markdown.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
exports.handlers = {
2+
mdxJsxFlowElement: mdxElement,
3+
mdxJsxTextElement: mdxElement
4+
}
5+
6+
mdxElement.peek = peekElement
7+
8+
var encode = require('stringify-entities/light')
9+
var flow = require('mdast-util-to-markdown/lib/util/container-flow')
10+
var phrasing = require('mdast-util-to-markdown/lib/util/container-phrasing')
11+
var checkQuote = require('mdast-util-to-markdown/lib/util/check-quote')
12+
13+
var eol = /\r?\n|\r/g
14+
15+
// eslint-disable-next-line complexity
16+
function mdxElement(node, _, context) {
17+
var selfClosing = node.name && (!node.children || !node.children.length)
18+
var quote = checkQuote(context)
19+
var exit = context.enter(node.type)
20+
var index = -1
21+
var attributes = []
22+
var attribute
23+
var result
24+
var value
25+
26+
// None.
27+
if (node.attributes && node.attributes.length) {
28+
if (!node.name) {
29+
throw new Error('Cannot serialize fragment w/ attributes')
30+
}
31+
32+
while (++index < node.attributes.length) {
33+
attribute = node.attributes[index]
34+
35+
if (attribute.type === 'mdxJsxExpressionAttribute') {
36+
result = '{' + (attribute.value || '') + '}'
37+
} else {
38+
if (!attribute.name) {
39+
throw new Error('Cannot serialize attribute w/o name')
40+
}
41+
42+
result =
43+
attribute.name +
44+
(attribute.value == null
45+
? ''
46+
: '=' +
47+
(typeof attribute.value === 'object'
48+
? '{' + (attribute.value.value || '') + '}'
49+
: quote + encode(attribute.value, {subset: [quote]}) + quote))
50+
}
51+
52+
attributes.push(result)
53+
}
54+
}
55+
56+
value =
57+
'<' +
58+
(node.name || '') +
59+
(node.type === 'mdxJsxFlowElement' && attributes.length > 1
60+
? // Flow w/ multiple attributes.
61+
'\n' + indent(attributes.join('\n')) + '\n'
62+
: attributes.length // Text or flow w/ a single attribute.
63+
? ' ' + dedentStart(indent(attributes.join(' ')))
64+
: '') +
65+
(selfClosing ? '/' : '') +
66+
'>' +
67+
(node.children && node.children.length
68+
? node.type === 'mdxJsxFlowElement'
69+
? '\n' + indent(flow(node, context)) + '\n'
70+
: phrasing(node, context, {before: '<', after: '>'})
71+
: '') +
72+
(selfClosing ? '' : '</' + (node.name || '') + '>')
73+
74+
exit()
75+
return value
76+
}
77+
78+
function peekElement() {
79+
return '<'
80+
}
81+
82+
function dedentStart(value) {
83+
return value.replace(/^ +/, '')
84+
}
85+
86+
function indent(value) {
87+
var result = []
88+
var start = 0
89+
var match
90+
91+
while ((match = eol.exec(value))) {
92+
one(value.slice(start, match.index))
93+
result.push(match[0])
94+
start = match.index + match[0].length
95+
}
96+
97+
one(value.slice(start))
98+
99+
return result.join('')
100+
101+
function one(slice) {
102+
result.push((slice ? ' ' : '') + slice)
103+
}
104+
}

0 commit comments

Comments
 (0)
Please sign in to comment.