@fincity/kirun-js 2.16.2 → 3.0.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__/engine/runtime/expression/ExpressionEvaluatorLengthSubtractionBugTest.ts +384 -0
- package/__tests__/engine/runtime/expression/ExpressionEvaluatorLengthSubtractionTest.ts +338 -0
- package/__tests__/engine/runtime/expression/ExpressionTest.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +56 -39
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/engine/function/system/context/SetFunction.ts +226 -75
- package/src/engine/runtime/expression/Expression.ts +267 -71
- package/src/engine/runtime/expression/ExpressionEvaluator.ts +317 -22
- package/src/engine/runtime/expression/ExpressionLexer.ts +365 -0
- package/src/engine/runtime/expression/ExpressionParser.ts +541 -0
- package/src/engine/runtime/expression/ExpressionParserDebug.ts +21 -0
- package/src/engine/runtime/expression/Operation.ts +1 -0
- package/src/engine/runtime/expression/tokenextractor/ObjectValueSetterExtractor.ts +134 -31
- package/src/engine/runtime/expression/tokenextractor/TokenValueExtractor.ts +32 -8
- package/src/engine/util/LinkedList.ts +1 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { MapUtil, TokenValueExtractor } from '../../../../src';
|
|
2
|
+
import { ExpressionEvaluator } from '../../../../src/engine/runtime/expression/ExpressionEvaluator';
|
|
3
|
+
|
|
4
|
+
class TestTokenValueExtractor extends TokenValueExtractor {
|
|
5
|
+
private readonly store: any;
|
|
6
|
+
|
|
7
|
+
constructor(store: any) {
|
|
8
|
+
super();
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
protected getValueInternal(token: string): any {
|
|
13
|
+
return this.retrieveElementFrom(token, token.split('.'), 1, this.store);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getPrefix(): string {
|
|
17
|
+
return 'Page.';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getStore(): any {
|
|
21
|
+
return this.store;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('Expression Evaluator - Length Subtraction Bug Reproduction', () => {
|
|
26
|
+
/**
|
|
27
|
+
* These tests reproduce the bug where:
|
|
28
|
+
* Page.mainFilterCondition.condition.conditions[0].conditions.length-1
|
|
29
|
+
* fails with: "Cannot evaluate expression [object Object] - 1"
|
|
30
|
+
*
|
|
31
|
+
* The issue occurs when accessing .length on an object/array and then subtracting.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
describe('Bug reproduction - nested array length subtraction', () => {
|
|
35
|
+
test('nested array access with length-1 (exact failing case from bug report)', () => {
|
|
36
|
+
const ttv = new TestTokenValueExtractor({
|
|
37
|
+
mainFilterCondition: {
|
|
38
|
+
condition: {
|
|
39
|
+
conditions: [
|
|
40
|
+
{
|
|
41
|
+
conditions: [1, 2, 3, 4],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
conditions: [5, 6],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// This is the exact expression from the bug report
|
|
52
|
+
// NOTE: This test currently PASSES, suggesting the bug may be data-dependent
|
|
53
|
+
// or related to specific object structures with length properties
|
|
54
|
+
const ev = new ExpressionEvaluator(
|
|
55
|
+
'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Expected result: 3 (array length 4 - 1)
|
|
59
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
60
|
+
expect(result).toBe(3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('nested array access with length - 1 (with spaces)', () => {
|
|
64
|
+
const ttv = new TestTokenValueExtractor({
|
|
65
|
+
mainFilterCondition: {
|
|
66
|
+
condition: {
|
|
67
|
+
conditions: [
|
|
68
|
+
{
|
|
69
|
+
conditions: [1, 2, 3, 4],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const ev = new ExpressionEvaluator(
|
|
77
|
+
'Page.mainFilterCondition.condition.conditions[0].conditions.length - 1',
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(() => {
|
|
81
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
82
|
+
}).not.toThrow();
|
|
83
|
+
|
|
84
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
85
|
+
expect(result).toBe(3);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Edge cases - object with length property', () => {
|
|
90
|
+
test('FIXED: object with length property that is an object should return Object.keys length', () => {
|
|
91
|
+
const ttv = new TestTokenValueExtractor({
|
|
92
|
+
obj: {
|
|
93
|
+
length: { nested: 'object' }, // Object has a length property
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// When accessing obj.length, it should return Object.keys(obj).length
|
|
98
|
+
// (ignoring the length property on the object)
|
|
99
|
+
// Then obj.length - 1 should work correctly
|
|
100
|
+
const ev = new ExpressionEvaluator('Page.obj.length-1');
|
|
101
|
+
|
|
102
|
+
// Should return Object.keys(obj).length - 1 = 1 - 1 = 0
|
|
103
|
+
// (obj has one key: 'length')
|
|
104
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
105
|
+
expect(result).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('Nested object with length property in path', () => {
|
|
109
|
+
// This might be closer to the actual bug scenario
|
|
110
|
+
const ttv = new TestTokenValueExtractor({
|
|
111
|
+
mainFilterCondition: {
|
|
112
|
+
condition: {
|
|
113
|
+
conditions: [
|
|
114
|
+
{
|
|
115
|
+
conditions: [1, 2, 3, 4],
|
|
116
|
+
length: { some: 'object' }, // Object has length property
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// If conditions[0] has a length property, accessing conditions.length might return that
|
|
124
|
+
const ev = new ExpressionEvaluator(
|
|
125
|
+
'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// This might fail if the parser gets confused
|
|
129
|
+
// The issue: conditions[0] has a length property, but we want conditions.length (array length)
|
|
130
|
+
expect(() => {
|
|
131
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
132
|
+
}).not.toThrow();
|
|
133
|
+
|
|
134
|
+
// Should still return 3 (array length 4 - 1), not affected by the length property on the parent
|
|
135
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
136
|
+
expect(result).toBe(3);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('object with length property that is a number should work', () => {
|
|
140
|
+
const ttv = new TestTokenValueExtractor({
|
|
141
|
+
obj: {
|
|
142
|
+
length: 5, // length property exists and is a number
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// When obj has a length property, it should return that value
|
|
147
|
+
const ev = new ExpressionEvaluator('Page.obj.length-1');
|
|
148
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
149
|
+
|
|
150
|
+
// Should return 5 - 1 = 4 (the length property value minus 1)
|
|
151
|
+
expect(result).toBe(4);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('array length should always work (no length property conflict)', () => {
|
|
155
|
+
const ttv = new TestTokenValueExtractor({
|
|
156
|
+
arr: [1, 2, 3, 4, 5],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const ev = new ExpressionEvaluator('Page.arr.length-1');
|
|
160
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
161
|
+
|
|
162
|
+
// Arrays always have .length, should return 5 - 1 = 4
|
|
163
|
+
expect(result).toBe(4);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Complex nested scenarios', () => {
|
|
168
|
+
test('deeply nested structure with multiple array accesses', () => {
|
|
169
|
+
const ttv = new TestTokenValueExtractor({
|
|
170
|
+
level1: {
|
|
171
|
+
level2: {
|
|
172
|
+
level3: {
|
|
173
|
+
items: [
|
|
174
|
+
{
|
|
175
|
+
subItems: ['a', 'b', 'c', 'd', 'e'],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
subItems: ['f', 'g'],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const ev = new ExpressionEvaluator(
|
|
187
|
+
'Page.level1.level2.level3.items[0].subItems.length-1',
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(() => {
|
|
191
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
192
|
+
}).not.toThrow();
|
|
193
|
+
|
|
194
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
195
|
+
expect(result).toBe(4); // 5 - 1
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('FIXED: multiple length operations in same expression should work', () => {
|
|
199
|
+
const ttv = new TestTokenValueExtractor({
|
|
200
|
+
arr1: [1, 2, 3],
|
|
201
|
+
arr2: [4, 5, 6, 7, 8],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const ev = new ExpressionEvaluator(
|
|
205
|
+
'Page.arr1.length-1 + Page.arr2.length-1',
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Should work correctly now
|
|
209
|
+
expect(() => {
|
|
210
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
211
|
+
}).not.toThrow();
|
|
212
|
+
|
|
213
|
+
// Expected result: (3-1) + (5-1) = 2 + 4 = 6
|
|
214
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
215
|
+
expect(result).toBe(6);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('length-1 used as array index', () => {
|
|
219
|
+
const ttv = new TestTokenValueExtractor({
|
|
220
|
+
items: [10, 20, 30, 40, 50],
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Access last element using length-1
|
|
224
|
+
const ev = new ExpressionEvaluator('Page.items[Page.items.length-1]');
|
|
225
|
+
|
|
226
|
+
expect(() => {
|
|
227
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
228
|
+
}).not.toThrow();
|
|
229
|
+
|
|
230
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
231
|
+
expect(result).toBe(50);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('Different data types with length', () => {
|
|
236
|
+
test('string length subtraction', () => {
|
|
237
|
+
const ttv = new TestTokenValueExtractor({
|
|
238
|
+
text: 'hello world',
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const ev = new ExpressionEvaluator('Page.text.length-1');
|
|
242
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
243
|
+
|
|
244
|
+
expect(result).toBe(10); // 11 - 1
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('empty array length minus 1', () => {
|
|
248
|
+
const ttv = new TestTokenValueExtractor({
|
|
249
|
+
empty: [],
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const ev = new ExpressionEvaluator('Page.empty.length-1');
|
|
253
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
254
|
+
|
|
255
|
+
expect(result).toBe(-1); // 0 - 1
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('object with no length property (should use Object.keys)', () => {
|
|
259
|
+
const ttv = new TestTokenValueExtractor({
|
|
260
|
+
obj: { a: 1, b: 2, c: 3 },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const ev = new ExpressionEvaluator('Page.obj.length-1');
|
|
264
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
265
|
+
|
|
266
|
+
expect(result).toBe(2); // 3 keys - 1
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('Specific bug scenarios', () => {
|
|
271
|
+
test('scenario where intermediate object has length property', () => {
|
|
272
|
+
// This test checks if having a length property on an intermediate object
|
|
273
|
+
// causes issues when accessing array.length further down the path
|
|
274
|
+
const ttv = new TestTokenValueExtractor({
|
|
275
|
+
mainFilterCondition: {
|
|
276
|
+
condition: {
|
|
277
|
+
length: { conflict: true }, // This might cause issues
|
|
278
|
+
conditions: [
|
|
279
|
+
{
|
|
280
|
+
conditions: [1, 2, 3, 4],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Accessing conditions.length should still work (it's an array)
|
|
288
|
+
const ev = new ExpressionEvaluator(
|
|
289
|
+
'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(() => {
|
|
293
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
294
|
+
}).not.toThrow();
|
|
295
|
+
|
|
296
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
297
|
+
expect(result).toBe(3);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('scenario where array element has length property', () => {
|
|
301
|
+
// What if one of the array elements has a length property?
|
|
302
|
+
const ttv = new TestTokenValueExtractor({
|
|
303
|
+
mainFilterCondition: {
|
|
304
|
+
condition: {
|
|
305
|
+
conditions: [
|
|
306
|
+
{
|
|
307
|
+
conditions: [1, 2, 3, 4],
|
|
308
|
+
length: { nested: 'value' }, // Array element has length
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const ev = new ExpressionEvaluator(
|
|
316
|
+
'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// This should still work - we're accessing conditions.length (the array)
|
|
320
|
+
// not the length property of the object
|
|
321
|
+
expect(() => {
|
|
322
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
323
|
+
}).not.toThrow();
|
|
324
|
+
|
|
325
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
326
|
+
expect(result).toBe(3);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('Expression parsing edge cases', () => {
|
|
331
|
+
test('length-1 without spaces in complex path', () => {
|
|
332
|
+
const ttv = new TestTokenValueExtractor({
|
|
333
|
+
data: {
|
|
334
|
+
nested: {
|
|
335
|
+
list: [1, 2, 3, 4, 5, 6],
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const ev = new ExpressionEvaluator('Page.data.nested.list.length-1');
|
|
341
|
+
|
|
342
|
+
expect(() => {
|
|
343
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
344
|
+
}).not.toThrow();
|
|
345
|
+
|
|
346
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
347
|
+
expect(result).toBe(5);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('length - 1 with spaces in complex path', () => {
|
|
351
|
+
const ttv = new TestTokenValueExtractor({
|
|
352
|
+
data: {
|
|
353
|
+
nested: {
|
|
354
|
+
list: [1, 2, 3, 4, 5, 6],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const ev = new ExpressionEvaluator('Page.data.nested.list.length - 1');
|
|
360
|
+
|
|
361
|
+
expect(() => {
|
|
362
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
363
|
+
}).not.toThrow();
|
|
364
|
+
|
|
365
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
366
|
+
expect(result).toBe(5);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('parentheses around length-1', () => {
|
|
370
|
+
const ttv = new TestTokenValueExtractor({
|
|
371
|
+
items: [1, 2, 3, 4],
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const ev = new ExpressionEvaluator('(Page.items.length-1)');
|
|
375
|
+
|
|
376
|
+
expect(() => {
|
|
377
|
+
ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
378
|
+
}).not.toThrow();
|
|
379
|
+
|
|
380
|
+
const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
|
|
381
|
+
expect(result).toBe(3);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|