• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / deck.gl / 15161017841

21 May 2025 11:30AM UTC coverage: 91.477% (-0.2%) from 91.64%
15161017841

Pull #9641

github

web-flow
Merge de533b34f into 0dc352422
Pull Request #9641: chore(json): Typing improvements

6804 of 7474 branches covered (91.04%)

Branch coverage included in aggregate %.

143 of 152 new or added lines in 12 files covered. (94.08%)

7 existing lines in 2 files now uncovered.

56050 of 61236 relevant lines covered (91.53%)

14398.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.81
/modules/json/src/utils/expression-eval.ts
1
// deck.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import jsep from 'jsep';
1!
6

1✔
7
/**
1✔
8
 * Sources:
1✔
9
 * - Copyright (c) 2013 Stephen Oney, http://jsep.from.so/, MIT License
1✔
10
 * - Copyright (c) 2023 Don McCurdy, https://github.com/donmccurdy/expression-eval, MIT License
1✔
11
 */
1✔
12

1✔
13
// Default operator precedence from https://github.com/EricSmekens/jsep/blob/master/src/jsep.js#L55
1✔
14
const DEFAULT_PRECEDENCE = {
1✔
15
  '||': 1,
1✔
16
  '&&': 2,
1✔
17
  '|': 3,
1✔
18
  '^': 4,
1✔
19
  '&': 5,
1✔
20
  '==': 6,
1✔
21
  '!=': 6,
1✔
22
  '===': 6,
1✔
23
  '!==': 6,
1✔
24
  '<': 7,
1✔
25
  '>': 7,
1✔
26
  '<=': 7,
1✔
27
  '>=': 7,
1✔
28
  '<<': 8,
1✔
29
  '>>': 8,
1✔
30
  '>>>': 8,
1✔
31
  '+': 9,
1✔
32
  '-': 9,
1✔
33
  '*': 10,
1✔
34
  '/': 10,
1✔
35
  '%': 10
1✔
36
};
1✔
37

1✔
38
const binops = {
1✔
39
  '||': (a: unknown, b: unknown) => {
1✔
40
    return a || b;
×
41
  },
×
42
  '&&': (a: unknown, b: unknown) => {
1✔
43
    return a && b;
×
44
  },
×
45
  '|': (a: number, b: number) => {
1✔
46
    return a | b;
2✔
47
  },
2✔
48
  '^': (a: number, b: number) => {
1✔
49
    return a ^ b;
2✔
50
  },
2✔
51
  '&': (a: number, b: number) => {
1✔
52
    return a & b;
2✔
53
  },
2✔
54
  '==': (a: unknown, b: unknown) => {
1✔
55
    // eslint-disable-next-line eqeqeq
2✔
56
    return a == b;
2✔
57
  },
2✔
58
  '!=': (a: unknown, b: unknown) => {
1✔
59
    // eslint-disable-next-line eqeqeq
2✔
60
    return a != b;
2✔
61
  },
2✔
62
  '===': (a: unknown, b: unknown) => {
1✔
63
    return a === b;
2✔
64
  },
2✔
65
  '!==': (a: unknown, b: unknown) => {
1✔
66
    return a !== b;
2✔
67
  },
2✔
68
  '<': (a: number | string, b: number | string) => {
1✔
69
    return a < b;
2✔
70
  },
2✔
71
  '>': (a: number | string, b: number | string) => {
1✔
72
    return a > b;
2✔
73
  },
2✔
74
  '<=': (a: number | string, b: number | string) => {
1✔
75
    return a <= b;
2✔
76
  },
2✔
77
  '>=': (a: number | string, b: number | string) => {
1✔
78
    return a >= b;
2✔
79
  },
2✔
80
  '<<': (a: number, b: number) => {
1✔
81
    return a << b;
2✔
82
  },
2✔
83
  '>>': (a: number, b: number) => {
1✔
84
    return a >> b;
2✔
85
  },
2✔
86
  '>>>': (a: number, b: number) => {
1✔
87
    return a >>> b;
2✔
88
  },
2✔
89
  '+': (a: unknown, b: unknown) => {
1✔
90
    // @ts-expect-error
14✔
91
    return a + b;
14✔
92
  },
14✔
93
  '-': (a: number, b: number) => {
1✔
94
    return a - b;
2✔
95
  },
2✔
96
  '*': (a: number, b: number) => {
1✔
97
    return a * b;
4✔
98
  },
4✔
99
  '/': (a: number, b: number) => {
1✔
100
    return a / b;
2✔
101
  },
2✔
102
  '%': (a: number, b: number) => {
1✔
103
    return a % b;
2✔
104
  }
2✔
105
};
1✔
106

1✔
107
const unops = {
1✔
108
  '-': (a: number) => {
1✔
109
    return -a;
4✔
110
  },
4✔
111
  '+': (a: unknown) => {
1✔
112
    // @ts-expect-error
4✔
113
    // eslint-disable-next-line no-implicit-coercion
4✔
114
    return +a;
4✔
115
  },
4✔
116
  '~': (a: number) => {
1✔
117
    return ~a;
2✔
118
  },
2✔
119
  '!': (a: unknown) => {
1✔
120
    return !a;
6✔
121
  }
6✔
122
};
1✔
123

