IOweb/node_modules/esast-util-from-estree/lib/index.js
2026-07-03 15:07:38 -05:00

121 lines
3.2 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.

/**
* @typedef {import('estree-jsx').Node} Nodes
*/
/**
* @typedef Options
* Configuration.
* @property {boolean | null | undefined} [dirty=false]
* Leave discouraged fields in the tree (default: `false`).
*/
/**
* @template T
* @template U
* @typedef {{[K in keyof T]: T[K] extends U ? K : never}[keyof T]} KeysOfType
*/
/**
* @template T
* @typedef {Exclude<KeysOfType<T, Exclude<T[keyof T], undefined>>, undefined>} RequiredKeys
*/
/**
* @template T
* @typedef {Exclude<keyof T, RequiredKeys<T>>} OptionalKeys
*/
import {visit} from 'estree-util-visit'
import {positionFromEstree} from 'unist-util-position-from-estree'
/** @type {Options} */
const emptyOptions = {}
/**
* Turn an estree into an esast.
*
* @template {Nodes} Kind
* Node kind.
* @param {Kind} estree
* estree.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {Kind}
* Clean clone of `estree`.
*/
export function fromEstree(estree, options) {
const settings = options || emptyOptions
/** @type {Kind} */
// Drop the `Node` and such constructors on Acorn nodes.
const esast = JSON.parse(JSON.stringify(estree, ignoreBigint))
visit(esast, {
leave(node) {
const position = positionFromEstree(node)
if (!settings.dirty) {
// Acorn specific.
// @ts-expect-error: acorn adds this.
if ('end' in node) remove(node, 'end')
// @ts-expect-error: acorn adds this.
if ('start' in node) remove(node, 'start')
if (node.type === 'JSXOpeningFragment') {
// @ts-expect-error: acorn adds this, but it should not exist.
if ('attributes' in node) remove(node, 'attributes')
// @ts-expect-error: acorn adds this, but it should not exist.
if ('selfClosing' in node) remove(node, 'selfClosing')
}
// Estree.
if ('loc' in node) remove(node, 'loc')
// @ts-expect-error: `JSXText` types are wrong: `raw` is optional.
if ('raw' in node) remove(node, 'raw')
if (node.type === 'Literal') {
// These `value`s on bigint/regex literals represent a raw value,
// which is an antipattern.
if ('bigint' in node) remove(node, 'value')
if ('regex' in node) remove(node, 'value')
}
}
if (node.type === 'Literal' && 'bigint' in node) {
const bigint = node.bigint
const match = /0[box]/.exec(bigint.slice(0, 2).toLowerCase())
if (match) {
const code = match[0].charCodeAt(1)
const base =
code === 98 /* `x` */ ? 2 : code === 111 /* `o` */ ? 8 : 16
node.bigint = Number.parseInt(bigint.slice(2), base).toString()
}
}
// @ts-expect-error: `position` is not in `Node`, but we add it anyway
// because its useful.
node.position = position
}
})
return esast
}
/**
* @template {Nodes} Kind
* @param {Kind} value
* @param {OptionalKeys<Kind>} key
* @returns {undefined}
*/
function remove(value, key) {
delete value[key]
}
/**
*
* @param {string} _
* @param {unknown} value
* @returns {unknown}
*/
function ignoreBigint(_, value) {
return typeof value === 'bigint' ? undefined : value
}