/** * @typedef {import('estree-jsx').JSXAttribute} JSXAttribute * @typedef {import('estree-jsx').JSXClosingElement} JSXClosingElement * @typedef {import('estree-jsx').JSXClosingFragment} JSXClosingFragment * @typedef {import('estree-jsx').JSXElement} JSXElement * @typedef {import('estree-jsx').JSXExpressionContainer} JSXExpressionContainer * @typedef {import('estree-jsx').JSXFragment} JSXFragment * @typedef {import('estree-jsx').JSXIdentifier} JSXIdentifier * @typedef {import('estree-jsx').JSXMemberExpression} JSXMemberExpression * @typedef {import('estree-jsx').JSXNamespacedName} JSXNamespacedName * @typedef {import('estree-jsx').JSXOpeningElement} JSXOpeningElement * @typedef {import('estree-jsx').JSXOpeningFragment} JSXOpeningFragment * @typedef {import('estree-jsx').JSXSpreadAttribute} JSXSpreadAttribute * @typedef {import('estree-jsx').JSXText} JSXText * @typedef {import('./types.js').Generator} Generator * @typedef {import('./types.js').State} State */ export const jsx = { JSXAttribute, JSXClosingElement, JSXClosingFragment, JSXElement, JSXEmptyExpression, JSXExpressionContainer, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, JSXOpeningElement, JSXOpeningFragment, JSXSpreadAttribute, JSXText } /** * `attr` * `attr="something"` * `attr={1}` * * @this {Generator} * `astring` generator. * @param {JSXAttribute} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXAttribute(node, state) { this[node.name.type](node.name, state) if (node.value !== undefined && node.value !== null) { state.write('=') // Encode double quotes in attribute values. if (node.value.type === 'Literal') { state.write( '"' + encodeJsx(String(node.value.value)).replace(/"/g, '"') + '"', node ) } else { this[node.value.type](node.value, state) } } } /** * `` * * @this {Generator} * `astring` generator. * @param {JSXClosingElement} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXClosingElement(node, state) { state.write('') } /** * `` * * @this {Generator} * `astring` generator. * @param {JSXClosingFragment} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXClosingFragment(node, state) { state.write('', node) } /** * `
` * `
` * * @this {Generator} * `astring` generator. * @param {JSXElement} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXElement(node, state) { let index = -1 this[node.openingElement.type](node.openingElement, state) if (node.children) { while (++index < node.children.length) { const child = node.children[index] // Supported in types but not by Acorn. /* c8 ignore next 3 */ if (child.type === 'JSXSpreadChild') { throw new Error('JSX spread children are not supported') } this[child.type](child, state) } } if (node.closingElement) { this[node.closingElement.type](node.closingElement, state) } } /** * `{}` (always in a `JSXExpressionContainer`, which does the curlies) * * @this {Generator} * `astring` generator. * @returns {void} * Nothing. */ function JSXEmptyExpression() {} /** * `{expression}` * * @this {Generator} * `astring` generator. * @param {JSXExpressionContainer} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXExpressionContainer(node, state) { state.write('{') this[node.expression.type](node.expression, state) state.write('}') } /** * `<>` * * @this {Generator} * `astring` generator. * @param {JSXFragment} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXFragment(node, state) { let index = -1 this[node.openingFragment.type](node.openingFragment, state) if (node.children) { while (++index < node.children.length) { const child = node.children[index] // Supported in types but not by Acorn. /* c8 ignore next 3 */ if (child.type === 'JSXSpreadChild') { throw new Error('JSX spread children are not supported') } this[child.type](child, state) } } this[node.closingFragment.type](node.closingFragment, state) } /** * `div` * * @this {Generator} * `astring` generator. * @param {JSXIdentifier} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXIdentifier(node, state) { state.write(node.name, node) } /** * `member.expression` * * @this {Generator} * `astring` generator. * @param {JSXMemberExpression} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXMemberExpression(node, state) { this[node.object.type](node.object, state) state.write('.') this[node.property.type](node.property, state) } /** * `ns:name` * * @this {Generator} * `astring` generator. * @param {JSXNamespacedName} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXNamespacedName(node, state) { this[node.namespace.type](node.namespace, state) state.write(':') this[node.name.type](node.name, state) } /** * `
` * * @this {Generator} * `astring` generator. * @param {JSXOpeningElement} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXOpeningElement(node, state) { let index = -1 state.write('<') this[node.name.type](node.name, state) if (node.attributes) { while (++index < node.attributes.length) { state.write(' ') this[node.attributes[index].type](node.attributes[index], state) } } state.write(node.selfClosing ? ' />' : '>') } /** * `<>` * * @this {Generator} * `astring` generator. * @param {JSXOpeningFragment} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXOpeningFragment(node, state) { state.write('<>', node) } /** * `{...argument}` * * @this {Generator} * `astring` generator. * @param {JSXSpreadAttribute} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXSpreadAttribute(node, state) { state.write('{') // eslint-disable-next-line new-cap this.SpreadElement(node, state) state.write('}') } /** * `!` * * @this {Generator} * `astring` generator. * @param {JSXText} node * Node to serialize. * @param {State} state * Info passed around. * @returns {void} * Nothing. */ function JSXText(node, state) { state.write( encodeJsx(node.value).replace(/[<>{}]/g, ($0) => $0 === '<' ? '<' : $0 === '>' ? '>' : $0 === '{' ? '{' : '}' ), node ) } /** * Make sure that character references don’t pop up. * * For example, the text `©` should stay that way, and not turn into `©`. * We could encode all `&` (easy but verbose) or look for actual valid * references (complex but cleanest output). * Looking for the 2nd character gives us a middle ground. * The `#` is for (decimal and hexadecimal) numeric references, the letters * are for the named references. * * @param {string} value * Value to encode. * @returns {string} * Encoded value. */ function encodeJsx(value) { return value.replace(/&(?=[#a-z])/gi, '&') }