1✔
124
declare type operand = number | string;
1✔
125
declare type unaryCallback = (a: operand) => operand;
1✔
126
declare type binaryCallback = (a: operand, b: operand) => operand;
1✔
127

1✔
128
type AnyExpression =
1✔
129
  | jsep.ArrayExpression
1✔
130
  | jsep.BinaryExpression
1✔
131
  | jsep.MemberExpression
1✔
132
  | jsep.CallExpression
1✔
133
  | jsep.ConditionalExpression
1✔
134
  | jsep.Identifier
1✔
135
  | jsep.Literal
1✔
136
  | jsep.LogicalExpression
1✔
137
  | jsep.ThisExpression
1✔
138
  | jsep.UnaryExpression;
1✔
139

1✔
140
function evaluateArray(list, context) {
10✔
141
  return list.map(function (v) {
10✔
142
    return evaluate(v, context);
19✔
143
  });
10✔
144
}
10✔
145

1✔
146
async function evaluateArrayAsync(list, context) {
12✔
147
  const res = await Promise.all(list.map(v => evalAsync(v, context)));
12✔
148
  return res;
12✔
149
}
12✔
150

1✔
151
function evaluateMember(node: jsep.MemberExpression, context: object) {
21✔
152
  const object = evaluate(node.object, context);
21✔
153
  let key: string;
21✔
154
  if (node.computed) {
21✔
155
    key = evaluate(node.property, context);
13✔
156
  } else {
21✔
157
    key = (node.property as jsep.Identifier).name;
8✔
158
  }
8✔
159
  if (/^__proto__|prototype|constructor$/.test(key)) {
21✔
160
    throw Error(`Access to member "${key}" disallowed.`);
9✔
161
  }
9✔
162
  return [object, object[key]];
12✔
163
}
12✔
164

1✔
165
async function evaluateMemberAsync(node: jsep.MemberExpression, context: object) {
12✔
166
  const object = await evalAsync(node.object, context);
12✔
167
  let key: string;
12✔
168
  if (node.computed) {
12✔
169
    key = await evalAsync(node.property, context);
7✔
170
  } else {
12✔
171
    key = (node.property as jsep.Identifier).name;
5✔
172
  }
5✔
173
  if (/^__proto__|prototype|constructor$/.test(key)) {
12!
174
    throw Error(`Access to member "${key}" disallowed.`);
×
175
  }
×
176
  return [object, object[key]];
12✔
177
}
12✔
178

1✔
179
// eslint-disable-next-line complexity
1✔
180
function evaluate(_node: jsep.Expression, context: object) {
214✔
181
  const node = _node as AnyExpression;
214✔
182

214✔
183
  switch (node.type) {
214✔
184
    case 'ArrayExpression':
214✔
185
      return evaluateArray(node.elements, context);
6✔
186

214✔
187
    case 'BinaryExpression':
214✔
188
      return binops[node.operator](evaluate(node.left, context), evaluate(node.right, context));
30✔
189

214✔
190
    case 'CallExpression':
214✔
191
      let caller: object;
4✔
192
      let fn: Function;
4✔
193
      let assign: unknown[];
4✔
194
      if (node.callee.type === 'MemberExpression') {
4✔
195
        assign = evaluateMember(node.callee as jsep.MemberExpression, context);
1✔
196
        caller = assign[0] as object;
1✔
197
        fn = assign[1] as Function;
1✔
198
      } else {
4✔
199
        fn = evaluate(node.callee, context);
3✔
200
      }
3✔
201
      if (typeof fn !== 'function') {
4!
202
        return undefined;
×
203
      }
×
204
      return fn.apply(caller!, evaluateArray(node.arguments, context));
4✔
205

214✔
206
    case 'ConditionalExpression':
214✔
207
      return evaluate(node.test, context)
4✔
208
        ? evaluate(node.consequent, context)
3✔
209
        : evaluate(node.alternate, context);
1✔
210

214✔
211
    case 'Identifier':
214✔
212
      return context[node.name];
33✔
213

214✔
214
    case 'Literal':
214✔
215
      return node.value;
100✔
216

214✔
217
    case 'LogicalExpression':
214✔
218
      if (node.operator === '||') {
7✔
219
        return evaluate(node.left, context) || evaluate(node.right, context);
4✔
220
      } else if (node.operator === '&&') {
7✔
221
        return evaluate(node.left, context) && evaluate(node.right, context);
3✔
222
      }
3!
223
      return binops[node.operator](evaluate(node.left, context), evaluate(node.right, context));
×
224

214✔
225
    case 'MemberExpression':
214✔
226
      return evaluateMember(node, context)[1];
20✔
227

214✔
228
    case 'ThisExpression':
214✔
229
      return context;
1✔
230

214✔
231
    case 'UnaryExpression':
214✔
232
      return unops[node.operator](evaluate(node.argument, context));
9✔
233

214✔
234
    default:
214!
UNCOV
235
      return undefined;
×
236
  }
214!
237
}
×
238

