Socket
Socket
Sign inDemoInstall

hast-util-to-estree

Package Overview
Dependencies
Maintainers
2
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hast-util-to-estree - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

lib/handlers/comment.d.ts

8

index.d.ts

@@ -0,4 +1,6 @@

export {handlers as defaultHandlers} from './lib/handlers/index.js'
export {toEstree} from './lib/index.js'
export type Handle = import('./lib/index.js').Handle
export type Space = import('./lib/index.js').Space
export type Options = import('./lib/index.js').Options
export type Handle = import('./lib/state.js').Handle
export type Options = import('./lib/state.js').Options
export type Space = import('./lib/state.js').Space
export type State = import('./lib/state.js').State
/**
* @typedef {import('./lib/index.js').Handle} Handle
* @typedef {import('./lib/index.js').Space} Space
* @typedef {import('./lib/index.js').Options} Options
* @typedef {import('./lib/state.js').Handle} Handle
* @typedef {import('./lib/state.js').Options} Options
* @typedef {import('./lib/state.js').Space} Space
* @typedef {import('./lib/state.js').State} State
*/
export {handlers as defaultHandlers} from './lib/handlers/index.js'
export {toEstree} from './lib/index.js'
/**
* @param {Node|MdxJsxAttributeValueExpression|MdxJsxAttribute|MdxJsxExpressionAttribute|MdxJsxFlowElement|MdxJsxTextElement|MdxFlowExpression|MdxTextExpression} tree
* @param {Options} options
* @returns {EstreeProgram}
* Transform a hast tree (with embedded MDX nodes) into an estree.
*
* ###### Notes
*
* Comments are attached to the tree in their neighbouring nodes (`recast`,
* `babel` style) and also added as a `comments` array on the program node
* (`espree` style).
* You may have to do `program.comments = undefined` for certain compilers.
*
* @param {Node} tree
* hast tree.
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {Program}
* estree program node.
*
* The program’s last child in `body` is most likely an `ExpressionStatement`,
* whose expression is a `JSXFragment` or a `JSXElement`.
*
* Typically, there is only one node in `body`, however, this utility also
* supports embedded MDX nodes in the HTML (when `mdast-util-mdx` is used
* with mdast to parse markdown before passing its nodes through to hast).
* When MDX ESM import/exports are used, those nodes are added before the
* fragment or element in body.
*
* There aren’t many great estree serializers out there that support JSX.
* To do that, you can use `estree-util-to-js`.
* Or, use `estree-util-build-jsx` to turn JSX into function calls, and then
* serialize with whatever (`astring`, `escodegen`).
*/
export function toEstree(
tree:
| Node
| MdxJsxAttributeValueExpression
| MdxJsxAttribute
| MdxJsxExpressionAttribute
| MdxJsxFlowElement
| MdxJsxTextElement
| MdxFlowExpression
| MdxTextExpression,
options?: Options
): EstreeProgram
tree: Node,
options?: Options | null | undefined
): Program
export type Content = import('hast').Content
export type Root = import('hast').Root
export type Element = import('hast').Element
export type Text = import('hast').Text
export type Comment = import('hast').Comment
export type Content = import('hast').Content
export type Node = Root | Content
export type Parent = Extract<Node, import('unist').Parent>
export type EstreeNode = import('estree').Node
export type EstreeProgram = import('estree').Program
export type EstreeComment = import('estree').Comment
export type EstreeDirective = import('estree').Directive
export type EstreeStatement = import('estree').Statement
export type EstreeModuleDeclaration = import('estree').ModuleDeclaration
export type EstreeExpression = import('estree').Expression
export type EstreeProperty = import('estree').Property
export type EstreeJsxExpressionContainer =
import('estree-jsx').JSXExpressionContainer
export type EstreeJsxElement = import('estree-jsx').JSXElement
export type EstreeJsxOpeningElement = import('estree-jsx').JSXOpeningElement
export type EstreeJsxFragment = import('estree-jsx').JSXFragment
export type EstreeJsxAttribute = import('estree-jsx').JSXAttribute
export type EstreeJsxSpreadAttribute = import('estree-jsx').JSXSpreadAttribute
export type JSXIdentifier = import('estree-jsx').JSXIdentifier
export type JSXMemberExpression = import('estree-jsx').JSXMemberExpression
export type EstreeJsxElementName = EstreeJsxOpeningElement['name']
export type EstreeJsxAttributeName = EstreeJsxAttribute['name']
export type EstreeJsxChild = EstreeJsxElement['children'][number]
export type ExpressionStatement = import('estree').ExpressionStatement
export type Program = import('estree').Program
export type MdxJsxAttribute = import('mdast-util-mdx-jsx').MdxJsxAttribute
export type MdxJsxAttributeValueExpression =
import('mdast-util-mdx-jsx').MdxJsxAttributeValueExpression
export type MdxJsxAttribute = import('mdast-util-mdx-jsx').MdxJsxAttribute
export type MdxJsxExpressionAttribute =

