site/node_modules/hast-util-to-jsx-runtime/lib/index.js
2024-10-14 08:09:33 +02:00

1085 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Register MDX nodes in mdast:
/// <reference types="mdast-util-mdx-expression" />
/// <reference types="mdast-util-mdx-jsx" />
/// <reference types="mdast-util-mdxjs-esm" />
/**
* @typedef {import('estree').Identifier} Identifier
* @typedef {import('estree').Literal} Literal
* @typedef {import('estree').MemberExpression} MemberExpression
* @typedef {import('estree').Expression} Expression
* @typedef {import('estree').Program} Program
*
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Text} Text
*
* @typedef {import('mdast-util-mdx-expression').MdxFlowExpressionHast} MdxFlowExpression
* @typedef {import('mdast-util-mdx-expression').MdxTextExpressionHast} MdxTextExpression
*
* @typedef {import('mdast-util-mdx-jsx').MdxJsxFlowElementHast} MdxJsxFlowElement
* @typedef {import('mdast-util-mdx-jsx').MdxJsxTextElementHast} MdxJsxTextElement
*
* @typedef {import('mdast-util-mdxjs-esm').MdxjsEsmHast} MdxjsEsm
*
* @typedef {import('property-information').Schema} Schema
*
* @typedef {import('unist').Position} Position
*
* @typedef {import('./components.js').Components} Components
*/
/**
* @typedef {JSX.Element | string | null | undefined} Child
* Child.
*
* @callback Create
* Create something in development or production.
* @param {Nodes} node
* hast node.
* @param {unknown} type
* Fragment symbol or tag name.
* @param {Props} props
* Properties and children.
* @param {string | undefined} key
* Key.
* @returns {JSX.Element}
* Result.
*
* @callback CreateEvaluater
* Create an evaluator that turns ESTree ASTs from embedded MDX into values.
* @returns {Evaluater}
* Evaluater.
*
* @typedef {'html' | 'react'} ElementAttributeNameCase
* Casing to use for attribute names.
*
* HTML casing is for example `class`, `stroke-linecap`, `xml:lang`.
* React casing is for example `className`, `strokeLinecap`, `xmlLang`.
*
* @callback EvaluateExpression
* Turn an MDX expression into a value.
* @param {Expression} expression
* ESTree expression.
* @returns {unknown}
* Result of expression.
*
* @callback EvaluateProgram
* Turn an MDX program (export/import statements) into a value.
* @param {Program} expression
* ESTree program.
* @returns {unknown}
* Result of program;
* should likely be `undefined` as ESM changes the scope but doesnt yield
* something.
*
* @typedef Evaluater
* Evaluator that turns ESTree ASTs from embedded MDX into values.
* @property {EvaluateExpression} evaluateExpression
* Evaluate an expression.
* @property {EvaluateProgram} evaluateProgram
* Evaluate a program.
*
* @typedef {[string, Value]} Field
* Property field.
*
* @typedef {unknown} Fragment
* Represent the children, typically a symbol.
*
* @callback Jsx
* Create a production element.
* @param {unknown} type
* Element type: `Fragment` symbol, tag name (`string`), component.
* @param {Props} props
* Element props, `children`, and maybe `node`.
* @param {string | undefined} [key]
* Dynamicly generated key to use.
* @returns {JSX.Element}
* Element from your framework.
*
* @callback JsxDev
* Create a development element.
* @param {unknown} type
* Element type: `Fragment` symbol, tag name (`string`), component.
* @param {Props} props
* Element props, `children`, and maybe `node`.
* @param {string | undefined} key
* Dynamicly generated key to use.
* @param {boolean} isStaticChildren
* Whether two or more children are passed (in an array), which is whether
* `jsxs` or `jsx` would be used.
* @param {Source} source
* Info about source.
* @param {undefined} self
* Nothing (this is used by frameworks that have components, we dont).
* @returns {JSX.Element}
* Element from your framework.
*
* @typedef {{children?: Array<Child> | Child, node?: Element | MdxJsxFlowElement | MdxJsxTextElement | undefined, [prop: string]: Array<Child> | Child | Element | MdxJsxFlowElement | MdxJsxTextElement | Value | undefined}} Props
* Properties and children.
*
* @typedef RegularFields
* Configuration.
* @property {Partial<Components> | null | undefined} [components]
* Components to use (optional).
* @property {CreateEvaluater | null | undefined} [createEvaluater]
* Create an evaluator that turns ESTree ASTs into values (optional).
* @property {ElementAttributeNameCase | null | undefined} [elementAttributeNameCase='react']
* Specify casing to use for attribute names (default: `'react'`).
* @property {string | null | undefined} [filePath]
* File path to the original source file (optional).
*
* Passed in source info to `jsxDEV` when using the automatic runtime with
* `development: true`.
* @property {boolean | null | undefined} [ignoreInvalidStyle=false]
* Ignore invalid CSS in `style` props (default: `false`);
* the default behavior is to throw an error.
* @property {boolean | null | undefined} [passKeys=true]
* Generate keys to optimize frameworks that support them (default: `true`).
*
* > 👉 **Note**: Solid currently fails if keys are passed.
* @property {boolean | null | undefined} [passNode=false]
* Pass the hast element node to components (default: `false`).
* @property {Space | null | undefined} [space='html']
* Whether `tree` is in the `'html'` or `'svg'` space (default: `'html'`).
*
* When an `<svg>` element is found in the HTML space, this package already
* automatically switches to and from the SVG space when entering and exiting
* it.
* @property {StylePropertyNameCase | null | undefined} [stylePropertyNameCase='dom']
* Specify casing to use for property names in `style` objects (default:
* `'dom'`).
* @property {boolean | null | undefined} [tableCellAlignToStyle=true]
* Turn obsolete `align` props on `td` and `th` into CSS `style` props
* (default: `true`).
*
* @typedef RuntimeDevelopment
* Runtime fields when development is on.
* @property {Fragment} Fragment
* Fragment.
* @property {true} development
* Whether to use `jsxDEV` (when on) or `jsx` and `jsxs` (when off).
* @property {Jsx | null | undefined} [jsx]
* Dynamic JSX (optional).
* @property {JsxDev} jsxDEV
* Development JSX.
* @property {Jsx | null | undefined} [jsxs]
* Static JSX (optional).
*
* @typedef RuntimeProduction
* Runtime fields when development is off.
* @property {Fragment} Fragment
* Fragment.
* @property {false | null | undefined} [development]
* Whether to use `jsxDEV` (when on) or `jsx` and `jsxs` (when off) (optional).
* @property {Jsx} jsx
* Dynamic JSX.
* @property {JsxDev | null | undefined} [jsxDEV]
* Development JSX (optional).
* @property {Jsx} jsxs
* Static JSX.
*
* @typedef RuntimeUnknown
* Runtime fields when development might be on or off.
* @property {Fragment} Fragment
* Fragment.
* @property {boolean} development
* Whether to use `jsxDEV` (when on) or `jsx` and `jsxs` (when off).
* @property {Jsx | null | undefined} [jsx]
* Dynamic JSX (optional).
* @property {JsxDev | null | undefined} [jsxDEV]
* Development JSX (optional).
* @property {Jsx | null | undefined} [jsxs]
* Static JSX (optional).
*
* @typedef Source
* Info about source.
* @property {number | undefined} columnNumber
* Column where thing starts (0-indexed).
* @property {string | undefined} fileName
* Name of source file.
* @property {number | undefined} lineNumber
* Line where thing starts (1-indexed).
*
* @typedef {'html' | 'svg'} Space
* Namespace.
*
* > 👉 **Note**: hast is not XML.
* > It supports SVG as embedded in HTML.
* > It does not support the features available in XML.
* > Passing SVG might break but fragments of modern SVG should be fine.
* > Use `xast` if you need to support SVG as XML.
*
* @typedef State
* Info passed around.
* @property {unknown} Fragment
* Fragment symbol.
* @property {Array<Parents>} ancestors
* Stack of parents.
* @property {Partial<Components>} components
* Components to swap.
* @property {Create} create
* Create something in development or production.
* @property {ElementAttributeNameCase} elementAttributeNameCase
* Casing to use for attribute names.
* @property {Evaluater | undefined} evaluater
* Evaluator that turns ESTree ASTs into values.
* @property {string | undefined} filePath
* File path.
* @property {boolean} ignoreInvalidStyle
* Ignore invalid CSS in `style` props.
* @property {boolean} passKeys
* Generate keys to optimize frameworks that support them.
* @property {boolean} passNode
* Pass `node` to components.
* @property {Schema} schema
* Current schema.
* @property {StylePropertyNameCase} stylePropertyNameCase
* Casing to use for property names in `style` objects.
* @property {boolean} tableCellAlignToStyle
* Turn obsolete `align` props on `td` and `th` into CSS `style` props.
*
* @typedef {Record<string, string>} Style
* Style map.
*
* @typedef {'css' | 'dom'} StylePropertyNameCase
* Casing to use for property names in `style` objects.
*
* CSS casing is for example `background-color` and `-webkit-line-clamp`.
* DOM casing is for example `backgroundColor` and `WebkitLineClamp`.
*
* @typedef {Style | boolean | number | string} Value
* Primitive property value and `Style` map.
*/
/**
* @typedef {RuntimeDevelopment & RegularFields} Development
* Configuration (development).
* @typedef {Development | Production | Unknown} Options
* Configuration.
* @typedef {RegularFields & RuntimeProduction} Production
* Configuration (production).
* @typedef {RegularFields & RuntimeUnknown} Unknown
* Configuration (production or development).
*/
import {stringify as commas} from 'comma-separated-tokens'
import {ok as assert} from 'devlop'
import {name as isIdentifierName} from 'estree-util-is-identifier-name'
import {whitespace} from 'hast-util-whitespace'
import {find, hastToReact, html, svg} from 'property-information'
import {stringify as spaces} from 'space-separated-tokens'
import styleToObject from 'style-to-object'
import {pointStart} from 'unist-util-position'
import {VFileMessage} from 'vfile-message'
const own = {}.hasOwnProperty
/** @type {Map<string, number>} */
const emptyMap = new Map()
const cap = /[A-Z]/g
const dashSomething = /-([a-z])/g
// `react-dom` triggers a warning for *any* white space in tables.
// To follow GFM, `mdast-util-to-hast` injects line endings between elements.
// Other tools might do so too, but they dont do here, so we remove all of
// that.
// See: <https://github.com/facebook/react/pull/7081>.
// See: <https://github.com/facebook/react/pull/7515>.
// See: <https://github.com/remarkjs/remark-react/issues/64>.
// See: <https://github.com/rehypejs/rehype-react/pull/29>.
// See: <https://github.com/rehypejs/rehype-react/pull/32>.
// See: <https://github.com/rehypejs/rehype-react/pull/45>.
const tableElements = new Set(['table', 'tbody', 'thead', 'tfoot', 'tr'])
const tableCellElement = new Set(['td', 'th'])
const docs = 'https://github.com/syntax-tree/hast-util-to-jsx-runtime'
/**
* Transform a hast tree to preact, react, solid, svelte, vue, etc.,
* with an automatic JSX runtime.
*
* @param {Nodes} tree
* Tree to transform.
* @param {Options} options
* Configuration (required).
* @returns {JSX.Element}
* JSX element.
*/
export function toJsxRuntime(tree, options) {
if (!options || options.Fragment === undefined) {
throw new TypeError('Expected `Fragment` in options')
}
const filePath = options.filePath || undefined
/** @type {Create} */
let create
if (options.development) {
if (typeof options.jsxDEV !== 'function') {
throw new TypeError(
'Expected `jsxDEV` in options when `development: true`'
)
}
create = developmentCreate(filePath, options.jsxDEV)
} else {
if (typeof options.jsx !== 'function') {
throw new TypeError('Expected `jsx` in production options')
}
if (typeof options.jsxs !== 'function') {
throw new TypeError('Expected `jsxs` in production options')
}
create = productionCreate(filePath, options.jsx, options.jsxs)
}
/** @type {State} */
const state = {
Fragment: options.Fragment,
ancestors: [],
components: options.components || {},
create,
elementAttributeNameCase: options.elementAttributeNameCase || 'react',
evaluater: options.createEvaluater ? options.createEvaluater() : undefined,
filePath,
ignoreInvalidStyle: options.ignoreInvalidStyle || false,
passKeys: options.passKeys !== false,
passNode: options.passNode || false,
schema: options.space === 'svg' ? svg : html,
stylePropertyNameCase: options.stylePropertyNameCase || 'dom',
tableCellAlignToStyle: options.tableCellAlignToStyle !== false
}
const result = one(state, tree, undefined)
// JSX element.
if (result && typeof result !== 'string') {
return result
}
// Text node or something that turned into nothing.
return state.create(
tree,
state.Fragment,
{children: result || undefined},
undefined
)
}
/**
* Transform a node.
*
* @param {State} state
* Info passed around.
* @param {Nodes} node
* Current node.
* @param {string | undefined} key
* Key.
* @returns {Child | undefined}
* Child, optional.
*/
function one(state, node, key) {
if (node.type === 'element') {
return element(state, node, key)
}
if (node.type === 'mdxFlowExpression' || node.type === 'mdxTextExpression') {
return mdxExpression(state, node)
}
if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
return mdxJsxElement(state, node, key)
}
if (node.type === 'mdxjsEsm') {
return mdxEsm(state, node)
}
if (node.type === 'root') {
return root(state, node, key)
}
if (node.type === 'text') {
return text(state, node)
}
}
/**
* Handle element.
*
* @param {State} state
* Info passed around.
* @param {Element} node
* Current node.
* @param {string | undefined} key
* Key.
* @returns {Child | undefined}
* Child, optional.
*/
function element(state, node, key) {
const parentSchema = state.schema
let schema = parentSchema
if (node.tagName.toLowerCase() === 'svg' && parentSchema.space === 'html') {
schema = svg
state.schema = schema
}
state.ancestors.push(node)
const type = findComponentFromName(state, node.tagName, false)
const props = createElementProps(state, node)
let children = createChildren(state, node)
if (tableElements.has(node.tagName)) {
children = children.filter(function (child) {
return typeof child === 'string' ? !whitespace(child) : true
})
}
addNode(state, props, type, node)
addChildren(props, children)
// Restore.
state.ancestors.pop()
state.schema = parentSchema
return state.create(node, type, props, key)
}
/**
* Handle MDX expression.
*
* @param {State} state
* Info passed around.
* @param {MdxFlowExpression | MdxTextExpression} node
* Current node.
* @returns {Child | undefined}
* Child, optional.
*/
function mdxExpression(state, node) {
if (node.data && node.data.estree && state.evaluater) {
const program = node.data.estree
const expression = program.body[0]
assert(expression.type === 'ExpressionStatement')
// Assume result is a child.
return /** @type {Child | undefined} */ (
state.evaluater.evaluateExpression(expression.expression)
)
}
crashEstree(state, node.position)
}
/**
* Handle MDX ESM.
*
* @param {State} state
* Info passed around.
* @param {MdxjsEsm} node
* Current node.
* @returns {Child | undefined}
* Child, optional.
*/
function mdxEsm(state, node) {
if (node.data && node.data.estree && state.evaluater) {
// Assume result is a child.
return /** @type {Child | undefined} */ (
state.evaluater.evaluateProgram(node.data.estree)
)
}
crashEstree(state, node.position)
}
/**
* Handle MDX JSX.
*
* @param {State} state
* Info passed around.
* @param {MdxJsxFlowElement | MdxJsxTextElement} node
* Current node.
* @param {string | undefined} key
* Key.
* @returns {Child | undefined}
* Child, optional.
*/
function mdxJsxElement(state, node, key) {
const parentSchema = state.schema
let schema = parentSchema
if (node.name === 'svg' && parentSchema.space === 'html') {
schema = svg
state.schema = schema
}
state.ancestors.push(node)
const type =
node.name === null
? state.Fragment
: findComponentFromName(state, node.name, true)
const props = createJsxElementProps(state, node)
const children = createChildren(state, node)
addNode(state, props, type, node)
addChildren(props, children)
// Restore.
state.ancestors.pop()
state.schema = parentSchema
return state.create(node, type, props, key)
}
/**
* Handle root.
*
* @param {State} state
* Info passed around.
* @param {Root} node
* Current node.
* @param {string | undefined} key
* Key.
* @returns {Child | undefined}
* Child, optional.
*/
function root(state, node, key) {
/** @type {Props} */
const props = {}
addChildren(props, createChildren(state, node))
return state.create(node, state.Fragment, props, key)
}
/**
* Handle text.
*
* @param {State} _
* Info passed around.
* @param {Text} node
* Current node.
* @returns {Child | undefined}
* Child, optional.
*/
function text(_, node) {
return node.value
}
/**
* Add `node` to props.
*
* @param {State} state
* Info passed around.
* @param {Props} props
* Props.
* @param {unknown} type
* Type.
* @param {Element | MdxJsxFlowElement | MdxJsxTextElement} node
* Node.
* @returns {undefined}
* Nothing.
*/
function addNode(state, props, type, node) {
// If this is swapped out for a component:
if (typeof type !== 'string' && type !== state.Fragment && state.passNode) {
props.node = node
}
}
/**
* Add children to props.
*
* @param {Props} props
* Props.
* @param {Array<Child>} children
* Children.
* @returns {undefined}
* Nothing.
*/
function addChildren(props, children) {
if (children.length > 0) {
const value = children.length > 1 ? children : children[0]
if (value) {
props.children = value
}
}
}
/**
* @param {string | undefined} _
* Path to file.
* @param {Jsx} jsx
* Dynamic.
* @param {Jsx} jsxs
* Static.
* @returns {Create}
* Create a production element.
*/
function productionCreate(_, jsx, jsxs) {
return create
/** @type {Create} */
function create(_, type, props, key) {
// Only an array when there are 2 or more children.
const isStaticChildren = Array.isArray(props.children)
const fn = isStaticChildren ? jsxs : jsx
return key ? fn(type, props, key) : fn(type, props)
}
}
/**
* @param {string | undefined} filePath
* Path to file.
* @param {JsxDev} jsxDEV
* Development.
* @returns {Create}
* Create a development element.
*/
function developmentCreate(filePath, jsxDEV) {
return create
/** @type {Create} */
function create(node, type, props, key) {
// Only an array when there are 2 or more children.
const isStaticChildren = Array.isArray(props.children)
const point = pointStart(node)
return jsxDEV(
type,
props,
key,
isStaticChildren,
{
columnNumber: point ? point.column - 1 : undefined,
fileName: filePath,
lineNumber: point ? point.line : undefined
},
undefined
)
}
}
/**
* Create props from an element.
*
* @param {State} state
* Info passed around.
* @param {Element} node
* Current element.
* @returns {Props}
* Props.
*/
function createElementProps(state, node) {
/** @type {Props} */
const props = {}
/** @type {string | undefined} */
let alignValue
/** @type {string} */
let prop
for (prop in node.properties) {
if (prop !== 'children' && own.call(node.properties, prop)) {
const result = createProperty(state, prop, node.properties[prop])
if (result) {
const [key, value] = result
if (
state.tableCellAlignToStyle &&
key === 'align' &&
typeof value === 'string' &&
tableCellElement.has(node.tagName)
) {
alignValue = value
} else {
props[key] = value
}
}
}
}
if (alignValue) {
// Assume style is an object.
const style = /** @type {Style} */ (props.style || (props.style = {}))
style[state.stylePropertyNameCase === 'css' ? 'text-align' : 'textAlign'] =
alignValue
}
return props
}
/**
* Create props from a JSX element.
*
* @param {State} state
* Info passed around.
* @param {MdxJsxFlowElement | MdxJsxTextElement} node
* Current JSX element.
* @returns {Props}
* Props.
*/
function createJsxElementProps(state, node) {
/** @type {Props} */
const props = {}
for (const attribute of node.attributes) {
if (attribute.type === 'mdxJsxExpressionAttribute') {
if (attribute.data && attribute.data.estree && state.evaluater) {
const program = attribute.data.estree
const expression = program.body[0]
assert(expression.type === 'ExpressionStatement')
const objectExpression = expression.expression
assert(objectExpression.type === 'ObjectExpression')
const property = objectExpression.properties[0]
assert(property.type === 'SpreadElement')
Object.assign(
props,
state.evaluater.evaluateExpression(property.argument)
)
} else {
crashEstree(state, node.position)
}
} else {
// For JSX, the author is responsible of passing in the correct values.
const name = attribute.name
/** @type {unknown} */
let value
if (attribute.value && typeof attribute.value === 'object') {
if (
attribute.value.data &&
attribute.value.data.estree &&
state.evaluater
) {
const program = attribute.value.data.estree
const expression = program.body[0]
assert(expression.type === 'ExpressionStatement')
value = state.evaluater.evaluateExpression(expression.expression)
} else {
crashEstree(state, node.position)
}
} else {
value = attribute.value === null ? true : attribute.value
}
// Assume a prop.
props[name] = /** @type {Props[keyof Props]} */ (value)
}
}
return props
}
/**
* Create children.
*
* @param {State} state
* Info passed around.
* @param {Parents} node
* Current element.
* @returns {Array<Child>}
* Children.
*/
function createChildren(state, node) {
/** @type {Array<Child>} */
const children = []
let index = -1
/** @type {Map<string, number>} */
// Note: test this when Solid doesnt want to merge my upcoming PR.
/* c8 ignore next */
const countsByName = state.passKeys ? new Map() : emptyMap
while (++index < node.children.length) {
const child = node.children[index]
/** @type {string | undefined} */
let key
if (state.passKeys) {
const name =
child.type === 'element'
? child.tagName
: child.type === 'mdxJsxFlowElement' ||
child.type === 'mdxJsxTextElement'
? child.name
: undefined
if (name) {
const count = countsByName.get(name) || 0
key = name + '-' + count
countsByName.set(name, count + 1)
}
}
const result = one(state, child, key)
if (result !== undefined) children.push(result)
}
return children
}
/**
* Handle a property.
*
* @param {State} state
* Info passed around.
* @param {string} prop
* Key.
* @param {Array<number | string> | boolean | number | string | null | undefined} value
* hast property value.
* @returns {Field | undefined}
* Field for runtime, optional.
*/
function createProperty(state, prop, value) {
const info = find(state.schema, prop)
// Ignore nullish and `NaN` values.
if (
value === null ||
value === undefined ||
(typeof value === 'number' && Number.isNaN(value))
) {
return
}
if (Array.isArray(value)) {
// Accept `array`.
// Most props are space-separated.
value = info.commaSeparated ? commas(value) : spaces(value)
}
// React only accepts `style` as object.
if (info.property === 'style') {
let styleObject =
typeof value === 'object' ? value : parseStyle(state, String(value))
if (state.stylePropertyNameCase === 'css') {
styleObject = transformStylesToCssCasing(styleObject)
}
return ['style', styleObject]
}
return [
state.elementAttributeNameCase === 'react' && info.space
? hastToReact[info.property] || info.property
: info.attribute,
value
]
}
/**
* Parse a CSS declaration to an object.
*
* @param {State} state
* Info passed around.
* @param {string} value
* CSS declarations.
* @returns {Style}
* Properties.
* @throws
* Throws `VFileMessage` when CSS cannot be parsed.
*/
function parseStyle(state, value) {
/** @type {Style} */
const result = {}
try {
// @ts-expect-error: `style-to-object` types are broken.
styleToObject(value, replacer)
} catch (error) {
if (!state.ignoreInvalidStyle) {
const cause = /** @type {Error} */ (error)
const message = new VFileMessage('Cannot parse `style` attribute', {
ancestors: state.ancestors,
cause,
ruleId: 'style',
source: 'hast-util-to-jsx-runtime'
})
message.file = state.filePath || undefined
message.url = docs + '#cannot-parse-style-attribute'
throw message
}
}
return result
/**
* Add a CSS property (normal, so with dashes) to `result` as a DOM CSS
* property.
*
* @param {string} name
* Key.
* @param {string} value
* Value
* @returns {undefined}
* Nothing.
*/
function replacer(name, value) {
let key = name
if (key.slice(0, 2) !== '--') {
if (key.slice(0, 4) === '-ms-') key = 'ms-' + key.slice(4)
key = key.replace(dashSomething, toCamel)
}
result[key] = value
}
}
/**
* Create a JSX name from a string.
*
* @param {State} state
* To do.
* @param {string} name
* Name.
* @param {boolean} allowExpression
* Allow member expressions and identifiers.
* @returns {unknown}
* To do.
*/
function findComponentFromName(state, name, allowExpression) {
/** @type {Identifier | Literal | MemberExpression} */
let result
if (!allowExpression) {
result = {type: 'Literal', value: name}
} else if (name.includes('.')) {
const identifiers = name.split('.')
let index = -1
/** @type {Identifier | Literal | MemberExpression | undefined} */
let node
while (++index < identifiers.length) {
/** @type {Identifier | Literal} */
const prop = isIdentifierName(identifiers[index])
? {type: 'Identifier', name: identifiers[index]}
: {type: 'Literal', value: identifiers[index]}
node = node
? {
type: 'MemberExpression',
object: node,
property: prop,
computed: Boolean(index && prop.type === 'Literal'),
optional: false
}
: prop
}
assert(node, 'always a result')
result = node
} else {
result =
isIdentifierName(name) && !/^[a-z]/.test(name)
? {type: 'Identifier', name}
: {type: 'Literal', value: name}
}
// Only literals can be passed in `components` currently.
// No identifiers / member expressions.
if (result.type === 'Literal') {
const name = /** @type {keyof JSX.IntrinsicElements} */ (result.value)
return own.call(state.components, name) ? state.components[name] : name
}
// Assume component.
if (state.evaluater) {
return state.evaluater.evaluateExpression(result)
}
crashEstree(state)
}
/**
* @param {State} state
* @param {Position | undefined} [place]
* @returns {never}
*/
function crashEstree(state, place) {
const message = new VFileMessage(
'Cannot handle MDX estrees without `createEvaluater`',
{
ancestors: state.ancestors,
place,
ruleId: 'mdx-estree',
source: 'hast-util-to-jsx-runtime'
}
)
message.file = state.filePath || undefined
message.url = docs + '#cannot-handle-mdx-estrees-without-createevaluater'
throw message
}
/**
* Transform a DOM casing style object to a CSS casing style object.
*
* @param {Style} domCasing
* @returns {Style}
*/
function transformStylesToCssCasing(domCasing) {
/** @type {Style} */
const cssCasing = {}
/** @type {string} */
let from
for (from in domCasing) {
if (own.call(domCasing, from)) {
cssCasing[transformStyleToCssCasing(from)] = domCasing[from]
}
}
return cssCasing
}
/**
* Transform a DOM casing style field to a CSS casing style field.
*
* @param {string} from
* @returns {string}
*/
function transformStyleToCssCasing(from) {
let to = from.replace(cap, toDash)
// Handle `ms-xxx` -> `-ms-xxx`.
if (to.slice(0, 3) === 'ms-') to = '-' + to
return to
}
/**
* Make `$1` capitalized.
*
* @param {string} _
* Whatever.
* @param {string} $1
* Single ASCII alphabetical.
* @returns {string}
* Capitalized `$1`.
*/
function toCamel(_, $1) {
return $1.toUpperCase()
}
/**
* Make `$0` dash cased.
*
* @param {string} $0
* Capitalized ASCII leter.
* @returns {string}
* Dash and lower letter.
*/
function toDash($0) {
return '-' + $0.toLowerCase()
}