Skip to content

Commit

Permalink
Fix generated JSX for custom elements
Browse files Browse the repository at this point in the history
Related to: ddcebdb.
Related to: remarkjs/remark-math#72.
Closes GH-106.

Co-authored-by: Titus Wormer <[email protected]>
  • Loading branch information
mdynnl and wooorm authored Jan 18, 2022
1 parent adae4b2 commit 0c6d8ac
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 15 deletions.
9 changes: 4 additions & 5 deletions lib/plugin/recma-jsx-rewrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,10 @@ export function recmaJsxRewrite(options = {}) {
fnScope.tags.push(id)
}

node.openingElement.name = {
type: 'JSXMemberExpression',
object: {type: 'JSXIdentifier', name: '_components'},
property: name
}
node.openingElement.name = toJsxIdOrMemberExpression([
'_components',
id
])

if (node.closingElement) {
node.closingElement.name = toJsxIdOrMemberExpression([
Expand Down
65 changes: 55 additions & 10 deletions lib/util/estree-util-to-id-or-member-expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,35 @@
* @typedef {import('estree-jsx').JSXMemberExpression} JSXMemberExpression
*/

import {name as isIdentifierName} from 'estree-util-is-identifier-name'
import {
start as esStart,
cont as esCont,
name as isIdentifierName
} from 'estree-util-is-identifier-name'

export const toIdOrMemberExpression = toIdOrMemberExpressionFactory(
'Identifier',
'MemberExpression'
'MemberExpression',
isIdentifierName
)

export const toJsxIdOrMemberExpression =
// @ts-expect-error: fine
/** @type {(ids: Array<string|number>) => JSXIdentifier|JSXMemberExpression)} */
(toIdOrMemberExpressionFactory('JSXIdentifier', 'JSXMemberExpression'))
(
toIdOrMemberExpressionFactory(
'JSXIdentifier',
'JSXMemberExpression',
isJsxIdentifierName
)
)

/**
* @param {string} [idType]
* @param {string} [memberType]
* @param {string} idType
* @param {string} memberType
* @param {(value: string) => boolean} isIdentifier
*/
function toIdOrMemberExpressionFactory(idType, memberType) {
function toIdOrMemberExpressionFactory(idType, memberType, isIdentifier) {
return toIdOrMemberExpression
/**
* @param {Array<string|number>} ids
Expand All @@ -35,12 +47,19 @@ function toIdOrMemberExpressionFactory(idType, memberType) {

while (++index < ids.length) {
const name = ids[index]
const valid = typeof name === 'string' && isIdentifier(name)

// A value of `asd.123` could be turned into `asd['123']` in the JS form,
// but JSX does not have a form for it, so throw.
/* c8 ignore next 3 */
if (idType === 'JSXIdentifier' && !valid) {
throw new Error('Cannot turn `' + name + '` into a JSX identifier')
}

/** @type {Identifier|Literal} */
// @ts-expect-error: JSX is fine.
const id =
typeof name === 'string' && isIdentifierName(name)
? {type: idType, name}
: {type: 'Literal', value: name}
const id = valid ? {type: idType, name} : {type: 'Literal', value: name}

// @ts-expect-error: JSX is fine.
object = object
? {
Expand All @@ -62,3 +81,29 @@ function toIdOrMemberExpressionFactory(idType, memberType) {
return object
}
}

/**
* Checks if the given string is a valid JSX identifier name.
* @param {string} name
*/
function isJsxIdentifierName(name) {
let index = -1

while (++index < name.length) {
// We currently receive valid input, but this catches bugs and is needed
// when externalized.
/* c8 ignore next */
if (!(index ? jsxCont : esStart)(name.charCodeAt(index))) return false
}

// `false` if `name` is empty.
return index > 0
}

/**
* Checks if the given character code can continue a JSX identifier.
* @param {number} code
*/
function jsxCont(code) {
return code === 45 /* `-` */ || esCont(code)
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"prettier": true,
"rules": {
"unicorn/no-await-expression-member": "off",
"unicorn/prefer-code-point": "off",
"capitalized-comments": "off",
"complexity": "off",
"max-depth": "off",
Expand Down
20 changes: 20 additions & 0 deletions test/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,26 @@ test('jsx', async (t) => {
'should serialize fragments, expressions'
)

t.equal(
String(compileSync('{<a-b></a-b>}', {jsx: true})),
[
'/*@jsxRuntime automatic @jsxImportSource react*/',
'function MDXContent(props = {}) {',
' let {wrapper: MDXLayout} = props.components || ({});',
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
' function _createMdxContent() {',
' let _components = Object.assign({',
' "a-b": "a-b"',
' }, props.components);',
' return <>{<_components.a-b></_components.a-b>}</>;',
' }',
'}',
'export default MDXContent;',
''
].join('\n'),
'should serialize custom elements inside expressions'
)

t.equal(
String(compileSync('Hello {props.name}', {jsx: true})),
[
Expand Down

0 comments on commit 0c6d8ac

Please sign in to comment.