Skip to content

Latest commit

 

History

History
295 lines (202 loc) · 7.75 KB

.verb.md

File metadata and controls

295 lines (202 loc) · 7.75 KB

Usage

Requires Node.js version 14 or greater.

Evaluates an estree expression from [@babel/parser][], [esprima][], [acorn][], or any other library that parses and returns a valid estree expression.

const { evaluate } = require('{%= name %}');
// or
import { evaluate } from '{%= name %}';

evaluate.sync(expressionTree[, context]); // sync
evaluate(expressionTree[, context]).then(console.log); // async

See the unit tests for hundreds of additional usage examples.

Params

The evaluate function takes the following arguments:

  • expressionTree {object} - a valid 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:

const { evaluate } = require('{%= name %}');
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:

const { parse } = require('esprima');
const { evaluate } = require('{%= name %}');
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.

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.

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:

const { parseExpression } = require('@babel/parser');
const { variables } = require('{%= name %}');

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.

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

const { parse } = require('esprima');
const { generate } = require('escodegen');
const { evaluate } = require('{%= name %}');

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 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.
const { evaluate } = require('{%= name %}');
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 option AND by passing the .generate() function from the [escodegen][] library.

Example

const escodegen = require('escodegen');
const { parse } = require('esprima');
const { evaluate } = require('{%= name %}');

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:

console.log(evaluate.sync(parse('/^ab+c$/ig.test("abbbbbc")'), {}, { functions: true }));

You can do this:

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 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):

// 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.