Gathering detailed insights and metrics for @zvenigora/jse-eval
Gathering detailed insights and metrics for @zvenigora/jse-eval
Gathering detailed insights and metrics for @zvenigora/jse-eval
Gathering detailed insights and metrics for @zvenigora/jse-eval
npm install @zvenigora/jse-eval
Typescript
Module System
Node Version
NPM Version
JavaScript (59.35%)
TypeScript (40.65%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
280 Commits
1 Forks
2 Branches
1 Contributors
Updated on Jan 02, 2024
Latest Version
1.10.0
Package Id
@zvenigora/jse-eval@1.10.0
Unpacked Size
294.53 kB
Size
73.32 kB
File Count
15
NPM Version
7.24.2
Node Version
16.20.2
Published on
Dec 31, 2023
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
24
Heavily based on jse-eval, expression-eval and jsep, with thanks to their awesome work.
Forked from jse-eval v1.5.1. Many thanks to @Shelly for the initial package
jse-eval was forked from expression-eval v5.0.0. Many thanks to @donmccurdy for the initial package
JavaScript expression parsing and evaluation.
IMPORTANT: As mentioned under Security below, this library does not attempt to provide a secure sandbox for evaluation. Evaluation involving user inputs (expressions or values) may lead to unsafe behavior. If your project requires a secure sandbox, consider alternatives such as vm2.
Evaluates an estree expression from jsep
(as well as @babel/parser, esprima,
acorn, or any other library that parses and returns a valid estree
expression).
Install:
npm install --save jse-eval
Import:
1// ES6 2import { parse, evaluate, compile, jsep } from 'jse-eval'; 3 4// CommonJS 5const { parse, evaluate, compile, jsep } = require('jse-eval'); 6 7// UMD / standalone script 8const { parse, evaluate, compile, jsep } = window.jseEval;
1import { parse } from 'jse-eval'; 2const ast = parse('1 + foo');
The result of the parse is an AST (abstract syntax tree), like:
1{ 2 "type": "BinaryExpression", 3 "operator": "+", 4 "left": { 5 "type": "Literal", 6 "value": 1, 7 "raw": "1" 8 }, 9 "right": { 10 "type": "Identifier", 11 "name": "foo" 12 } 13}
Evaluation executes the AST using the given context (eval(ast, context)
. By default, the context is empty.
1import { parse, evaluate } from 'jse-eval'; 2const ast = parse('a + b / c'); // abstract syntax tree (AST) 3const value = eval(ast, {a: 2, b: 2, c: 5}); // 2.4 4 5// alternatively: 6const value = await evalAsync(ast, {a: 2, b: 2, c: 5}); // 2.4
Since the default context is empty, it prevents using built-in JS functions.
To allow those functions, they can be added to the context
argument passed into the eval
method:
1const context = { 2 Date, 3 Array, 4 Object, 5 encodeURI, 6 decodeURI, 7 isFinite, 8 isNaN, 9 JSON, 10 Math, 11 parseFloat, 12 parseInt, 13 RegExp, 14 // ...myCustomPropertiesAndFunctions, 15};
1import { compile } from 'jse-eval'; 2const fn = compile('foo.bar + 10'); 3fn({foo: {bar: 'baz'}}); // 'baz10' 4 5// alternatively: 6import { compileAsync } from 'jse-eval'; 7const fn = compileAsync('foo.bar + 10'); 8fn({foo: {bar: 'baz'}}); // 'baz10'
1import { evalExpr } from 'jse-eval'; 2evalExpr('foo.bar + 10', {foo: {bar: 'baz'}}); // baz10 3 4// alternatively: 5import { evalExprAsync } from 'jse-eval'; 6evalExprAsync('foo.bar + 10', {foo: {bar: 'baz'}}); // baz10
1import { registerPlugin } from 'jse-eval'; 2registerPlugin( 3 require('@jsep-plugin/arrow'), 4 require('@jsep-plugin/assignment'), 5 require('@jsep-plugin/async-await'), 6 require('@jsep-plugin/new'), 7 require('@jsep-plugin/object'), 8 require('@jsep-plugin/regex'), 9 require('@jsep-plugin/spread'), 10 require('@jsep-plugin/template'), 11 require('@jsep-plugin/ternary') 12); 13 14// or alternatively: 15const { jsep } = require('jse-eval'); 16jsep.plugins.register( 17 require('@jsep-plugin/arrow'), 18 require('@jsep-plugin/assignment'), 19 // ... 20);
To modify the evaluation, use any of the modification methods:
addUnaryOp(operator, evaluator)
. Will add the operator to jsep, and the function to evaluate the operatoraddBinaryOp(operator, precedence | evaluator, evaluator)
. Will add the operator to jsep at the given
precedence (if provided), and the function to evaluate the operatoraddEvaluator(nodeType, evaluator)
. Will add the evaluator function to the map of functions
for each node type. This evaluator will be called with the ExpressionEval instance bound to it.
The evaluator is responsible for handling both sync and async, as needed, but can use the this.isAsync
or this.evalSyncAsync()
to help.
CallExpression
and ArrowFunctionExpression
to throw an errordefault
node type handler before
throwing an error for an unknown node type. If any other behavior is desired, this can be overridden
by providing a new default
evaluator.addConditionalEvaluator(nodeType, predicate, evaluator)
. Will evaluate predicate function and add the evaluator function to the map of functions for each node type.Extensions may also be added as plugins using the registerPlugin(myPlugin1, myPlugin2...)
method.
The plugins are extensions of the JSEP format. If the init
method is defined in the plugin,
then the plugin will be added to JSEP, and/or if the initEval
method is defined in the plugin,
then the initEval
method will be called with the JseEval class as both this
and as an argument
so the plugin code may extend as necessary.
1import * as expr from 'jse-eval'; 2 3expr.addBinaryOp('**', 11, true, (a, b) => a ** b); 4console.log(expr.evalExpr('2 ** 3 ** 2')); // 512 5 6expr.addBinaryOp('^', 11, (a, b) => Math.pow(a, b)); // Replace XOR with Exponent 7console.log(expr.evalExpr('3^2')); // 9 8 9expr.addEvaluator('TestNodeType', function(node, context) { 10 return node.test + this.context.string 11}); 12console.log(expr.eval({ type: 'TestNodeType', test: 'testing ' }, { string: 'jse-eval' })); // 'testing jse-eval' 13 14// override default implementation: 15expr.addEvaluator('Identifier', function myIdentifier(node: Identifier, context: Context) { 16 return context?.[node.name]; 17}); 18console.log(expr.eval({ type: 'Identifier', name: 'x' }, { x: 'jse-eval' })); // 'jse-eval' 19 20 21const myPlugin = { 22 name: 'Exponentiation', 23 init(jsep) { 24 // if only adding to jsep. Otherwise it's redundant with initEval 25 jsep.addBinaryOp('**', 11, true); 26 }, 27 initEval(JseEval) { 28 JseEval.addBinaryOp('**', (a, b) => a ** b); 29 }, 30}; 31expr.registerPlugin(myPlugin); 32console.log(expr.evalExpr('2 ** 3 ** 2')); // 512
This project will try to stay current with all JSEP's node types::
ArrayExpression
LogicalExpression
/BinaryExpression
CallExpression
potentially unsafeConditionalExpression
Compound
Compound support will evaluate each expression and return the result of the final oneIdentifier
Literal
MemberExpression
ThisExpression
UnaryExpression
As well as the optional plugin node types:
ArrowFunctionExpression
potentially unsafeAssignmentExpression
/UpdateExpression
AwaitExpression
NewExpression
ObjectExpression
SpreadElement
TaggedTemplateExpression
/TemplateLiteral
To change the default behavior of the evaluator, use options
. Options may be provided as an argument to the function call of eval
or options may be added as default to JseEval
.
While JavaScript is a case-sensitive language, some may find it hard to use. To provide case-insensitive evaluation, set caseSensitive to false.
1import { parse, evaluate } from 'jse-eval'; 2 3const options = {caseSensitive: false}; 4const ast = parse('A + B / C'); 5 6// Pass options as argument 7const value = eval(ast, {a: 2, b: 2, c: 5}, options); // 2.4
1import { compile } from 'jse-eval'; 2 3// Add options to evaluator 4const options = {caseSensitive: false}; 5JseEval.addOptions(options); 6const fn = JseEval.compile('Foo.BAR + 10', options); 7const value = fn({foo: {bar: 'baz'}}); // 'baz10'
blockList
prevents the execution of functions or the evaluation of variables, except those explictly specified. For example, blocklist may restrict the calling of the non-secure JavaScript eval
function.
1import { parse, evaluate } from 'jse-eval'; 2 3const options = {blockList: ['badName', 'eval']}; 4const ast = parse('eval("1+2")'); 5 6const value = eval(ast, {}, options); // error: Access to member "eval" from blockList disallowed
allowList
explictly permits the execution of functions or the evaluation of variables. For example, allowlist may restrict the calling of the non-secure JavaScript eval
function.
1import { parse, evaluate } from 'jse-eval'; 2 3const options = {allowList: ['goodName', 'func']}; 4const ast = parse('eval("1+2")'); 5 6const value = eval(ast, {}, options); // error: Access to member "eval" not in allowList disallowed
To give the reference to this
of the context
or / and provide additional arguments, use functionBindings
. The feature utilises the JavaScript Function.prototype.bind()
method.
NOTE: Add "allowSyntheticDefaultImports": true to compilerOptions of tsconfig.json
1import { parse, evaluate } from 'jse-eval'; 2 3const context = { 4 num: 3, 5 name: 'Miss Kitty', 6 action: function(args, n, t) {return this.name + ' ' + args.join(' ') + ' ' + n + ' ' + t;}, 7 says: function() {return this.name + ' says meow';}, 8} 9 10const bindings = { 11 action: { thisRef: context, arguments: [['says', 'meow']] }, 12 says: { thisRef: context }, 13} 14 15const options = { functionBindings: {...bindings} }; 16 17const ast = parse('says()'); 18const value = eval(ast, context, options); // Miss Kitty says meow 19 20const ast2 = parse('action(num, "times")'); 21const value2 = eval(ast2, context, options); // Miss Kitty says meow 3 times 22
Function Bindings
may be extended with scopes
. Scopes faintly resemble namespaces and they allow to use the functions with the same name.
CurrentScopeName
and GlobalScopeName
allow to remove reference to object instance.
1import { parse, evaluate } from 'jse-eval'; 2 3const catObject = { 4 type: 'Cat', 5 name: 'Miss Kitty', 6 num: 3, 7 says: function() {return this.type + ' ' + this.name + ' says meow';}, 8 action: function(args, n, t) {return this.name + ' ' + args.join(' ') + ' ' + n + ' ' + t;}, 9} 10 11const catFunctionBindings = { 12 says: { thisRef: catObject }, 13 action: { thisRef: catObject, arguments: [['says', 'meow']] 14 }, 15} 16 17const dogObject = { 18 type: 'Dog', 19 name: 'Ralph', 20 num: 5, 21 says: function() {return this.type + ' ' + this.name + ' says woof';}, 22 action: function(args, n, t) {return this.name + ' ' + args.join(' ') + ' ' + n + ' ' + t;}, 23} 24 25const dogFunctionBindings = { 26 says: { thisRef: dogObject }, 27 action: { thisRef: dogObject, arguments: [['says', 'woof']] }, 28} 29 30const context = { 31 cat: catObject, 32 dog: dogObject 33} 34 35const options = { 36 scopes: { 37 cat: { options: {functionBindings: {...catFunctionBindings}} }, 38 dog: { options: {functionBindings: {...dogFunctionBindings}} } 39 }, 40 currentScopeName: 'cat' 41} 42 43const ast = parse('cat.says()'); 44const value = eval(ast, context, options); // Cat Miss Kitty says meow 45 46const ast2 = parse('dog.says()'); 47const value2 = eval(ast2, context, options); // Dog Ralph says woof 48 49const ast3 = parse('cat.action(cat.num, "times")'); 50const value3 = eval(ast3, context, options); // Miss Kitty says meow 3 times 51 52const ast4 = parse('dog.action(dog.num, "times")'); 53const value4 = eval(ast4, context, options); // Ralph says woof 5 times 54 55const ast5 = parse('says()'); // reference to 'cat' is omitted because of currentScopeName 56const value5 = eval(ast, context, options); // Cat Miss Kitty says meow 57 58
Depending on your specific use-case, there are other related packages available, including:
Although this package does avoid the use of eval()
, it cannot guarantee that user-provided expressions, or user-provided inputs to evaluation, will not modify the state or behavior of your application. This library does not attempt to provide a secure sandbox for evaluation. Evaluation of arbitrary user inputs (expressions or values) may lead to unsafe behavior. If your project requires a secure sandbox, consider alternatives such as vm2.
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on the guidelines for contributing and then feel free to submit a PR with your contribution.
Help us keep this project open and inclusive. Please read and follow the Code of Conduct.
MIT License.
No vulnerabilities found.
No security vulnerabilities found.