UNPKG

eval-estree-expression

Version:

Safely evaluate JavaScript (estree) expressions, sync and async.

392 lines (269 loc) 11.8 kB
# eval-estree-expression [![NPM version](https://img.shields.io/npm/v/eval-estree-expression.svg?style=flat)](https://www.npmjs.com/package/eval-estree-expression) [![NPM monthly downloads](https://img.shields.io/npm/dm/eval-estree-expression.svg?style=flat)](https://npmjs.org/package/eval-estree-expression) [![NPM total downloads](https://img.shields.io/npm/dt/eval-estree-expression.svg?style=flat)](https://npmjs.org/package/eval-estree-expression) > Safely evaluate JavaScript (estree) expressions, sync and async. Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support. - [Install](#install) - [Usage](#usage) * [Usage with Babel](#usage-with-babel) * [Usage with Esprima](#usage-with-esprima) - [API](#api) * [.evaluate](#evaluate) * [.evaluate.sync](#evaluatesync) * [.variables](#variables) - [Options](#options) * [booleanLogicalOperators](#booleanlogicaloperators) * [functions](#functions) * [Top-Level await](#top-level-await) * [generate](#generate) * [regexOperator](#regexoperator) * [strict](#strict) * [withMembers](#withmembers) - [Examples](#examples) * [Operators](#operators) - [About](#about) _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ ## Install Install with [npm](https://www.npmjs.com/): ```sh $ npm install --save eval-estree-expression ``` ## Usage Requires [Node.js](https://nodejs.org/en/download/) version 14 or greater. Evaluates an [estree](https://github.com/estree/estree) expression from [@babel/parser][], [esprima](http://esprima.org), [acorn](https://github.com/acornjs/acorn), or any other library that parses and returns a valid `estree` expression. ```js const { evaluate } = require('eval-estree-expression'); // or import { evaluate } from 'eval-estree-expression'; evaluate.sync(expressionTree[, context]); // sync evaluate(expressionTree[, context]).then(console.log); // async ``` See the [unit tests](./test) for hundreds of additional usage examples. **Params** The `evaluate` function takes the following arguments: * `expressionTree` **{object}** - a valid [estree](https://github.com/estree/estree) expression AST. * `context` **{object}** - a data object with values to replace variables in expressions ### Usage with Babel Most of the examples in this document assume the following setup code is used: ```js const { evaluate } = require('eval-estree-expression'); const { parseExpression } = require('@babel/parser'); // parse your JavaScript expression const ast = parseExpression('1 + 2'); // evaluate synchronously console.log(evaluate.sync(ast)); //=> 3 // or asynchronously console.log(await evaluate(ast)); //=> 3 ``` ### Usage with Esprima [Esprima][esprimar] doesn't have a "parseExpression" method like @babel/parser, so you'll need to return the expression from the AST, like so: ```js const { parse } = require('esprima'); const { evaluate } = require('eval-estree-expression'); const ast = parse('[1, 2, 3].map(n => n * x);').body[0].expression; // evaluate synchronously console.log(evaluate.sync(ast)); // => [2, 4, 6] // or asynchronously console.log(await evaluate(ast)); // => [2, 4, 6] ``` ## API ### .evaluate Evaluate expressions asynchronously. ```js console.log(await evaluate(parse('1 + 2'))); //=> 3 console.log(await evaluate(parse('5 * 2'))); //=> 10 console.log(await evaluate(parse('1 > 2'))); //=> false console.log(await evaluate(parse('1 < 2'))); //=> true // with context object console.log(await evaluate(parse('page.title === "home"'), { page: { title: 'home' } })); //=> true ``` ### .evaluate.sync Evaluate expressions synchronously. ```js console.log(evaluate.sync(parse('1 + 2'))); //=> 3 console.log(evaluate.sync(parse('5 * 2'))); //=> 10 console.log(evaluate.sync(parse('1 > 2'))); //=> false console.log(evaluate.sync(parse('1 < 2'))); //=> true // with context object console.log(evaluate.sync(parse('page.title === "home"'), { page: { title: 'home' } })); //=> true ``` ### .variables Get an array of variables from an expression: ```js const { parseExpression } = require('@babel/parser'); const { variables } = require('eval-estree-expression'); console.log(variables(parseExpression('x * (y * 3) + z.y.x'))); //=> ['x', 'y', 'z'] console.log(variables(parseExpression('(a || b) ? c + d : e * f'))); //=> ['a', 'b', 'c', 'd', 'e', 'f'] ``` ## Options ### booleanLogicalOperators Type: `boolean` Default: `undefined` Force logical operators to return a boolean result. ```js console.log(await evaluate(parse('a && b'), { a: undefined, b: true })); //=> undefined console.log(await evaluate(parse('a && b'), { a: undefined, b: false })); //=> undefined console.log(await evaluate(parse('a || b'), { a: false, b: null })); //=> null console.log(await evaluate(parse('a || b'), { a: false, b: undefined })); //=> undefined // // With booleanLogicalOperators enabled // const options = { booleanLogicalOperators: true }; console.log(await evaluate(parse('a || b'), { a: false, b: null }, options)); //=> false console.log(await evaluate(parse('a && b'), { a: undefined, b: true }, options)); //=> false console.log(await evaluate(parse('a && b'), { a: undefined, b: false }, options)); //=> false console.log(await evaluate(parse('a || b'), { a: false, b: undefined }, options)); //=> false ``` ### functions Type: `boolean` Default: `false` Allow function calls to be evaluated. This is unsafe, please enable this option at your own risk. **Example** ```js const { parse } = require('esprima'); const { generate } = require('escodegen'); const { evaluate } = require('eval-estree-expression'); const options = { functions: true }; // works with native methods console.log(evaluate.sync(parse('/([a-z]+)/.exec(" foo ")'), { x: 2 }, options)); //=> [ 'foo', 'foo', index: 1, input: ' foo ', groups: undefined ] // and functions defined on the context console.log(evaluate.sync('a.upper("b")', { a: { upper: v => v.toUpperCase() } }, options)); //=> 'B' ``` However, this does NOT support function expressions or function statements. To enable function statements and expressions (not just function calls) to be evaluated, you must also use the [generate](#generate) option. ### Top-Level await To use top-level `await` with, you need to: 1. Enable `functions` 2. Enable `allowAwaitOutsideFunction`. This option is passed directly to the babel parser. ```ts const { evaluate } = require('eval-estree-expression'); const { parseExpression } = require('@babel/parser'); const opts = { functions: true, allowAwaitOutsideFunction: true }; const e = (input, ctx, options) => { return evaluate(parseExpression(input, options), ctx, options); }; const run = async () => { console.log(await e('await 1', {}, opts)); //=> 1 console.log(await e('Promise.resolve(1)', { Promise }, opts)); //=> 1 console.log(await e('await Promise.resolve(1)', { Promise }, opts)); //=> 1 }; run(); ``` ### generate Type: `boolean` Default: `undefined` Enable support for function statements and expressions by enabling the [functions](#functions) option AND by passing the `.generate()` function from the [escodegen](http://github.com/estools/escodegen) library. **Example** ```js const escodegen = require('escodegen'); const { parse } = require('esprima'); const { evaluate } = require('eval-estree-expression'); const options = { functions: true, generate: escodegen.generate }; console.log(await evaluate(parse('[1, 2, 3].map(n => n * x);'), { x: 2 }, options)); // => [2, 4, 6] ``` ### regexOperator Type: `boolean` Default: `true` Enable the `=~` regex operator to support testing values without using functions (example `name =~ /^a.*c$/`). **Why is this needed?** In expressions, if you wish to test a value using a regular expression, you have two options: 1. Enable function support so that you can use methods like `.test()` and `.match()`, or 2. Use this option, which uses a special syntax to match against regular expressions _without evaluating any functions_. In other words, instead of having to do this: ```js console.log(evaluate.sync(parse('/^ab+c$/ig.test("abbbbbc")'), {}, { functions: true })); ``` You can do this: ```js console.log(evaluate.sync(parse('name =~ /^a.*c$/'), { name: 'abc' })); console.log(evaluate.sync(parse('name =~ regex'), { name: 'abc', regex: /^a.*c$/ })); ``` ### strict Type: `boolean` Default: `false` Throw an error when variables are undefined. ### withMembers Type: `boolean` Default: `undefined` Used with the [variables](#variables) method to return nested variables (e.g., variables with dot notation, like `foo.bar.baz`). ## Examples ### Operators Supports all JavaScript operators with the exception of assignment operators (`=`, `+=`, etc): ```js // Arithmetic operators evaluate('a + b'); evaluate('a - b'); evaluate('a / b'); evaluate('a * b'); evaluate('a % b'); evaluate('a ** b'); // Relational operators evaluate('a instanceof b'); evaluate('a < b'); evaluate('a > b'); evaluate('a <= b'); evaluate('a >= b'); // Equality operators evaluate('a !== b'); evaluate('a === b'); evaluate('a != b'); evaluate('a == b'); // Bitwise shift operators evaluate('a << b'); evaluate('a >> b'); evaluate('a >>> b'); // Binary bitwise operators evaluate('a & b'); evaluate('a | b'); evaluate('a ^ b'); // Binary logical operators evaluate('a && b'); // Logical AND. evaluate('a || b'); // Logical OR. evaluate('a ?? b'); // Nullish Coalescing Operator. ``` ## About <details> <summary><strong>Contributing</strong></summary> Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). </details> <details> <summary><strong>Running Tests</strong></summary> Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: ```sh $ npm install && npm test ``` </details> <details> <summary><strong>Building docs</strong></summary> _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ To generate the readme, run the following command: ```sh $ npm install -g verbose/verb#dev verb-generate-readme && verb ``` </details> ### Related projects You might also be interested in these projects: [whence](https://www.npmjs.com/package/whence): Add context awareness to your apps and frameworks by safely evaluating user-defined conditional expressions. Useful… [more](https://github.com/jonschlinkert/whence) | [homepage](https://github.com/jonschlinkert/whence "Add context awareness to your apps and frameworks by safely evaluating user-defined conditional expressions. Useful for evaluating expressions in config files, prompts, key bindings, completions, templates, and many other user cases.") ### Contributors | **Commits** | **Contributor** | | --- | --- | | 42 | [jonschlinkert](https://github.com/jonschlinkert) | | 1 | [6utt3rfly](https://github.com/6utt3rfly) | | 1 | [freshgum-bubbles](https://github.com/freshgum-bubbles) | ### Author **Jon Schlinkert** * [GitHub Profile](https://github.com/jonschlinkert) * [Twitter Profile](https://twitter.com/jonschlinkert) * [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) ### License Copyright © 2025, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT License](LICENSE). *** _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on February 06, 2025._