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

TotalTechGeek / json-logic-engine / 12225897151

08 Dec 2024 10:42PM UTC coverage: 91.489% (-0.8%) from 92.254%
12225897151

Pull #38

github

web-flow
Merge 75365ec13 into 91d18201d
Pull Request #38: Modified RFC6901

792 of 901 branches covered (87.9%)

Branch coverage included in aggregate %.

24 of 27 new or added lines in 2 files covered. (88.89%)

6 existing lines in 3 files now uncovered.

799 of 838 relevant lines covered (95.35%)

31649.37 hits per line

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

90.46
/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)) {
25,536✔
14
    return method.every((i) => isDeterministic(i, engine, buildState))
16,080✔
15
  }
16
  if (method && typeof method === 'object') {
19,848✔
17
    const func = Object.keys(method)[0]
5,790✔
18
    const lower = method[func]
5,790✔
19

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

23
    if (engine.methods[func].traverse === false) {
5,706✔
24
      return typeof engine.methods[func].deterministic === 'function'
216!
25
        ? engine.methods[func].deterministic(lower, buildState)
26
        : engine.methods[func].deterministic
27
    }
28
    return typeof engine.methods[func].deterministic === 'function'
5,490✔
29
      ? engine.methods[func].deterministic(lower, buildState)
30
      : engine.methods[func].deterministic &&
3,354✔
31
          isDeterministic(lower, engine, buildState)
32
  }
33
  return true
14,058✔
34
}
35

36
function isSyncDeep (method, engine, buildState) {
37
  if (Array.isArray(method)) {
26,748✔
38
    return method.every((i) => isSyncDeep(i, engine, buildState))
14,712✔
39
  }
40

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

50
  return true
12,396✔
51
}
52