1✔
239
// eslint-disable-next-line complexity
1✔
240
async function evalAsync(_node: jsep.Expression, context: object) {
198✔
241
  const node = _node as AnyExpression;
198✔
242

198✔
243
  // Brackets used for some case blocks here, to avoid edge cases related to variable hoisting.
198✔
244
  // See: https://stackoverflow.com/questions/57759348/const-and-let-variable-shadowing-in-a-switch-statement
198✔
245
  switch (node.type) {
198✔
246
    case 'ArrayExpression':
198✔
247
      return await evaluateArrayAsync(node.elements, context);
6✔
248

198✔
249
    case 'BinaryExpression': {
198✔
250
      const [left, right] = await Promise.all([
30✔
251
        evalAsync(node.left, context),
30✔
252
        evalAsync(node.right, context)
30✔
253
      ]);
30✔
254
      return binops[node.operator](left, right);
30✔
255
    }
30✔
256

198✔
257
    case 'CallExpression': {
198✔
258
      let caller: object;
6✔
259
      let fn: Function;
6✔
260
      let assign: unknown[];
6✔
261
      if (node.callee.type === 'MemberExpression') {
6✔
262
        assign = await evaluateMemberAsync(node.callee as jsep.MemberExpression, context);
1✔
263
        caller = assign[0] as object;
1✔
264
        fn = assign[1] as Function;
1✔
265
      } else {
6✔
266
        fn = await evalAsync(node.callee, context);
5✔
267
      }
5✔
268
      if (typeof fn !== 'function') {
6!
269
        return undefined;
×
270
      }
×
271
      return await fn.apply(caller!, await evaluateArrayAsync(node.arguments, context));
6✔
272
    }
6✔
273

198✔
274
    case 'ConditionalExpression':
198✔
275
      return (await evalAsync(node.test, context))
4✔
276
        ? await evalAsync(node.consequent, context)
3✔
277
        : await evalAsync(node.alternate, context);
1✔
278

198✔
279
    case 'Identifier':
198✔
280
      return context[node.name];
27✔
281

198✔
282
    case 'Literal':
198✔
283
      return node.value;
97✔
284

198✔
285
    case 'LogicalExpression': {
198✔
286
      if (node.operator === '||') {
7✔
287
        return (await evalAsync(node.left, context)) || (await evalAsync(node.right, context));
4✔
288
      } else if (node.operator === '&&') {
7✔
289
        return (await evalAsync(node.left, context)) && (await evalAsync(node.right, context));
3✔
290
      }
3!
291

×
292
      const [left, right] = await Promise.all([
×
293
        evalAsync(node.left, context),
×
294
        evalAsync(node.right, context)
×
295
      ]);
×
296

×
297
      return binops[node.operator](left, right);
×
298
    }
×
299

198✔
300
    case 'MemberExpression':
198✔
301
      return (await evaluateMemberAsync(node, context))[1];
11✔
302

198✔
303
    case 'ThisExpression':
198✔
304
      return context;
1✔
305

198✔
306
    case 'UnaryExpression':
198✔
307
      return unops[node.operator](await evalAsync(node.argument, context));
9✔
308

198✔
309
    default:
198!
310
      return undefined;
×
311
  }
198✔
312
}
198✔
313

1✔
314
function compile(expression: string | jsep.Expression): (context: object) => any {
71✔
315
  return evaluate.bind(null, jsep(expression));
71✔
316
}
71✔
317

1✔
318
function compileAsync(expression: string | jsep.Expression): (context: object) => Promise<any> {
64✔
319
  return evalAsync.bind(null, jsep(expression));
64✔
320
}
64✔
321

1✔
322
// Added functions to inject Custom Unary Operators (and override existing ones)
1✔
323
function addUnaryOp(operator: string, _function: unaryCallback): void {
1✔
324
  jsep.addUnaryOp(operator);
1✔
325
  unops[operator] = _function;
1✔
326
}
1✔
327

1✔
328
// Added functions to inject Custom Binary Operators (and override existing ones)
1✔
329
function addBinaryOp(
2✔
330
  operator: string,
2✔
331
  precedenceOrFn: number | binaryCallback,
2✔
332
  _function: binaryCallback
2✔
333
): void {
2✔
334
  if (_function) {
2✔
335
    jsep.addBinaryOp(operator, precedenceOrFn as number);
1✔
336
    binops[operator] = _function;
1✔
337
  } else {
1✔
338
    jsep.addBinaryOp(operator, DEFAULT_PRECEDENCE[operator] || 1);
1✔
339
    binops[operator] = precedenceOrFn;
1✔
340
  }
1✔
341
}
2✔
342

1✔
343
export {jsep as parse, evaluate as eval, evalAsync, compile, compileAsync, addUnaryOp, addBinaryOp};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc