ecma-evaluator 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Badge](https://img.shields.io/badge/link-996.icu-%23FF4D5B.svg?style=flat-square)](https://996.icu/#/en_US)
4
4
  [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg?style=flat-square)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
5
- ![Node](https://img.shields.io/badge/node-%3E=14-blue.svg?style=flat-square)
5
+ ![Node](https://img.shields.io/badge/node-%3E=18-blue.svg?style=flat-square)
6
6
  [![npm version](https://badge.fury.io/js/ecma-evaluator.svg)](https://badge.fury.io/js/ecma-evaluator)
7
7
 
8
8
  A tiny, fast, and **secure** JavaScript expression evaluator for safely evaluating expressions and template strings in a sandboxed environment.
@@ -11,13 +11,9 @@ English | [简体中文](./README.zh-CN.md)
11
11
 
12
12
  ## Features
13
13
 
14
- - ✨ **Secure by design** - Sandboxed; blocks mutations/side effects
15
- - 🚀 **Fast & lightweight** - Minimal dependencies, uses the efficient `acorn` parser
16
- - 📦 **Zero configuration** - Works out of the box with sensible defaults
17
- - 🎯 **Rich feature set** - Most JS expressions/functions
18
- - 🔒 **No eval()** - Does not use `eval()` or `Function()` constructor
19
- - 💪 **TypeScript support** - Includes TypeScript type definitions
20
- - 📝 **Template strings** - Evaluate expressions within template strings using `{{ }}` syntax
14
+ - Sandboxed evaluation without `eval()` or `Function()`
15
+ - Supports JavaScript expressions and `{{ }}` templates
16
+ - TypeScript definitions included
21
17
 
22
18
  ## Installation
23
19
 
@@ -30,494 +26,14 @@ npm install ecma-evaluator
30
26
  ```js
31
27
  import { evalExpression, evalTemplate } from "ecma-evaluator";
32
28
 
33
- // Evaluate expression
34
- const result = evalExpression("a + b * c", { a: 1, b: 2, c: 3 });
35
- console.log(result); // Output: 7
36
-
37
- // Evaluate template
38
- const text = evalTemplate("Hello {{ name }}!", { name: "World" });
39
- console.log(text); // Output: "Hello World!"
40
- ```
41
-
42
- ## API Reference
43
-
44
- ### `evalExpression(expression, context?)`
45
-
46
- Evaluates a JavaScript expression with an optional context.
47
-
48
- **Parameters:**
49
-
50
- - `expression` (string): The JavaScript expression to evaluate
51
- - `context` (object, optional): An object containing variables to use in the expression
52
-
53
- **Returns:** The result of evaluating the expression
54
-
55
- **Example:**
56
-
57
- ```js
58
- import { evalExpression } from "ecma-evaluator";
59
-
60
- // Basic arithmetic
61
- evalExpression("2 + 3 * 4"); // 14
62
-
63
- // With variables
64
- evalExpression("x + y", { x: 10, y: 20 }); // 30
65
-
66
- // Using built-in functions
67
- evalExpression("Math.max(a, b, c)", { a: 5, b: 15, c: 10 }); // 15
68
-
69
- // String operations
70
- evalExpression("greeting + ', ' + name", {
71
- greeting: "Hello",
72
- name: "Alice",
73
- }); // "Hello, Alice"
74
-
75
- // Array methods
76
- evalExpression("[1, 2, 3].map(x => x * 2)"); // [2, 4, 6]
77
-
78
- // Conditional expressions
79
- evalExpression("score >= 60 ? 'Pass' : 'Fail'", { score: 75 }); // "Pass"
80
- ```
81
-
82
- ### `evalTemplate(template, context?, templateParserOptions?)`
83
-
84
- Evaluates a template string by replacing `{{ expression }}` patterns with their evaluated values.
85
-
86
- **Parameters:**
87
-
88
- - `template` (string): The template string to evaluate
89
- - `context` (object, optional): An object containing variables to use in the template
90
- - `templateParserOptions` (object, optional): Options for the template parser
91
-
92
- **Returns:** The evaluated template string
93
-
94
- **Example:**
95
-
96
- ```js
97
- import { evalTemplate } from "ecma-evaluator";
98
-
99
- // Basic variable replacement
100
- evalTemplate("Hello, {{ name }}!", { name: "World" });
101
- // Output: "Hello, World!"
102
-
103
- // Multiple expressions
104
- evalTemplate("{{ a }} + {{ b }} = {{ a + b }}", { a: 10, b: 20 });
105
- // Output: "10 + 20 = 30"
106
-
107
- // Complex expressions
108
- evalTemplate("The sum is {{ [1, 2, 3].reduce((a, b) => a + b, 0) }}");
109
- // Output: "The sum is 6"
110
-
111
- // Template literals within expressions
112
- evalTemplate("{{ `Hello ${name}, welcome!` }}", { name: "Alice" });
113
- // Output: "Hello Alice, welcome!"
114
-
115
- // Date formatting
116
- evalTemplate("Today is {{ new Date().toLocaleDateString() }}");
117
- // Output: "Today is 11/18/2025" (varies by locale)
118
-
119
- // Conditional rendering
120
- evalTemplate("Status: {{ isActive ? 'Active' : 'Inactive' }}", { isActive: true });
121
- // Output: "Status: Active"
122
-
123
- // Optional chaining
124
- evalTemplate("Value: {{ obj?.prop?.value ?? 'N/A' }}", { obj: null });
125
- // Output: "Value: N/A"
126
- ```
127
-
128
- ### Error Handling
129
-
130
- When an undefined variable is referenced in a template, it's replaced with `"undefined"` instead of throwing an error:
131
-
132
- ```js
133
- evalTemplate("Hello {{ name }}!", {}); // "Hello undefined!"
134
- ```
135
-
136
- For other errors (syntax errors, type errors, etc.), an exception will be thrown:
137
-
138
- ```js
139
- evalTemplate("{{ 1 + }}", {}); // Throws SyntaxError
140
- evalTemplate("{{ obj.prop }}", { obj: null }); // Throws TypeError
141
- ```
142
-
143
- ## Supported JavaScript Features
144
-
145
- ### Operators
146
-
147
- #### Arithmetic Operators
148
-
149
- ```js
150
- evalExpression("10 + 5"); // 15 (addition)
151
- evalExpression("10 - 5"); // 5 (subtraction)
152
- evalExpression("10 * 5"); // 50 (multiplication)
153
- evalExpression("10 / 5"); // 2 (division)
154
- evalExpression("10 % 3"); // 1 (modulo)
155
- evalExpression("2 ** 3"); // 8 (exponentiation)
156
- ```
157
-
158
- #### Comparison Operators
159
-
160
- ```js
161
- evalExpression("5 > 3"); // true
162
- evalExpression("5 >= 5"); // true
163
- evalExpression("5 < 3"); // false
164
- evalExpression("5 <= 5"); // true
165
- evalExpression("5 == '5'"); // true (loose equality)
166
- evalExpression("5 === '5'"); // false (strict equality)
167
- evalExpression("5 != '5'"); // false (loose inequality)
168
- evalExpression("5 !== '5'"); // true (strict inequality)
169
- ```
170
-
171
- #### Logical Operators
172
-
173
- ```js
174
- evalExpression("true && false"); // false (AND)
175
- evalExpression("true || false"); // true (OR)
176
- evalExpression("null ?? 'default'"); // "default" (nullish coalescing)
177
- evalExpression("!true"); // false (NOT)
178
- ```
179
-
180
- #### Bitwise Operators
181
-
182
- ```js
183
- evalExpression("5 & 3"); // 1 (AND)
184
- evalExpression("5 | 3"); // 7 (OR)
185
- evalExpression("5 ^ 3"); // 6 (XOR)
186
- evalExpression("~5"); // -6 (NOT)
187
- evalExpression("5 << 1"); // 10 (left shift)
188
- evalExpression("5 >> 1"); // 2 (right shift)
189
- evalExpression("5 >>> 1"); // 2 (unsigned right shift)
190
- ```
191
-
192
- #### Unary Operators
193
-
194
- ```js
195
- evalExpression("-5"); // -5 (negation)
196
- evalExpression("+5"); // 5 (unary plus)
197
- evalExpression("typeof 5"); // "number"
198
- evalExpression("void 0"); // undefined
199
- ```
200
-
201
- ### Data Types
202
-
203
- #### Literals
204
-
205
- ```js
206
- evalExpression("42"); // Number
207
- evalExpression("'hello'"); // String
208
- evalExpression("true"); // Boolean
209
- evalExpression("null"); // null
210
- evalExpression("undefined"); // undefined
211
- ```
212
-
213
- #### Arrays
214
-
215
- ```js
216
- evalExpression("[1, 2, 3]"); // [1, 2, 3]
217
- evalExpression("[1, 2, 3][1]"); // 2
218
- evalExpression("[1, 2, 3].length"); // 3
219
- evalExpression("[1, 2, 3].map(x => x * 2)"); // [2, 4, 6]
220
- evalExpression("[1, 2, 3].filter(x => x > 1)"); // [2, 3]
221
- evalExpression("[1, 2, 3].reduce((a, b) => a + b, 0)"); // 6
222
- ```
223
-
224
- #### Objects
225
-
226
- ```js
227
- evalExpression("{ a: 1, b: 2 }"); // { a: 1, b: 2 }
228
- evalExpression("({ a: 1, b: 2 }).a"); // 1
229
- evalExpression("({ a: 1, b: 2 })['b']"); // 2
230
- ```
231
-
232
- #### Template Literals
233
-
234
- ```js
235
- evalExpression("`Hello ${'World'}`"); // "Hello World"
236
- evalExpression("`2 + 2 = ${2 + 2}`", {}); // "2 + 2 = 4"
237
- evalExpression("`Hello ${name}`", { name: "Bob" }); // "Hello Bob"
238
- ```
239
-
240
- ### Functions
241
-
242
- #### Arrow Functions
243
-
244
- ```js
245
- evalExpression("((x) => x * 2)(5)"); // 10
246
- evalExpression("[1, 2, 3].map(x => x * 2)"); // [2, 4, 6]
247
- evalExpression("((a, b) => a + b)(3, 4)"); // 7
248
- ```
249
-
250
- #### Built-in Objects and Functions
251
-
252
- ```js
253
- // Math
254
- evalExpression("Math.max(1, 2, 3)"); // 3
255
- evalExpression("Math.min(1, 2, 3)"); // 1
256
- evalExpression("Math.round(4.7)"); // 5
257
- evalExpression("Math.floor(4.7)"); // 4
258
- evalExpression("Math.ceil(4.3)"); // 5
259
- evalExpression("Math.abs(-5)"); // 5
260
- evalExpression("Math.sqrt(16)"); // 4
261
-
262
- // String methods
263
- evalExpression("'hello'.toUpperCase()"); // "HELLO"
264
- evalExpression("'HELLO'.toLowerCase()"); // "hello"
265
- evalExpression("'hello world'.split(' ')"); // ["hello", "world"]
266
-
267
- // Array methods (non-mutating only)
268
- evalExpression("[1,2,3].join(', ')"); // "1, 2, 3"
269
- evalExpression("[1,2,3].slice(1)"); // [2, 3]
270
- evalExpression("[1,2,3].concat([4,5])"); // [1, 2, 3, 4, 5]
271
-
272
- // JSON
273
- evalExpression("JSON.stringify({ a: 1 })"); // '{"a":1}'
274
- evalExpression("JSON.parse('{\"a\":1}')"); // { a: 1 }
275
-
276
- // Date
277
- evalExpression("new Date(0).getTime()"); // 0
278
- evalExpression("new Date().getFullYear()"); // current year
279
-
280
- // Object methods
281
- evalExpression("Object.keys({ a: 1, b: 2 })"); // ["a", "b"]
282
- evalExpression("Object.values({ a: 1, b: 2 })"); // [1, 2]
283
-
284
- // Number methods
285
- evalExpression("Number.parseInt('42')"); // 42
286
- evalExpression("Number.parseFloat('3.14')"); // 3.14
287
- evalExpression("Number.isNaN(NaN)"); // true
288
- evalExpression("Number.isFinite(42)"); // true
289
-
290
- // Global functions
291
- evalExpression("isNaN(NaN)"); // true
292
- evalExpression("isFinite(Infinity)"); // false
293
- evalExpression("parseInt('42')"); // 42
294
- evalExpression("parseFloat('3.14')"); // 3.14
295
- evalExpression("encodeURIComponent('hello world')"); // "hello%20world"
296
- evalExpression("decodeURIComponent('hello%20world')"); // "hello world"
297
- ```
298
-
299
- ### Advanced Features
300
-
301
- #### Conditional (Ternary) Operator
302
-
303
- ```js
304
- evalExpression("5 > 3 ? 'yes' : 'no'"); // "yes"
305
- evalExpression("age >= 18 ? 'adult' : 'minor'", { age: 20 }); // "adult"
306
- ```
307
-
308
- #### Optional Chaining
309
-
310
- ```js
311
- evalExpression("obj?.prop", { obj: null }); // undefined
312
- evalExpression("obj?.prop?.value", { obj: {} }); // undefined
313
- evalExpression("arr?.[0]", { arr: null }); // undefined
314
- evalExpression("func?.()", { func: null }); // undefined
315
- ```
316
-
317
- #### Member Access
318
-
319
- ```js
320
- evalExpression("obj.prop", { obj: { prop: 42 } }); // 42
321
- evalExpression("obj['prop']", { obj: { prop: 42 } }); // 42
322
- evalExpression("arr[0]", { arr: [1, 2, 3] }); // 1
323
- ```
324
-
325
- #### Constructor Expressions
326
-
327
- ```js
328
- evalExpression("new Date(2024, 0, 1)"); // Date object
329
- evalExpression("new Array(1, 2, 3)"); // [1, 2, 3]
330
- evalExpression("new Set([1, 2, 2, 3])"); // Set {1, 2, 3}
331
- evalExpression("new Map([['a', 1], ['b', 2]])"); // Map {"a" => 1, "b" => 2}
332
- ```
333
-
334
- ## Security Features
335
-
336
- ### Sandboxed Environment
337
-
338
- `ecma-evaluator` runs expressions in a sandboxed environment with several security features:
339
-
340
- 1. **No access to `eval()` or `Function()` constructor** - Prevents dynamic code execution
341
- 2. **Blocked mutable methods** - Methods that mutate objects are blocked to prevent side effects:
342
-
343
- - Array: `push`, `pop`, `shift`, `unshift`, `splice`, `reverse`, `sort`, `fill`, `copyWithin`
344
- - Object: `freeze`, `defineProperty`, `defineProperties`, `preventExtensions`, `setPrototypeOf`, `assign`
345
- - Set/Map: `add`, `set`, `delete`, `clear`
346
- - Date: All setter methods (`setDate`, `setFullYear`, etc.)
347
- - TypedArray: `set`, `fill`, `copyWithin`, `reverse`, `sort`
348
-
349
- 3. **No `delete` operator** - The `delete` operator is blocked as it's a mutating operation
350
- 4. **Limited global scope** - Only safe built-in objects are available (Math, JSON, Array, Object, etc.)
351
- 5. **No file system or network access** - Cannot access Node.js APIs or perform I/O operations
352
- 6. **No access to process or global variables** - Cannot access `process`, `global`, `require`, etc.
353
- 7. **Prevention of prototype pollution** - Accessing prototype properties (e.g., `__proto__`) is blocked
354
-
355
- ### Safe Built-in Objects
356
-
357
- The following built-in objects are available in the sandboxed environment:
358
-
359
- - **Numbers & Math**: `Number`, `Math`, `Infinity`, `NaN`, `isNaN`, `isFinite`, `parseInt`, `parseFloat`
360
- - **Strings**: `String`, `encodeURI`, `encodeURIComponent`, `decodeURI`, `decodeURIComponent`
361
- - **Data Structures**: `Array`, `Object`, `Set`, `WeakSet`, `Map`, `WeakMap`
362
- - **Date & Time**: `Date`
363
- - **JSON**: `JSON`
364
- - **Types**: `Boolean`, `Symbol`, `BigInt`, `RegExp`
365
- - **TypedArrays**: `Int8Array`, `Uint8Array`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float32Array`, `Float64Array`, `BigInt64Array`, `BigUint64Array`
366
- - **Errors**: `Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`
367
- - **Promises**: `Promise`
368
-
369
-
370
- ### Error Prevention
371
-
372
- ```js
373
- // ❌ These will throw errors:
374
- evalExpression("arr.push(1)", { arr: [1, 2, 3] });
375
- // Error: Array.prototype.push is not allow
376
-
377
- evalExpression("new Function('return 1')");
378
- // Error: Cannot use new with Function constructor
379
-
380
- evalExpression("delete obj.prop", { obj: { prop: 1 } });
381
- // Error: Delete operator is not allow
382
- ```
383
-
384
- ## Use Cases
385
-
386
- ### Configuration & Rules Engine
387
-
388
- ```js
389
- // Evaluate business rules
390
- const rule = "age >= 18 && country === 'US'";
391
- const isEligible = evalExpression(rule, { age: 25, country: "US" }); // true
392
-
393
- // Dynamic pricing
394
- const priceFormula = "basePrice * (1 - discount / 100)";
395
- const finalPrice = evalExpression(priceFormula, {
396
- basePrice: 100,
397
- discount: 20,
398
- }); // 80
399
- ```
400
-
401
- ### Template Rendering
402
-
403
- ```js
404
- // Email templates
405
- const emailTemplate = `
406
- Hello {{ user.name }},
407
-
408
- Your order #{{ order.id }} has been {{ order.status }}.
409
- Total: {{ order.total.toFixed(2) }}
410
-
411
- Thank you for shopping with us!
412
- `;
413
-
414
- const email = evalTemplate(emailTemplate, {
415
- user: { name: "John Doe" },
416
- order: { id: 12345, status: "shipped", total: 99.99 },
417
- });
418
-
419
- // Dynamic content
420
- const greeting = evalTemplate("Good {{ hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening' }}, {{ name }}!", {
421
- hour: new Date().getHours(),
422
- name: "Alice",
423
- });
424
- ```
425
-
426
- ### Data Transformation
427
-
428
- ```js
429
- // Transform API responses
430
- const transform = "data.items.filter(x => x.active).map(x => x.name)";
431
- const result = evalExpression(transform, {
432
- data: {
433
- items: [
434
- { name: "Item 1", active: true },
435
- { name: "Item 2", active: false },
436
- { name: "Item 3", active: true },
437
- ],
438
- },
439
- }); // ["Item 1", "Item 3"]
440
- ```
441
-
442
- ### Form Validation
443
-
444
- ```js
445
- // Conditional validation
446
- const validationRule = "email.includes('@') && password.length >= 8";
447
- const isValid = evalExpression(validationRule, {
448
- email: "user@example.com",
449
- password: "secretpassword",
450
- }); // true
451
- ```
452
-
453
- ## Limitations
454
-
455
- 1. **No statements** - Only expressions are supported, not statements (no `if`, `for`, `while`, etc.)
456
- 2. **No variable assignment** - Cannot use assignment operators (`=`, `+=`, etc.)
457
- 3. **No mutable operations** - Mutable array/object methods are blocked
458
- 4. **No async operations** - Promises work but cannot use `await`
459
- 5. **No function declarations** - Only arrow functions in expressions are supported
460
- 6. **Limited error recovery** - Syntax errors will throw immediately
461
- 7. **No imports/requires** - Cannot import external modules
462
- 8. **Cloneable context only** - Context objects must be cloneable (no functions, DOM nodes, etc.)
463
-
464
- ## Advanced Usage
465
-
466
- ### Custom Evaluator Instance
467
-
468
- ```js
469
- import { Evaluator } from "ecma-evaluator";
470
-
471
- // Create a reusable evaluator with fixed context
472
- const evaluator = new Evaluator({ x: 10, y: 20 });
473
-
474
- // Evaluate multiple expressions with the same context
475
- console.log(evaluator.evaluate("x + y")); // 30
476
- console.log(evaluator.evaluate("x * y")); // 200
477
- console.log(evaluator.evaluate("Math.max(x, y)")); // 20
478
- ```
479
-
480
- ### Custom Template Parser
481
-
482
- ```js
483
- import { evalTemplate } from "ecma-evaluator";
484
-
485
- evalTemplate(
486
- "Hello ${ name }!",
487
- { name: "World" },
488
- {
489
- expressionStart: "${",
490
- expressionEnd: "}",
491
- preserveWhitespace: false,
492
- }
493
- );
494
- // Output: "Hello World!"
29
+ evalExpression("a + b * c", { a: 1, b: 2, c: 3 }); // 7
30
+ evalTemplate("Hello {{ name }}!", { name: "World" }); // "Hello World!"
495
31
  ```
496
32
 
497
- ## Performance Tips
498
-
499
- 1. **Reuse evaluator instances** when evaluating multiple expressions with the same context
500
- 2. **Avoid complex nested expressions** - Break them into smaller parts if possible
501
- 3. **Cache parsed templates** if you're rendering the same template multiple times
502
- 4. **Use simple variable access** instead of complex property chains when possible
503
-
504
- ## TypeScript Support
505
-
506
- The package includes TypeScript type definitions:
507
-
508
- ## Compatibility
509
-
510
- Compatible with Nodejs and major browsers. For older environments, use Babel for transpilation and polyfills.
511
-
512
- ```js
513
- import 'core-js/actual/structured-clone.js';
514
- import 'core-js/actual/object/has-own.js';
515
- ```
516
-
517
- ## Contributing
518
-
519
- Contributions are welcome! Please feel free to submit a Pull Request.
33
+ ## Documentation
520
34
 
521
- ## License
35
+ - [Getting started](/guide/getting-started)
36
+ - [API, supported features, security, and usage reference](/guide/reference)
37
+ - [Online playground](https://axetroy.github.io/ecma-evaluator/#playground)
522
38
 
523
- The [Anti 996 License](LICENSE)
39
+ See the [documentation site](https://axetroy.github.io/ecma-evaluator/).