ecma-evaluator 2.0.4 → 2.0.6
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 +24 -12
- package/README.zh-CN.md +521 -0
- package/dist/cjs/index.cjs +180 -60
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +180 -60
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
A tiny, fast, and **secure** JavaScript expression evaluator for safely evaluating expressions and template strings in a sandboxed environment.
|
|
9
9
|
|
|
10
|
+
English | [简体中文](./README.zh-CN.md)
|
|
11
|
+
|
|
10
12
|
## Features
|
|
11
13
|
|
|
12
|
-
- ✨ **Secure by design** - Sandboxed
|
|
14
|
+
- ✨ **Secure by design** - Sandboxed; blocks mutations/side effects
|
|
13
15
|
- 🚀 **Fast & lightweight** - Minimal dependencies, uses the efficient `acorn` parser
|
|
14
16
|
- 📦 **Zero configuration** - Works out of the box with sensible defaults
|
|
15
|
-
- 🎯 **Rich feature set** -
|
|
17
|
+
- 🎯 **Rich feature set** - Most JS expressions/functions
|
|
16
18
|
- 🔒 **No eval()** - Does not use `eval()` or `Function()` constructor
|
|
17
19
|
- 💪 **TypeScript support** - Includes TypeScript type definitions
|
|
18
20
|
- 📝 **Template strings** - Evaluate expressions within template strings using `{{ }}` syntax
|
|
@@ -223,8 +225,8 @@ evalExpression("[1, 2, 3].reduce((a, b) => a + b, 0)"); // 6
|
|
|
223
225
|
|
|
224
226
|
```js
|
|
225
227
|
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
|
+
evalExpression("({ a: 1, b: 2 }).a"); // 1
|
|
229
|
+
evalExpression("({ a: 1, b: 2 })['b']"); // 2
|
|
228
230
|
```
|
|
229
231
|
|
|
230
232
|
#### Template Literals
|
|
@@ -336,7 +338,7 @@ evalExpression("new Map([['a', 1], ['b', 2]])"); // Map {"a" => 1, "b" => 2}
|
|
|
336
338
|
`ecma-evaluator` runs expressions in a sandboxed environment with several security features:
|
|
337
339
|
|
|
338
340
|
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
|
|
341
|
+
2. **Blocked mutable methods** - Methods that mutate objects are blocked to prevent side effects:
|
|
340
342
|
|
|
341
343
|
- Array: `push`, `pop`, `shift`, `unshift`, `splice`, `reverse`, `sort`, `fill`, `copyWithin`
|
|
342
344
|
- Object: `freeze`, `defineProperty`, `defineProperties`, `preventExtensions`, `setPrototypeOf`, `assign`
|
|
@@ -348,6 +350,7 @@ evalExpression("new Map([['a', 1], ['b', 2]])"); // Map {"a" => 1, "b" => 2}
|
|
|
348
350
|
4. **Limited global scope** - Only safe built-in objects are available (Math, JSON, Array, Object, etc.)
|
|
349
351
|
5. **No file system or network access** - Cannot access Node.js APIs or perform I/O operations
|
|
350
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
|
|
351
354
|
|
|
352
355
|
### Safe Built-in Objects
|
|
353
356
|
|
|
@@ -363,12 +366,21 @@ The following built-in objects are available in the sandboxed environment:
|
|
|
363
366
|
- **Errors**: `Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`
|
|
364
367
|
- **Promises**: `Promise`
|
|
365
368
|
|
|
369
|
+
### ❌ Unsafe Context (Strict)
|
|
370
|
+
|
|
371
|
+
```js
|
|
372
|
+
// Do not pass unsafe functions into the context:
|
|
373
|
+
evalExpression(`eval("alert('this is a unsafe script')")`, { eval: eval });
|
|
374
|
+
evalExpression(`Function("return 1")()`, { Function: Function });
|
|
375
|
+
evalExpression(`require('fs').readFileSync('/etc/passwd')`, { require: require });
|
|
376
|
+
```
|
|
377
|
+
|
|
366
378
|
### Error Prevention
|
|
367
379
|
|
|
368
380
|
```js
|
|
369
381
|
// ❌ These will throw errors:
|
|
370
382
|
evalExpression("arr.push(1)", { arr: [1, 2, 3] });
|
|
371
|
-
// Error:
|
|
383
|
+
// Error: Array.prototype.push is not allow
|
|
372
384
|
|
|
373
385
|
evalExpression("new Function('return 1')");
|
|
374
386
|
// Error: Cannot use new with Function constructor
|
|
@@ -408,15 +420,15 @@ Thank you for shopping with us!
|
|
|
408
420
|
`;
|
|
409
421
|
|
|
410
422
|
const email = evalTemplate(emailTemplate, {
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
user: { name: "John Doe" },
|
|
424
|
+
order: { id: 12345, status: "shipped", total: 99.99 },
|
|
413
425
|
});
|
|
414
426
|
|
|
415
427
|
// Dynamic content
|
|
416
|
-
const greeting = evalTemplate(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
);
|
|
428
|
+
const greeting = evalTemplate("Good {{ hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening' }}, {{ name }}!", {
|
|
429
|
+
hour: new Date().getHours(),
|
|
430
|
+
name: "Alice",
|
|
431
|
+
});
|
|
420
432
|
```
|
|
421
433
|
|
|
422
434
|
### Data Transformation
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
# ecma-evaluator
|
|
2
|
+
|
|
3
|
+
[](https://996.icu/#/zh_CN)
|
|
4
|
+
[](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
|
5
|
+

|
|
6
|
+
[](https://badge.fury.io/js/ecma-evaluator)
|
|
7
|
+
|
|
8
|
+
一个小巧、快速且**安全**的 JavaScript 表达式求值器,用于在沙箱环境中安全地执行表达式和模板字符串。
|
|
9
|
+
|
|
10
|
+
[English](./README.md) | 简体中文
|
|
11
|
+
|
|
12
|
+
## 特性
|
|
13
|
+
|
|
14
|
+
- ✨ **安全设计** - 沙箱化;阻止变更/副作用
|
|
15
|
+
- 🚀 **快速且轻量** - 最小化依赖,使用高效的 `acorn` 解析器
|
|
16
|
+
- 📦 **零配置** - 开箱即用,具有合理的默认设置
|
|
17
|
+
- 🎯 **功能丰富** - 支持大多数 JS 表达式/函数
|
|
18
|
+
- 🔒 **不使用 eval()** - 不使用 `eval()` 或 `Function()` 构造函数
|
|
19
|
+
- 💪 **TypeScript 支持** - 包含 TypeScript 类型定义
|
|
20
|
+
- 📝 **模板字符串** - 使用 `{{ }}` 语法在模板字符串中执行表达式
|
|
21
|
+
|
|
22
|
+
## 安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install ecma-evaluator
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { evalExpression, evalTemplate } from "ecma-evaluator";
|
|
32
|
+
|
|
33
|
+
// 执行表达式
|
|
34
|
+
const result = evalExpression("a + b * c", { a: 1, b: 2, c: 3 });
|
|
35
|
+
console.log(result); // 输出: 7
|
|
36
|
+
|
|
37
|
+
// 执行模板
|
|
38
|
+
const text = evalTemplate("Hello {{ name }}!", { name: "World" });
|
|
39
|
+
console.log(text); // 输出: "Hello World!"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API 参考
|
|
43
|
+
|
|
44
|
+
### `evalExpression(expression, context?)`
|
|
45
|
+
|
|
46
|
+
使用可选的上下文执行 JavaScript 表达式。
|
|
47
|
+
|
|
48
|
+
**参数:**
|
|
49
|
+
|
|
50
|
+
- `expression` (string): 要执行的 JavaScript 表达式
|
|
51
|
+
- `context` (object, 可选): 包含表达式中使用的变量的对象
|
|
52
|
+
|
|
53
|
+
**返回值:** 执行表达式的结果
|
|
54
|
+
|
|
55
|
+
**示例:**
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { evalExpression } from "ecma-evaluator";
|
|
59
|
+
|
|
60
|
+
// 基础算术
|
|
61
|
+
evalExpression("2 + 3 * 4"); // 14
|
|
62
|
+
|
|
63
|
+
// 使用变量
|
|
64
|
+
evalExpression("x + y", { x: 10, y: 20 }); // 30
|
|
65
|
+
|
|
66
|
+
// 使用内置函数
|
|
67
|
+
evalExpression("Math.max(a, b, c)", { a: 5, b: 15, c: 10 }); // 15
|
|
68
|
+
|
|
69
|
+
// 字符串操作
|
|
70
|
+
evalExpression("greeting + ', ' + name", {
|
|
71
|
+
greeting: "Hello",
|
|
72
|
+
name: "Alice",
|
|
73
|
+
}); // "Hello, Alice"
|
|
74
|
+
|
|
75
|
+
// 数组方法
|
|
76
|
+
evalExpression("[1, 2, 3].map(x => x * 2)"); // [2, 4, 6]
|
|
77
|
+
|
|
78
|
+
// 条件表达式
|
|
79
|
+
evalExpression("score >= 60 ? 'Pass' : 'Fail'", { score: 75 }); // "Pass"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `evalTemplate(template, context?, templateParserOptions?)`
|
|
83
|
+
|
|
84
|
+
通过将 `{{ expression }}` 模式替换为其执行后的值来执行模板字符串。
|
|
85
|
+
|
|
86
|
+
**参数:**
|
|
87
|
+
|
|
88
|
+
- `template` (string): 要执行的模板字符串
|
|
89
|
+
- `context` (object, 可选): 包含模板中使用的变量的对象
|
|
90
|
+
- `templateParserOptions` (object, 可选): 模板解析器的选项
|
|
91
|
+
|
|
92
|
+
**返回值:** 执行后的模板字符串
|
|
93
|
+
|
|
94
|
+
**示例:**
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import { evalTemplate } from "ecma-evaluator";
|
|
98
|
+
|
|
99
|
+
// 基础变量替换
|
|
100
|
+
evalTemplate("Hello, {{ name }}!", { name: "World" });
|
|
101
|
+
// 输出: "Hello, World!"
|
|
102
|
+
|
|
103
|
+
// 多个表达式
|
|
104
|
+
evalTemplate("{{ a }} + {{ b }} = {{ a + b }}", { a: 10, b: 20 });
|
|
105
|
+
// 输出: "10 + 20 = 30"
|
|
106
|
+
|
|
107
|
+
// 复杂表达式
|
|
108
|
+
evalTemplate("The sum is {{ [1, 2, 3].reduce((a, b) => a + b, 0) }}");
|
|
109
|
+
// 输出: "The sum is 6"
|
|
110
|
+
|
|
111
|
+
// 表达式内的模板字面量
|
|
112
|
+
evalTemplate("{{ `Hello ${name}, welcome!` }}", { name: "Alice" });
|
|
113
|
+
// 输出: "Hello Alice, welcome!"
|
|
114
|
+
|
|
115
|
+
// 日期格式化
|
|
116
|
+
evalTemplate("Today is {{ new Date().toLocaleDateString() }}");
|
|
117
|
+
// 输出: "Today is 11/18/2025" (根据语言环境而异)
|
|
118
|
+
|
|
119
|
+
// 条件渲染
|
|
120
|
+
evalTemplate("Status: {{ isActive ? 'Active' : 'Inactive' }}", { isActive: true });
|
|
121
|
+
// 输出: "Status: Active"
|
|
122
|
+
|
|
123
|
+
// 可选链
|
|
124
|
+
evalTemplate("Value: {{ obj?.prop?.value ?? 'N/A' }}", { obj: null });
|
|
125
|
+
// 输出: "Value: N/A"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 错误处理
|
|
129
|
+
|
|
130
|
+
当在模板中引用未定义的变量时,它将被替换为 `"undefined"` 而不是抛出错误:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
evalTemplate("Hello {{ name }}!", {}); // "Hello undefined!"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
对于其他错误(语法错误、类型错误等),将抛出异常:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
evalTemplate("{{ 1 + }}", {}); // 抛出 SyntaxError
|
|
140
|
+
evalTemplate("{{ obj.prop }}", { obj: null }); // 抛出 TypeError
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 支持的 JavaScript 特性
|
|
144
|
+
|
|
145
|
+
### 运算符
|
|
146
|
+
|
|
147
|
+
#### 算术运算符
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
evalExpression("10 + 5"); // 15 (加法)
|
|
151
|
+
evalExpression("10 - 5"); // 5 (减法)
|
|
152
|
+
evalExpression("10 * 5"); // 50 (乘法)
|
|
153
|
+
evalExpression("10 / 5"); // 2 (除法)
|
|
154
|
+
evalExpression("10 % 3"); // 1 (取模)
|
|
155
|
+
evalExpression("2 ** 3"); // 8 (幂运算)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 比较运算符
|
|
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 (宽松相等)
|
|
166
|
+
evalExpression("5 === '5'"); // false (严格相等)
|
|
167
|
+
evalExpression("5 != '5'"); // false (宽松不等)
|
|
168
|
+
evalExpression("5 !== '5'"); // true (严格不等)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### 逻辑运算符
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
evalExpression("true && false"); // false (AND)
|
|
175
|
+
evalExpression("true || false"); // true (OR)
|
|
176
|
+
evalExpression("null ?? 'default'"); // "default" (空值合并)
|
|
177
|
+
evalExpression("!true"); // false (NOT)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### 位运算符
|
|
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 (左移)
|
|
188
|
+
evalExpression("5 >> 1"); // 2 (右移)
|
|
189
|
+
evalExpression("5 >>> 1"); // 2 (无符号右移)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### 一元运算符
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
evalExpression("-5"); // -5 (取负)
|
|
196
|
+
evalExpression("+5"); // 5 (一元加)
|
|
197
|
+
evalExpression("typeof 5"); // "number"
|
|
198
|
+
evalExpression("void 0"); // undefined
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 数据类型
|
|
202
|
+
|
|
203
|
+
#### 字面量
|
|
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
|
+
#### 数组
|
|
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
|
+
#### 对象
|
|
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
|
+
#### 模板字面量
|
|
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
|
+
### 函数
|
|
241
|
+
|
|
242
|
+
#### 箭头函数
|
|
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
|
+
#### 内置对象和函数
|
|
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
|
+
// 字符串方法
|
|
263
|
+
evalExpression("'hello'.toUpperCase()"); // "HELLO"
|
|
264
|
+
evalExpression("'HELLO'.toLowerCase()"); // "hello"
|
|
265
|
+
evalExpression("'hello world'.split(' ')"); // ["hello", "world"]
|
|
266
|
+
|
|
267
|
+
// 数组方法(仅非变更方法)
|
|
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()"); // 当前年份
|
|
279
|
+
|
|
280
|
+
// Object 方法
|
|
281
|
+
evalExpression("Object.keys({ a: 1, b: 2 })"); // ["a", "b"]
|
|
282
|
+
evalExpression("Object.values({ a: 1, b: 2 })"); // [1, 2]
|
|
283
|
+
|
|
284
|
+
// Number 方法
|
|
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
|
+
// 全局函数
|
|
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
|
+
### 高级特性
|
|
300
|
+
|
|
301
|
+
#### 条件(三元)运算符
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
evalExpression("5 > 3 ? 'yes' : 'no'"); // "yes"
|
|
305
|
+
evalExpression("age >= 18 ? 'adult' : 'minor'", { age: 20 }); // "adult"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### 可选链
|
|
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
|
+
#### 成员访问
|
|
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
|
+
#### 构造函数表达式
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
evalExpression("new Date(2024, 0, 1)"); // Date 对象
|
|
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
|
+
## 安全特性
|
|
335
|
+
|
|
336
|
+
### 沙箱环境
|
|
337
|
+
|
|
338
|
+
`ecma-evaluator` 在具有多项安全特性的沙箱环境中运行表达式:
|
|
339
|
+
|
|
340
|
+
1. **无法访问 `eval()` 或 `Function()` 构造函数** - 防止动态代码执行
|
|
341
|
+
2. **阻止可变方法** - 阻止变更对象的方法以防止副作用:
|
|
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: 所有 setter 方法 (`setDate`, `setFullYear`, 等)
|
|
347
|
+
- TypedArray: `set`, `fill`, `copyWithin`, `reverse`, `sort`
|
|
348
|
+
|
|
349
|
+
3. **无 `delete` 运算符** - `delete` 运算符被阻止,因为它是一个变更操作
|
|
350
|
+
4. **有限的全局作用域** - 只有安全的内置对象可用(Math、JSON、Array、Object 等)
|
|
351
|
+
5. **无文件系统或网络访问** - 无法访问 Node.js API 或执行 I/O 操作
|
|
352
|
+
6. **无法访问进程或全局变量** - 无法访问 `process`、`global`、`require` 等
|
|
353
|
+
7. **防止原型污染** - 阻止访问原型属性(例如,`__proto__`)
|
|
354
|
+
|
|
355
|
+
### 安全的内置对象
|
|
356
|
+
|
|
357
|
+
以下内置对象在沙箱环境中可用:
|
|
358
|
+
|
|
359
|
+
- **数字与数学**: `Number`, `Math`, `Infinity`, `NaN`, `isNaN`, `isFinite`, `parseInt`, `parseFloat`
|
|
360
|
+
- **字符串**: `String`, `encodeURI`, `encodeURIComponent`, `decodeURI`, `decodeURIComponent`
|
|
361
|
+
- **数据结构**: `Array`, `Object`, `Set`, `WeakSet`, `Map`, `WeakMap`
|
|
362
|
+
- **日期与时间**: `Date`
|
|
363
|
+
- **JSON**: `JSON`
|
|
364
|
+
- **类型**: `Boolean`, `Symbol`, `BigInt`, `RegExp`
|
|
365
|
+
- **类型化数组**: `Int8Array`, `Uint8Array`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float32Array`, `Float64Array`, `BigInt64Array`, `BigUint64Array`
|
|
366
|
+
- **错误**: `Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`
|
|
367
|
+
- **Promise**: `Promise`
|
|
368
|
+
|
|
369
|
+
### ❌ 不安全的上下文(严谨)
|
|
370
|
+
|
|
371
|
+
```js
|
|
372
|
+
// 不要将不安全的函数传递给上下文:
|
|
373
|
+
evalExpression(`eval("alert('this is a unsafe script')")`, { eval: eval });
|
|
374
|
+
evalExpression(`Function("return 1")()`, { Function: Function });
|
|
375
|
+
evalExpression(`require('fs').readFileSync('/etc/passwd')`, { require: require });
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### 错误预防
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
// ❌ 这些将抛出错误:
|
|
382
|
+
evalExpression("arr.push(1)", { arr: [1, 2, 3] });
|
|
383
|
+
// Error: Array.prototype.push is not allow
|
|
384
|
+
|
|
385
|
+
evalExpression("new Function('return 1')");
|
|
386
|
+
// Error: Cannot use new with Function constructor
|
|
387
|
+
|
|
388
|
+
evalExpression("delete obj.prop", { obj: { prop: 1 } });
|
|
389
|
+
// Error: Delete operator is not allow
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## 使用场景
|
|
393
|
+
|
|
394
|
+
### 配置与规则引擎
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
// 执行业务规则
|
|
398
|
+
const rule = "age >= 18 && country === 'US'";
|
|
399
|
+
const isEligible = evalExpression(rule, { age: 25, country: "US" }); // true
|
|
400
|
+
|
|
401
|
+
// 动态定价
|
|
402
|
+
const priceFormula = "basePrice * (1 - discount / 100)";
|
|
403
|
+
const finalPrice = evalExpression(priceFormula, {
|
|
404
|
+
basePrice: 100,
|
|
405
|
+
discount: 20,
|
|
406
|
+
}); // 80
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 模板渲染
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
// 邮件模板
|
|
413
|
+
const emailTemplate = `
|
|
414
|
+
Hello {{ user.name }},
|
|
415
|
+
|
|
416
|
+
Your order #{{ order.id }} has been {{ order.status }}.
|
|
417
|
+
Total: {{ order.total.toFixed(2) }}
|
|
418
|
+
|
|
419
|
+
Thank you for shopping with us!
|
|
420
|
+
`;
|
|
421
|
+
|
|
422
|
+
const email = evalTemplate(emailTemplate, {
|
|
423
|
+
user: { name: "John Doe" },
|
|
424
|
+
order: { id: 12345, status: "shipped", total: 99.99 },
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// 动态内容
|
|
428
|
+
const greeting = evalTemplate("Good {{ hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening' }}, {{ name }}!", {
|
|
429
|
+
hour: new Date().getHours(),
|
|
430
|
+
name: "Alice",
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 数据转换
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
// 转换 API 响应
|
|
438
|
+
const transform = "data.items.filter(x => x.active).map(x => x.name)";
|
|
439
|
+
const result = evalExpression(transform, {
|
|
440
|
+
data: {
|
|
441
|
+
items: [
|
|
442
|
+
{ name: "Item 1", active: true },
|
|
443
|
+
{ name: "Item 2", active: false },
|
|
444
|
+
{ name: "Item 3", active: true },
|
|
445
|
+
],
|
|
446
|
+
},
|
|
447
|
+
}); // ["Item 1", "Item 3"]
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### 表单验证
|
|
451
|
+
|
|
452
|
+
```js
|
|
453
|
+
// 条件验证
|
|
454
|
+
const validationRule = "email.includes('@') && password.length >= 8";
|
|
455
|
+
const isValid = evalExpression(validationRule, {
|
|
456
|
+
email: "user@example.com",
|
|
457
|
+
password: "secretpassword",
|
|
458
|
+
}); // true
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## 限制
|
|
462
|
+
|
|
463
|
+
1. **无语句** - 仅支持表达式,不支持语句(无 `if`、`for`、`while` 等)
|
|
464
|
+
2. **无变量赋值** - 不能使用赋值运算符(`=`、`+=` 等)
|
|
465
|
+
3. **无可变操作** - 阻止可变的数组/对象方法
|
|
466
|
+
4. **无异步操作** - Promise 可以工作,但不能使用 `await`
|
|
467
|
+
5. **无函数声明** - 仅支持表达式中的箭头函数
|
|
468
|
+
6. **有限的错误恢复** - 语法错误将立即抛出
|
|
469
|
+
7. **无导入/require** - 无法导入外部模块
|
|
470
|
+
|
|
471
|
+
## 高级用法
|
|
472
|
+
|
|
473
|
+
### 自定义求值器实例
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
import { Evaluator } from "ecma-evaluator";
|
|
477
|
+
|
|
478
|
+
// 创建具有固定上下文的可重用求值器
|
|
479
|
+
const evaluator = new Evaluator({ x: 10, y: 20 });
|
|
480
|
+
|
|
481
|
+
// 使用相同上下文执行多个表达式
|
|
482
|
+
console.log(evaluator.evaluate("x + y")); // 30
|
|
483
|
+
console.log(evaluator.evaluate("x * y")); // 200
|
|
484
|
+
console.log(evaluator.evaluate("Math.max(x, y)")); // 20
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 自定义模板解析器
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
import { evalTemplate } from "ecma-evaluator";
|
|
491
|
+
|
|
492
|
+
evalTemplate(
|
|
493
|
+
"Hello ${ name }!",
|
|
494
|
+
{ name: "World" },
|
|
495
|
+
{
|
|
496
|
+
expressionStart: "${",
|
|
497
|
+
expressionEnd: "}",
|
|
498
|
+
preserveWhitespace: false,
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
// 输出: "Hello World!"
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## 性能提示
|
|
505
|
+
|
|
506
|
+
1. **重用求值器实例** - 当使用相同上下文执行多个表达式时
|
|
507
|
+
2. **避免复杂的嵌套表达式** - 如果可能,将它们分解为更小的部分
|
|
508
|
+
3. **缓存解析的模板** - 如果多次渲染同一模板
|
|
509
|
+
4. **使用简单的变量访问** - 在可能的情况下,使用简单的变量访问而不是复杂的属性链
|
|
510
|
+
|
|
511
|
+
## TypeScript 支持
|
|
512
|
+
|
|
513
|
+
该包包含 TypeScript 类型定义。
|
|
514
|
+
|
|
515
|
+
## 贡献
|
|
516
|
+
|
|
517
|
+
欢迎贡献!请随时提交 Pull Request。
|
|
518
|
+
|
|
519
|
+
## 许可证
|
|
520
|
+
|
|
521
|
+
[Anti 996 License](LICENSE)
|