@variojs/core 0.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/__tests__/README.md +101 -0
- package/__tests__/errors.test.ts +139 -0
- package/__tests__/expression/cache.test.ts +118 -0
- package/__tests__/expression/compiler.test.ts +111 -0
- package/__tests__/expression/evaluate.test.ts +95 -0
- package/__tests__/expression/parser.test.ts +57 -0
- package/__tests__/expression/whitelist.test.ts +59 -0
- package/__tests__/performance.test.ts +379 -0
- package/__tests__/runtime/create-context.test.ts +78 -0
- package/__tests__/runtime/loop-context-pool.test.ts +74 -0
- package/__tests__/runtime/path.test.ts +128 -0
- package/__tests__/vm/executor-timeout.test.ts +117 -0
- package/__tests__/vm/executor.test.ts +173 -0
- package/__tests__/vm/handlers/array.test.ts +113 -0
- package/__tests__/vm/handlers/call.test.ts +93 -0
- package/dist/errors.d.ts +100 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +132 -0
- package/dist/errors.js.map +1 -0
- package/dist/expression/cache.d.ts +53 -0
- package/dist/expression/cache.d.ts.map +1 -0
- package/dist/expression/cache.js +158 -0
- package/dist/expression/cache.js.map +1 -0
- package/dist/expression/compiler.d.ts +34 -0
- package/dist/expression/compiler.d.ts.map +1 -0
- package/dist/expression/compiler.js +123 -0
- package/dist/expression/compiler.js.map +1 -0
- package/dist/expression/dependencies.d.ts +17 -0
- package/dist/expression/dependencies.d.ts.map +1 -0
- package/dist/expression/dependencies.js +106 -0
- package/dist/expression/dependencies.js.map +1 -0
- package/dist/expression/evaluate.d.ts +22 -0
- package/dist/expression/evaluate.d.ts.map +1 -0
- package/dist/expression/evaluate.js +75 -0
- package/dist/expression/evaluate.js.map +1 -0
- package/dist/expression/evaluator.d.ts +22 -0
- package/dist/expression/evaluator.d.ts.map +1 -0
- package/dist/expression/evaluator.js +506 -0
- package/dist/expression/evaluator.js.map +1 -0
- package/dist/expression/index.d.ts +12 -0
- package/dist/expression/index.d.ts.map +1 -0
- package/dist/expression/index.js +12 -0
- package/dist/expression/index.js.map +1 -0
- package/dist/expression/parser.d.ts +15 -0
- package/dist/expression/parser.d.ts.map +1 -0
- package/dist/expression/parser.js +42 -0
- package/dist/expression/parser.js.map +1 -0
- package/dist/expression/utils.d.ts +46 -0
- package/dist/expression/utils.d.ts.map +1 -0
- package/dist/expression/utils.js +78 -0
- package/dist/expression/utils.js.map +1 -0
- package/dist/expression/whitelist.d.ts +24 -0
- package/dist/expression/whitelist.d.ts.map +1 -0
- package/dist/expression/whitelist.js +198 -0
- package/dist/expression/whitelist.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/context.d.ts +8 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +7 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/create-context.d.ts +50 -0
- package/dist/runtime/create-context.d.ts.map +1 -0
- package/dist/runtime/create-context.js +73 -0
- package/dist/runtime/create-context.js.map +1 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/loop-context-pool.d.ts +58 -0
- package/dist/runtime/loop-context-pool.d.ts.map +1 -0
- package/dist/runtime/loop-context-pool.js +114 -0
- package/dist/runtime/loop-context-pool.js.map +1 -0
- package/dist/runtime/path.d.ts +114 -0
- package/dist/runtime/path.d.ts.map +1 -0
- package/dist/runtime/path.js +302 -0
- package/dist/runtime/path.js.map +1 -0
- package/dist/runtime/proxy.d.ts +18 -0
- package/dist/runtime/proxy.d.ts.map +1 -0
- package/dist/runtime/proxy.js +53 -0
- package/dist/runtime/proxy.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +32 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/vm/action.d.ts +11 -0
- package/dist/vm/action.d.ts.map +1 -0
- package/dist/vm/action.js +10 -0
- package/dist/vm/action.js.map +1 -0
- package/dist/vm/errors.d.ts +5 -0
- package/dist/vm/errors.d.ts.map +1 -0
- package/dist/vm/errors.js +5 -0
- package/dist/vm/errors.js.map +1 -0
- package/dist/vm/executor.d.ts +35 -0
- package/dist/vm/executor.d.ts.map +1 -0
- package/dist/vm/executor.js +137 -0
- package/dist/vm/executor.js.map +1 -0
- package/dist/vm/handlers/array/pop.d.ts +12 -0
- package/dist/vm/handlers/array/pop.d.ts.map +1 -0
- package/dist/vm/handlers/array/pop.js +28 -0
- package/dist/vm/handlers/array/pop.js.map +1 -0
- package/dist/vm/handlers/array/push.d.ts +13 -0
- package/dist/vm/handlers/array/push.d.ts.map +1 -0
- package/dist/vm/handlers/array/push.js +42 -0
- package/dist/vm/handlers/array/push.js.map +1 -0
- package/dist/vm/handlers/array/shift.d.ts +12 -0
- package/dist/vm/handlers/array/shift.d.ts.map +1 -0
- package/dist/vm/handlers/array/shift.js +28 -0
- package/dist/vm/handlers/array/shift.js.map +1 -0
- package/dist/vm/handlers/array/splice.d.ts +12 -0
- package/dist/vm/handlers/array/splice.d.ts.map +1 -0
- package/dist/vm/handlers/array/splice.js +59 -0
- package/dist/vm/handlers/array/splice.js.map +1 -0
- package/dist/vm/handlers/array/unshift.d.ts +13 -0
- package/dist/vm/handlers/array/unshift.d.ts.map +1 -0
- package/dist/vm/handlers/array/unshift.js +42 -0
- package/dist/vm/handlers/array/unshift.js.map +1 -0
- package/dist/vm/handlers/array/utils.d.ts +10 -0
- package/dist/vm/handlers/array/utils.d.ts.map +1 -0
- package/dist/vm/handlers/array/utils.js +33 -0
- package/dist/vm/handlers/array/utils.js.map +1 -0
- package/dist/vm/handlers/batch.d.ts +12 -0
- package/dist/vm/handlers/batch.d.ts.map +1 -0
- package/dist/vm/handlers/batch.js +40 -0
- package/dist/vm/handlers/batch.js.map +1 -0
- package/dist/vm/handlers/call.d.ts +14 -0
- package/dist/vm/handlers/call.d.ts.map +1 -0
- package/dist/vm/handlers/call.js +65 -0
- package/dist/vm/handlers/call.js.map +1 -0
- package/dist/vm/handlers/emit.d.ts +12 -0
- package/dist/vm/handlers/emit.d.ts.map +1 -0
- package/dist/vm/handlers/emit.js +26 -0
- package/dist/vm/handlers/emit.js.map +1 -0
- package/dist/vm/handlers/if.d.ts +13 -0
- package/dist/vm/handlers/if.d.ts.map +1 -0
- package/dist/vm/handlers/if.js +35 -0
- package/dist/vm/handlers/if.js.map +1 -0
- package/dist/vm/handlers/index.d.ts +11 -0
- package/dist/vm/handlers/index.d.ts.map +1 -0
- package/dist/vm/handlers/index.js +46 -0
- package/dist/vm/handlers/index.js.map +1 -0
- package/dist/vm/handlers/log.d.ts +12 -0
- package/dist/vm/handlers/log.d.ts.map +1 -0
- package/dist/vm/handlers/log.js +41 -0
- package/dist/vm/handlers/log.js.map +1 -0
- package/dist/vm/handlers/loop.d.ts +12 -0
- package/dist/vm/handlers/loop.d.ts.map +1 -0
- package/dist/vm/handlers/loop.js +71 -0
- package/dist/vm/handlers/loop.js.map +1 -0
- package/dist/vm/handlers/navigate.d.ts +12 -0
- package/dist/vm/handlers/navigate.d.ts.map +1 -0
- package/dist/vm/handlers/navigate.js +43 -0
- package/dist/vm/handlers/navigate.js.map +1 -0
- package/dist/vm/handlers/set.d.ts +15 -0
- package/dist/vm/handlers/set.d.ts.map +1 -0
- package/dist/vm/handlers/set.js +30 -0
- package/dist/vm/handlers/set.js.map +1 -0
- package/dist/vm/index.d.ts +8 -0
- package/dist/vm/index.d.ts.map +1 -0
- package/dist/vm/index.js +7 -0
- package/dist/vm/index.js.map +1 -0
- package/package.json +34 -0
- package/src/errors.ts +194 -0
- package/src/expression/README.md +192 -0
- package/src/expression/cache.ts +199 -0
- package/src/expression/compiler.ts +144 -0
- package/src/expression/dependencies.ts +116 -0
- package/src/expression/evaluate.ts +95 -0
- package/src/expression/evaluator.ts +640 -0
- package/src/expression/index.ts +27 -0
- package/src/expression/parser.ts +54 -0
- package/src/expression/utils.ts +89 -0
- package/src/expression/whitelist.ts +224 -0
- package/src/globals.d.ts +10 -0
- package/src/index.ts +72 -0
- package/src/runtime/context.ts +8 -0
- package/src/runtime/create-context.ts +133 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/loop-context-pool.ts +134 -0
- package/src/runtime/path.ts +372 -0
- package/src/runtime/proxy.ts +66 -0
- package/src/runtime/sandbox.ts +43 -0
- package/src/types.ts +177 -0
- package/src/vm/errors.ts +10 -0
- package/src/vm/executor.ts +210 -0
- package/src/vm/handlers/array/pop.ts +47 -0
- package/src/vm/handlers/array/push.ts +68 -0
- package/src/vm/handlers/array/shift.ts +47 -0
- package/src/vm/handlers/array/splice.ts +78 -0
- package/src/vm/handlers/array/unshift.ts +68 -0
- package/src/vm/handlers/array/utils.ts +41 -0
- package/src/vm/handlers/batch.ts +57 -0
- package/src/vm/handlers/call.ts +92 -0
- package/src/vm/handlers/emit.ts +39 -0
- package/src/vm/handlers/if.ts +48 -0
- package/src/vm/handlers/index.ts +52 -0
- package/src/vm/handlers/log.ts +54 -0
- package/src/vm/handlers/loop.ts +102 -0
- package/src/vm/handlers/navigate.ts +64 -0
- package/src/vm/handlers/set.ts +43 -0
- package/src/vm/index.ts +8 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值入口函数
|
|
3
|
+
*
|
|
4
|
+
* 整合解析、验证、缓存、求值流程
|
|
5
|
+
*/
|
|
6
|
+
import type { RuntimeContext, ExpressionOptions } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* 求值表达式(完整流程)
|
|
9
|
+
*
|
|
10
|
+
* @param expr 表达式字符串
|
|
11
|
+
* @param ctx 运行时上下文
|
|
12
|
+
* @returns 求值结果(类型无法静态推导,返回 unknown)
|
|
13
|
+
*
|
|
14
|
+
* 注意:表达式求值结果类型无法在编译时确定,因为:
|
|
15
|
+
* 1. 表达式是运行时字符串
|
|
16
|
+
* 2. 状态类型是动态的
|
|
17
|
+
* 3. 表达式可能返回任意类型
|
|
18
|
+
*
|
|
19
|
+
* 如果需要类型安全,应在使用结果时进行类型守卫或类型断言
|
|
20
|
+
*/
|
|
21
|
+
export declare function evaluate(expr: string, ctx: RuntimeContext, options?: ExpressionOptions): unknown;
|
|
22
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/expression/evaluate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAYpE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CA0DT"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值入口函数
|
|
3
|
+
*
|
|
4
|
+
* 整合解析、验证、缓存、求值流程
|
|
5
|
+
*/
|
|
6
|
+
import { ExpressionError, ErrorCodes } from '../errors.js';
|
|
7
|
+
import { parseExpression } from './parser.js';
|
|
8
|
+
import { validateAST } from './whitelist.js';
|
|
9
|
+
import { evaluateExpression } from './evaluator.js';
|
|
10
|
+
import { extractDependencies } from './dependencies.js';
|
|
11
|
+
import { getCompiledExpression } from './compiler.js';
|
|
12
|
+
import { getCachedExpression, setCachedExpression, } from './cache.js';
|
|
13
|
+
/**
|
|
14
|
+
* 求值表达式(完整流程)
|
|
15
|
+
*
|
|
16
|
+
* @param expr 表达式字符串
|
|
17
|
+
* @param ctx 运行时上下文
|
|
18
|
+
* @returns 求值结果(类型无法静态推导,返回 unknown)
|
|
19
|
+
*
|
|
20
|
+
* 注意:表达式求值结果类型无法在编译时确定,因为:
|
|
21
|
+
* 1. 表达式是运行时字符串
|
|
22
|
+
* 2. 状态类型是动态的
|
|
23
|
+
* 3. 表达式可能返回任意类型
|
|
24
|
+
*
|
|
25
|
+
* 如果需要类型安全,应在使用结果时进行类型守卫或类型断言
|
|
26
|
+
*/
|
|
27
|
+
export function evaluate(expr, ctx, options = {}) {
|
|
28
|
+
try {
|
|
29
|
+
// 合并选项:ctx.$exprOptions 优先级低于直接传入的 options
|
|
30
|
+
const mergedOptions = {
|
|
31
|
+
...ctx.$exprOptions,
|
|
32
|
+
...options
|
|
33
|
+
};
|
|
34
|
+
// 1. 检查结果缓存
|
|
35
|
+
const cached = getCachedExpression(expr, ctx);
|
|
36
|
+
if (cached !== null) {
|
|
37
|
+
return cached;
|
|
38
|
+
}
|
|
39
|
+
// 2. 解析为 AST
|
|
40
|
+
const ast = parseExpression(expr);
|
|
41
|
+
// 3. AST 白名单校验(传递 allowGlobals 和 maxNestingDepth 选项)
|
|
42
|
+
validateAST(ast, {
|
|
43
|
+
allowGlobals: mergedOptions.allowGlobals,
|
|
44
|
+
maxNestingDepth: mergedOptions.maxNestingDepth
|
|
45
|
+
});
|
|
46
|
+
// 4. 尝试使用编译缓存(简单表达式)
|
|
47
|
+
const compiled = getCompiledExpression(expr, ast);
|
|
48
|
+
if (compiled) {
|
|
49
|
+
const result = compiled(ctx);
|
|
50
|
+
// 提取依赖并缓存结果
|
|
51
|
+
const dependencies = extractDependencies(ast);
|
|
52
|
+
setCachedExpression(expr, result, dependencies, ctx);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
// 5. 复杂表达式,使用解释执行
|
|
56
|
+
const result = evaluateExpression(ast, ctx, mergedOptions);
|
|
57
|
+
// 6. 提取依赖并缓存
|
|
58
|
+
const dependencies = extractDependencies(ast);
|
|
59
|
+
setCachedExpression(expr, result, dependencies, ctx);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof ExpressionError) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
throw new ExpressionError(expr, `Expression evaluation failed: ${errorMessage}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR, {
|
|
68
|
+
metadata: {
|
|
69
|
+
originalError: error instanceof Error ? error.name : 'Unknown',
|
|
70
|
+
stack: error instanceof Error ? error.stack?.split('\n').slice(0, 5) : undefined
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/expression/evaluate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,GAAmB,EACnB,UAA6B,EAAE;IAE/B,IAAI,CAAC;QACH,2CAA2C;QAC3C,MAAM,aAAa,GAAG;YACpB,GAAG,GAAG,CAAC,YAAY;YACnB,GAAG,OAAO;SACX,CAAA;QAED,YAAY;QACZ,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC7C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,MAAM,CAAA;QACf,CAAC;QAED,aAAa;QACb,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;QAEjC,qDAAqD;QACrD,WAAW,CAAC,GAAG,EAAE;YACf,YAAY,EAAE,aAAa,CAAC,YAAY;YACxC,eAAe,EAAE,aAAa,CAAC,eAAe;SAC/C,CAAC,CAAA;QAEF,qBAAqB;QACrB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;YAC5B,YAAY;YACZ,MAAM,YAAY,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC7C,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;YACpD,OAAO,MAAM,CAAA;QACf,CAAC;QAED,kBAAkB;QAClB,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,aAAa,CAAC,CAAA;QAE1D,aAAa;QACb,MAAM,YAAY,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAA;QAC7C,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;QAEpD,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;YACrC,MAAM,KAAK,CAAA;QACb,CAAC;QACD,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC3E,MAAM,IAAI,eAAe,CACvB,IAAI,EACJ,iCAAiC,YAAY,EAAE,EAC/C,UAAU,CAAC,2BAA2B,EACtC;YACE,QAAQ,EAAE;gBACR,aAAa,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;gBAC9D,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;aACjF;SACF,CACF,CAAA;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值器
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 安全求值 AST
|
|
6
|
+
* - 支持白名单函数调用
|
|
7
|
+
* - 执行步数/时间限制
|
|
8
|
+
*/
|
|
9
|
+
import type * as ESTree from '@babel/types';
|
|
10
|
+
import type { RuntimeContext, ExpressionOptions } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* 安全求值 AST
|
|
13
|
+
*
|
|
14
|
+
* @param ast AST 节点
|
|
15
|
+
* @param ctx 运行时上下文
|
|
16
|
+
* @param options 求值选项
|
|
17
|
+
* @returns 求值结果(类型无法静态推导)
|
|
18
|
+
*
|
|
19
|
+
* 注意:表达式求值结果类型无法在编译时确定,返回 unknown
|
|
20
|
+
*/
|
|
21
|
+
export declare function evaluateExpression(ast: ESTree.Node, ctx: RuntimeContext, options?: ExpressionOptions): unknown;
|
|
22
|
+
//# sourceMappingURL=evaluator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/expression/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAoFpE;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,CAAC,IAAI,EAChB,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAmhBT"}
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值器
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 安全求值 AST
|
|
6
|
+
* - 支持白名单函数调用
|
|
7
|
+
* - 执行步数/时间限制
|
|
8
|
+
*/
|
|
9
|
+
import { ExpressionError, ErrorCodes } from '../errors.js';
|
|
10
|
+
import { isSafePropertyAccess } from '../runtime/sandbox.js';
|
|
11
|
+
/**
|
|
12
|
+
* 白名单全局函数
|
|
13
|
+
*/
|
|
14
|
+
const WHITELISTED_GLOBALS = new Set([
|
|
15
|
+
'String', 'Number', 'Boolean', 'BigInt', 'Symbol',
|
|
16
|
+
'Array', 'Object', 'Math', 'Date',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* 白名单函数(带命名空间)
|
|
20
|
+
*/
|
|
21
|
+
const WHITELISTED_FUNCTIONS = new Set([
|
|
22
|
+
'Array.isArray',
|
|
23
|
+
'Object.is',
|
|
24
|
+
'Number.isFinite',
|
|
25
|
+
'Number.isInteger',
|
|
26
|
+
'Number.isNaN',
|
|
27
|
+
'Number.isSafeInteger',
|
|
28
|
+
'Math.abs',
|
|
29
|
+
'Math.round',
|
|
30
|
+
'Math.floor',
|
|
31
|
+
'Math.ceil',
|
|
32
|
+
'Math.random',
|
|
33
|
+
'Math.max',
|
|
34
|
+
'Math.min',
|
|
35
|
+
'Date.now',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* 安全的数组方法(只读或返回新数组,不修改原状态)
|
|
39
|
+
*/
|
|
40
|
+
const SAFE_ARRAY_METHODS = new Set([
|
|
41
|
+
// 返回新数组
|
|
42
|
+
'slice',
|
|
43
|
+
'concat',
|
|
44
|
+
'filter',
|
|
45
|
+
'map',
|
|
46
|
+
'flat',
|
|
47
|
+
'flatMap',
|
|
48
|
+
'toReversed', // ES2023 不修改原数组的 reverse
|
|
49
|
+
'toSorted', // ES2023 不修改原数组的 sort
|
|
50
|
+
'toSpliced', // ES2023 不修改原数组的 splice
|
|
51
|
+
'with', // ES2023 不修改原数组的索引赋值
|
|
52
|
+
// 只读方法
|
|
53
|
+
'indexOf',
|
|
54
|
+
'lastIndexOf',
|
|
55
|
+
'includes',
|
|
56
|
+
'find',
|
|
57
|
+
'findIndex',
|
|
58
|
+
'findLast',
|
|
59
|
+
'findLastIndex',
|
|
60
|
+
'every',
|
|
61
|
+
'some',
|
|
62
|
+
'at',
|
|
63
|
+
// 返回字符串
|
|
64
|
+
'join',
|
|
65
|
+
'toString',
|
|
66
|
+
'toLocaleString',
|
|
67
|
+
// 在链式调用中安全使用(如 slice().reverse())
|
|
68
|
+
'reverse',
|
|
69
|
+
'sort',
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* 运行时辅助函数
|
|
73
|
+
*/
|
|
74
|
+
const RUNTIME_HELPERS = {
|
|
75
|
+
'$truncate': (str, length) => {
|
|
76
|
+
if (typeof str !== 'string')
|
|
77
|
+
return str;
|
|
78
|
+
const len = typeof length === 'number' ? length : 0;
|
|
79
|
+
return str.length > len ? str.slice(0, len) + '...' : str;
|
|
80
|
+
},
|
|
81
|
+
'$format': (date, _format) => {
|
|
82
|
+
const d = typeof date === 'number' ? new Date(date) : date;
|
|
83
|
+
// 简单格式化,可根据需要扩展
|
|
84
|
+
// format 参数暂未实现,保留接口以便未来扩展
|
|
85
|
+
return d.toISOString();
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 安全求值 AST
|
|
90
|
+
*
|
|
91
|
+
* @param ast AST 节点
|
|
92
|
+
* @param ctx 运行时上下文
|
|
93
|
+
* @param options 求值选项
|
|
94
|
+
* @returns 求值结果(类型无法静态推导)
|
|
95
|
+
*
|
|
96
|
+
* 注意:表达式求值结果类型无法在编译时确定,返回 unknown
|
|
97
|
+
*/
|
|
98
|
+
export function evaluateExpression(ast, ctx, options = {}) {
|
|
99
|
+
const mergedOptions = {
|
|
100
|
+
...ctx.$exprOptions,
|
|
101
|
+
...options
|
|
102
|
+
};
|
|
103
|
+
const allowGlobals = mergedOptions.allowGlobals === true;
|
|
104
|
+
const maxSteps = mergedOptions.maxSteps ?? 1000;
|
|
105
|
+
const timeout = mergedOptions.timeout ?? 100;
|
|
106
|
+
let stepCount = 0;
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
function getGlobalValueByName(name) {
|
|
109
|
+
if (!allowGlobals)
|
|
110
|
+
return undefined;
|
|
111
|
+
// 只有在 allowGlobals 为 true 时才返回全局对象
|
|
112
|
+
if (name === 'globalThis')
|
|
113
|
+
return globalThis;
|
|
114
|
+
if (name === 'window' && typeof window !== 'undefined')
|
|
115
|
+
return window;
|
|
116
|
+
if (name === 'document' && typeof document !== 'undefined')
|
|
117
|
+
return document;
|
|
118
|
+
if (name === 'global' && typeof global !== 'undefined')
|
|
119
|
+
return global;
|
|
120
|
+
if (name === 'self' && typeof self !== 'undefined')
|
|
121
|
+
return self;
|
|
122
|
+
// 只有在 allowGlobals 为 true 时才访问 globalThis 的属性
|
|
123
|
+
return globalThis[name];
|
|
124
|
+
}
|
|
125
|
+
function isGlobalObjectName(name) {
|
|
126
|
+
return ['window', 'document', 'global', 'globalThis', 'self'].includes(name);
|
|
127
|
+
}
|
|
128
|
+
function checkLimits() {
|
|
129
|
+
stepCount++;
|
|
130
|
+
if (stepCount > maxSteps) {
|
|
131
|
+
throw new ExpressionError(JSON.stringify(ast), `Expression evaluation exceeded max steps (${maxSteps})`, ErrorCodes.EXPRESSION_MAX_STEPS_EXCEEDED, {
|
|
132
|
+
metadata: {
|
|
133
|
+
maxSteps,
|
|
134
|
+
currentSteps: stepCount
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (Date.now() - startTime > timeout) {
|
|
139
|
+
throw new ExpressionError(JSON.stringify(ast), `Expression evaluation exceeded timeout (${timeout}ms)`, ErrorCodes.EXPRESSION_TIMEOUT, {
|
|
140
|
+
metadata: {
|
|
141
|
+
timeout,
|
|
142
|
+
elapsedTime: Date.now() - startTime
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function evaluate(node) {
|
|
148
|
+
checkLimits();
|
|
149
|
+
// 使用类型断言处理 node.type,因为 @babel/types 的类型定义可能不完整
|
|
150
|
+
// TypeScript 的严格类型检查可能不识别某些节点类型,使用类型断言绕过
|
|
151
|
+
const nodeType = node.type;
|
|
152
|
+
switch (nodeType) {
|
|
153
|
+
case 'NumericLiteral':
|
|
154
|
+
case 'StringLiteral':
|
|
155
|
+
case 'BooleanLiteral':
|
|
156
|
+
case 'BigIntLiteral':
|
|
157
|
+
case 'DecimalLiteral':
|
|
158
|
+
case 'RegExpLiteral': {
|
|
159
|
+
// 这些字面量类型都有 value 属性
|
|
160
|
+
return node.value;
|
|
161
|
+
}
|
|
162
|
+
case 'NullLiteral': {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
case 'Literal': {
|
|
166
|
+
const literal = node;
|
|
167
|
+
// 处理不同类型的字面量
|
|
168
|
+
// @babel/types 的 Literal 类型包含多种字面量类型
|
|
169
|
+
// 使用类型守卫安全访问 value 属性
|
|
170
|
+
if ('value' in literal && literal.value !== undefined) {
|
|
171
|
+
return literal.value;
|
|
172
|
+
}
|
|
173
|
+
// NullLiteral 或 BigIntLiteral 可能没有 value 属性,或 value 为 null
|
|
174
|
+
// 检查类型名称
|
|
175
|
+
const literalType = literal.type;
|
|
176
|
+
if (literalType === 'NullLiteral') {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
// 其他情况:尝试访问 value,如果不存在则返回 undefined
|
|
180
|
+
return literal.value ?? undefined;
|
|
181
|
+
}
|
|
182
|
+
case 'Identifier': {
|
|
183
|
+
const name = node.name;
|
|
184
|
+
// 检查是否为全局对象名称(无论是否存在,都应该被禁止,除非 allowGlobals)
|
|
185
|
+
// 必须在检查 ctx 之前,防止 ctx 中有同名属性绕过检查
|
|
186
|
+
if (isGlobalObjectName(name) && !allowGlobals) {
|
|
187
|
+
throw new ExpressionError(name, `Access to global "${name}" is not allowed in expressions`, ErrorCodes.EXPRESSION_UNSAFE_ACCESS);
|
|
188
|
+
}
|
|
189
|
+
// 从上下文获取值(优先从 ctx,但全局对象名称已经被上面的检查拦截)
|
|
190
|
+
// 注意:即使 ctx 中有 window/document 等属性,也不应该访问(安全考虑)
|
|
191
|
+
if (name in ctx && !isGlobalObjectName(name)) {
|
|
192
|
+
return ctx[name];
|
|
193
|
+
}
|
|
194
|
+
// 允许直接访问全局对象(由 allowGlobals 控制)
|
|
195
|
+
if (allowGlobals) {
|
|
196
|
+
const globalValue = getGlobalValueByName(name);
|
|
197
|
+
if (globalValue !== undefined) {
|
|
198
|
+
return globalValue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// 如果是白名单全局函数,允许访问(但仅用于函数调用,这里返回 undefined)
|
|
202
|
+
if (WHITELISTED_GLOBALS.has(name)) {
|
|
203
|
+
return globalThis[name];
|
|
204
|
+
}
|
|
205
|
+
// 未定义的标识符返回 undefined(而不是抛出错误,允许可选链等场景)
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
case 'MemberExpression': {
|
|
209
|
+
const member = node;
|
|
210
|
+
const object = evaluate(member.object);
|
|
211
|
+
// null/undefined 检查
|
|
212
|
+
if (object == null) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
// 禁止访问全局对象(检查对象是否为全局对象)
|
|
216
|
+
if (!allowGlobals) {
|
|
217
|
+
// 检查对象是否为全局对象
|
|
218
|
+
const isGlobal = object === globalThis ||
|
|
219
|
+
(typeof window !== 'undefined' && object === window) ||
|
|
220
|
+
(typeof global !== 'undefined' && object === global) ||
|
|
221
|
+
(typeof self !== 'undefined' && object === self);
|
|
222
|
+
if (isGlobal) {
|
|
223
|
+
throw new ExpressionError(JSON.stringify(node), 'Access to global object is not allowed in expressions', ErrorCodes.EXPRESSION_UNSAFE_ACCESS);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (member.computed) {
|
|
227
|
+
// 计算属性:obj[key]
|
|
228
|
+
const key = evaluate(member.property);
|
|
229
|
+
return object[key];
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// 静态属性:obj.prop
|
|
233
|
+
const prop = member.property.name;
|
|
234
|
+
return object[prop];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
case 'OptionalMemberExpression': {
|
|
238
|
+
const member = node;
|
|
239
|
+
const object = evaluate(member.object);
|
|
240
|
+
// null/undefined 检查(可选链特性)
|
|
241
|
+
if (object == null) {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
// 禁止访问全局对象(检查对象是否为全局对象)
|
|
245
|
+
if (!allowGlobals) {
|
|
246
|
+
// 检查对象是否为全局对象
|
|
247
|
+
const isGlobal = object === globalThis ||
|
|
248
|
+
(typeof window !== 'undefined' && object === window) ||
|
|
249
|
+
(typeof global !== 'undefined' && object === global) ||
|
|
250
|
+
(typeof self !== 'undefined' && object === self);
|
|
251
|
+
if (isGlobal) {
|
|
252
|
+
throw new ExpressionError(JSON.stringify(node), 'Access to global object is not allowed in expressions', ErrorCodes.EXPRESSION_UNSAFE_ACCESS);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (member.computed) {
|
|
256
|
+
// 计算属性:obj?.[key]
|
|
257
|
+
const key = evaluate(member.property);
|
|
258
|
+
return object?.[key];
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// 静态属性:obj?.prop
|
|
262
|
+
const prop = member.property.name;
|
|
263
|
+
return object?.[prop];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
case 'BinaryExpression': {
|
|
267
|
+
const binary = node;
|
|
268
|
+
const left = evaluate(binary.left);
|
|
269
|
+
const right = evaluate(binary.right);
|
|
270
|
+
const operator = binary.operator;
|
|
271
|
+
// 类型安全的二元运算
|
|
272
|
+
switch (operator) {
|
|
273
|
+
case '+': return left + right;
|
|
274
|
+
case '-': return left - right;
|
|
275
|
+
case '*': return left * right;
|
|
276
|
+
case '/': return left / right;
|
|
277
|
+
case '%': return left % right;
|
|
278
|
+
case '**': return left ** right;
|
|
279
|
+
case '==': return left == right;
|
|
280
|
+
case '!=': return left != right;
|
|
281
|
+
case '===': return left === right;
|
|
282
|
+
case '!==': return left !== right;
|
|
283
|
+
case '<': return left < right;
|
|
284
|
+
case '<=': return left <= right;
|
|
285
|
+
case '>': return left > right;
|
|
286
|
+
case '>=': return left >= right;
|
|
287
|
+
case '<<': return left << right;
|
|
288
|
+
case '>>': return left >> right;
|
|
289
|
+
case '>>>': return left >>> right;
|
|
290
|
+
case '&': return left & right;
|
|
291
|
+
case '|': return left | right;
|
|
292
|
+
case '^': return left ^ right;
|
|
293
|
+
case 'in': return left in right;
|
|
294
|
+
case 'instanceof': {
|
|
295
|
+
// instanceof 需要检查 right 是否为构造函数
|
|
296
|
+
if (typeof right !== 'function' && typeof right !== 'object') {
|
|
297
|
+
throw new ExpressionError(JSON.stringify(node), 'Right-hand side of instanceof must be a constructor', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
298
|
+
}
|
|
299
|
+
return left instanceof right;
|
|
300
|
+
}
|
|
301
|
+
default:
|
|
302
|
+
throw new ExpressionError(JSON.stringify(node), `Unsupported binary operator: ${operator}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
case 'LogicalExpression': {
|
|
306
|
+
const logical = node;
|
|
307
|
+
const left = evaluate(logical.left);
|
|
308
|
+
const operator = logical.operator;
|
|
309
|
+
if (operator === '&&') {
|
|
310
|
+
return left && evaluate(logical.right);
|
|
311
|
+
}
|
|
312
|
+
else if (operator === '||') {
|
|
313
|
+
return left || evaluate(logical.right);
|
|
314
|
+
}
|
|
315
|
+
else if (operator === '??') {
|
|
316
|
+
return left ?? evaluate(logical.right);
|
|
317
|
+
}
|
|
318
|
+
throw new ExpressionError(JSON.stringify(node), `Unsupported logical operator: ${operator}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
319
|
+
}
|
|
320
|
+
case 'UnaryExpression': {
|
|
321
|
+
const unary = node;
|
|
322
|
+
const argument = evaluate(unary.argument);
|
|
323
|
+
const operator = unary.operator;
|
|
324
|
+
switch (operator) {
|
|
325
|
+
case '+': return +argument;
|
|
326
|
+
case '-': return -argument;
|
|
327
|
+
case '!': return !argument;
|
|
328
|
+
case '~': return ~argument;
|
|
329
|
+
case 'typeof': return typeof argument;
|
|
330
|
+
case 'void': return void argument;
|
|
331
|
+
case 'delete': throw new ExpressionError(JSON.stringify(node), 'delete operator is not allowed', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
332
|
+
default:
|
|
333
|
+
throw new ExpressionError(JSON.stringify(node), `Unsupported unary operator: ${operator}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
case 'ConditionalExpression': {
|
|
337
|
+
const conditional = node;
|
|
338
|
+
const test = evaluate(conditional.test);
|
|
339
|
+
return test ? evaluate(conditional.consequent) : evaluate(conditional.alternate);
|
|
340
|
+
}
|
|
341
|
+
// NullishCoalescingExpression 在 @babel/types 中可能不存在
|
|
342
|
+
// 使用 LogicalExpression 处理 ?? 运算符(operator === '??')
|
|
343
|
+
// 如果类型系统支持,可以添加此 case
|
|
344
|
+
case 'CallExpression': {
|
|
345
|
+
const call = node;
|
|
346
|
+
const callee = call.callee;
|
|
347
|
+
// 获取函数名和对象
|
|
348
|
+
let funcName;
|
|
349
|
+
let funcObj;
|
|
350
|
+
let callContext = null; // 函数调用的上下文(this)
|
|
351
|
+
if (callee.type === 'Identifier') {
|
|
352
|
+
// 直接函数调用:String(), Math.max()
|
|
353
|
+
funcName = callee.name;
|
|
354
|
+
// 检查是否为全局对象(禁止访问)
|
|
355
|
+
if (!isSafePropertyAccess(funcName, ctx, { allowGlobals })) {
|
|
356
|
+
throw new ExpressionError(funcName, `Access to global "${funcName}" is not allowed in expressions`, 'UNSAFE_ACCESS');
|
|
357
|
+
}
|
|
358
|
+
// 从上下文或全局获取函数
|
|
359
|
+
funcObj = ctx[funcName] ?? globalThis[funcName];
|
|
360
|
+
callContext = null; // 全局函数调用,this 为 null
|
|
361
|
+
}
|
|
362
|
+
else if (callee.type === 'MemberExpression') {
|
|
363
|
+
// 成员函数调用:Math.max(), Array.isArray(), array.slice()
|
|
364
|
+
const member = callee;
|
|
365
|
+
const obj = evaluate(member.object);
|
|
366
|
+
// null/undefined 检查
|
|
367
|
+
if (obj == null) {
|
|
368
|
+
throw new ExpressionError(JSON.stringify(node), `Cannot call method on ${obj === null ? 'null' : 'undefined'}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
369
|
+
}
|
|
370
|
+
// 检查对象是否为全局对象(禁止访问)
|
|
371
|
+
if (!allowGlobals) {
|
|
372
|
+
// 检查对象是否为全局对象
|
|
373
|
+
const isGlobal = obj === globalThis ||
|
|
374
|
+
(typeof window !== 'undefined' && obj === window) ||
|
|
375
|
+
(typeof global !== 'undefined' && obj === global) ||
|
|
376
|
+
(typeof self !== 'undefined' && obj === self);
|
|
377
|
+
if (isGlobal) {
|
|
378
|
+
throw new ExpressionError(JSON.stringify(node), 'Access to global object is not allowed in expressions', ErrorCodes.EXPRESSION_UNSAFE_ACCESS);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// 检查是否是数组实例方法调用(安全的只读方法)
|
|
382
|
+
if (Array.isArray(obj) && !member.computed && member.property.type === 'Identifier') {
|
|
383
|
+
const methodName = member.property.name;
|
|
384
|
+
if (SAFE_ARRAY_METHODS.has(methodName)) {
|
|
385
|
+
// 这是安全的数组方法,直接使用
|
|
386
|
+
funcObj = obj[methodName];
|
|
387
|
+
funcName = `Array.${methodName}`; // 用于标识和错误信息
|
|
388
|
+
callContext = obj; // 数组方法调用,this 为数组本身
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
// 不是安全的数组方法,需要检查白名单
|
|
392
|
+
const objName = member.object.type === 'Identifier'
|
|
393
|
+
? member.object.name
|
|
394
|
+
: String(obj);
|
|
395
|
+
funcName = `${objName}.${methodName}`;
|
|
396
|
+
funcObj = obj?.[methodName];
|
|
397
|
+
callContext = obj;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// 非数组对象,正常处理
|
|
402
|
+
// 获取对象标识符名称(如果是 Identifier)
|
|
403
|
+
let objName = '';
|
|
404
|
+
if (member.object.type === 'Identifier') {
|
|
405
|
+
objName = member.object.name;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// 对于其他情况,使用字符串表示,但可能不在白名单内
|
|
409
|
+
objName = String(obj);
|
|
410
|
+
}
|
|
411
|
+
if (member.computed) {
|
|
412
|
+
const prop = evaluate(member.property);
|
|
413
|
+
funcName = `${objName}[${String(prop)}]`;
|
|
414
|
+
funcObj = obj?.[prop];
|
|
415
|
+
callContext = obj;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
const prop = member.property.name;
|
|
419
|
+
funcName = `${objName}.${prop}`;
|
|
420
|
+
funcObj = obj?.[prop];
|
|
421
|
+
callContext = obj;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
throw new ExpressionError(JSON.stringify(node), 'Invalid function call: only Identifier and MemberExpression are allowed', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
427
|
+
}
|
|
428
|
+
// 检查白名单
|
|
429
|
+
// 如果是数组方法(funcName 以 Array. 开头且方法在安全列表中),则允许
|
|
430
|
+
const isArrayMethod = funcName.startsWith('Array.') && SAFE_ARRAY_METHODS.has(funcName.split('.')[1]);
|
|
431
|
+
const isWhitelisted = isArrayMethod ||
|
|
432
|
+
WHITELISTED_FUNCTIONS.has(funcName) ||
|
|
433
|
+
WHITELISTED_GLOBALS.has(funcName.split('.')[0]) ||
|
|
434
|
+
RUNTIME_HELPERS[funcName] !== undefined;
|
|
435
|
+
if (!allowGlobals && !isWhitelisted) {
|
|
436
|
+
throw new ExpressionError(funcName, `Function "${funcName}" is not in whitelist`, ErrorCodes.EXPRESSION_FUNCTION_NOT_WHITELISTED);
|
|
437
|
+
}
|
|
438
|
+
// 执行函数调用
|
|
439
|
+
const args = call.arguments.map(arg => {
|
|
440
|
+
if (arg.type === 'SpreadElement') {
|
|
441
|
+
throw new ExpressionError(JSON.stringify(node), 'Spread operator is not allowed', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
442
|
+
}
|
|
443
|
+
return evaluate(arg);
|
|
444
|
+
});
|
|
445
|
+
// 调用运行时辅助函数
|
|
446
|
+
if (RUNTIME_HELPERS[funcName]) {
|
|
447
|
+
return RUNTIME_HELPERS[funcName](...args);
|
|
448
|
+
}
|
|
449
|
+
// 调用白名单全局函数或数组方法
|
|
450
|
+
if (typeof funcObj === 'function') {
|
|
451
|
+
try {
|
|
452
|
+
// 如果有上下文(如数组方法),使用 apply;否则直接调用
|
|
453
|
+
return callContext !== null
|
|
454
|
+
? funcObj.apply(callContext, args)
|
|
455
|
+
: funcObj(...args);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
459
|
+
throw new ExpressionError(funcName, `Error calling "${funcName}": ${errorMessage}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
throw new ExpressionError(funcName, `"${funcName}" is not a function${funcObj === undefined ? ' (undefined)' : funcObj === null ? ' (null)' : ''}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
463
|
+
}
|
|
464
|
+
case 'ArrayExpression': {
|
|
465
|
+
const array = node;
|
|
466
|
+
return array.elements.map(el => {
|
|
467
|
+
if (el == null)
|
|
468
|
+
return null;
|
|
469
|
+
if (el.type === 'SpreadElement') {
|
|
470
|
+
throw new ExpressionError(JSON.stringify(node), 'Spread operator is not allowed', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
471
|
+
}
|
|
472
|
+
return evaluate(el);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
case 'ObjectExpression': {
|
|
476
|
+
const object = node;
|
|
477
|
+
const result = {};
|
|
478
|
+
for (const prop of object.properties) {
|
|
479
|
+
if (prop.type === 'ObjectMethod' || prop.type === 'SpreadElement') {
|
|
480
|
+
throw new ExpressionError(JSON.stringify(node), 'Object methods and spread are not allowed', ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
481
|
+
}
|
|
482
|
+
const key = prop.key.type === 'Identifier'
|
|
483
|
+
? prop.key.name
|
|
484
|
+
: String(evaluate(prop.key));
|
|
485
|
+
result[key] = evaluate(prop.value);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
case 'TemplateLiteral': {
|
|
490
|
+
const template = node;
|
|
491
|
+
let result = '';
|
|
492
|
+
for (let i = 0; i < template.quasis.length; i++) {
|
|
493
|
+
result += template.quasis[i].value.cooked;
|
|
494
|
+
if (i < template.expressions.length) {
|
|
495
|
+
result += String(evaluate(template.expressions[i]));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
default:
|
|
501
|
+
throw new ExpressionError(JSON.stringify(node), `Unsupported node type: ${node.type}`, ErrorCodes.EXPRESSION_EVALUATION_ERROR);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return evaluate(ast);
|
|
505
|
+
}
|
|
506
|
+
//# sourceMappingURL=evaluator.js.map
|