diff --git a/packages/core/evaluators/src/server/__tests__/index.test.ts b/packages/core/evaluators/src/server/__tests__/index.test.ts new file mode 100644 index 000000000..1683883f6 --- /dev/null +++ b/packages/core/evaluators/src/server/__tests__/index.test.ts @@ -0,0 +1,42 @@ +import evaluators from '..'; + +const mathEval = evaluators.get('math.js'); +const formulaEval = evaluators.get('formula.js'); + + + +describe('evaluate', () => { + it('reference null or undefined', () => { + const result = formulaEval('{{a.b}}', { a: null }); + expect(result).toBeNull(); + }); + + it('function result string with quote', () => { + const result = formulaEval('{{a}}', { a() { return "I'm done." } }); + expect(result).toBe("I'm done."); + }); + + it('function result number', () => { + const result = formulaEval('{{a}}', { a() { return 1 } }); + expect(result).toBe(1); + }); + + it('number path to array item 0 (math.js)', () => { + expect(() => mathEval('{{a.0}}', { a: [1, 2, 3] })).toThrow(); + }); + + it('number path to array item 1 (math.js)', () => { + const result = mathEval('{{a}}[1]', { a: [1, 2, 3] }); + expect(result).toBe(1); + }); + + it('number path to array item 1 (math.js)', () => { + const result = mathEval('{{a.1}}', { a: [1, 2, 3] }); + expect(result).toBe(1); + }); + + it('number path to object member 0 (math.js)', () => { + const result = mathEval('{{a.1}}', { a: { 1: 1 } }); + expect(result).toBe(1); + }); +}); diff --git a/packages/core/evaluators/src/utils/index.ts b/packages/core/evaluators/src/utils/index.ts index c86fcc17d..1616bc59f 100644 --- a/packages/core/evaluators/src/utils/index.ts +++ b/packages/core/evaluators/src/utils/index.ts @@ -9,7 +9,7 @@ export type Evaluator = (expression: string, scope?: Scope) => any; function appendArrayColumn(scope, key) { const paths = key.split('.'); let data = scope; - for (let p = 0; p < paths.length; p++) { + for (let p = 0; p < paths.length && data != null; p++) { const path = paths[p]; const isIndex = path.match(/^\d+$/); if (Array.isArray(data) && !isIndex && !data[path]) { @@ -19,13 +19,43 @@ function appendArrayColumn(scope, key) { } } +function replaceNumberIndex(path: string, scope: Scope): string { + const segments = path.split('.'); + const paths = []; + + for (let i = 0; i < segments.length; i++) { + const p = segments[i]; + if (p.match(/^\d+$/)) { + paths.push(Array.isArray(get(scope, segments.slice(0, i))) ? `[${p}]` : `["${p}"]`); + } else { + if (i) { + paths.push('.', p); + } else { + paths.push(p); + } + } + } + + return paths.join(''); +} + export function evaluate(this: Evaluator, expression: string, scope: Scope = {}) { const context = cloneDeep(scope); const exp = expression.trim().replace(/{{\s*([^{}]+)\s*}}/g, (_, v) => { appendArrayColumn(context, v); + const item = get(context, v); - const key = v.replace(/\.(\d+)/g, '["$1"]'); - return ` ${typeof item === 'function' ? item() : key} `; + + if (item == null) { + return 'null'; + } + + if (typeof item === 'function') { + const result = item(); + return typeof result === 'string' ? `'${result.replace(/'/g, "\\'")}'` : result; + } + + return replaceNumberIndex(v, context); }); return this(exp, context); } diff --git a/packages/plugins/workflow/src/server/__tests__/instructions/calculation.test.ts b/packages/plugins/workflow/src/server/__tests__/instructions/calculation.test.ts index 42c14b479..77b35c025 100644 --- a/packages/plugins/workflow/src/server/__tests__/instructions/calculation.test.ts +++ b/packages/plugins/workflow/src/server/__tests__/instructions/calculation.test.ts @@ -148,7 +148,7 @@ describe('workflow > instructions > calculation', () => { const [execution] = await workflow.getExecutions(); const [job] = await execution.getJobs(); - expect(job.result).toBe('a $context.data.title '); + expect(job.result).toBe('a$context.data.title'); }); it('text', async () => {