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

TotalTechGeek / json-logic-engine / 12308415913

13 Dec 2024 02:24AM UTC coverage: 92.947% (+0.7%) from 92.254%
12308415913

push

github

TotalTechGeek
Add access to iterator values

811 of 907 branches covered (89.42%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 2 files covered. (100.0%)

21 existing lines in 1 file now uncovered.

810 of 837 relevant lines covered (96.77%)

31455.56 hits per line

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

91.39
/defaultMethods.js
1
// @ts-check
2
'use strict'
3

4
import asyncIterators from './async_iterators.js'
5
import { Sync, isSync } from './constants.js'
6
import declareSync from './utilities/declareSync.js'
7
import { build, buildString } from './compiler.js'
8
import chainingSupported from './utilities/chainingSupported.js'
9
import InvalidControlInput from './errors/InvalidControlInput.js'
10
import { splitPathMemoized } from './utilities/splitPath.js'
11

12
function isDeterministic (method, engine, buildState) {
13
  if (Array.isArray(method)) {
31,284✔
14
    return method.every((i) => isDeterministic(i, engine, buildState))
19,896✔
15
  }
16
  if (method && typeof method === 'object') {
23,580✔
17
    const func = Object.keys(method)[0]
6,162✔
18
    const lower = method[func]
6,162✔
19

20
    if (engine.isData(method, func)) return true
6,162✔
21
    if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
6,078!
22

23
    if (engine.methods[func].traverse === false) {
6,078✔
24
      return typeof engine.methods[func].deterministic === 'function'
384✔
25
        ? engine.methods[func].deterministic(lower, buildState)
26
        : engine.methods[func].deterministic
27
    }
28
    return typeof engine.methods[func].deterministic === 'function'
5,694✔
29
      ? engine.methods[func].deterministic(lower, buildState)
30
      : engine.methods[func].deterministic &&
3,474✔
31
          isDeterministic(lower, engine, buildState)
32
  }
33
  return true
17,418✔
34
}
35

36
function isSyncDeep (method, engine, buildState) {
37
  if (Array.isArray(method)) {
25,248✔
38
    return method.every((i) => isSyncDeep(i, engine, buildState))
13,800✔
39
  }
40

41
  if (method && typeof method === 'object') {
20,112✔
42
    const func = Object.keys(method)[0]
8,028✔
43
    const lower = method[func]
8,028✔
44
    if (engine.isData(method, func)) return true
8,028✔
45
    if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
7,986!
46
    if (engine.methods[func].traverse === false) return typeof engine.methods[func][Sync] === 'function' ? engine.methods[func][Sync](lower, buildState) : engine.methods[func][Sync]
7,986!
47
    return typeof engine.methods[func][Sync] === 'function' ? engine.methods[func][Sync](lower, buildState) : engine.methods[func][Sync] && isSyncDeep(lower, engine, buildState)
5,106!
48
  }
49

50
  return true
12,084✔
51
}
52

53
const defaultMethods = {
54✔
54
  '+': (data) => {
55
    if (typeof data === 'string') return +data
483,060✔
56
    if (typeof data === 'number') return +data
483,048!
57
    let res = 0
483,048✔
58
    for (let i = 0; i < data.length; i++) res += +data[i]
966,084✔
59
    return res
483,048✔
60
  },
61
  '*': (data) => {
62
    let res = 1
864✔
63
    for (let i = 0; i < data.length; i++) res *= +data[i]
1,752✔
64
    return res
864✔
65
  },
66
  '/': (data) => {
67
    let res = data[0]
300✔
68
    for (let i = 1; i < data.length; i++) res /= +data[i]
324✔
69
    return res
300✔
70
  },
71
  '-': (data) => {
72
    if (typeof data === 'string') return -data
516✔
73
    if (typeof data === 'number') return -data
504!
74
    if (data.length === 1) return -data[0]
504✔
75
    let res = data[0]
264✔
76
    for (let i = 1; i < data.length; i++) res -= +data[i]
288✔
77
    return res
264✔
78
  },
79
  '%': (data) => {
80
    let res = data[0]
504✔
81
    for (let i = 1; i < data.length; i++) res %= +data[i]
528✔
82
    return res
504✔
83
  },
84
  max: (data) => Math.max(...data),
732✔
85
  min: (data) => Math.min(...data),
336✔
86
  in: ([item, array]) => (array || []).includes(item),
456✔
87
  '>': ([a, b]) => a > b,
1,476✔
88
  '<': ([a, b, c]) => (c === undefined ? a < b : a < b && b < c),
1,740✔
89
  preserve: {
90
    traverse: false,
91
    method: declareSync((i) => i, true),
1,770✔
92
    [Sync]: () => true
474✔
93
  },
94
  if: {
95
    method: (input, context, above, engine) => {
96
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
3,234!
97

98
      if (input.length === 1) return engine.run(input[0], context, { above })
3,234✔
99
      if (input.length < 2) return null
3,006✔
100

101
      input = [...input]
2,922✔
102
      if (input.length % 2 !== 1) input.push(null)
2,922✔
103

104
      // fallback to the default if the condition is false
105
      const onFalse = input.pop()
2,922✔
106

107
      // while there are still conditions
108
      while (input.length) {
2,922✔
109
        const check = input.shift()
3,678✔
110
        const onTrue = input.shift()
3,678✔
111

112
        const test = engine.run(check, context, { above })
3,678✔
113

114
        // if the condition is true, run the true branch
115
        if (engine.truthy(test)) return engine.run(onTrue, context, { above })
3,678✔
116
      }
117

118
      return engine.run(onFalse, context, { above })
942✔
119
    },
120
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
3,612✔
121
    deterministic: (data, buildState) => {
122
      return isDeterministic(data, buildState.engine, buildState)
3,216✔
123
    },
124
    asyncMethod: async (input, context, above, engine) => {
125
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
102!
126

127
      // check the bounds
128
      if (input.length === 1) return engine.run(input[0], context, { above })
102✔
129
      if (input.length < 2) return null
66✔
130

131
      input = [...input]
54✔
132

133
      if (input.length % 2 !== 1) input.push(null)
54✔
134

135
      // fallback to the default if the condition is false
136
      const onFalse = input.pop()
54✔
137

138
      // while there are still conditions
139
      while (input.length) {
54✔
140
        const check = input.shift()
54✔
141
        const onTrue = input.shift()
54✔
142

143
        const test = await engine.run(check, context, { above })
54✔
144

145
        // if the condition is true, run the true branch
146
        if (engine.truthy(test)) return engine.run(onTrue, context, { above })
54✔
147
      }
148

149
      return engine.run(onFalse, context, { above })
18✔
150
    },
151
    traverse: false
152
  },
153
  '<=': ([a, b, c]) => (c === undefined ? a <= b : a <= b && b <= c),
696✔
154
  '>=': ([a, b]) => a >= b,
1,380✔
155
  // eslint-disable-next-line eqeqeq
156
  '==': ([a, b]) => a == b,
888✔
157
  '===': ([a, b]) => a === b,
1,608✔
158
  // eslint-disable-next-line eqeqeq
159
  '!=': ([a, b]) => a != b,
516✔
160
  '!==': ([a, b]) => a !== b,
576✔
161
  xor: ([a, b]) => a ^ b,
264✔
162
  // Why "executeInLoop"? Because if it needs to execute to get an array, I do not want to execute the arguments,
163
  // Both for performance and safety reasons.
164
  or: {
165
    method: (arr, _1, _2, engine) => {
166
      // See "executeInLoop" above
167
      const executeInLoop = Array.isArray(arr)
780✔
168
      if (!executeInLoop) arr = engine.run(arr, _1, { above: _2 })
780✔
169

170
      let item
171
      for (let i = 0; i < arr.length; i++) {
780✔
172
        item = executeInLoop ? engine.run(arr[i], _1, { above: _2 }) : arr[i]
1,224✔
173
        if (engine.truthy(item)) return item
1,224✔
174
      }
175

176
      return item
156✔
177
    },
178
    asyncMethod: async (arr, _1, _2, engine) => {
179
      // See "executeInLoop" above
180
      const executeInLoop = Array.isArray(arr)
780✔
181
      if (!executeInLoop) arr = await engine.run(arr, _1, { above: _2 })
780✔
182

183
      let item
184
      for (let i = 0; i < arr.length; i++) {
780✔
185
        item = executeInLoop ? await engine.run(arr[i], _1, { above: _2 }) : arr[i]
1,224✔
186
        if (engine.truthy(item)) return item
1,224✔
187
      }
188

189
      return item
156✔
190
    },
191
    deterministic: (data, buildState) => isDeterministic(data, buildState.engine, buildState),
1,032✔
192
    compile: (data, buildState) => {
193
      if (!buildState.engine.truthy.IDENTITY) return false
432✔
194
      if (Array.isArray(data)) {
72✔
195
        return `(${data.map((i) => buildString(i, buildState)).join(' || ')})`
144✔
196
      } else {
197
        return `(${buildString(data, buildState)}).reduce((a,b) => a||b, false)`
24✔
198
      }
199
    },
200
    traverse: false
201
  },
202
  and: {
203
    method: (arr, _1, _2, engine) => {
204
      // See "executeInLoop" above
205
      const executeInLoop = Array.isArray(arr)
1,068✔
206
      if (!executeInLoop) arr = engine.run(arr, _1, { above: _2 })
1,068✔
207

208
      let item
209
      for (let i = 0; i < arr.length; i++) {
1,068✔
210
        item = executeInLoop ? engine.run(arr[i], _1, { above: _2 }) : arr[i]
1,788✔
211
        if (!engine.truthy(item)) return item
1,788✔
212
      }
213
      return item
420✔
214
    },
215
    asyncMethod: async (arr, _1, _2, engine) => {
216
      // See "executeInLoop" above
217
      const executeInLoop = Array.isArray(arr)
1,068✔
218
      if (!executeInLoop) arr = await engine.run(arr, _1, { above: _2 })
1,068✔
219

220
      let item
221
      for (let i = 0; i < arr.length; i++) {
1,068✔
222
        item = executeInLoop ? await engine.run(arr[i], _1, { above: _2 }) : arr[i]
1,788✔
223
        if (!engine.truthy(item)) return item
1,788✔
224
      }
225
      return item
420✔
226
    },
227
    traverse: false,
228
    deterministic: (data, buildState) => isDeterministic(data, buildState.engine, buildState),
1,272✔
229
    compile: (data, buildState) => {
230
      if (!buildState.engine.truthy.IDENTITY) return false
576✔
231
      if (Array.isArray(data)) {
72✔
232
        return `(${data.map((i) => buildString(i, buildState)).join(' && ')})`
144✔
233
      } else {
234
        return `(${buildString(data, buildState)}).reduce((a,b) => a&&b, true)`
24✔
235
      }
236
    }
237
  },
238
  substr: ([string, from, end]) => {
239
    if (end < 0) {
720✔
240
      const result = string.substr(from)
192✔
241
      return result.substr(0, result.length + end)
192✔
242
    }
243
    return string.substr(from, end)
528✔
244
  },
245
  length: ([i]) => {
246
    if (typeof i === 'string' || Array.isArray(i)) return i.length
144✔
247
    if (i && typeof i === 'object') return Object.keys(i).length
96✔
248
    return 0
24✔
249
  },
250
  get: {
251
    method: ([data, key, defaultValue], context, above, engine) => {
252
      const notFound = defaultValue === undefined ? null : defaultValue
480✔
253

254
      const subProps = splitPathMemoized(String(key))
480✔
255
      for (let i = 0; i < subProps.length; i++) {
480✔
256
        if (data === null || data === undefined) {
480!
UNCOV
257
          return notFound
×
258
        }
259
        // Descending into context
260
        data = data[subProps[i]]
480✔
261
        if (data === undefined) {
480✔
262
          return notFound
48✔
263
        }
264
      }
265
      if (engine.allowFunctions || typeof data[key] !== 'function') {
432!
266
        return data
432✔
267
      }
268
    }
269
  },
270
  var: (key, context, above, engine) => {
271
    let b
272
    if (Array.isArray(key)) {
23,130✔
273
      b = key[1]
17,160✔
274
      key = key[0]
17,160✔
275
    }
276
    let iter = 0
23,130✔
277
    while (
23,130✔
278
      typeof key === 'string' &&
65,520✔
279
      key.startsWith('../') &&
280
      iter < above.length
281
    ) {
282
      context = above[iter++]
6,468✔
283
      key = key.substring(3)
6,468✔
284
      // A performance optimization that allows you to pass the previous above array without spreading it as the last argument
285
      if (iter === above.length && Array.isArray(context)) {
6,468✔
286
        iter = 0
288✔
287
        above = context
288✔
288
        context = above[iter++]
288✔
289
      }
290
    }
291

292
    const notFound = b === undefined ? null : b
23,130✔
293
    if (typeof key === 'undefined' || key === '' || key === null) {
23,130✔
294
      if (engine.allowFunctions || typeof context !== 'function') {
3,432!
295
        return context
3,432✔
296
      }
UNCOV
297
      return null
×
298
    }
299
    const subProps = splitPathMemoized(String(key))
19,698✔
300
    for (let i = 0; i < subProps.length; i++) {
19,698✔
301
      if (context === null || context === undefined) {
22,506✔
302
        return notFound
96✔
303
      }
304
      // Descending into context
305
      context = context[subProps[i]]
22,410✔
306
      if (context === undefined) {
22,410✔
307
        return notFound
3,144✔
308
      }
309
    }
310
    if (engine.allowFunctions || typeof context !== 'function') {
16,458✔
311
      return context
16,386✔
312
    }
313
    return null
72✔
314
  },
315
  missing: (checked, context, above, engine) => {
316
    return (Array.isArray(checked) ? checked : [checked]).filter((key) => {
2,736!
317
      return defaultMethods.var(key, context, above, engine) === null
4,656✔
318
    })
319
  },
320
  missing_some: ([needCount, options], context, above, engine) => {
321
    const missing = defaultMethods.missing(options, context, above, engine)
864✔
322
    if (options.length - missing.length >= needCount) {
864✔
323
      return []
576✔
324
    } else {
325
      return missing
288✔
326
    }
327
  },
328
  map: createArrayIterativeMethod('map'),
329
  some: createArrayIterativeMethod('some', true),
330
  all: createArrayIterativeMethod('every', true),
331
  none: {
332
    traverse: false,
333
    // todo: add async build & build
334
    method: (val, context, above, engine) => {
335
      return !defaultMethods.some.method(val, context, above, engine)
192✔
336
    },
337
    asyncMethod: async (val, context, above, engine) => {
338
      return !(await defaultMethods.some.asyncMethod(
192✔
339
        val,
340
        context,
341
        above,
342
        engine
343
      ))
344
    },
345
    compile: (data, buildState) => {
346
      const result = defaultMethods.some.compile(data, buildState)
384✔
347
      return result ? buildState.compile`!(${result})` : false
384!
348
    }
349
  },
350
  merge: (arrays) => (Array.isArray(arrays) ? [].concat(...arrays) : [arrays]),
1,140!
351
  every: createArrayIterativeMethod('every'),
352
  filter: createArrayIterativeMethod('filter'),
353
  reduce: {
354
    deterministic: (data, buildState) => {
355
      return (
420✔
356
        isDeterministic(data[0], buildState.engine, buildState) &&
480✔
357
        isDeterministic(data[1], buildState.engine, {
358
          ...buildState,
359
          insideIterator: true
360
        })
361
      )
362
    },
363
    compile: (data, buildState) => {
364
      if (!Array.isArray(data)) throw new InvalidControlInput(data)
336!
365
      const { async } = buildState
336✔
366
      let [selector, mapper, defaultValue] = data
336✔
367
      selector = buildString(selector, buildState)
336✔
368
      if (typeof defaultValue !== 'undefined') {
336!
369
        defaultValue = buildString(defaultValue, buildState)
336✔
370
      }
371
      const mapState = {
336✔
372
        ...buildState,
373
        extraArguments: 'above',
374
        avoidInlineAsync: true
375
      }
376
      mapper = build(mapper, mapState)
336✔
377
      const aboveArray = mapper.aboveDetected ? '[null, context, above]' : 'null'
336!
378

379
      buildState.methods.push(mapper)
336✔
380
      if (async) {
336✔
381
        if (!isSync(mapper) || selector.includes('await')) {
168!
UNCOV
382
          buildState.detectAsync = true
×
UNCOV
383
          if (typeof defaultValue !== 'undefined') {
×
UNCOV
384
            return `await asyncIterators.reduce(${selector} || [], (a,b) => methods[${
×
385
              buildState.methods.length - 1
386
            }]({ accumulator: a, current: b }, ${aboveArray}), ${defaultValue})`
387
          }
UNCOV
388
          return `await asyncIterators.reduce(${selector} || [], (a,b) => methods[${
×
389
            buildState.methods.length - 1
390
          }]({ accumulator: a, current: b }, ${aboveArray}))`
391
        }
392
      }
393
      if (typeof defaultValue !== 'undefined') {
336!
394
        return `(${selector} || []).reduce((a,b) => methods[${
336✔
395
          buildState.methods.length - 1
396
        }]({ accumulator: a, current: b }, ${aboveArray}), ${defaultValue})`
397
      }
UNCOV
398
      return `(${selector} || []).reduce((a,b) => methods[${
×
399
        buildState.methods.length - 1
400
      }]({ accumulator: a, current: b }, ${aboveArray}))`
401
    },
402
    method: (input, context, above, engine) => {
403
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
432✔
404
      let [selector, mapper, defaultValue] = input
408✔
405
      defaultValue = engine.run(defaultValue, context, {
408✔
406
        above
407
      })
408
      selector =
408✔
409
        engine.run(selector, context, {
456✔
410
          above
411
        }) || []
412
      const func = (accumulator, current) => {
408✔
413
        return engine.run(
1,410✔
414
          mapper,
415
          {
416
            accumulator,
417
            current
418
          },
419
          {
420
            above: [selector, context, above]
421
          }
422
        )
423
      }
424
      if (typeof defaultValue === 'undefined') {
408✔
425
        return selector.reduce(func)
54✔
426
      }
427
      return selector.reduce(func, defaultValue)
354✔
428
    },
429
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
408✔
430
    asyncMethod: async (input, context, above, engine) => {
431
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
24!
432
      let [selector, mapper, defaultValue] = input
24✔
433
      defaultValue = await engine.run(defaultValue, context, {
24✔
434
        above
435
      })
436
      selector =
24✔
437
        (await engine.run(selector, context, {
24!
438
          above
439
        })) || []
440
      return asyncIterators.reduce(
24✔
441
        selector,
442
        (accumulator, current) => {
443
          return engine.run(
102✔
444
            mapper,
445
            {
446
              accumulator,
447
              current
448
            },
449
            {
450
              above: [selector, context, above]
451
            }
452
          )
453
        },
454
        defaultValue
455
      )
456
    },
457
    traverse: false
458
  },
459
  '!': (value, _1, _2, engine) => Array.isArray(value) ? !engine.truthy(value[0]) : !engine.truthy(value),
840!
460
  '!!': (value, _1, _2, engine) => Boolean(Array.isArray(value) ? engine.truthy(value[0]) : engine.truthy(value)),
324!
461
  cat: (arr) => {
462
    if (typeof arr === 'string') return arr
912!
463
    let res = ''
912✔
464
    for (let i = 0; i < arr.length; i++) res += arr[i]
2,244✔
465
    return res
912✔
466
  },
467
  keys: ([obj]) => typeof obj === 'object' ? Object.keys(obj) : [],
48✔
468
  pipe: {
469
    traverse: false,
470
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
168✔
471
    method: (args, context, above, engine) => {
472
      if (!Array.isArray(args)) throw new Error('Data for pipe must be an array')
108!
473
      let answer = engine.run(args[0], context, { above: [args, context, above] })
108✔
474
      for (let i = 1; i < args.length; i++) answer = engine.run(args[i], answer, { above: [args, context, above] })
108✔
475
      return answer
108✔
476
    },
477
    asyncMethod: async (args, context, above, engine) => {
478
      if (!Array.isArray(args)) throw new Error('Data for pipe must be an array')
60!
479
      let answer = await engine.run(args[0], context, { above: [args, context, above] })
60✔
480
      for (let i = 1; i < args.length; i++) answer = await engine.run(args[i], answer, { above: [args, context, above] })
96✔
481
      return answer
60✔
482
    },
483
    compile: (args, buildState) => {
484
      let res = buildState.compile`${args[0]}`
48✔
485
      for (let i = 1; i < args.length; i++) res = buildState.compile`${build(args[i], { ...buildState, extraArguments: 'above' })}(${res}, [null, context, above])`
48✔
486
      return res
48✔
487
    },
488
    deterministic: (data, buildState) => {
489
      if (!Array.isArray(data)) return false
192!
490
      data = [...data]
192✔
491
      const first = data.shift()
192✔
492
      return isDeterministic(first, buildState.engine, buildState) && isDeterministic(data, buildState.engine, { ...buildState, insideIterator: true })
192✔
493
    }
494
  },
495
  eachKey: {
496
    traverse: false,
497
    [Sync]: (data, buildState) => isSyncDeep(Object.values(data[Object.keys(data)[0]]), buildState.engine, buildState),
30✔
498
    method: (object, context, above, engine) => {
499
      const result = Object.keys(object).reduce((accumulator, key) => {
78✔
500
        const item = object[key]
138✔
501
        Object.defineProperty(accumulator, key, {
138✔
502
          enumerable: true,
503
          value: engine.run(item, context, { above })
504
        })
505
        return accumulator
138✔
506
      }, {})
507
      return result
78✔
508
    },
509
    deterministic: (data, buildState) => {
510
      if (data && typeof data === 'object') {
192!
511
        return Object.values(data).every((i) => {
192✔
512
          return isDeterministic(i, buildState.engine, buildState)
252✔
513
        })
514
      }
UNCOV
515
      throw new InvalidControlInput(data)
×
516
    },
517
    compile: (data, buildState) => {
518
      // what's nice about this is that I don't have to worry about whether it's async or not, the lower entries take care of that ;)
519
      // however, this is not engineered support yields, I will have to make a note of that & possibly support it at a later point.
520
      if (data && typeof data === 'object') {
168!
521
        const result = `({ ${Object.keys(data)
168✔
522
          .reduce((accumulator, key) => {
523
            accumulator.push(
240✔
524
              // @ts-ignore Never[] is not accurate
525
              `${JSON.stringify(key)}: ${buildString(data[key], buildState)}`
526
            )
527
            return accumulator
240✔
528
          }, [])
529
          .join(',')} })`
530
        return result
168✔
531
      }
UNCOV
532
      throw new InvalidControlInput(data)
×
533
    },
534
    asyncMethod: async (object, context, above, engine) => {
535
      const result = await asyncIterators.reduce(
18✔
536
        Object.keys(object),
537
        async (accumulator, key) => {
538
          const item = object[key]
30✔
539
          Object.defineProperty(accumulator, key, {
30✔
540
            enumerable: true,
541
            value: await engine.run(item, context, { above })
542
          })
543
          return accumulator
30✔
544
        },
545
        {}
546
      )
547
      return result
18✔
548
    }
549
  }
550
}
551

552
function createArrayIterativeMethod (name, useTruthy = false) {
162✔
553
  return {
270✔
554
    deterministic: (data, buildState) => {
555
      return (
2,580✔
556
        isDeterministic(data[0], buildState.engine, buildState) &&
3,210✔
557
        isDeterministic(data[1], buildState.engine, {
558
          ...buildState,
559
          insideIterator: true
560
        })
561
      )
562
    },
563
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
1,398✔
564
    method: (input, context, above, engine) => {
565
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
1,962✔
566
      let [selector, mapper] = input
1,938✔
567
      selector =
1,938✔
568
        engine.run(selector, context, {
1,986✔
569
          above
570
        }) || []
571

572
      return selector[name]((i, index) => {
1,938✔
573
        const result = engine.run(mapper, i, {
4,818✔
574
          above: [{ iterator: selector, index }, context, above]
575
        })
576
        return useTruthy ? engine.truthy(result) : result
4,818✔
577
      })
578
    },
579
    asyncMethod: async (input, context, above, engine) => {
580
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
654!
581
      let [selector, mapper] = input
654✔
582
      selector =
654✔
583
        (await engine.run(selector, context, {
654!
584
          above
585
        })) || []
586
      return asyncIterators[name](selector, (i, index) => {
654✔
587
        const result = engine.run(mapper, i, {
1,158✔
588
          above: [{ iterator: selector, index }, context, above]
589
        })
590
        return useTruthy ? engine.truthy(result) : result
1,158✔
591
      })
592
    },
593
    compile: (data, buildState) => {
594
      if (!Array.isArray(data)) throw new InvalidControlInput(data)
1,602!
595
      const { async } = buildState
1,602✔
596
      const [selector, mapper] = data
1,602✔
597

598
      const mapState = {
1,602✔
599
        ...buildState,
600
        avoidInlineAsync: true,
601
        iteratorCompile: true,
602
        extraArguments: 'index, above'
603
      }
604

605
      const method = build(mapper, mapState)
1,602✔
606
      const aboveArray = method.aboveDetected ? buildState.compile`[{ iterator: z, index: x }, context, above]` : buildState.compile`null`
1,602✔
607

608
      if (async) {
1,602✔
609
        if (!isSyncDeep(mapper, buildState.engine, buildState)) {
810✔
610
          buildState.detectAsync = true
18✔
611
          return buildState.compile`await asyncIterators[${name}](${selector} || [], async (i, x, z) => ${method}(i, x, ${aboveArray}))`
18✔
612
        }
613
      }
614

615
      return buildState.compile`(${selector} || [])[${name}]((i, x, z) => ${method}(i, x, ${aboveArray}))`
1,584✔
616
    },
617
    traverse: false
618
  }
619
}
620
defaultMethods['?:'] = defaultMethods.if
54✔
621
// declare all of the functions here synchronous
622
Object.keys(defaultMethods).forEach((item) => {
54✔
623
  if (typeof defaultMethods[item] === 'function') {
2,268✔
624
    defaultMethods[item][Sync] = true
1,458✔
625
  }
626
  defaultMethods[item].deterministic =
2,268✔
627
    typeof defaultMethods[item].deterministic === 'undefined'
2,268✔
628
      ? true
629
      : defaultMethods[item].deterministic
630
})
631
// @ts-ignore Allow custom attribute
632
defaultMethods.var.deterministic = (data, buildState) => {
54✔
633
  return buildState.insideIterator && !String(data).includes('../../')
16,164✔
634
}
635
Object.assign(defaultMethods.missing, {
54✔
636
  deterministic: false
637
})
638
Object.assign(defaultMethods.missing_some, {
54✔
639
  deterministic: false
640
})
641
// @ts-ignore Allow custom attribute
642
defaultMethods['<'].compile = function (data, buildState) {
54✔
643
  if (!Array.isArray(data)) return false
528✔
644
  if (data.length === 2) return buildState.compile`(${data[0]} < ${data[1]})`
504✔
645
  if (data.length === 3) return buildState.compile`(${data[0]} < ${data[1]} && ${data[1]} < ${data[2]})`
72!
UNCOV
646
  return false
×
647
}
648
// @ts-ignore Allow custom attribute
649
defaultMethods['<='].compile = function (data, buildState) {
54✔
650
  if (!Array.isArray(data)) return false
168✔
651
  if (data.length === 2) return buildState.compile`(${data[0]} <= ${data[1]})`
144✔
652
  if (data.length === 3) return buildState.compile`(${data[0]} <= ${data[1]} && ${data[1]} <= ${data[2]})`
48!
UNCOV
653
  return false
×
654
}
655
// @ts-ignore Allow custom attribute
656
defaultMethods.min.compile = function (data, buildState) {
54✔
657
  if (!Array.isArray(data)) return false
120✔
658
  return `Math.min(${data
96✔
659
    .map((i) => buildString(i, buildState))
240✔
660
    .join(', ')})`
661
}
662
// @ts-ignore Allow custom attribute
663
defaultMethods.max.compile = function (data, buildState) {
54✔
664
  if (!Array.isArray(data)) return false
264✔
665
  return `Math.max(${data
144✔
666
    .map((i) => buildString(i, buildState))
336✔
667
    .join(', ')})`
668
}
669
// @ts-ignore Allow custom attribute
670
defaultMethods['>'].compile = function (data, buildState) {
54✔
671
  if (!Array.isArray(data)) return false
288✔
672
  if (data.length !== 2) return false
264!
673
  return buildState.compile`(${data[0]} > ${data[1]})`
264✔
674
}
675
// @ts-ignore Allow custom attribute
676
defaultMethods['>='].compile = function (data, buildState) {
54✔
677
  if (!Array.isArray(data)) return false
456✔
678
  if (data.length !== 2) return false
432!
679
  return buildState.compile`(${data[0]} >= ${data[1]})`
432✔
680
}
681
// @ts-ignore Allow custom attribute
682
defaultMethods['=='].compile = function (data, buildState) {
54✔
683
  if (!Array.isArray(data)) return false
192✔
684
  if (data.length !== 2) return false
168!
685
  return buildState.compile`(${data[0]} == ${data[1]})`
168✔
686
}
687
// @ts-ignore Allow custom attribute
688
defaultMethods['!='].compile = function (data, buildState) {
54✔
689
  if (!Array.isArray(data)) return false
96✔
690
  if (data.length !== 2) return false
72!
691
  return buildState.compile`(${data[0]} != ${data[1]})`
72✔
692
}
693
// @ts-ignore Allow custom attribute
694
defaultMethods.if.compile = function (data, buildState) {
54✔
695
  if (!Array.isArray(data)) return false
1,416!
696
  if (data.length < 3) return false
1,416✔
697

698
  data = [...data]
1,272✔
699
  if (data.length % 2 !== 1) data.push(null)
1,272✔
700
  const onFalse = data.pop()
1,272✔
701

702
  let res = buildState.compile``
1,272✔
703
  while (data.length) {
1,272✔
704
    const condition = data.shift()
1,896✔
705
    const onTrue = data.shift()
1,896✔
706
    res = buildState.compile`${res} engine.truthy(${condition}) ? ${onTrue} : `
1,896✔
707
  }
708

709
  return buildState.compile`(${res} ${onFalse})`
1,272✔
710
}
711
// @ts-ignore Allow custom attribute
712
defaultMethods['==='].compile = function (data, buildState) {
54✔
713
  if (!Array.isArray(data)) return false
240✔
714
  if (data.length !== 2) return false
216!
715
  return buildState.compile`(${data[0]} === ${data[1]})`
216✔
716
}
717
// @ts-ignore Allow custom attribute
718
defaultMethods['+'].compile = function (data, buildState) {
54✔
719
  if (Array.isArray(data)) {
786✔
720
    return `(${data
762✔
721
      .map((i) => `(+${buildString(i, buildState)})`)
1,578✔
722
      .join(' + ')})`
723
  } else if (typeof data === 'string' || typeof data === 'number') {
24!
UNCOV
724
    return `(+${buildString(data, buildState)})`
×
725
  } else {
726
    return `([].concat(${buildString(
24✔
727
      data,
728
      buildState
729
    )})).reduce((a,b) => (+a)+(+b), 0)`
730
  }
731
}
732

733
// @ts-ignore Allow custom attribute
734
defaultMethods['%'].compile = function (data, buildState) {
54✔
735
  if (Array.isArray(data)) {
168✔
736
    return `(${data
144✔
737
      .map((i) => `(+${buildString(i, buildState)})`)
288✔
738
      .join(' % ')})`
739
  } else {
740
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)%(+b))`
24✔
741
  }
742
}
743

744
// @ts-ignore Allow custom attribute
745
defaultMethods.in.compile = function (data, buildState) {
54✔
746
  if (!Array.isArray(data)) return false
216!
747
  return buildState.compile`(${data[1]} || []).includes(${data[0]})`
216✔
748
}
749

750
// @ts-ignore Allow custom attribute
751
defaultMethods['-'].compile = function (data, buildState) {
54✔
752
  if (Array.isArray(data)) {
216✔
753
    return `${data.length === 1 ? '-' : ''}(${data
192✔
754
      .map((i) => `(+${buildString(i, buildState)})`)
264✔
755
      .join(' - ')})`
756
  }
757
  if (typeof data === 'string' || typeof data === 'number') {
24!
UNCOV
758
    return `(-${buildString(data, buildState)})`
×
759
  } else {
760
    return `((a=>(a.length===1?a[0]=-a[0]:a)&0||a)([].concat(${buildString(
24✔
761
      data,
762
      buildState
763
    )}))).reduce((a,b) => (+a)-(+b))`
764
  }
765
}
766
// @ts-ignore Allow custom attribute
767
defaultMethods['/'].compile = function (data, buildState) {
54✔
768
  if (Array.isArray(data)) {
120✔
769
    return `(${data
96✔
770
      .map((i) => `(+${buildString(i, buildState)})`)
192✔
771
      .join(' / ')})`
772
  } else {
773
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)/(+b))`
24✔
774
  }
