init
This commit is contained in:
18
node_modules/estree-util-value-to-estree/LICENSE.md
generated
vendored
Normal file
18
node_modules/estree-util-value-to-estree/LICENSE.md
generated
vendored
Normal 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
228
node_modules/estree-util-value-to-estree/README.md
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
# estree-util-value-to-estree
|
||||
|
||||
[](https://github.com/remcohaszing/estree-util-value-to-estree/actions/workflows/ci.yaml)
|
||||
[](https://codecov.io/gh/remcohaszing/estree-util-value-to-estree)
|
||||
[](https://www.npmjs.com/package/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)
|
||||
38
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.d.ts
generated
vendored
Normal file
38
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.d.ts
generated
vendored
Normal 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
|
||||
1
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.d.ts.map
generated
vendored
Normal file
1
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.d.ts.map
generated
vendored
Normal 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"}
|
||||
672
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js
generated
vendored
Normal file
672
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js
generated
vendored
Normal 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 don’t 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
|
||||
1
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js.map
generated
vendored
Normal file
1
node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js.map
generated
vendored
Normal file
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
49
node_modules/estree-util-value-to-estree/package.json
generated
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
852
node_modules/estree-util-value-to-estree/src/estree-util-value-to-estree.ts
generated
vendored
Normal file
852
node_modules/estree-util-value-to-estree/src/estree-util-value-to-estree.ts
generated
vendored
Normal 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 don’t 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user