ecma-evaluator 1.0.0 → 2.0.1
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 +484 -10
- package/dist/cjs/Evaluator.d.cts +11 -0
- package/dist/cjs/TemplateParser.d.cts +29 -0
- package/dist/cjs/index.cjs +611 -299
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +12 -7
- package/dist/esm/Evaluator.d.mts +11 -0
- package/dist/esm/TemplateParser.d.mts +29 -0
- package/dist/esm/index.d.mts +12 -7
- package/dist/esm/index.mjs +601 -294
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -5,31 +5,505 @@
|
|
|
5
5
|

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