775
}
776
// @ts-ignore Allow custom attribute
777
defaultMethods['*'].compile = function (data, buildState) {
54✔
778
  if (Array.isArray(data)) {
336✔
779
    return `(${data
312✔
780
      .map((i) => `(+${buildString(i, buildState)})`)
624✔
781
      .join(' * ')})`
782
  } else {
783
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)*(+b))`
24✔
784
  }
785
}
786
// @ts-ignore Allow custom attribute
787
defaultMethods.cat.compile = function (data, buildState) {
54✔
788
  if (typeof data === 'string') return JSON.stringify(data)
384!
789
  if (!Array.isArray(data)) return false
384✔
790
  let res = buildState.compile`''`
288✔
791
  for (let i = 0; i < data.length; i++) res = buildState.compile`${res} + ${data[i]}`
648✔
792
  return buildState.compile`(${res})`
288✔
793
}
794

795
// @ts-ignore Allow custom attribute
796
defaultMethods['!'].compile = function (
54✔
797
  data,
798
  buildState
799
) {
800
  if (Array.isArray(data)) return buildState.compile`(!engine.truthy(${data[0]}))`
240!
UNCOV
801
  return buildState.compile`(!engine.truthy(${data}))`
×
802
}
803

804
defaultMethods.not = defaultMethods['!']
54✔
805

806
// @ts-ignore Allow custom attribute
807
defaultMethods['!!'].compile = function (data, buildState) {
54✔
808
  if (Array.isArray(data)) return buildState.compile`(!!engine.truthy(${data[0]}))`
96!
UNCOV
809
  return `(!!engine.truthy(${data}))`
×
810
}
811
defaultMethods.none.deterministic = defaultMethods.some.deterministic
54✔
812
defaultMethods.get.compile = function (data, buildState) {
54✔
813
  let defaultValue = null
396✔
814
  let key = data
396✔
815
  let obj = null
396✔
816
  if (Array.isArray(data) && data.length <= 3) {
396!
817
    obj = data[0]
396✔
818
    key = data[1]
396✔
819
    defaultValue = typeof data[2] === 'undefined' ? null : data[2]
396✔
820

821
    // Bail out if the key is dynamic; dynamic keys are not really optimized by this block.
822
    if (key && typeof key === 'object') return false
396✔
823

824
    key = key.toString()
288✔
825
    const pieces = splitPathMemoized(key)
288✔
826
    if (!chainingSupported) {
288✔
UNCOV
827
      return `(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
48✔
828
        (text, i) => {
UNCOV
829
          return `(${text}||0)[${JSON.stringify(i)}]`
48✔
830
        },
831
        `(${buildString(obj, buildState)}||0)`
832
      )}, ${buildString(defaultValue, buildState)}))`
833
    }
834
    return `((${buildString(obj, buildState)})${pieces
240✔
835
      .map((i) => `?.[${buildString(i, buildState)}]`)
240✔
836
      .join('')} ?? ${buildString(defaultValue, buildState)})`
837
  }
UNCOV
838
  return false
×
839
}
840
// @ts-ignore Allow custom attribute
841
defaultMethods.var.compile = function (data, buildState) {
54✔
842
  let key = data
7,542✔
843
  let defaultValue = null
7,542✔
844
  buildState.varTop = buildState.varTop || new Set()
7,542✔
845
  if (
7,542!
846
    !key ||
37,710✔
847
    typeof data === 'string' ||
848
    typeof data === 'number' ||
849
    (Array.isArray(data) && data.length <= 2)
850
  ) {
851
    if (Array.isArray(data)) {
7,542!
852
      key = data[0]
7,542✔
853
      defaultValue = typeof data[1] === 'undefined' ? null : data[1]
7,542✔
854
    }
855

856
    if (key === '../index' && buildState.iteratorCompile) return 'index'
7,542✔
857

858
    // this counts the number of var accesses to determine if they're all just using this override.
859
    // this allows for a small optimization :)
860
    if (typeof key === 'undefined' || key === null || key === '') return 'context'
7,494✔
861
    if (typeof key !== 'string' && typeof key !== 'number') return false
6,516✔
862

863
    key = key.toString()
6,468✔
864
    if (key.includes('../')) return false
6,468✔
865

866
    const pieces = splitPathMemoized(key)
6,126✔
867
    const [top] = pieces
6,126✔
868
    buildState.varTop.add(top)
6,126✔
869

870
    if (!buildState.engine.allowFunctions) buildState.methods.preventFunctions = a => typeof a === 'function' ? null : a
8,820✔
871
    else buildState.methods.preventFunctions = a => a
60✔
872

873
    // support older versions of node
874
    if (!chainingSupported) {
6,126✔
UNCOV
875
      return `(methods.preventFunctions(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
1,021✔
UNCOV
876
        (text, i) => `(${text}||0)[${JSON.stringify(i)}]`,
1,189✔
877
        '(context||0)'
878
      )}, ${buildString(defaultValue, buildState)})))`
879
    }
880
    return `(methods.preventFunctions(context${pieces
5,105✔
881
      .map((i) => `?.[${JSON.stringify(i)}]`)
5,945✔
882
      .join('')} ?? ${buildString(defaultValue, buildState)}))`
883
  }
UNCOV
884
  return false
×
885
}
886

887
// @ts-ignore Allowing a optimizeUnary attribute that can be used for performance optimizations
888
defaultMethods['+'].optimizeUnary = defaultMethods['-'].optimizeUnary = defaultMethods.var.optimizeUnary = defaultMethods['!'].optimizeUnary = defaultMethods['!!'].optimizeUnary = defaultMethods.cat.optimizeUnary = true
54✔
889

890
export default {
891
  ...defaultMethods
892
}
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

© 2026 Coveralls, Inc