This commit is contained in:
2026-01-19 19:13:27 +01:00
commit 9d0442e88f
4968 changed files with 1142016 additions and 0 deletions

18
node_modules/estree-util-value-to-estree/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,18 @@
# MIT License
Copyright © 2021 Remco Haszing
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the “Software”), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

228
node_modules/estree-util-value-to-estree/README.md generated vendored Normal file
View File

@@ -0,0 +1,228 @@
# estree-util-value-to-estree
[![github actions](https://github.com/remcohaszing/estree-util-value-to-estree/actions/workflows/ci.yaml/badge.svg)](https://github.com/remcohaszing/estree-util-value-to-estree/actions/workflows/ci.yaml)
[![codecov](https://codecov.io/gh/remcohaszing/estree-util-value-to-estree/branch/main/graph/badge.svg)](https://codecov.io/gh/remcohaszing/estree-util-value-to-estree)
[![npm version](https://img.shields.io/npm/v/estree-util-value-to-estree)](https://www.npmjs.com/package/estree-util-value-to-estree)
[![npm downloads](https://img.shields.io/npm/dm/estree-util-value-to-estree)](https://www.npmjs.com/package/estree-util-value-to-estree)
Convert a JavaScript value to an [ESTree](https://github.com/estree/estree) expression.
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [API](#api)
- [`valueToEstree(value, options?)`](#valuetoestreevalue-options)
- [Examples](#examples)
- [Transform unsupported values into plain objects](#transform-unsupported-values-into-plain-objects)
- [Serialize custom values](#serialize-custom-values)
- [Compatibility](#compatibility)
- [License](#license)
## Installation
```sh
npm install estree-util-value-to-estree
```
## Usage
This package converts a JavaScript value to an [ESTree](https://github.com/estree/estree) expression
for values that can be constructed without the need for a context.
Currently the following types are supported:
- [`bigint`](https://developer.mozilla.org/docs/Glossary/BigInt)
- [`boolean`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
- [`null`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/null)
- [`number`](https://developer.mozilla.org/docs/Glossary/Number) (Including
[Infinity](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Infinity)
and [NaN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/NaN))
- [`string`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)
- [`symbol`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
([shared](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry)
and
[well-known](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol#well-known_symbols))
- [`object`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)
- [null-prototype `Object`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects)
- [`undefined`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Undefined)
- [`Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
- [`BigInt64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array)
- [`BigUint64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array)
- [`Boolean`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
- [`Buffer`](https://nodejs.org/api/buffer.html)
- [`Date`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date)
- [`Float16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float16Array)
- [`Float32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float32Array)
- [`Float64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float64Array)
- [`Int8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array)
- [`Int16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int16Array)
- [`Int32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array)
- [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)
- [`Number`](https://developer.mozilla.org/docs/Glossary/Number)
- [`RegExp`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
- [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)
- [`String`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)
- [`Temporal.Duration`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration)
- [`Temporal.Instant`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant)
- [`Temporal.PlainDate`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate)
- [`Temporal.PlainDateTime`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime)
- [`Temporal.PlainYearMonth`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth)
- [`Temporal.PlainMonthDay`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay)
- [`Temporal.PlainTime`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime)
- [`Temporal.ZonedDateTime`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Temporal/ZonedDateTime)
- [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)
- [`Uint8ClampedArray`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray)
- [`Uint16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array)
- [`Uint32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array)
- [`URL`](https://developer.mozilla.org/docs/Web/API/URL)
- [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams)
- [custom](#serialize-custom-values)
```ts
import { valueToEstree } from 'estree-util-value-to-estree'
const result = valueToEstree({
string: 'Hello world!',
number: 42
})
console.log(result)
// {
// type: 'ObjectExpression',
// properties: [
// {
// type: 'Property',
// method: false,
// shorthand: false,
// computed: false,
// kind: 'init',
// key: { type: 'Literal', value: 'string' },
// value: { type: 'Literal', value: 'Hello world!' }
// },
// {
// type: 'Property',
// method: false,
// shorthand: false,
// computed: false,
// kind: 'init',
// key: { type: 'Literal', value: 'number' },
// value: { type: 'Literal', value: 42 }
// }
// ]
// })
```
## API
This API exports the function `valueToEstree`.
### `valueToEstree(value, options?)`
Convert a value to an [ESTree](https://github.com/estree/estree) node.
#### options
- `instanceAsObject` (boolean, default: `false`) — If true, treat objects that have a prototype as
plain objects.
- `preserveReferences` (boolean, default: `false`) — If true, preserve references to the same object
found within the input. This also allows to serialize recursive structures. If needed, the
resulting expression will be an iife.
- `replacer` (Function) — A function to customize the serialization of a value. It accepts the value
to serialize as an argument. It must return the value serialized to an ESTree expression. If
nothing is returned, the value is processed by the builtin logic.
## Examples
### Transform unsupported values into plain objects
By default custom types result in an error. If `options.instanceAsObject` is set to `true` however,
they are turned into plain objects.
```ts
import { valueToEstree } from 'estree-util-value-to-estree'
class Point {
line: number
column: number
constructor(line: number, column: number) {
this.line = line
this.column = column
}
}
const point = new Point(2, 3)
const result = valueToEstree(point, { instanceAsObject: true })
console.log(result)
// {
// type: 'ObjectExpression',
// properties: [
// {
// type: 'Property',
// method: false,
// shorthand: false,
// computed: false,
// kind: 'init',
// key: { type: 'Literal', value: 'line' },
// value: { type: 'Literal', value: 2 }
// },
// {
// type: 'Property',
// method: false,
// shorthand: false,
// computed: false,
// kind: 'init',
// key: { type: 'Literal', value: 'column' },
// value: { type: 'Literal', value: 3 }
// }
// ]
// }
```
### Serialize custom values
You can customize the serialization of values using the `replacer` option. For example, to serialize
a custom class:
```ts
import { valueToEstree } from 'estree-util-value-to-estree'
class Point {
line: number
column: number
constructor(line: number, column: number) {
this.line = line
this.column = column
}
}
const point = new Point(2, 3)
const result = valueToEstree(point, {
replacer(value) {
if (value instanceof Point) {
return {
type: 'NewExpression',
callee: { type: 'Identifier', name: 'Point' },
arguments: [
{ type: 'Literal', value: value.line },
{ type: 'Literal', value: value.column }
]
}
}
}
})
console.log(result)
```
## Compatibility
This project is compatible with Node.js 16 or greater.
## License
[MIT](LICENSE.md) © [Remco Haszing](https://github.com/remcohaszing)

View File

@@ -0,0 +1,38 @@
import type { Expression } from 'estree';
export interface Options {
/**
* If true, treat objects that have a prototype as plain objects.
*
* @default false
*/
instanceAsObject?: boolean | undefined;
/**
* If true, preserve references to the same object found within the input. This also allows to
* serialize recursive structures. If needed, the resulting expression will be an iife.
*
* @default false
*/
preserveReferences?: boolean | undefined;
/**
* A function to customize the serialization of a value.
*
* @param value
* The value to serialize.
* @returns
* The value serialized to an ESTree expression. If nothing is returned, the value is processed
* by the builtin logic.
*/
replacer?: ((value: unknown) => Expression | undefined | void) | undefined;
}
/**
* Convert a value to an ESTree node.
*
* @param value
* The value to convert.
* @param options
* Additional options to configure the output.
* @returns
* The ESTree node.
*/
export declare function valueToEstree(value: unknown, options?: Options): Expression;
//# sourceMappingURL=estree-util-value-to-estree.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"estree-util-value-to-estree.d.ts","sourceRoot":"","sources":["../src/estree-util-value-to-estree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,UAAU,EAMX,MAAM,QAAQ,CAAA;AAuQf,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAEtC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAExC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,SAAS,CAAA;CAC3E;AAiFD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,OAAY,GAAG,UAAU,CA+c/E"}

View File

@@ -0,0 +1,672 @@
/**
* Create an ESTree identifier node for a given name.
*
* @param name
* The name of the identifier.
* @returns
* The identifier node.
*/
function identifier(name) {
return { type: 'Identifier', name };
}
/**
* Create an ESTree literal node for a given value.
*
* @param value
* The value for which to create a literal.
* @returns
* The literal node.
*/
function literal(value) {
return { type: 'Literal', value };
}
/**
* Create an ESTree call expression on an object member.
*
* @param object
* The object to call the method on.
* @param name
* The name of the method to call.
* @param args
* Arguments to pass to the function call
* @returns
* The call expression node.
*/
function methodCall(object, name, args) {
return {
type: 'CallExpression',
optional: false,
callee: {
type: 'MemberExpression',
computed: false,
optional: false,
object,
property: identifier(name)
},
arguments: args
};
}
/**
* Turn a number or bigint into an ESTree expression. This handles positive and negative numbers and
* bigints as well as special numbers.
*
* @param number
* The value to turn into an ESTree expression.
* @returns
* An expression that represents the given value.
*/
function processNumber(number) {
if (number < 0 || Object.is(number, -0)) {
return {
type: 'UnaryExpression',
operator: '-',
prefix: true,
argument: processNumber(-number)
};
}
if (typeof number === 'bigint') {
return { type: 'Literal', bigint: String(number) };
}
if (number === Number.POSITIVE_INFINITY || Number.isNaN(number)) {
return identifier(String(number));
}
return literal(number);
}
/**
* Process an array of numbers. This is a shortcut for iterables whose constructor takes an array of
* numbers as input.
*
* @param numbers
* The numbers to add to the array expression.
* @returns
* An ESTree array expression whose elements match the input numbers.
*/
function processNumberArray(numbers) {
return { type: 'ArrayExpression', elements: Array.from(numbers, processNumber) };
}
/**
* Check whether a value can be constructed from its string representation.
*
* @param value
* The value to check
* @returns
* Whether or not the value can be constructed from its string representation.
*/
function isStringReconstructable(value) {
return value instanceof URL || value instanceof URLSearchParams;
}
/**
* Check whether a value can be constructed from its `valueOf()` result.
*
* @param value
* The value to check
* @returns
* Whether or not the value can be constructed from its `valueOf()` result.
*/
function isValueReconstructable(value) {
return (value instanceof Boolean ||
value instanceof Date ||
value instanceof Number ||
value instanceof String);
}
const wellKnownSymbols = new Map();
for (const name of Reflect.ownKeys(Symbol)) {
const value = Symbol[name];
if (typeof value === 'symbol') {
wellKnownSymbols.set(value, name);
}
}
/**
* Check whether a value is a Temporal value.
*
* @param value
* The value to check
* @returns
* Whether or not the value is a Temporal value.
*/
function isTemporal(value) {
return (typeof Temporal !== 'undefined' &&
(value instanceof Temporal.Duration ||
value instanceof Temporal.Instant ||
value instanceof Temporal.PlainDate ||
value instanceof Temporal.PlainDateTime ||
value instanceof Temporal.PlainYearMonth ||
value instanceof Temporal.PlainMonthDay ||
value instanceof Temporal.PlainTime ||
value instanceof Temporal.ZonedDateTime));
}
/**
* Check whether a value is a typed array.
*
* @param value
* The value to check
* @returns
* Whether or not the value is a typed array.
*/
function isTypedArray(value) {
return (value instanceof BigInt64Array ||
value instanceof BigUint64Array ||
(typeof Float16Array !== 'undefined' && value instanceof Float16Array) ||
value instanceof Float32Array ||
value instanceof Float64Array ||
value instanceof Int8Array ||
value instanceof Int16Array ||
value instanceof Int32Array ||
value instanceof Uint8Array ||
value instanceof Uint8ClampedArray ||
value instanceof Uint16Array ||
value instanceof Uint32Array);
}
/**
* Compare two value contexts for sorting them based on reference count.
*
* @param a
* The first context to compare.
* @param b
* The second context to compare.
* @returns
* The count of context a minus the count of context b.
*/
function compareContexts(a, b) {
const aReferencedByB = a.referencedBy.has(b.value);
const bReferencedByA = b.referencedBy.has(a.value);
if (aReferencedByB) {
if (bReferencedByA) {
return a.count - b.count;
}
return -1;
}
if (bReferencedByA) {
return 1;
}
return a.count - b.count;
}
/**
* Replace the assigned right hand expression with the new expression.
*
* If there is no assignment expression, the original expression is returned. Otherwise the
* assignment is modified and returned.
*
* @param expression
* The expression to use for the assignment.
* @param assignment
* The existing assignmentexpression
* @returns
* The new expression.
*/
function replaceAssignment(expression, assignment) {
if (!assignment || assignment.type !== 'AssignmentExpression') {
return expression;
}
let node = assignment;
while (node.right.type === 'AssignmentExpression') {
node = node.right;
}
node.right = expression;
return assignment;
}
/**
* Create an ESTree epxression to represent a symbol. Global and well-known symbols are supported.
*
* @param symbol
* The symbol to represent.
* @returns
* An ESTree expression to represent the symbol.
*/
function symbolToEstree(symbol) {
const name = wellKnownSymbols.get(symbol);
if (name) {
return {
type: 'MemberExpression',
computed: false,
optional: false,
object: identifier('Symbol'),
property: identifier(name)
};
}
if (symbol.description && symbol === Symbol.for(symbol.description)) {
return methodCall(identifier('Symbol'), 'for', [literal(symbol.description)]);
}
throw new TypeError(`Only global symbols are supported, got: ${String(symbol)}`, {
cause: symbol
});
}
/**
* Create an ESTree property from a key and a value expression.
*
* @param key
* The property key value
* @param value
* The property value as an ESTree expression.
* @returns
* The ESTree properry node.
*/
function property(key, value) {
const isString = typeof key === 'string';
return {
type: 'Property',
method: false,
shorthand: false,
computed: key === '__proto__' || !isString,
kind: 'init',
key: isString ? literal(key) : symbolToEstree(key),
value
};
}
/**
* Convert a value to an ESTree node.
*
* @param value
* The value to convert.
* @param options
* Additional options to configure the output.
* @returns
* The ESTree node.
*/
export function valueToEstree(value, options = {}) {
const stack = [];
const collectedContexts = new Map();
const namedContexts = [];
const customTrees = new Map();
/**
* Analyze a value and collect all reference contexts.
*
* @param val
* The value to analyze.
*/
function analyze(val) {
if (typeof val !== 'object' && typeof val !== 'function') {
return;
}
if (val == null) {
return;
}
const context = collectedContexts.get(val);
if (context) {
if (options.preserveReferences) {
context.count += 1;
}
for (const ancestor of stack) {
context.referencedBy.add(ancestor);
}
if (stack.includes(val)) {
if (!options.preserveReferences) {
throw new Error(`Found circular reference: ${val}`, { cause: val });
}
const parent = stack.at(-1);
const parentContext = collectedContexts.get(parent);
parentContext.recursive = true;
context.recursive = true;
}
return;
}
collectedContexts.set(val, {
count: 1,
recursive: false,
referencedBy: new Set(stack),
value: val
});
const estree = options?.replacer?.(val);
if (estree) {
customTrees.set(val, estree);
return;
}
if (typeof val === 'function') {
throw new TypeError(`Unsupported value: ${val}`, { cause: val });
}
if (isTypedArray(val)) {
return;
}
if (isStringReconstructable(val)) {
return;
}
if (isValueReconstructable(val)) {
return;
}
if (value instanceof RegExp) {
return;
}
if (isTemporal(value)) {
return;
}
stack.push(val);
if (val instanceof Map) {
for (const pair of val) {
analyze(pair[0]);
analyze(pair[1]);
}
}
else if (Array.isArray(val) || val instanceof Set) {
for (const entry of val) {
analyze(entry);
}
}
else {
const proto = Object.getPrototypeOf(val);
if (proto != null && proto !== Object.prototype && !options.instanceAsObject) {
throw new TypeError(`Unsupported value: ${val}`, { cause: val });
}
for (const key of Reflect.ownKeys(val)) {
analyze(val[key]);
}
}
stack.pop();
}
/**
* Recursively generate the ESTree expression needed to reconstruct the value.
*
* @param val
* The value to process.
* @param isDeclaration
* Whether or not this is for a variable declaration.
* @returns
* The ESTree expression to reconstruct the value.
*/
function generate(val, isDeclaration) {
if (val === undefined) {
return identifier(String(val));
}
if (val == null || typeof val === 'string' || typeof val === 'boolean') {
return literal(val);
}
if (typeof val === 'bigint' || typeof val === 'number') {
return processNumber(val);
}
if (typeof val === 'symbol') {
return symbolToEstree(val);
}
const context = collectedContexts.get(val);
if (!isDeclaration && context?.name) {
return identifier(context.name);
}
const tree = customTrees.get(val);
if (tree) {
return tree;
}
if (isValueReconstructable(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [generate(val.valueOf())]
};
}
if (val instanceof RegExp) {
return {
type: 'Literal',
regex: { pattern: val.source, flags: val.flags }
};
}
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(val)) {
return methodCall(identifier('Buffer'), 'from', [processNumberArray(val)]);
}
if (isTypedArray(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [processNumberArray(val)]
};
}
if (isStringReconstructable(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [literal(String(val))]
};
}
if (isTemporal(val)) {
return methodCall({
type: 'MemberExpression',
computed: false,
optional: false,
object: identifier('Temporal'),
property: identifier(val.constructor.name)
}, 'from', [literal(String(val))]);
}
if (Array.isArray(val)) {
const elements = Array.from({ length: val.length });
let trimmable;
for (let index = 0; index < val.length; index += 1) {
if (!(index in val)) {
elements[index] = null;
trimmable = undefined;
continue;
}
const child = val[index];
const childContext = collectedContexts.get(child);
if (context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
elements[index] = null;
trimmable ||= index;
childContext.assignment = {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
computed: true,
optional: false,
object: identifier(context.name),
property: literal(index)
},
right: childContext.assignment || identifier(childContext.name)
};
}
else {
elements[index] = generate(child);
trimmable = undefined;
}
}
if (trimmable != null) {
elements.splice(trimmable);
}
return {
type: 'ArrayExpression',
elements
};
}
if (val instanceof Set) {
const elements = [];
let finalizer;
for (const child of val) {
if (finalizer) {
finalizer = methodCall(finalizer, 'add', [generate(child)]);
}
else {
const childContext = collectedContexts.get(child);
if (context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
finalizer = methodCall(identifier(context.name), 'add', [generate(child)]);
}
else {
elements.push(generate(child));
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment);
}
return {
type: 'NewExpression',
callee: identifier('Set'),
arguments: elements.length ? [{ type: 'ArrayExpression', elements }] : []
};
}
if (val instanceof Map) {
const elements = [];
let finalizer;
for (const [key, item] of val) {
if (finalizer) {
finalizer = methodCall(finalizer, 'set', [generate(key), generate(item)]);
}
else {
const keyContext = collectedContexts.get(key);
const itemContext = collectedContexts.get(item);
if (context &&
((keyContext && namedContexts.indexOf(keyContext) >= namedContexts.indexOf(context)) ||
(itemContext && namedContexts.indexOf(itemContext) >= namedContexts.indexOf(context)))) {
finalizer = methodCall(identifier(context.name), 'set', [
generate(key),
generate(item)
]);
}
else {
elements.push({
type: 'ArrayExpression',
elements: [generate(key), generate(item)]
});
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment);
}
return {
type: 'NewExpression',
callee: identifier('Map'),
arguments: elements.length ? [{ type: 'ArrayExpression', elements }] : []
};
}
const properties = [];
if (Object.getPrototypeOf(val) == null) {
properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: identifier('__proto__'),
value: literal(null)
});
}
const object = val;
const propertyDescriptors = [];
for (const key of Reflect.ownKeys(val)) {
// TODO [>=4] Throw an error for getters.
const child = object[key];
const { configurable, enumerable, writable } = Object.getOwnPropertyDescriptor(val, key);
const childContext = collectedContexts.get(child);
if (!configurable || !enumerable || !writable) {
const propertyDescriptor = [property('value', generate(child))];
if (configurable) {
propertyDescriptor.push(property('configurable', literal(true)));
}
if (enumerable) {
propertyDescriptor.push(property('enumerable', literal(true)));
}
if (writable) {
propertyDescriptor.push(property('writable', literal(true)));
}
propertyDescriptors.push([
key,
{ type: 'ObjectExpression', properties: propertyDescriptor }
]);
}
else if (context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
if (key === '__proto__') {
propertyDescriptors.push([
key,
{
type: 'ObjectExpression',
properties: [
property('value', generate(child)),
property('configurable', literal(true)),
property('enumerable', literal(true)),
property('writable', literal(true))
]
}
]);
}
else {
childContext.assignment = {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
computed: true,
optional: false,
object: identifier(context.name),
property: generate(key)
},
right: childContext.assignment || generate(child)
};
}
}
else {
properties.push(property(key, generate(child)));
}
}
const objectExpression = {
type: 'ObjectExpression',
properties
};
if (propertyDescriptors.length) {
let name;
let args;
if (propertyDescriptors.length === 1) {
const [[key, expression]] = propertyDescriptors;
name = 'defineProperty';
args = [typeof key === 'string' ? literal(key) : symbolToEstree(key), expression];
}
else {
name = 'defineProperties';
args = [
{
type: 'ObjectExpression',
properties: propertyDescriptors.map(([key, expression]) => property(key, expression))
}
];
}
if (!context) {
return methodCall(identifier('Object'), name, [objectExpression, ...args]);
}
context.assignment = replaceAssignment(methodCall(identifier('Object'), name, [identifier(context.name), ...args]), context.assignment);
}
return objectExpression;
}
analyze(value);
for (const [val, context] of collectedContexts) {
if (context.recursive || context.count > 1) {
// Assign reused or recursive references to a variable.
context.name = `$${namedContexts.length}`;
namedContexts.push(context);
}
else {
// Otherwise dont treat it as a reference.
collectedContexts.delete(val);
}
}
if (!namedContexts.length) {
return generate(value);
}
const params = namedContexts.sort(compareContexts).map((context) => ({
type: 'AssignmentPattern',
left: identifier(context.name),
right: generate(context.value, true)
}));
const rootContext = collectedContexts.get(value);
const finalizers = [];
for (const context of collectedContexts.values()) {
if (context !== rootContext && context.assignment) {
finalizers.push(context.assignment);
}
}
finalizers.push(rootContext ? rootContext.assignment || identifier(rootContext.name) : generate(value));
return {
type: 'CallExpression',
optional: false,
arguments: [],
callee: {
type: 'ArrowFunctionExpression',
expression: false,
params,
body: {
type: 'SequenceExpression',
expressions: finalizers
}
}
};
}
//# sourceMappingURL=estree-util-value-to-estree.js.map

File diff suppressed because one or more lines are too long

49
node_modules/estree-util-value-to-estree/package.json generated vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "estree-util-value-to-estree",
"description": "Convert a JavaScript value to an estree expression",
"version": "3.5.0",
"main": "./dist/estree-util-value-to-estree.js",
"exports": "./dist/estree-util-value-to-estree.js",
"type": "module",
"files": [
"dist",
"src",
"!test*"
],
"author": "Remco Haszing <remcohaszing@gmail.com>",
"license": "MIT",
"repository": "remcohaszing/estree-util-value-to-estree",
"sideEffects": false,
"bugs": "https://github.com/remcohaszing/estree-util-value-to-estree/issues",
"homepage": "https://github.com/remcohaszing/estree-util-value-to-estree#readme",
"funding": "https://github.com/sponsors/remcohaszing",
"keywords": [
"esast",
"estree",
"estree-util",
"language",
"unist"
],
"scripts": {
"prepack": "tsc --build",
"pretest": "tsc --build",
"test": "c8 node --enable-source-maps dist/test.js"
},
"dependencies": {
"@types/estree": "^1.0.0"
},
"devDependencies": {
"@petamoriken/float16": "^3.0.0",
"@remcohaszing/eslint": "^12.0.0",
"@types/node": "^24.0.0",
"astring": "^1.0.0",
"c8": "^9.0.0",
"prettier": "^3.0.0",
"remark-cli": "^12.0.0",
"remark-preset-remcohaszing": "^3.0.0",
"snapshot-fixtures": "^1.0.0",
"source-map": "^0.7.0",
"temporal-polyfill": "^0.3.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,852 @@
import type {
ArrayExpression,
Expression,
Identifier,
ObjectExpression,
Pattern,
Property,
SimpleLiteral
} from 'estree'
/**
* Create an ESTree identifier node for a given name.
*
* @param name
* The name of the identifier.
* @returns
* The identifier node.
*/
function identifier(name: string): Identifier {
return { type: 'Identifier', name }
}
/**
* Create an ESTree literal node for a given value.
*
* @param value
* The value for which to create a literal.
* @returns
* The literal node.
*/
function literal(value: SimpleLiteral['value']): SimpleLiteral {
return { type: 'Literal', value }
}
/**
* Create an ESTree call expression on an object member.
*
* @param object
* The object to call the method on.
* @param name
* The name of the method to call.
* @param args
* Arguments to pass to the function call
* @returns
* The call expression node.
*/
function methodCall(object: Expression, name: string, args: Expression[]): Expression {
return {
type: 'CallExpression',
optional: false,
callee: {
type: 'MemberExpression',
computed: false,
optional: false,
object,
property: identifier(name)
},
arguments: args
}
}
/**
* Turn a number or bigint into an ESTree expression. This handles positive and negative numbers and
* bigints as well as special numbers.
*
* @param number
* The value to turn into an ESTree expression.
* @returns
* An expression that represents the given value.
*/
function processNumber(number: bigint | number): Expression {
if (number < 0 || Object.is(number, -0)) {
return {
type: 'UnaryExpression',
operator: '-',
prefix: true,
argument: processNumber(-number)
}
}
if (typeof number === 'bigint') {
return { type: 'Literal', bigint: String(number) }
}
if (number === Number.POSITIVE_INFINITY || Number.isNaN(number)) {
return identifier(String(number))
}
return literal(number)
}
/**
* Process an array of numbers. This is a shortcut for iterables whose constructor takes an array of
* numbers as input.
*
* @param numbers
* The numbers to add to the array expression.
* @returns
* An ESTree array expression whose elements match the input numbers.
*/
function processNumberArray(numbers: Iterable<bigint | number>): Expression {
return { type: 'ArrayExpression', elements: Array.from(numbers, processNumber) }
}
/**
* Check whether a value can be constructed from its string representation.
*
* @param value
* The value to check
* @returns
* Whether or not the value can be constructed from its string representation.
*/
function isStringReconstructable(value: unknown): value is URL | URLSearchParams {
return value instanceof URL || value instanceof URLSearchParams
}
/**
* Check whether a value can be constructed from its `valueOf()` result.
*
* @param value
* The value to check
* @returns
* Whether or not the value can be constructed from its `valueOf()` result.
*/
function isValueReconstructable(value: unknown): value is Boolean | Date | Number | String {
return (
value instanceof Boolean ||
value instanceof Date ||
value instanceof Number ||
value instanceof String
)
}
const wellKnownSymbols = new Map<symbol, string>()
for (const name of Reflect.ownKeys(Symbol) as (keyof typeof Symbol)[]) {
const value = Symbol[name]
if (typeof value === 'symbol') {
wellKnownSymbols.set(value, name)
}
}
/**
* Check whether a value is a Temporal value.
*
* @param value
* The value to check
* @returns
* Whether or not the value is a Temporal value.
*/
function isTemporal(
value: unknown
): value is
| Temporal.Duration
| Temporal.Instant
| Temporal.PlainDate
| Temporal.PlainDateTime
| Temporal.PlainMonthDay
| Temporal.PlainTime
| Temporal.PlainYearMonth
| Temporal.ZonedDateTime {
return (
typeof Temporal !== 'undefined' &&
(value instanceof Temporal.Duration ||
value instanceof Temporal.Instant ||
value instanceof Temporal.PlainDate ||
value instanceof Temporal.PlainDateTime ||
value instanceof Temporal.PlainYearMonth ||
value instanceof Temporal.PlainMonthDay ||
value instanceof Temporal.PlainTime ||
value instanceof Temporal.ZonedDateTime)
)
}
/**
* Check whether a value is a typed array.
*
* @param value
* The value to check
* @returns
* Whether or not the value is a typed array.
*/
function isTypedArray(
value: unknown
): value is
| BigInt64Array
| BigUint64Array
| Float16Array
| Float32Array
| Float64Array
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint8ClampedArray
| Uint16Array
| Uint32Array {
return (
value instanceof BigInt64Array ||
value instanceof BigUint64Array ||
(typeof Float16Array !== 'undefined' && value instanceof Float16Array) ||
value instanceof Float32Array ||
value instanceof Float64Array ||
value instanceof Int8Array ||
value instanceof Int16Array ||
value instanceof Int32Array ||
value instanceof Uint8Array ||
value instanceof Uint8ClampedArray ||
value instanceof Uint16Array ||
value instanceof Uint32Array
)
}
interface Context {
/**
* The assignment expression of the variable.
*/
assignment?: Expression
/**
* The number of references to this value.
*/
count: number
/**
* The variable name used to reference the value.
*/
name?: string
/**
* Whether or not this value recursively references itself.
*/
recursive: boolean
/**
* A set of values that reference the value in this context.
*/
referencedBy: Set<unknown>
/**
* The value this context belongs to.
*/
value: unknown
}
/**
* Compare two value contexts for sorting them based on reference count.
*
* @param a
* The first context to compare.
* @param b
* The second context to compare.
* @returns
* The count of context a minus the count of context b.
*/
function compareContexts(a: Context, b: Context): number {
const aReferencedByB = a.referencedBy.has(b.value)
const bReferencedByA = b.referencedBy.has(a.value)
if (aReferencedByB) {
if (bReferencedByA) {
return a.count - b.count
}
return -1
}
if (bReferencedByA) {
return 1
}
return a.count - b.count
}
export interface Options {
/**
* If true, treat objects that have a prototype as plain objects.
*
* @default false
*/
instanceAsObject?: boolean | undefined
/**
* If true, preserve references to the same object found within the input. This also allows to
* serialize recursive structures. If needed, the resulting expression will be an iife.
*
* @default false
*/
preserveReferences?: boolean | undefined
/**
* A function to customize the serialization of a value.
*
* @param value
* The value to serialize.
* @returns
* The value serialized to an ESTree expression. If nothing is returned, the value is processed
* by the builtin logic.
*/
replacer?: ((value: unknown) => Expression | undefined | void) | undefined
}
/**
* Replace the assigned right hand expression with the new expression.
*
* If there is no assignment expression, the original expression is returned. Otherwise the
* assignment is modified and returned.
*
* @param expression
* The expression to use for the assignment.
* @param assignment
* The existing assignmentexpression
* @returns
* The new expression.
*/
function replaceAssignment(expression: Expression, assignment: Expression | undefined): Expression {
if (!assignment || assignment.type !== 'AssignmentExpression') {
return expression
}
let node = assignment
while (node.right.type === 'AssignmentExpression') {
node = node.right
}
node.right = expression
return assignment
}
/**
* Create an ESTree epxression to represent a symbol. Global and well-known symbols are supported.
*
* @param symbol
* The symbol to represent.
* @returns
* An ESTree expression to represent the symbol.
*/
function symbolToEstree(symbol: symbol): Expression {
const name = wellKnownSymbols.get(symbol)
if (name) {
return {
type: 'MemberExpression',
computed: false,
optional: false,
object: identifier('Symbol'),
property: identifier(name)
}
}
if (symbol.description && symbol === Symbol.for(symbol.description)) {
return methodCall(identifier('Symbol'), 'for', [literal(symbol.description)])
}
throw new TypeError(`Only global symbols are supported, got: ${String(symbol)}`, {
cause: symbol
})
}
/**
* Create an ESTree property from a key and a value expression.
*
* @param key
* The property key value
* @param value
* The property value as an ESTree expression.
* @returns
* The ESTree properry node.
*/
function property(key: string | symbol, value: Expression): Property {
const isString = typeof key === 'string'
return {
type: 'Property',
method: false,
shorthand: false,
computed: key === '__proto__' || !isString,
kind: 'init',
key: isString ? literal(key) : symbolToEstree(key),
value
}
}
/**
* Convert a value to an ESTree node.
*
* @param value
* The value to convert.
* @param options
* Additional options to configure the output.
* @returns
* The ESTree node.
*/
export function valueToEstree(value: unknown, options: Options = {}): Expression {
const stack: unknown[] = []
const collectedContexts = new Map<unknown, Context>()
const namedContexts: Context[] = []
const customTrees = new Map<unknown, Expression>()
/**
* Analyze a value and collect all reference contexts.
*
* @param val
* The value to analyze.
*/
function analyze(val: unknown): undefined {
if (typeof val !== 'object' && typeof val !== 'function') {
return
}
if (val == null) {
return
}
const context = collectedContexts.get(val)
if (context) {
if (options.preserveReferences) {
context.count += 1
}
for (const ancestor of stack) {
context.referencedBy.add(ancestor)
}
if (stack.includes(val)) {
if (!options.preserveReferences) {
throw new Error(`Found circular reference: ${val}`, { cause: val })
}
const parent = stack.at(-1)!
const parentContext = collectedContexts.get(parent)!
parentContext.recursive = true
context.recursive = true
}
return
}
collectedContexts.set(val, {
count: 1,
recursive: false,
referencedBy: new Set(stack),
value: val
})
const estree = options?.replacer?.(val)
if (estree) {
customTrees.set(val, estree)
return
}
if (typeof val === 'function') {
throw new TypeError(`Unsupported value: ${val}`, { cause: val })
}
if (isTypedArray(val)) {
return
}
if (isStringReconstructable(val)) {
return
}
if (isValueReconstructable(val)) {
return
}
if (value instanceof RegExp) {
return
}
if (isTemporal(value)) {
return
}
stack.push(val)
if (val instanceof Map) {
for (const pair of val) {
analyze(pair[0])
analyze(pair[1])
}
} else if (Array.isArray(val) || val instanceof Set) {
for (const entry of val) {
analyze(entry)
}
} else {
const proto = Object.getPrototypeOf(val)
if (proto != null && proto !== Object.prototype && !options.instanceAsObject) {
throw new TypeError(`Unsupported value: ${val}`, { cause: val })
}
for (const key of Reflect.ownKeys(val)) {
analyze((val as Record<string | symbol, unknown>)[key])
}
}
stack.pop()
}
/**
* Recursively generate the ESTree expression needed to reconstruct the value.
*
* @param val
* The value to process.
* @param isDeclaration
* Whether or not this is for a variable declaration.
* @returns
* The ESTree expression to reconstruct the value.
*/
function generate(val: unknown, isDeclaration?: boolean): Expression {
if (val === undefined) {
return identifier(String(val))
}
if (val == null || typeof val === 'string' || typeof val === 'boolean') {
return literal(val)
}
if (typeof val === 'bigint' || typeof val === 'number') {
return processNumber(val)
}
if (typeof val === 'symbol') {
return symbolToEstree(val)
}
const context = collectedContexts.get(val)
if (!isDeclaration && context?.name) {
return identifier(context.name)
}
const tree = customTrees.get(val)
if (tree) {
return tree
}
if (isValueReconstructable(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [generate(val.valueOf())]
}
}
if (val instanceof RegExp) {
return {
type: 'Literal',
regex: { pattern: val.source, flags: val.flags }
}
}
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(val)) {
return methodCall(identifier('Buffer'), 'from', [processNumberArray(val)])
}
if (isTypedArray(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [processNumberArray(val)]
}
}
if (isStringReconstructable(val)) {
return {
type: 'NewExpression',
callee: identifier(val.constructor.name),
arguments: [literal(String(val))]
}
}
if (isTemporal(val)) {
return methodCall(
{
type: 'MemberExpression',
computed: false,
optional: false,
object: identifier('Temporal'),
property: identifier(val.constructor.name)
},
'from',
[literal(String(val))]
)
}
if (Array.isArray(val)) {
const elements: (Expression | null)[] = Array.from({ length: val.length })
let trimmable: number | undefined
for (let index = 0; index < val.length; index += 1) {
if (!(index in val)) {
elements[index] = null
trimmable = undefined
continue
}
const child = val[index]
const childContext = collectedContexts.get(child)
if (
context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)
) {
elements[index] = null
trimmable ||= index
childContext.assignment = {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
computed: true,
optional: false,
object: identifier(context.name!),
property: literal(index)
},
right: childContext.assignment || identifier(childContext.name!)
}
} else {
elements[index] = generate(child)
trimmable = undefined
}
}
if (trimmable != null) {
elements.splice(trimmable)
}
return {
type: 'ArrayExpression',
elements
}
}
if (val instanceof Set) {
const elements: Expression[] = []
let finalizer: Expression | undefined
for (const child of val) {
if (finalizer) {
finalizer = methodCall(finalizer, 'add', [generate(child)])
} else {
const childContext = collectedContexts.get(child)
if (
context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)
) {
finalizer = methodCall(identifier(context.name!), 'add', [generate(child)])
} else {
elements.push(generate(child))
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment)
}
return {
type: 'NewExpression',
callee: identifier('Set'),
arguments: elements.length ? [{ type: 'ArrayExpression', elements }] : []
}
}
if (val instanceof Map) {
const elements: ArrayExpression[] = []
let finalizer: Expression | undefined
for (const [key, item] of val) {
if (finalizer) {
finalizer = methodCall(finalizer, 'set', [generate(key), generate(item)])
} else {
const keyContext = collectedContexts.get(key)
const itemContext = collectedContexts.get(item)
if (
context &&
((keyContext && namedContexts.indexOf(keyContext) >= namedContexts.indexOf(context)) ||
(itemContext && namedContexts.indexOf(itemContext) >= namedContexts.indexOf(context)))
) {
finalizer = methodCall(identifier(context.name!), 'set', [
generate(key),
generate(item)
])
} else {
elements.push({
type: 'ArrayExpression',
elements: [generate(key), generate(item)]
})
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment)
}
return {
type: 'NewExpression',
callee: identifier('Map'),
arguments: elements.length ? [{ type: 'ArrayExpression', elements }] : []
}
}
const properties: Property[] = []
if (Object.getPrototypeOf(val) == null) {
properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: identifier('__proto__'),
value: literal(null)
})
}
const object = val as Record<string | symbol, unknown>
const propertyDescriptors: [string | symbol, ObjectExpression][] = []
for (const key of Reflect.ownKeys(val)) {
// TODO [>=4] Throw an error for getters.
const child = object[key]
const { configurable, enumerable, writable } = Object.getOwnPropertyDescriptor(val, key)!
const childContext = collectedContexts.get(child)
if (!configurable || !enumerable || !writable) {
const propertyDescriptor = [property('value', generate(child))]
if (configurable) {
propertyDescriptor.push(property('configurable', literal(true)))
}
if (enumerable) {
propertyDescriptor.push(property('enumerable', literal(true)))
}
if (writable) {
propertyDescriptor.push(property('writable', literal(true)))
}
propertyDescriptors.push([
key,
{ type: 'ObjectExpression', properties: propertyDescriptor }
])
} else if (
context &&
childContext &&
namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)
) {
if (key === '__proto__') {
propertyDescriptors.push([
key,
{
type: 'ObjectExpression',
properties: [
property('value', generate(child)),
property('configurable', literal(true)),
property('enumerable', literal(true)),
property('writable', literal(true))
]
}
])
} else {
childContext.assignment = {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
computed: true,
optional: false,
object: identifier(context.name!),
property: generate(key)
},
right: childContext.assignment || generate(child)
}
}
} else {
properties.push(property(key, generate(child)))
}
}
const objectExpression: ObjectExpression = {
type: 'ObjectExpression',
properties
}
if (propertyDescriptors.length) {
let name: string
let args: Expression[]
if (propertyDescriptors.length === 1) {
const [[key, expression]] = propertyDescriptors
name = 'defineProperty'
args = [typeof key === 'string' ? literal(key) : symbolToEstree(key), expression]
} else {
name = 'defineProperties'
args = [
{
type: 'ObjectExpression',
properties: propertyDescriptors.map(([key, expression]) => property(key, expression))
}
]
}
if (!context) {
return methodCall(identifier('Object'), name, [objectExpression, ...args])
}
context.assignment = replaceAssignment(
methodCall(identifier('Object'), name, [identifier(context.name!), ...args]),
context.assignment
)
}
return objectExpression
}
analyze(value)
for (const [val, context] of collectedContexts) {
if (context.recursive || context.count > 1) {
// Assign reused or recursive references to a variable.
context.name = `$${namedContexts.length}`
namedContexts.push(context)
} else {
// Otherwise dont treat it as a reference.
collectedContexts.delete(val)
}
}
if (!namedContexts.length) {
return generate(value)
}
const params = namedContexts.sort(compareContexts).map<Pattern>((context) => ({
type: 'AssignmentPattern',
left: identifier(context.name!),
right: generate(context.value, true)
}))
const rootContext = collectedContexts.get(value)
const finalizers: Expression[] = []
for (const context of collectedContexts.values()) {
if (context !== rootContext && context.assignment) {
finalizers.push(context.assignment)
}
}
finalizers.push(
rootContext ? rootContext.assignment || identifier(rootContext.name!) : generate(value)
)
return {
type: 'CallExpression',
optional: false,
arguments: [],
callee: {
type: 'ArrowFunctionExpression',
expression: false,
params,
body: {
type: 'SequenceExpression',
expressions: finalizers
}
}
}
}