Skip to content

Commit d84fa7e

Browse files
committed
Add support using x as a JSX pragma
This PR tests that `xastscript` can be used as the pragma for JSX with bublé and babel. Code-wise, this adds support for using `x` to generate root nodes. This is done by omitting the tag name (like so: `x()`, `x(null, 'child')`). Previously, omitting a `name` resulted in an exception. Another aspect of supporting JSX is supporting fragments as children. As fragments yield root nodes, we unravel them and use only their children. While this could be seen a change, xast prohibits roots occurring in nodes, so the unraveling instead fixes what would otherwise be a broken tree. Related to: GH-3.
1 parent 2216947 commit d84fa7e

File tree

7 files changed

+193
-9
lines changed

7 files changed

+193
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
.nyc_output/
44
coverage/
55
node_modules/
6+
test/jsx-*.js
67
yarn.lock

index.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ module.exports = x
44

55
// Creating xast elements.
66
function x(name, attributes) {
7-
var node = {type: 'element', name: name, attributes: {}, children: []}
7+
var node =
8+
name == null
9+
? {type: 'root', children: []}
10+
: {type: 'element', name: name, attributes: {}, children: []}
811
var index = 1
912
var key
1013

11-
if (typeof name !== 'string' || !name) {
14+
if (name != null && typeof name !== 'string') {
1215
throw new Error('Expected element name, got `' + name + '`')
1316
}
1417

1518
// Handle props.
1619
if (attributes) {
1720
if (
21+
name == null ||
1822
typeof attributes === 'string' ||
1923
typeof attributes === 'number' ||
2024
'length' in attributes
@@ -51,7 +55,8 @@ function addChild(nodes, value) {
5155
addChild(nodes, value[index])
5256
}
5357
} else if (typeof value === 'object' && value.type) {
54-
nodes.push(value)
58+
if (value.type === 'root') addChild(nodes, value.children)
59+
else nodes.push(value)
5560
} else {
5661
throw new TypeError('Expected node, nodes, string, got `' + value + '`')
5762
}

package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,26 @@
3434
"@types/xast": "^1.0.0"
3535
},
3636
"devDependencies": {
37+
"@babel/core": "^7.12.3",
38+
"@babel/plugin-syntax-jsx": "^7.12.1",
39+
"@babel/plugin-transform-react-jsx": "^7.12.1",
40+
"buble": "^0.20.0",
3741
"dtslint": "^4.0.0",
3842
"nyc": "^15.0.0",
3943
"prettier": "^2.0.0",
4044
"remark-cli": "^9.0.0",
4145
"remark-preset-wooorm": "^8.0.0",
4246
"tape": "^5.0.0",
47+
"unist-builder": "^2.0.3",
4348
"xo": "^0.34.0"
4449
},
4550
"scripts": {
51+
"generate": "node script/generate-jsx",
4652
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
4753
"test-api": "node test",
48-
"test-coverage": "nyc --reporter lcov tape test.js",
54+
"test-coverage": "nyc --reporter lcov tape test/index.js",
4955
"test-types": "dtslint types",
50-
"test": "npm run format && npm run test-coverage && npm run test-types"
56+
"test": "npm run generate && npm run format && npm run test-coverage && npm run test-types"
5157
},
5258
"nyc": {
5359
"check-coverage": true,

script/generate-jsx.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
var fs = require('fs')
4+
var path = require('path')
5+
var buble = require('buble')
6+
var babel = require('@babel/core')
7+
8+
var doc = String(fs.readFileSync(path.join('test', 'jsx.jsx')))
9+
10+
fs.writeFileSync(
11+
path.join('test', 'jsx-buble.js'),
12+
buble.transform(doc.replace(/'name'/, "'jsx (buble)'"), {
13+
jsx: 'x',
14+
jsxFragment: 'null'
15+
}).code
16+
)
17+
18+
fs.writeFileSync(
19+
path.join('test', 'jsx-babel.js'),
20+
babel.transform(doc.replace(/'name'/, "'jsx (babel)'"), {
21+
plugins: [
22+
['@babel/plugin-transform-react-jsx', {pragma: 'x', pragmaFrag: 'null'}]
23+
]
24+
}).code
25+
)

test.js renamed to test/core.js

+42-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
'use strict'
22

33
var test = require('tape')
4-
var x = require('.')
4+
var x = require('..')
55

66
test('xastscript', function (t) {
77
t.equal(typeof x, 'function', 'should expose a function')
88

9+
t.deepEqual(
10+
x(),
11+
{type: 'root', children: []},
12+
'should create a root when w/o `name`'
13+
)
14+
915
t.throws(
1016
function () {
11-
x()
17+
x(1)
1218
},
13-
/Expected element name, got `undefined`/,
14-
'should throw without `name`'
19+
/Expected element name, got `1`/,
20+
'should throw w/ incorrect `name`'
1521
)
1622

1723
t.deepEqual(
@@ -156,5 +162,37 @@ test('xastscript', function (t) {
156162
'should support omitting attributes when given an array for a child'
157163
)
158164

165+
t.deepEqual(
166+
x(null, '1'),
167+
{type: 'root', children: [{type: 'text', value: '1'}]},
168+
'should create a root with a textual child'
169+
)
170+
171+
t.deepEqual(
172+
x(null, 1),
173+
{type: 'root', children: [{type: 'text', value: '1'}]},
174+
'should create a root with a numerical child'
175+
)
176+
177+
t.deepEqual(
178+
x(null, x('a')),
179+
{
180+
type: 'root',
181+
children: [{type: 'element', name: 'a', attributes: {}, children: []}]
182+
},
183+
'should create a root with a node child'
184+
)
185+
186+
t.deepEqual(
187+
x('a', {}, [x(null, x('b'))]),
188+
{
189+
type: 'element',
190+
name: 'a',
191+
attributes: {},
192+
children: [{type: 'element', name: 'b', attributes: {}, children: []}]
193+
},
194+
'should create a node w/ by unraveling roots'
195+
)
196+
159197
t.end()
160198
})

test/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
3+
/* eslint-disable import/no-unassigned-import */
4+
require('./core')
5+
require('./jsx-babel')
6+
require('./jsx-buble')
7+
/* eslint-enable import/no-unassigned-import */

test/jsx.jsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict'
2+
3+
var test = require('tape')
4+
var u = require('unist-builder')
5+
var x = require('..')
6+
7+
test('name', function (t) {
8+
t.deepEqual(<a />, x('a'), 'should support a self-closing element')
9+
10+
t.deepEqual(<a>b</a>, x('a', 'b'), 'should support a value as a child')
11+
12+
var A = 'a'
13+
14+
t.deepEqual(<A />, x(A), 'should support an uppercase tag name')
15+
16+
t.deepEqual(
17+
<a>{1 + 1}</a>,
18+
x('a', '2'),
19+
'should support expressions as children'
20+
)
21+
22+
t.deepEqual(<></>, u('root', []), 'should support a fragment')
23+
24+
t.deepEqual(
25+
<>a</>,
26+
u('root', [u('text', 'a')]),
27+
'should support a fragment with text'
28+
)
29+
30+
t.deepEqual(
31+
<>
32+
<a />
33+
</>,
34+
u('root', [x('a')]),
35+
'should support a fragment with an element'
36+
)
37+
38+
t.deepEqual(
39+
<>{-1}</>,
40+
u('root', [u('text', '-1')]),
41+
'should support a fragment with an expression'
42+
)
43+
44+
var com = {acme: {a: 'A', b: 'B'}}
45+
46+
t.deepEqual(
47+
<com.acme.a />,
48+
x(com.acme.a),
49+
'should support members as names (`a.b`)'
50+
)
51+
52+
t.deepEqual(
53+
<a b />,
54+
x('a', {b: 'true'}),
55+
'should support a boolean attribute'
56+
)
57+
58+
t.deepEqual(
59+
<a b="" />,
60+
x('a', {b: ''}),
61+
'should support a double quoted attribute'
62+
)
63+
64+
t.deepEqual(
65+
<a b='"' />,
66+
x('a', {b: '"'}),
67+
'should support a single quoted attribute'
68+
)
69+
70+
t.deepEqual(
71+
<a b={1 + 1} />,
72+
x('a', {b: '2'}),
73+
'should support expression value attributes'
74+
)
75+
76+
var props = {a: 1, b: 2}
77+
78+
t.deepEqual(
79+
<a {...props} />,
80+
x('a', props),
81+
'should support expression spread attributes'
82+
)
83+
84+
t.deepEqual(
85+
<a>
86+
<b />c<d>e</d>
87+
{1 + 1}
88+
</a>,
89+
x('a', [x('b'), 'c', x('d', 'e'), '2']),
90+
'should support text, elements, and expressions in jsx'
91+
)
92+
93+
t.deepEqual(
94+
<a>
95+
<>{1}</>
96+
</a>,
97+
x('a', '1'),
98+
'should support a fragment in an element'
99+
)
100+
101+
t.end()
102+
})

0 commit comments

Comments
 (0)