@@ -56,15 +51,12 @@ import('mdast-util-mdx-jsx').MdxJsxExpressionAttribute

import('mdast-util-mdx-expression').MdxTextExpression
export type MdxjsEsm = import('mdast-util-mdxjs-esm').MdxjsEsm
export type Space = 'html' | 'svg'
export type Handle = (node: any, context: Context) => EstreeJsxChild | null
export type Options = {
space?: Space | undefined
handlers?: Record<string, Handle> | undefined
}
export type Context = {
schema: typeof html
comments: Array<EstreeComment>
esm: Array<EstreeDirective | EstreeStatement | EstreeModuleDeclaration>
handle: Handle
}
import {html} from 'property-information'
export type Options = import('./state.js').Options
export type Node =
| Root
| Content
| MdxJsxAttributeValueExpression
| MdxJsxAttribute
| MdxJsxExpressionAttribute
| MdxJsxFlowElement
| MdxJsxTextElement
| MdxFlowExpression
| MdxTextExpression
/**
* @typedef {import('hast').Content} Content
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Text} Text
* @typedef {import('hast').Comment} Comment
* @typedef {import('hast').Content} Content
* @typedef {Root|Content} Node
* @typedef {Extract<Node, import('unist').Parent>} Parent
* @typedef {import('estree').Node} EstreeNode
* @typedef {import('estree').Program} EstreeProgram
* @typedef {import('estree').Comment} EstreeComment
* @typedef {import('estree').Directive} EstreeDirective
* @typedef {import('estree').Statement} EstreeStatement
* @typedef {import('estree').ModuleDeclaration} EstreeModuleDeclaration
* @typedef {import('estree').Expression} EstreeExpression
* @typedef {import('estree').Property} EstreeProperty
* @typedef {import('estree-jsx').JSXExpressionContainer} EstreeJsxExpressionContainer
* @typedef {import('estree-jsx').JSXElement} EstreeJsxElement
* @typedef {import('estree-jsx').JSXOpeningElement} EstreeJsxOpeningElement
* @typedef {import('estree-jsx').JSXFragment} EstreeJsxFragment
* @typedef {import('estree-jsx').JSXAttribute} EstreeJsxAttribute
* @typedef {import('estree-jsx').JSXSpreadAttribute} EstreeJsxSpreadAttribute
* @typedef {import('estree-jsx').JSXIdentifier} JSXIdentifier
* @typedef {import('estree-jsx').JSXMemberExpression} JSXMemberExpression
*
* @typedef {EstreeJsxOpeningElement['name']} EstreeJsxElementName
* @typedef {EstreeJsxAttribute['name']} EstreeJsxAttributeName
* @typedef {EstreeJsxElement['children'][number]} EstreeJsxChild
* @typedef {import('estree').ExpressionStatement} ExpressionStatement
* @typedef {import('estree').Program} Program
*
* @typedef {import('mdast-util-mdx-jsx').MdxJsxAttribute} MdxJsxAttribute
* @typedef {import('mdast-util-mdx-jsx').MdxJsxAttributeValueExpression} MdxJsxAttributeValueExpression
* @typedef {import('mdast-util-mdx-jsx').MdxJsxAttribute} MdxJsxAttribute
* @typedef {import('mdast-util-mdx-jsx').MdxJsxExpressionAttribute} MdxJsxExpressionAttribute

@@ -39,86 +17,50 @@ * @typedef {import('mdast-util-mdx-jsx').MdxJsxFlowElement} MdxJsxFlowElement

*
* @typedef {import('mdast-util-mdxjs-esm').MdxjsEsm} MdxjsEsm
*
* @typedef {'html'|'svg'} Space
*
* @typedef {(node: any, context: Context) => EstreeJsxChild?} Handle
*
* @typedef Options
* @property {Space} [space='html']
* @property {Record<string, Handle>} [handlers={}]
*
* @typedef Context
* @property {typeof html} schema
* @property {Array<EstreeComment>} comments
* @property {Array<EstreeDirective|EstreeStatement|EstreeModuleDeclaration>} esm
* @property {Handle} handle
* @typedef {import('./state.js').Options} Options
*/
import {stringify as commas} from 'comma-separated-tokens'
import {attachComments} from 'estree-util-attach-comments'
import {
start as identifierStart,
cont as identifierCont
} from 'estree-util-is-identifier-name'
import {whitespace} from 'hast-util-whitespace'
import {html, svg, find, hastToReact} from 'property-information'
import {stringify as spaces} from 'space-separated-tokens'
import style from 'style-to-object'
import {position} from 'unist-util-position'
import {zwitch} from 'zwitch'
/**
* @typedef {Root | Content | MdxJsxAttributeValueExpression | MdxJsxAttribute | MdxJsxExpressionAttribute | MdxJsxFlowElement | MdxJsxTextElement | MdxFlowExpression | MdxTextExpression} Node
*/
const toReact = /** @type {Record<string, string>} */ (hastToReact)
import {createState} from './state.js'
const own = {}.hasOwnProperty
const tableElements = new Set([
'table',
'thead',
'tbody',
'tfoot',
'tr',
'th',
'td'
])
/**
* @param {Node|MdxJsxAttributeValueExpression|MdxJsxAttribute|MdxJsxExpressionAttribute|MdxJsxFlowElement|MdxJsxTextElement|MdxFlowExpression|MdxTextExpression} tree
* @param {Options} options
* @returns {EstreeProgram}
* Transform a hast tree (with embedded MDX nodes) into an estree.
*
* ###### Notes
*
* Comments are attached to the tree in their neighbouring nodes (`recast`,
* `babel` style) and also added as a `comments` array on the program node
* (`espree` style).
* You may have to do `program.comments = undefined` for certain compilers.
*
* @param {Node} tree
* hast tree.
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {Program}
* estree program node.
*
* The program’s last child in `body` is most likely an `ExpressionStatement`,
* whose expression is a `JSXFragment` or a `JSXElement`.
*
* Typically, there is only one node in `body`, however, this utility also
* supports embedded MDX nodes in the HTML (when `mdast-util-mdx` is used
* with mdast to parse markdown before passing its nodes through to hast).
* When MDX ESM import/exports are used, those nodes are added before the
* fragment or element in body.
*
* There aren’t many great estree serializers out there that support JSX.
* To do that, you can use `estree-util-to-js`.
* Or, use `estree-util-build-jsx` to turn JSX into function calls, and then
* serialize with whatever (`astring`, `escodegen`).
*/
export function toEstree(tree, options = {}) {
/** @type {Context} */
const context = {
schema: options.space === 'svg' ? svg : html,
comments: [],
esm: [],
// @ts-expect-error: hush.
handle: zwitch('type', {
invalid,
// @ts-expect-error: hush.
unknown,
// @ts-expect-error: hush.
handlers: Object.assign(
{},
{
comment,
doctype: ignore,
element,
mdxjsEsm,
mdxFlowExpression: mdxExpression,
mdxJsxFlowElement: mdxJsxElement,
mdxJsxTextElement: mdxJsxElement,
mdxTextExpression: mdxExpression,
root,
text
},
options.handlers
)
})
}
let result = context.handle(tree, context)
const body = context.esm
export function toEstree(tree, options) {
const state = createState(options || {})
let result = state.handle(tree)
const body = state.esm
if (result) {
if (result.type !== 'JSXFragment' && result.type !== 'JSXElement') {
result = create(tree, {
result = {
type: 'JSXFragment',

@@ -128,632 +70,22 @@ openingFragment: {type: 'JSXOpeningFragment'},

children: [result]
})
}
state.patch(tree, result)
}
/** @type {ExpressionStatement} */
// @ts-expect-error Types are wrong (`expression` *can* be JSX).
body.push(create(tree, {type: 'ExpressionStatement', expression: result}))
const statement = {type: 'ExpressionStatement', expression: result}
state.patch(tree, statement)
body.push(statement)
}
return create(tree, {
/** @type {Program} */
const program = {
type: 'Program',
body,
sourceType: 'module',
comments: context.comments
})
}
/**
* @param {unknown} value
*/
function invalid(value) {
throw new Error('Cannot handle value `' + value + '`, expected node')
}
/**
* @param {Node} node
*/
function unknown(node) {
throw new Error('Cannot handle unknown node `' + node.type + '`')
}
function ignore() {}
/**
* @param {Comment} node
* @param {Context} context
* @returns {EstreeJsxExpressionContainer}
*/
function comment(node, context) {
const esnode = inherit(node, {type: 'Block', value: node.value})
context.comments.push(esnode)
return create(node, {
type: 'JSXExpressionContainer',
expression: create(node, {
type: 'JSXEmptyExpression',
comments: [Object.assign({}, esnode, {leading: false, trailing: true})]
})
})
}
/**
* @param {Element} node
* @param {Context} context
* @returns {EstreeJsxElement}
*/
// eslint-disable-next-line complexity
function element(node, context) {
const parentSchema = context.schema
let schema = parentSchema
const props = node.properties || {}
if (parentSchema.space === 'html' && node.tagName.toLowerCase() === 'svg') {
schema = svg
context.schema = schema
comments: state.comments
}
const children = all(node, context)
/** @type {Array<EstreeJsxAttribute|EstreeJsxSpreadAttribute>} */
const attributes = []
/** @type {string} */
let prop
for (prop in props) {
if (own.call(props, prop)) {
let value = props[prop]
const info = find(schema, prop)
/** @type {EstreeJsxAttribute['value']} */
let attributeValue
// Ignore nullish and `NaN` values.
// Ignore `false` and falsey known booleans.
if (
value === undefined ||
value === null ||
(typeof value === 'number' && Number.isNaN(value)) ||
value === false ||
(!value && info.boolean)
) {
continue
}
prop = info.space
? toReact[info.property] || info.property
: info.attribute
if (Array.isArray(value)) {
// Accept `array`.
// Most props are space-separated.
value = info.commaSeparated ? commas(value) : spaces(value)
}
if (prop === 'style') {
/** @type {Record<string, string>} */
// @ts-expect-error Assume `value` is an object otherwise.
const styleValue =
typeof value === 'string' ? parseStyle(value, node.tagName) : value
/** @type {Array<EstreeProperty>} */
const cssProperties = []
/** @type {string} */
let cssProp
for (cssProp in styleValue) {
// eslint-disable-next-line max-depth
if (own.call(styleValue, cssProp)) {
cssProperties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: {type: 'Identifier', name: cssProp},
value: {type: 'Literal', value: String(styleValue[cssProp])},
kind: 'init'
})
}
}
attributeValue = {
type: 'JSXExpressionContainer',
expression: {type: 'ObjectExpression', properties: cssProperties}
}
} else if (value === true) {
attributeValue = null
} else {
attributeValue = {type: 'Literal', value: String(value)}
}
if (jsxIdentifierName(prop)) {
attributes.push({
type: 'JSXAttribute',
name: {type: 'JSXIdentifier', name: prop},
value: attributeValue
})
} else {
attributes.push({
type: 'JSXSpreadAttribute',
argument: {
type: 'ObjectExpression',
properties: [
{
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: {type: 'Literal', value: String(prop)},
// @ts-expect-error No need to worry about `style` (which has a
// `JSXExpressionContainer` value) because that’s a valid identifier.
value: attributeValue || {type: 'Literal', value: true},
kind: 'init'
}
]
}
})
}
}
}
// Restore parent schema.
context.schema = parentSchema
return inherit(node, {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
attributes,
name: createJsxName(node.tagName),
selfClosing: children.length === 0
},
closingElement:
children.length > 0
? {type: 'JSXClosingElement', name: createJsxName(node.tagName)}
: null,
children
})
state.patch(tree, program)
return program
}
/**
* @param {MdxjsEsm} node
* @param {Context} context
* @returns {void}
*/
function mdxjsEsm(node, context) {
const estree = node.data && node.data.estree
const comments = (estree && estree.comments) || []
if (estree) {
context.comments.push(...comments)
attachComments(estree, comments)
context.esm.push(...estree.body)
}
}
/**
* @param {MdxFlowExpression|MdxTextExpression} node
* @param {Context} context
* @returns {EstreeJsxExpressionContainer}
*/
function mdxExpression(node, context) {
const estree = node.data && node.data.estree
const comments = (estree && estree.comments) || []
/** @type {EstreeExpression|undefined} */
let expression
if (estree) {
context.comments.push(...comments)
attachComments(estree, estree.comments)
expression =
(estree.body[0] &&
estree.body[0].type === 'ExpressionStatement' &&
estree.body[0].expression) ||
undefined
}
return inherit(node, {
type: 'JSXExpressionContainer',
expression: expression || create(node, {type: 'JSXEmptyExpression'})
})
}
/**
* @param {MdxJsxFlowElement|MdxJsxTextElement} node
* @param {Context} context
* @returns {EstreeJsxElement|EstreeJsxFragment}
*/
// eslint-disable-next-line complexity
function mdxJsxElement(node, context) {
const parentSchema = context.schema
let schema = parentSchema
const attrs = node.attributes || []
let index = -1
if (
node.name &&
parentSchema.space === 'html' &&
node.name.toLowerCase() === 'svg'
) {
schema = svg
context.schema = schema
}
const children = all(node, context)
/** @type {Array<EstreeJsxAttribute|EstreeJsxSpreadAttribute>} */
const attributes = []
while (++index < attrs.length) {
const attr = attrs[index]
const value = attr.value
/** @type {EstreeJsxAttribute['value']} */
let attributeValue
if (attr.type === 'mdxJsxAttribute') {
if (value === undefined || value === null) {
attributeValue = null
// Empty.
}
// `MdxJsxAttributeValueExpression`.
else if (typeof value === 'object') {
const estree = value.data && value.data.estree
const comments = (estree && estree.comments) || []
/** @type {EstreeExpression|undefined} */
let expression
if (estree) {
context.comments.push(...comments)
attachComments(estree, estree.comments)
// Should exist.
/* c8 ignore next 5 */
expression =
(estree.body[0] &&
estree.body[0].type === 'ExpressionStatement' &&
estree.body[0].expression) ||
undefined
}
attributeValue = inherit(value, {
type: 'JSXExpressionContainer',
expression: expression || {type: 'JSXEmptyExpression'}
})
}
// Anything else.
else {
attributeValue = {type: 'Literal', value: String(value)}
}
attributes.push(
inherit(attr, {
type: 'JSXAttribute',
name: createJsxName(attr.name, true),
value: attributeValue
})
)
}
// MdxJsxExpressionAttribute.
else {
const estree = attr.data && attr.data.estree
const comments = (estree && estree.comments) || []
/** @type {EstreeJsxSpreadAttribute['argument']|undefined} */
let argumentValue
if (estree) {
context.comments.push(...comments)
attachComments(estree, estree.comments)
// Should exist.
/* c8 ignore next 10 */
argumentValue =
(estree.body[0] &&
estree.body[0].type === 'ExpressionStatement' &&
estree.body[0].expression &&
estree.body[0].expression.type === 'ObjectExpression' &&
estree.body[0].expression.properties &&
estree.body[0].expression.properties[0] &&
estree.body[0].expression.properties[0].type === 'SpreadElement' &&
estree.body[0].expression.properties[0].argument) ||
undefined
}
attributes.push(
inherit(attr, {
type: 'JSXSpreadAttribute',
argument: argumentValue || {type: 'ObjectExpression', properties: []}
})
)
}
}
// Restore parent schema.
context.schema = parentSchema
return inherit(
node,
node.name
? {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
attributes,
name: createJsxName(node.name),
selfClosing: children.length === 0
},
closingElement:
children.length > 0
? {type: 'JSXClosingElement', name: createJsxName(node.name)}
: null,
children
}
: {
type: 'JSXFragment',
openingFragment: {type: 'JSXOpeningFragment'},
closingFragment: {type: 'JSXClosingFragment'},
children
}
)
}
/**
* @param {Root} node
* @param {Context} context
* @returns {EstreeJsxFragment}
*/
function root(node, context) {
const children = all(node, context)
/** @type {Array<EstreeJsxChild>} */
const cleanChildren = []
let index = -1
/** @type {Array<EstreeJsxChild>|undefined} */
let queue
// Remove surrounding whitespace nodes from the fragment.
while (++index < children.length) {
const child = children[index]
if (
child.type === 'JSXExpressionContainer' &&
child.expression.type === 'Literal' &&
whitespace(child.expression.value)
) {
if (queue) queue.push(child)
} else {
if (queue) cleanChildren.push(...queue)
cleanChildren.push(child)
queue = []
}
}
return inherit(node, {
type: 'JSXFragment',
openingFragment: {type: 'JSXOpeningFragment'},
closingFragment: {type: 'JSXClosingFragment'},
children: cleanChildren
})
}
/**
* @param {Text} node
* @returns {EstreeJsxExpressionContainer|void}
*/
function text(node) {
const value = String(node.value || '')
if (!value) return
return create(node, {
type: 'JSXExpressionContainer',
expression: inherit(node, {type: 'Literal', value})
})
}
/**
* @param {Parent|MdxJsxFlowElement|MdxJsxTextElement} parent
* @param {Context} context
* @returns {Array<EstreeJsxChild>}
*/
function all(parent, context) {
const children = parent.children || []
let index = -1
/** @type {Array<EstreeJsxChild>} */
const results = []
// Currently, a warning is triggered by react for *any* white space in
// tables.
// So we remove the pretty lines for now.
// 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>.
const ignoreLineBreak =
context.schema.space === 'html' &&
parent.type === 'element' &&
tableElements.has(parent.tagName.toLowerCase())
while (++index < children.length) {
const child = children[index]
if (ignoreLineBreak && child.type === 'text' && child.value === '\n') {
continue
}
const result = context.handle(child, context)
if (Array.isArray(result)) {
results.push(...result)
} else if (result) {
results.push(result)
}
}
return results
}
/**
* Take positional info and data from `hast`.
*
* @template {EstreeNode|EstreeComment} T
* @param {Node|MdxJsxAttributeValueExpression|MdxJsxAttribute|MdxJsxExpressionAttribute|MdxJsxFlowElement|MdxJsxTextElement|MdxFlowExpression|MdxTextExpression} hast
* @param {T} esnode
* @returns {T}
*/
function inherit(hast, esnode) {
const left = hast.data
/** @type {Record<string, unknown>|undefined} */
let right
/** @type {string} */
let key
create(hast, esnode)
if (left) {
for (key in left) {
if (own.call(left, key) && key !== 'estree') {
if (!right) right = {}
right[key] = left[key]
}
}
if (right) {
// @ts-expect-error `esast` extension.
esnode.data = right
}
}
return esnode
}
/**
* Just positional info.
*
* @template {EstreeNode|EstreeComment} T
* @param {Node|MdxJsxAttributeValueExpression|MdxJsxAttribute|MdxJsxExpressionAttribute|MdxJsxFlowElement|MdxJsxTextElement|MdxFlowExpression|MdxTextExpression} hast
* @param {T} esnode
* @returns {T}
*/
function create(hast, esnode) {
const p = position(hast)
if (
p.start.line &&
p.start.offset !== undefined &&
p.end.offset !== undefined
) {
// @ts-expect-error acorn-style.
esnode.start = p.start.offset
// @ts-expect-error acorn-style.
esnode.end = p.end.offset
esnode.loc = {
start: {line: p.start.line, column: p.start.column - 1},
end: {line: p.end.line, column: p.end.column - 1}
}
esnode.range = [p.start.offset, p.end.offset]
}
return esnode
}
const createJsxName =
/**
* @type {(
* ((name: string, attribute: true) => EstreeJsxAttributeName) &
* ((name: string, attribute?: false) => EstreeJsxElementName)
* )}
*/
(
/**
* @param {string} name
* @param {boolean} [attribute=false]
* @returns {EstreeJsxElementName}
*/
function (name, attribute) {
if (!attribute && name.includes('.')) {
const parts = name.split('.')
let part = parts.shift()
/** @type {JSXIdentifier|JSXMemberExpression} */
// @ts-expect-error: hush, the first is always defined.
let node = {type: 'JSXIdentifier', name: part}
while ((part = parts.shift())) {
node = {
type: 'JSXMemberExpression',
object: node,
property: {type: 'JSXIdentifier', name: part}
}
}
return node
}
if (name.includes(':')) {
const parts = name.split(':')
return {
type: 'JSXNamespacedName',
namespace: {type: 'JSXIdentifier', name: parts[0]},
name: {type: 'JSXIdentifier', name: parts[1]}
}
}
return {type: 'JSXIdentifier', name}
}
)
/**
* @param {string} value
* @param {string} tagName
* @returns {Record<string, string>}
*/
function parseStyle(value, tagName) {
/** @type {Record<string, string>} */
const result = {}
try {
style(value, iterator)
} catch (error) {
const exception = /** @type {Error} */ (error)
exception.message =
tagName + '[style]' + exception.message.slice('undefined'.length)
throw error
}
return result
/**
* @param {string} name
* @param {string} value
* @returns {void}
*/
function iterator(name, value) {
if (name.slice(0, 4) === '-ms-') name = 'ms-' + name.slice(4)
result[name.replace(/-([a-z])/g, styleReplacer)] = value
}
}
/**
* @param {string} _
* @param {string} $1
* @returns {string}
*/
function styleReplacer(_, $1) {
return $1.toUpperCase()
}
/**
* Checks if the given string is a valid identifier name.
*
* @param {string} name
* @returns {boolean}
*/
function jsxIdentifierName(name) {
let index = -1
while (++index < name.length) {
if (!(index ? cont : identifierStart)(name.charCodeAt(index))) return false
}
// `false` if `name` is empty.
return index > 0
/**
* @param {number} code
* @returns {boolean}
*/
function cont(code) {
return identifierCont(code) || code === 45 /* `-` */
}
}
{
"name": "hast-util-to-estree",
"version": "2.1.0",
"version": "2.2.0",
"description": "hast utility to transform to estree (JavaScript AST) JSX",

@@ -55,3 +55,3 @@ "license": "MIT",

"space-separated-tokens": "^2.0.0",
"style-to-object": "^0.3.0",
"style-to-object": "^0.4.0",
"unist-util-position": "^4.0.0",

@@ -65,3 +65,4 @@ "zwitch": "^2.0.0"

"@types/babel__core": "^7.0.0",
"@types/tape": "^4.0.0",
"@types/babel__generator": "^7.0.0",
"@types/node": "^18.0.0",
"@vue/babel-plugin-jsx": "^1.0.0",

@@ -73,2 +74,3 @@ "acorn-jsx": "^5.0.0",

"estree-util-to-js": "^1.0.0",
"estree-walker": "^3.0.0",
"hastscript": "^7.0.0",

@@ -82,15 +84,13 @@ "mdast-util-from-markdown": "^1.0.0",

"remark-preset-wooorm": "^9.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"unist-util-visit": "^4.0.0",
"xo": "^0.51.0"
"xo": "^0.53.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "rimraf \"lib/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
"test-api": "node --conditions development test.js",
"test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"

@@ -124,5 +124,6 @@ },

