Skip to content

Commit e9b73c7

Browse files
committed
Fix to use spread for non-identifiers as attribute names
Related to mdx-js/mdx#1501.
1 parent c12c1ae commit e9b73c7

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

index.js

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module.exports = toEstree
44

55
var commas = require('comma-separated-tokens')
66
var attachComments = require('estree-util-attach-comments')
7+
var {
8+
start: identifierStart,
9+
cont: identifierCont
10+
} = require('estree-util-is-identifier-name')
711
var whitespace = require('hast-util-whitespace')
812
var find = require('property-information/find')
913
var hastToReact = require('property-information/hast-to-react.json')
@@ -167,11 +171,33 @@ function element(node, context) {
167171
value = {type: 'Literal', value: String(value)}
168172
}
169173

170-
attributes.push({
171-
type: 'JSXAttribute',
172-
name: {type: 'JSXIdentifier', name: prop},
173-
value: value
174-
})
174+
if (jsxIdentifierName(prop)) {
175+
attributes.push({
176+
type: 'JSXAttribute',
177+
name: {type: 'JSXIdentifier', name: prop},
178+
value: value
179+
})
180+
} else {
181+
// No need to worry about `style` (which has a `JSXExpressionContainer`
182+
// value) because that’s a valid identifier.
183+
attributes.push({
184+
type: 'JSXSpreadAttribute',
185+
argument: {
186+
type: 'ObjectExpression',
187+
properties: [
188+
{
189+
type: 'Property',
190+
method: false,
191+
shorthand: false,
192+
computed: false,
193+
key: {type: 'Literal', value: String(prop)},
194+
value: value || {type: 'Literal', value: true},
195+
kind: 'init'
196+
}
197+
]
198+
}
199+
})
200+
}
175201
}
176202

177203
// Restore parent schema.
@@ -490,3 +516,23 @@ function parseStyle(value, tagName) {
490516
function styleReplacer($0, $1) {
491517
return $1.toUpperCase()
492518
}
519+
520+
/**
521+
* Checks if the given string is a valid identifier name.
522+
*
523+
* @param {string} name
524+
*/
525+
function jsxIdentifierName(name) {
526+
var index = -1
527+
528+
while (++index < name.length) {
529+
if (!(index ? cont : identifierStart)(name.charCodeAt(index))) return false
530+
}
531+
532+
// `false` if `name` is empty.
533+
return index > 0
534+
535+
function cont(code) {
536+
return identifierCont(code) || code === 45 /* `-` */
537+
}
538+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"dependencies": {
3737
"comma-separated-tokens": "^1.0.0",
3838
"estree-util-attach-comments": "^1.0.0",
39+
"estree-util-is-identifier-name": "^1.1.0",
3940
"hast-util-whitespace": "^1.0.0",
4041
"property-information": "^5.0.0",
4142
"space-separated-tokens": "^1.0.0",

test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,24 @@ test('hast-util-to-estree', function (t) {
304304
'should crash on an incorrect style string'
305305
)
306306

307+
t.deepEqual(
308+
toEstree({type: 'element', tagName: 'a', properties: {1: true}}),
309+
acornClean(acornParse('<a {...{"1": true}} />')),
310+
'should support a non-identifier as a property (1)'
311+
)
312+
313+
t.deepEqual(
314+
toEstree({type: 'element', tagName: 'a', properties: {'b+': 'c'}}),
315+
acornClean(acornParse('<a {...{"b+": "c"}} />')),
316+
'should support a non-identifier as a property (2)'
317+
)
318+
319+
t.deepEqual(
320+
toEstree({type: 'element', tagName: 'a', properties: {'b-c': 'd'}}),
321+
acornClean(acornParse('<a b-c="d" />')),
322+
'should support a non-identifier as a property (3)'
323+
)
324+
307325
t.deepEqual(
308326
toEstree(h('a', [h('b')])),
309327
acornClean(acornParse('<a><b/></a>')),

0 commit comments

Comments
 (0)