53
const defaultMethods = {
54✔
54
  '+': (data) => {
55
    if (typeof data === 'string') return +data
483,444✔
56
    if (typeof data === 'number') return +data
483,432!
57
    let res = 0
483,432✔
58
    for (let i = 0; i < data.length; i++) res += +data[i]
966,852✔
59
    return res
483,432✔
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,452✔
88
  '<': ([a, b, c]) => (c === undefined ? a < b : a < b && b < c),
1,680✔
89
  preserve: {
90
    traverse: false,
91
    method: declareSync((i) => i, true),
1,626✔
92
    [Sync]: () => true
462✔
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,
840✔
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
  or: (arr, _1, _2, engine) => {
163
    for (let i = 0; i < arr.length; i++) {
1,584✔
164
      if (engine.truthy(arr[i])) return arr[i]
2,556✔
165
    }
166
    return arr[arr.length - 1]
312✔
167
  },
168
  and: (arr, _1, _2, engine) => {
169
    for (let i = 0; i < arr.length; i++) {
2,112✔
170
      if (!engine.truthy(arr[i])) return arr[i]
3,564✔
171
    }
172
    return arr[arr.length - 1]
852✔
173
  },
174
  substr: ([string, from, end]) => {
175
    if (end < 0) {
720✔
176
      const result = string.substr(from)
192✔
177
      return result.substr(0, result.length + end)
192✔
178
    }
179
    return string.substr(from, end)
528✔
180
  },
181
  length: ([i]) => {
182
    if (typeof i === 'string' || Array.isArray(i)) return i.length
144✔
183
    if (i && typeof i === 'object') return Object.keys(i).length
96✔
184
    return 0
24✔
185
  },
186
  get: {
187
    method: ([data, key, defaultValue], context, above, engine) => {
188
      const notFound = defaultValue === undefined ? null : defaultValue
480✔
189

190
      const subProps = splitPathMemoized(String(key))
480✔
191
      for (let i = 0; i < subProps.length; i++) {
480✔
192
        if (data === null || data === undefined) {
480!
193
          return notFound
×
194
        }
195
        // Descending into context
196
        data = data[subProps[i]]
480✔
197
        if (data === undefined) {
480✔
198
          return notFound
48✔
199
        }
200
      }
201
      if (engine.allowFunctions || typeof data[key] !== 'function') {
432!
202
        return data
432✔
203
      }
204
    }
205
  },
206
  // Adding this to spec something out, not to merge it.
207
  val: {
208
    method: (args, context, above) => {
209
      let result = context
2,880✔
210
      let start = 0
2,880✔
211
      if (Array.isArray(args[0]) && args[0].length === 1) {
2,880✔
212
        start++
864✔
213
        const climb = +Math.abs(args[0][0])
864✔
214
        let pos = 0
864✔
215
        for (let i = 0; i < climb; i++) {
864✔
216
          result = above[pos++]
2,016✔
217
          if (i === above.length - 1 && Array.isArray(result)) {
2,016✔
218
            above = result
288✔
219
            result = result[0]
288✔
220
            pos = 1
288✔
221
          }
222
        }
223
      }
224

225
      for (let i = start; i < args.length; i++) {
2,880✔
226
        if (args[i] === null) continue
3,168✔
227
        if (result === null || result === undefined) return null
1,632!
228
        result = result[args[i]]
1,632✔
229
      }
230
      if (typeof result === 'undefined') return null
2,880!
231
      return result
2,880✔
232
    },
233
    deterministic: false
234
  },
235
  var: (key, context, above, engine) => {
236
    let b
237
    if (Array.isArray(key)) {
22,458✔
238
      b = key[1]
16,488✔
239
      key = key[0]
16,488✔
240
    }
241
    let iter = 0
22,458✔
242
    while (
22,458✔
243
      typeof key === 'string' &&
63,168✔
244
      key.startsWith('../') &&
245
      iter < above.length
246
    ) {
247
      context = above[iter++]
6,132✔
248
      key = key.substring(3)
6,132✔
249
      // A performance optimization that allows you to pass the previous above array without spreading it as the last argument
250
      if (iter === above.length && Array.isArray(context)) {
6,132✔
251
        iter = 0
288✔
252
        above = context
288✔
253
        context = above[iter++]
288✔
254
      }
255
    }
256

257
    const notFound = b === undefined ? null : b
22,458✔
258
    if (typeof key === 'undefined' || key === '' || key === null) {
22,458✔
259
      if (engine.allowFunctions || typeof context !== 'function') {
3,096!
260
        return context
3,096✔
261
      }
262
      return null
×
263
    }
264
    const subProps = splitPathMemoized(String(key))
19,362✔
265
    for (let i = 0; i < subProps.length; i++) {
19,362✔
266
      if (context === null || context === undefined) {
21,930✔
267
        return notFound
96✔
268
      }
269
      // Descending into context
270
      context = context[subProps[i]]
21,834✔
271
      if (context === undefined) {
21,834✔
272
        return notFound
3,144✔
273
      }
274
    }
275
    if (engine.allowFunctions || typeof context !== 'function') {
16,122✔
276
      return context
16,050✔
277
    }
278
    return null
72✔
279
  },
280
  missing: (checked, context, above, engine) => {
281
    return (Array.isArray(checked) ? checked : [checked]).filter((key) => {
2,736!
282
      return defaultMethods.var(key, context, above, engine) === null
4,656✔
283
    })
284
  },
285
  missing_some: ([needCount, options], context, above, engine) => {
286
    const missing = defaultMethods.missing(options, context, above, engine)
864✔
287
    if (options.length - missing.length >= needCount) {
864✔
288
      return []
576✔
289
    } else {
290
      return missing
288✔
291
    }
292
  },
293
  map: createArrayIterativeMethod('map'),
294
  some: createArrayIterativeMethod('some', true),
295
  all: createArrayIterativeMethod('every', true),
296
  none: {
297
    traverse: false,
298
    // todo: add async build & build
299
    method: (val, context, above, engine) => {
300
      return !defaultMethods.some.method(val, context, above, engine)
192✔
301
    },
302
    asyncMethod: async (val, context, above, engine) => {
303
      return !(await defaultMethods.some.asyncMethod(
192✔
304
        val,
305
        context,
306
        above,
307
        engine
308
      ))
309
    },
310
    compile: (data, buildState) => {
311
      const result = defaultMethods.some.compile(data, buildState)
384✔
312
      return result ? buildState.compile`!(${result})` : false
384!
313
    }
314
  },
315
  merge: (arrays) => (Array.isArray(arrays) ? [].concat(...arrays) : [arrays]),
1,140!
316
  every: createArrayIterativeMethod('every'),
317
  filter: createArrayIterativeMethod('filter'),
318
  reduce: {
319
    deterministic: (data, buildState) => {
320
      return (
420✔
321
        isDeterministic(data[0], buildState.engine, buildState) &&
480✔
322
        isDeterministic(data[1], buildState.engine, {
323
          ...buildState,
324
          insideIterator: true
325
        })
326
      )
327
    },
328
    compile: (data, buildState) => {
329
      if (!Array.isArray(data)) throw new InvalidControlInput(data)
336!
330
      const { async } = buildState
336✔
331
      let [selector, mapper, defaultValue] = data
336✔
332
      selector = buildString(selector, buildState)
336✔
333
      if (typeof defaultValue !== 'undefined') {
336!
334
        defaultValue = buildString(defaultValue, buildState)
336✔
335
      }
336
      const mapState = {
336✔
337
        ...buildState,
338
        extraArguments: 'above',
339
        avoidInlineAsync: true
340
      }
341
      mapper = build(mapper, mapState)
336✔
342
      const aboveArray = mapper.aboveDetected ? '[null, context, above]' : 'null'
336!
343

344
      buildState.methods.push(mapper)
336✔
345
      if (async) {
336✔
346
        if (!isSync(mapper) || selector.includes('await')) {
168!
347
          buildState.detectAsync = true
×
348
          if (typeof defaultValue !== 'undefined') {
×
349
            return `await asyncIterators.reduce(${selector} || [], (a,b) => methods[${
×
350
              buildState.methods.length - 1
351
            }]({ accumulator: a, current: b }, ${aboveArray}), ${defaultValue})`
352
          }
353
          return `await asyncIterators.reduce(${selector} || [], (a,b) => methods[${
×
354
            buildState.methods.length - 1
355
          }]({ accumulator: a, current: b }, ${aboveArray}))`
356
        }
357
      }
358
      if (typeof defaultValue !== 'undefined') {
336!
359
        return `(${selector} || []).reduce((a,b) => methods[${
336✔
360
          buildState.methods.length - 1
361
        }]({ accumulator: a, current: b }, ${aboveArray}), ${defaultValue})`
362
      }
363
      return `(${selector} || []).reduce((a,b) => methods[${
×
364
        buildState.methods.length - 1
365
      }]({ accumulator: a, current: b }, ${aboveArray}))`
366
    },
367
    method: (input, context, above, engine) => {
368
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
432✔
369
      let [selector, mapper, defaultValue] = input
408✔
370
      defaultValue = engine.run(defaultValue, context, {
408✔
371
        above
372
      })
373
      selector =
408✔
374
        engine.run(selector, context, {
456✔
375
          above
376
        }) || []
377
      const func = (accumulator, current) => {
408✔
378
        return engine.run(
1,410✔
379
          mapper,
380
          {
381
            accumulator,
382
            current
383
          },
384
          {
385
            above: [selector, context, above]
386
          }
387
        )
388
      }
389
      if (typeof defaultValue === 'undefined') {
408✔
390
        return selector.reduce(func)
54✔
391
      }
392
      return selector.reduce(func, defaultValue)
354✔
393
    },
394
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
408✔
395
    asyncMethod: async (input, context, above, engine) => {
396
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
24!
397
      let [selector, mapper, defaultValue] = input
24✔
398
      defaultValue = await engine.run(defaultValue, context, {
24✔
399
        above
400
      })
401
      selector =
24✔
402
        (await engine.run(selector, context, {
24!
403
          above
404
        })) || []
405
      return asyncIterators.reduce(
24✔
406
        selector,
407
        (accumulator, current) => {
408
          return engine.run(
102✔
409
            mapper,
410
            {
411
              accumulator,
412
              current
413
            },
414
            {
415
              above: [selector, context, above]
416
            }
417
          )
418
        },
419
        defaultValue
420
      )
421
    },
422
    traverse: false
423
  },
424
  '!': (value, _1, _2, engine) => Array.isArray(value) ? !engine.truthy(value[0]) : !engine.truthy(value),
816!
425
  '!!': (value, _1, _2, engine) => Boolean(Array.isArray(value) ? engine.truthy(value[0]) : engine.truthy(value)),
324!
426
  cat: (arr) => {
427
    if (typeof arr === 'string') return arr
912!
428
    let res = ''
912✔
429
    for (let i = 0; i < arr.length; i++) res += arr[i]
2,244✔
430
    return res
912✔
431
  },
432
  keys: ([obj]) => typeof obj === 'object' ? Object.keys(obj) : [],
48✔
433
  pipe: {
434
    traverse: false,
435
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
168✔
436
    method: (args, context, above, engine) => {
437
      if (!Array.isArray(args)) throw new Error('Data for pipe must be an array')
108!
438
      let answer = engine.run(args[0], context, { above: [args, context, above] })
108✔
439
      for (let i = 1; i < args.length; i++) answer = engine.run(args[i], answer, { above: [args, context, above] })
108✔
440
      return answer
108✔
441
    },
442
    asyncMethod: async (args, context, above, engine) => {
443
      if (!Array.isArray(args)) throw new Error('Data for pipe must be an array')
60!
444
      let answer = await engine.run(args[0], context, { above: [args, context, above] })
60✔
445
      for (let i = 1; i < args.length; i++) answer = await engine.run(args[i], answer, { above: [args, context, above] })
96✔
446
      return answer
60✔
447
    },
448
    compile: (args, buildState) => {
449
      let res = buildState.compile`${args[0]}`
48✔
450
      for (let i = 1; i < args.length; i++) res = buildState.compile`${build(args[i], { ...buildState, extraArguments: 'above' })}(${res}, [null, context, above])`
48✔
451
      return res
48✔
452
    },
453
    deterministic: (data, buildState) => {
454
      if (!Array.isArray(data)) return false
192!
455
      data = [...data]
192✔
456
      const first = data.shift()
192✔
457
      return isDeterministic(first, buildState.engine, buildState) && isDeterministic(data, buildState.engine, { ...buildState, insideIterator: true })
192✔
458
    }
459
  },
460
  eachKey: {
461
    traverse: false,
462
    [Sync]: (data, buildState) => isSyncDeep(Object.values(data[Object.keys(data)[0]]), buildState.engine, buildState),
30✔
463
    method: (object, context, above, engine) => {
464
      const result = Object.keys(object).reduce((accumulator, key) => {
78✔
465
        const item = object[key]
138✔
466
        Object.defineProperty(accumulator, key, {
138✔
467
          enumerable: true,
468
          value: engine.run(item, context, { above })
469
        })
470
        return accumulator
138✔
471
      }, {})
472
      return result
78✔
473
    },
474
    deterministic: (data, buildState) => {
475
      if (data && typeof data === 'object') {
192!
476
        return Object.values(data).every((i) => {
192✔
477
          return isDeterministic(i, buildState.engine, buildState)
252✔
478
        })
479
      }
480
      throw new InvalidControlInput(data)
×
481
    },
482
    compile: (data, buildState) => {
483
      // 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 ;)
484
      // however, this is not engineered support yields, I will have to make a note of that & possibly support it at a later point.
485
      if (data && typeof data === 'object') {
168!
486
        const result = `({ ${Object.keys(data)
168✔
487
          .reduce((accumulator, key) => {
488
            accumulator.push(
240✔
489
              // @ts-ignore Never[] is not accurate
490
              `${JSON.stringify(key)}: ${buildString(data[key], buildState)}`
491
            )
492
            return accumulator
240✔
493
          }, [])
494
          .join(',')} })`
495
        return result
168✔
496
      }
497
      throw new InvalidControlInput(data)
×
498
    },
499
    asyncMethod: async (object, context, above, engine) => {
500
      const result = await asyncIterators.reduce(
18✔
501
        Object.keys(object),
502
        async (accumulator, key) => {
503
          const item = object[key]
30✔
504
          Object.defineProperty(accumulator, key, {
30✔
505
            enumerable: true,
506
            value: await engine.run(item, context, { above })
507
          })
508
          return accumulator
30✔
509
        },
510
        {}
511
      )
512
      return result
18✔
513
    }
514
  }
515
}
516

517
function createArrayIterativeMethod (name, useTruthy = false) {
162✔
518
  return {
270✔
519
    deterministic: (data, buildState) => {
520
      return (
2,832✔
521
        isDeterministic(data[0], buildState.engine, buildState) &&
3,714✔
522
        isDeterministic(data[1], buildState.engine, {
523
          ...buildState,
524
          insideIterator: true
525
        })
526
      )
527
    },
528
    [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
1,662✔
529
    method: (input, context, above, engine) => {
530
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
2,034✔
531
      let [selector, mapper] = input
2,010✔
532
      selector =
2,010✔
533
        engine.run(selector, context, {
2,058✔
534
          above
535
        }) || []
536

537
      return selector[name]((i, index) => {
2,010✔
538
        const result = engine.run(mapper, i, {
4,914✔
539
          above: [{ item: selector, index }, context, above]
540
        })
541
        return useTruthy ? engine.truthy(result) : result
4,914✔
542
      })
543
    },
544
    asyncMethod: async (input, context, above, engine) => {
545
      if (!Array.isArray(input)) throw new InvalidControlInput(input)
786!
546
      let [selector, mapper] = input
786✔
547
      selector =
786✔
548
        (await engine.run(selector, context, {
786!
549
          above
550
        })) || []
551
      return asyncIterators[name](selector, (i, index) => {
786✔
552
        const result = engine.run(mapper, i, {
1,494✔
553
          above: [{ item: selector, index }, context, above]
554
        })
555
        return useTruthy ? engine.truthy(result) : result
1,494✔
556
      })
557
    },
558
    compile: (data, buildState) => {
559
      if (!Array.isArray(data)) throw new InvalidControlInput(data)
1,890!
560
      const { async } = buildState
1,890✔
561
      const [selector, mapper] = data
1,890✔
562

563
      const mapState = {
1,890✔
564
        ...buildState,
565
        avoidInlineAsync: true,
566
        iteratorCompile: true,
567
        extraArguments: 'index, above'
568
      }
569

570
      const method = build(mapper, mapState)
1,890✔
571
      const aboveArray = method.aboveDetected ? buildState.compile`[{ item: null, index: x }, context, above]` : buildState.compile`null`
1,890✔
572

573
      if (async) {
1,890✔
574
        if (!isSyncDeep(mapper, buildState.engine, buildState)) {
954✔
575
          buildState.detectAsync = true
162✔
576
          return buildState.compile`await asyncIterators[${name}](${selector} || [], async (i, x) => ${method}(i, x, ${aboveArray}))`
162✔
577
        }
578
      }
579

580
      return buildState.compile`(${selector} || [])[${name}]((i, x) => ${method}(i, x, ${aboveArray}))`
1,728✔
581
    },
582
    traverse: false
583
  }
584
}
585
defaultMethods['?:'] = defaultMethods.if
54✔
586
// declare all of the functions here synchronous
587
Object.keys(defaultMethods).forEach((item) => {
54✔
588
  if (typeof defaultMethods[item] === 'function') {
2,322✔
589
    defaultMethods[item][Sync] = true
1,566✔
590
  }
591
  defaultMethods[item].deterministic =
2,322✔
592
    typeof defaultMethods[item].deterministic === 'undefined'
2,322✔
593
      ? true
594
      : defaultMethods[item].deterministic
595
})
596
// @ts-ignore Allow custom attribute
597
defaultMethods.var.deterministic = (data, buildState) => {
54✔
598
  return buildState.insideIterator && !String(data).includes('../../')
16,092✔
599
}
600
Object.assign(defaultMethods.missing, {
54✔
601
  deterministic: false
602
})
603
Object.assign(defaultMethods.missing_some, {
54✔
604
  deterministic: false
605
})
606
// @ts-ignore Allow custom attribute
607
defaultMethods['<'].compile = function (data, buildState) {
54✔
608
  if (!Array.isArray(data)) return false
600✔
609
  if (data.length === 2) return buildState.compile`(${data[0]} < ${data[1]})`
576✔
610
  if (data.length === 3) return buildState.compile`(${data[0]} < ${data[1]} && ${data[1]} < ${data[2]})`
72!
611
  return false
×
612
}
613
// @ts-ignore Allow custom attribute
614
defaultMethods['<='].compile = function (data, buildState) {
54✔
615
  if (!Array.isArray(data)) return false
168✔
616
  if (data.length === 2) return buildState.compile`(${data[0]} <= ${data[1]})`
144✔
617
  if (data.length === 3) return buildState.compile`(${data[0]} <= ${data[1]} && ${data[1]} <= ${data[2]})`
48!
618
  return false
×
619
}
620
// @ts-ignore Allow custom attribute
621
defaultMethods.min.compile = function (data, buildState) {
54✔
622
  if (!Array.isArray(data)) return false
120✔
623
  return `Math.min(${data
96✔
624
    .map((i) => buildString(i, buildState))
240✔
625
    .join(', ')})`
626
}
627
// @ts-ignore Allow custom attribute
628
defaultMethods.max.compile = function (data, buildState) {
54✔
629
  if (!Array.isArray(data)) return false
264✔
630
  return `Math.max(${data
144✔
631
    .map((i) => buildString(i, buildState))
336✔
632
    .join(', ')})`
633
}
634
// @ts-ignore Allow custom attribute
635
defaultMethods['>'].compile = function (data, buildState) {
54✔
636
  if (!Array.isArray(data)) return false
384✔
637
  if (data.length !== 2) return false
360!
638
  return buildState.compile`(${data[0]} > ${data[1]})`
360✔
639
}
640
// @ts-ignore Allow custom attribute
641
defaultMethods['>='].compile = function (data, buildState) {
54✔
642
  if (!Array.isArray(data)) return false
456✔
643
  if (data.length !== 2) return false
432!
644
  return buildState.compile`(${data[0]} >= ${data[1]})`
432✔
645
}
646
// @ts-ignore Allow custom attribute
647
defaultMethods['=='].compile = function (data, buildState) {
54✔
648
  if (!Array.isArray(data)) return false
240✔
649
  if (data.length !== 2) return false
216!
650
  return buildState.compile`(${data[0]} == ${data[1]})`
216✔
651
}
652
// @ts-ignore Allow custom attribute
653
defaultMethods['!='].compile = function (data, buildState) {
54✔
654
  if (!Array.isArray(data)) return false
96✔
655
  if (data.length !== 2) return false
72!
656
  return buildState.compile`(${data[0]} != ${data[1]})`
72✔
657
}
658
// @ts-ignore Allow custom attribute
659
defaultMethods.if.compile = function (data, buildState) {
54✔
660
  if (!Array.isArray(data)) return false
1,416!
661
  if (data.length < 3) return false
1,416✔
662

663
  data = [...data]
1,272✔
664
  if (data.length % 2 !== 1) data.push(null)
1,272✔
665
  const onFalse = data.pop()
1,272✔
666

667
  let res = buildState.compile``
1,272✔
668
  while (data.length) {
1,272✔
669
    const condition = data.shift()
1,896✔
670
    const onTrue = data.shift()
1,896✔
671
    res = buildState.compile`${res} engine.truthy(${condition}) ? ${onTrue} : `
1,896✔
672
  }
673

674
  return buildState.compile`(${res} ${onFalse})`
1,272✔
675
}
676
// @ts-ignore Allow custom attribute
677
defaultMethods['==='].compile = function (data, buildState) {
54✔
678
  if (!Array.isArray(data)) return false
240✔
679
  if (data.length !== 2) return false
216!
680
  return buildState.compile`(${data[0]} === ${data[1]})`
216✔
681
}
682
// @ts-ignore Allow custom attribute
683
defaultMethods['+'].compile = function (data, buildState) {
54✔
684
  if (Array.isArray(data)) {
1,026✔
685
    return `(${data
1,002✔
686
      .map((i) => `(+${buildString(i, buildState)})`)
2,058✔
687
      .join(' + ')})`
688
  } else if (typeof data === 'string' || typeof data === 'number') {
24!
689
    return `(+${buildString(data, buildState)})`
×
690
  } else {
691
    return `([].concat(${buildString(
24✔
692
      data,
693
      buildState
694
    )})).reduce((a,b) => (+a)+(+b), 0)`
695
  }
696
}
697

698
// @ts-ignore Allow custom attribute
699
defaultMethods['%'].compile = function (data, buildState) {
54✔
700
  if (Array.isArray(data)) {
168✔
701
    return `(${data
144✔
702
      .map((i) => `(+${buildString(i, buildState)})`)
288✔
703
      .join(' % ')})`
704
  } else {
705
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)%(+b))`
24✔
706
  }
707
}
708

709
// @ts-ignore Allow custom attribute
710
defaultMethods.or.compile = function (data, buildState) {
54✔
711
  if (!buildState.engine.truthy.IDENTITY) return false
384✔
712
  if (Array.isArray(data)) {
24!
713
    return `(${data.map((i) => buildString(i, buildState)).join(' || ')})`
×
714
  } else {
715
    return `(${buildString(data, buildState)}).reduce((a,b) => a||b, false)`
24✔
716
  }
717
}
718

719
// @ts-ignore Allow custom attribute
720
defaultMethods.in.compile = function (data, buildState) {
54✔
721
  if (!Array.isArray(data)) return false
216!
722
  return buildState.compile`(${data[1]} || []).includes(${data[0]})`
216✔
723
}
724

725
// @ts-ignore Allow custom attribute
726
defaultMethods.and.compile = function (data, buildState) {
54✔
727
  if (!buildState.engine.truthy.IDENTITY) return false
528✔
728
  if (Array.isArray(data)) {
24!
729
    return `(${data.map((i) => buildString(i, buildState)).join(' && ')})`
×
730
  } else {
731
    return `(${buildString(data, buildState)}).reduce((a,b) => a&&b, true)`
24✔
732
  }
733
}
734

735
// @ts-ignore Allow custom attribute
736
defaultMethods['-'].compile = function (data, buildState) {
54✔
737
  if (Array.isArray(data)) {
216✔
738
    return `${data.length === 1 ? '-' : ''}(${data
192✔
739
      .map((i) => `(+${buildString(i, buildState)})`)
264✔
740
      .join(' - ')})`
741
  }
742
  if (typeof data === 'string' || typeof data === 'number') {
24!
743
    return `(-${buildString(data, buildState)})`
×
744
  } else {
745
    return `((a=>(a.length===1?a[0]=-a[0]:a)&0||a)([].concat(${buildString(
24✔
746
      data,
747
      buildState
748
    )}))).reduce((a,b) => (+a)-(+b))`
749
  }
750
}
751
// @ts-ignore Allow custom attribute
752
defaultMethods['/'].compile = function (data, buildState) {
54✔
753
  if (Array.isArray(data)) {
120✔
754
    return `(${data
96✔
755
      .map((i) => `(+${buildString(i, buildState)})`)
192✔
756
      .join(' / ')})`
757
  } else {
758
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)/(+b))`
24✔
759
  }
760
}
761
// @ts-ignore Allow custom attribute
762
defaultMethods['*'].compile = function (data, buildState) {
54✔
763
  if (Array.isArray(data)) {
336✔
764
    return `(${data
312✔
765
      .map((i) => `(+${buildString(i, buildState)})`)
624✔
766
      .join(' * ')})`
767
  } else {
768
    return `(${buildString(data, buildState)}).reduce((a,b) => (+a)*(+b))`
24✔
769
  }
770
}
771
// @ts-ignore Allow custom attribute
772
defaultMethods.cat.compile = function (data, buildState) {
54✔
773
  if (typeof data === 'string') return JSON.stringify(data)
384!
774
  if (!Array.isArray(data)) return false
384✔
775
  let res = buildState.compile`''`
288✔
776
  for (let i = 0; i < data.length; i++) res = buildState.compile`${res} + ${data[i]}`
648✔
777
  return buildState.compile`(${res})`
288✔
778
}
779

780
// @ts-ignore Allow custom attribute
781
defaultMethods['!'].compile = function (
54✔
782
  data,
783
  buildState
784
) {
785
  if (Array.isArray(data)) return buildState.compile`(!engine.truthy(${data[0]}))`
264!
786
  return buildState.compile`(!engine.truthy(${data}))`
×
787
}
788

789
defaultMethods.not = defaultMethods['!']
54✔
790

791
// @ts-ignore Allow custom attribute
792
defaultMethods['!!'].compile = function (data, buildState) {
54✔
793
  if (Array.isArray(data)) return buildState.compile`(!!engine.truthy(${data[0]}))`
96!
794
  return `(!!engine.truthy(${data}))`
×
795
}
796
defaultMethods.none.deterministic = defaultMethods.some.deterministic
54✔
797
defaultMethods.get.compile = function (data, buildState) {
54✔
798
  let defaultValue = null
396✔
799
  let key = data
396✔
800
  let obj = null
396✔
801
  if (Array.isArray(data) && data.length <= 3) {
396!
802
    obj = data[0]
396✔
803
    key = data[1]
396✔
804
    defaultValue = typeof data[2] === 'undefined' ? null : data[2]
396✔
805

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

809
    key = key.toString()
288✔
810
    const pieces = splitPathMemoized(key)
288✔
811
    if (!chainingSupported) {
288✔
UNCOV
812
      return `(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
48✔
813
        (text, i) => {
UNCOV
814
          return `(${text}||0)[${JSON.stringify(i)}]`
48✔
815
        },
816
        `(${buildString(obj, buildState)}||0)`
817
      )}, ${buildString(defaultValue, buildState)}))`
818
    }
819
    return `((${buildString(obj, buildState)})${pieces
240✔
820
      .map((i) => `?.[${buildString(i, buildState)}]`)
240✔
821
      .join('')} ?? ${buildString(defaultValue, buildState)})`
822
  }
823
  return false
×
824
}
825
// @ts-ignore Allow custom attribute
826
defaultMethods.var.compile = function (data, buildState) {
54✔
827
  let key = data
7,734✔
828
  let defaultValue = null
7,734✔
829
  buildState.varTop = buildState.varTop || new Set()
7,734✔
830
  if (
7,734!
831
    !key ||
38,670✔
832
    typeof data === 'string' ||
833
    typeof data === 'number' ||
834
    (Array.isArray(data) && data.length <= 2)
835
  ) {
836
    if (Array.isArray(data)) {
7,734!
837
      key = data[0]
7,734✔
838
      defaultValue = typeof data[1] === 'undefined' ? null : data[1]
7,734✔
839
    }
840

841
    if (key === '../index' && buildState.iteratorCompile) return 'index'
7,734✔
842

843
    // this counts the number of var accesses to determine if they're all just using this override.
844
    // this allows for a small optimization :)
845
    if (typeof key === 'undefined' || key === null || key === '') return 'context'
7,686✔
846
    if (typeof key !== 'string' && typeof key !== 'number') return false
6,708✔
847

848
    key = key.toString()
6,660✔
849
    if (key.includes('../')) return false
6,660✔
850

851
    const pieces = splitPathMemoized(key)
6,318✔
852
    const [top] = pieces
6,318✔
853
    buildState.varTop.add(top)
6,318✔
854

855
    if (!buildState.engine.allowFunctions) buildState.methods.preventFunctions = a => typeof a === 'function' ? null : a
9,012✔
856
    else buildState.methods.preventFunctions = a => a
60✔
857

858
    // support older versions of node
859
    if (!chainingSupported) {
6,318✔
UNCOV
860
      return `(methods.preventFunctions(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
1,053✔
UNCOV
861
        (text, i) => `(${text}||0)[${JSON.stringify(i)}]`,
1,253✔
862
        '(context||0)'
863
      )}, ${buildString(defaultValue, buildState)})))`
864
    }
865
    return `(methods.preventFunctions(context${pieces
5,265✔
866
      .map((i) => `?.[${JSON.stringify(i)}]`)
6,265✔
867
      .join('')} ?? ${buildString(defaultValue, buildState)}))`
868
  }
869
  return false
×
870
}
871

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

875
export default {
876
  ...defaultMethods
877
}
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