"ignoreFiles": [
"lib/index.d.ts"
"lib/state.d.ts",
"lib/state.js"
]
}
}

@@ -20,3 +20,8 @@ # hast-util-to-estree

* [API](#api)
* [`toEstree(tree, options?)`](#toestreetree-options)
* [`toEstree(tree[, options])`](#toestreetree-options)
* [`defaultHandlers`](#defaulthandlers)
* [`Handle`](#handle)
* [`Options`](#options)
* [`Space`](#space-1)
* [`State`](#state)
* [Types](#types)

@@ -32,3 +37,3 @@ * [Compatibility](#compatibility)

This package is a utility that takes a [hast][] (HTML) syntax tree as input and
turns it into an [estree][] (JavaScript) syntax tree JSX extension.
turns it into an [estree][] (JavaScript) syntax tree (with a JSX extension).
This package also supports embedded MDX nodes.

@@ -40,3 +45,3 @@

working with syntax trees.
This us used in [MDX][].
This is used in [MDX][].

@@ -46,3 +51,3 @@ ## Install

This package is [ESM only][esm].
In Node.js (version 12.20+, 14.14+, 16.0+, 18.0+), install with [npm][]:
In Node.js (version 14.14+ and 16.0+), install with [npm][]:

@@ -148,31 +153,32 @@ ```sh

This package exports the identifier `toEstree`.
This package exports the identifiers [`defaultHandlers`][defaulthandlers] and
[`toEstree`][toestree].
There is no default export.
### `toEstree(tree, options?)`
### `toEstree(tree[, options])`
Transform to [estree][] (JSX).
Transform a hast tree (with embedded MDX nodes) into an estree (with JSX
nodes).
##### `options`
###### Notes
Configuration (optional).
Comments are attached to the tree in their neighbouring nodes (`recast`,
`babel` style) and also added as a `comments` array on the program node
(`espree` style).
You may have to do `program.comments = undefined` for certain compilers.
##### `options.space`
###### Parameters
Whether `tree` is in the HTML or SVG space (enum, `'svg'` or `'html'`, default:
`'html'`).
If an `svg` element is found when inside the HTML space, `toEstree`
automatically switches to the SVG space when entering the element, and
switches back when exiting
* `tree` ([`HastNode`][hast-node])
— hast tree
* `options` ([`Options`][options], optional)
— configuration
###### `options.handlers`
###### Returns
Object mapping node types to functions handling the corresponding nodes.
See the code for examples.
estree program node ([`Program`][program]).
###### Returns
The program’s last child in `body` is most likely an `ExpressionStatement`,
whose expression is a `JSXFragment` or a `JSXElement`.
Node ([`Program`][program]) whose last child in `body` is most likely an
`ExpressionStatement`, whose expression is a `JSXFragment` or a `JSXElement`.
Typically, there is only one node in `body`, however, this utility also supports

@@ -184,18 +190,93 @@ embedded MDX nodes in the HTML (when [`mdast-util-mdx`][mdast-util-mdx] is used

###### Note
Comments are both attached to the tree in their neighbouring nodes (recast and
babel style), and added as a `comments` array on the program node (espree
style).
You may have to do `program.comments = null` for certain compilers.
There aren’t many great estree serializers out there that support JSX.
To do that, you can use [`estree-util-to-js`][estree-util-to-js].
Or, use [`estree-util-build-jsx`][build-jsx] to turn JSX into function
calls, and then serialize with whatever (astring, escodegen).
calls, and then serialize with whatever (`astring`, `escodegen`).
### `defaultHandlers`
Default handlers for elements (`Record<string, Handle>`).
Each key is an element name, each value is a [`Handle`][handle].
### `Handle`
Turn a hast node into an estree node (TypeScript type).
###### Parameters
* `node` ([`HastNode`][hast-node])
— expected hast node
* `state` ([`State`][state])
— info passed around about the current state
###### Returns
JSX child (`JsxChild`, optional).
You can also add more results to `state.esm` and `state.comments`.
### `Options`
Configuration (TypeScript type).
##### Fields
###### `space`
Which space the document is in ([`Space`][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.
###### `handlers`
Object mapping node types to functions handling the corresponding nodes
(`Record<string, Handle>`, optional).
Merged into the defaults.
See [`Handle`][handle].
### `Space`
Namespace (TypeScript type).
###### Type
```ts
type Space = 'html' | 'svg'
```
### `State`
Info passed around about the current state (TypeScript type).
###### Fields
* `schema` ([`Schema`][schema])
— current schema
* `comments` (`Array<EstreeComment>`)
— list of estree comments
* `esm` (`Array<EstreeNode>`)
— list of top-level estree nodes
* `handle` (`(node: HastNode) => EstreeJsxChild | void`)
— transform a hast node to estree
* `handle` (`(node: HastParent) => EstreeJsxChild | void`)
— transform children of a hast parent to estree
* `patch` (`(from: HastNode, to: EstreeNode) => void`)
— take positional info from `from` (use `inherit` if you also want data)
* `inherit` (`(from: HastNode, to: EstreeNode) => void`)
— take positional info and data from `from` (use `patch` if you don’t want
data)
* `createJsxAttributeName` (`(name: string) => EstreeJsxAttributeName`)
— create a JSX attribute name
* `createJsxElementName` (`(name: string) => EstreeJsxElementName`)
— create a JSX attribute name
## Types
This package is fully typed with [TypeScript][].
It exports the additional types `Options`, `Space`, and `Handle`.
It exports the additional types [`Handle`][handle], [`Options`][options],
[`Space`][space], and [`State`][state].

@@ -206,3 +287,3 @@ ## Compatibility

versions of Node.js.
As of now, that is Node.js 12.20+, 14.14+, 16.0+, and 18.0+.
As of now, that is Node.js 14.14+ and 16.0+.
Our projects sometimes work with older versions, but this is not guaranteed.

@@ -290,2 +371,4 @@

[hast-node]: https://github.com/syntax-tree/hast#nodes
[estree]: https://github.com/estree/estree

@@ -301,2 +384,16 @@

[schema]: https://github.com/wooorm/property-information#api
[mdx]: https://mdxjs.com
[defaulthandlers]: #defaulthandlers
[toestree]: #toestreetree-options
[space]: #space-1
[options]: #options
[handle]: #handle
[state]: #state
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc