Skip to content

Commit c606a54

Browse files
committed
Add boundaries for zero arguments for arithmetic and modulo
1 parent 67e4e3f commit c606a54

10 files changed

+188
-35
lines changed

build.test.js

-3
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,16 @@ function timeout (n, x) {
4646

4747
test('Minus operator w/ Infinity', async () => {
4848
const f = await logic.build({ '-': Infinity })
49-
5049
expect(await f()).toEqual(-Infinity)
5150
})
5251

5352
test('Minus operator w/ array w/ variable input', async () => {
5453
const f = await logic.build({ '-': [{ preserve: 5 }] })
55-
5654
expect(await f()).toEqual(-5)
5755
})
5856

5957
test('Minus operator w/ array w/ variable input (Infinity)', async () => {
6058
const f = await logic.build({ '-': [{ preserve: Infinity }] })
61-
6259
expect(await f()).toEqual(-Infinity)
6360
})
6461

compatible.test.js

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe('All of the compatible tests', () => {
5656
if ((result || 0).toNumber) result = Number(result)
5757
if (Array.isArray(result)) result = result.map(i => (i || 0).toNumber ? Number(i) : i)
5858
expect(correction(result)).toStrictEqual(testCase.result)
59+
expect(testCase.error).toBeUndefined()
5960
} catch (err) {
6061
if (err.message && err.message.includes('expect')) throw err
6162
if (Number.isNaN(err)) err = { type: 'NaN' }
@@ -73,6 +74,7 @@ describe('All of the compatible tests', () => {
7374
if ((result || 0).toNumber) result = Number(result)
7475
if (Array.isArray(result)) result = result.map(i => i.toNumber ? Number(i) : i)
7576
expect(correction(result)).toStrictEqual(testCase.result)
77+
expect(testCase.error).toBeUndefined()
7678
} catch (err) {
7779
if (err.message && err.message.includes('expect')) throw err
7880
if (Number.isNaN(err)) err = { type: 'NaN' }

compiler.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import asyncIterators from './async_iterators.js'
1212
import { coerceArray } from './utilities/coerceArray.js'
1313
import { countArguments } from './utilities/countArguments.js'
14-
import { precoerceNumber } from './utilities/downgrade.js'
14+
import { precoerceNumber, assertSize } from './utilities/downgrade.js'
1515

1616
/**
1717
* Provides a simple way to compile logic into a function that can be run.
@@ -315,12 +315,12 @@ function processBuiltString (method, str, buildState) {
315315
str = str.replace(`__%%%${x}%%%__`, item)
316316
})
317317

318-
const final = `(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { ${str.includes('prev') ? 'let prev;' : ''} const result = ${str}; return result }`
318+
const final = `(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber, assertSize) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { ${str.includes('prev') ? 'let prev;' : ''} const result = ${str}; return result }`
319319
// console.log(str)
320320
// console.log(final)
321321
// eslint-disable-next-line no-eval
322322
return Object.assign(
323-
(typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber), {
323+
(typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber, assertSize), {
324324
[Sync]: !buildState.asyncDetected,
325325
aboveDetected: typeof str === 'string' && str.includes(', above')
326326
})

defaultMethods.js

+37-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import InvalidControlInput from './errors/InvalidControlInput.js'
1010
import legacyMethods from './legacy.js'
1111
import { precoerceNumber } from './utilities/downgrade.js'
1212

13+
const INVALID_ARGUMENTS = { type: 'Invalid Arguments' }
14+
1315
function isDeterministic (method, engine, buildState) {
1416
if (Array.isArray(method)) {
1517
return method.every((i) => isDeterministic(i, engine, buildState))
@@ -69,6 +71,8 @@ const defaultMethods = {
6971
return res
7072
},
7173
'*': (data) => {
74+
// eslint-disable-next-line no-throw-literal
75+
if (data.length === 0) throw INVALID_ARGUMENTS
7276
let res = 1
7377
for (let i = 0; i < data.length; i++) {
7478
if (data[i] && typeof data[i] === 'object') throw NaN
@@ -79,12 +83,18 @@ const defaultMethods = {
7983
},
8084
'/': (data) => {
8185
if (data[0] && typeof data[0] === 'object') throw NaN
86+
// eslint-disable-next-line no-throw-literal
87+
if (data.length === 0) throw INVALID_ARGUMENTS
88+
if (data.length === 1) {
89+
if (!+data[0] || (data[0] && typeof data[0] === 'object')) throw NaN
90+
return 1 / +data[0]
91+
}
8292
let res = +data[0]
8393
for (let i = 1; i < data.length; i++) {
8494
if ((data[i] && typeof data[i] === 'object') || !data[i]) throw NaN
8595
res /= +data[i]
8696
}
87-
if (Number.isNaN(res)) throw NaN
97+
if (Number.isNaN(res) || res === Infinity) throw NaN
8898
return res
8999
},
90100
'-': (data) => {
@@ -94,6 +104,7 @@ const defaultMethods = {
94104
if (typeof data === 'boolean') return precoerceNumber(-data)
95105
if (typeof data === 'object' && !Array.isArray(data)) throw NaN
96106
if (data[0] && typeof data[0] === 'object') throw NaN
107+
if (data.length === 0) return 0
97108
if (data.length === 1) return -data[0]
98109
let res = data[0]
99110
for (let i = 1; i < data.length; i++) {
@@ -105,6 +116,8 @@ const defaultMethods = {
105116
},
106117
'%': (data) => {
107118
if (data[0] && typeof data[0] === 'object') throw NaN
119+
// eslint-disable-next-line no-throw-literal
120+
if (data.length < 2) throw INVALID_ARGUMENTS
108121
let res = +data[0]
109122
for (let i = 1; i < data.length; i++) {
110123
if (data[i] && typeof data[i] === 'object') throw NaN
@@ -980,15 +993,21 @@ function numberCoercion (i, buildState) {
980993

981994
// @ts-ignore Allow custom attribute
982995
defaultMethods['+'].compile = function (data, buildState) {
983-
if (Array.isArray(data)) return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' + ')})`
996+
if (Array.isArray(data)) {
997+
if (data.length === 0) return '(+0)'
998+
return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' + ')})`
999+
}
9841000
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') return `precoerceNumber(+${buildString(data, buildState)})`
9851001
return buildState.compile`(Array.isArray(prev = ${data}) ? prev.reduce((a,b) => (+a)+(+precoerceNumber(b)), 0) : +precoerceNumber(prev))`
9861002
}
9871003

9881004
// @ts-ignore Allow custom attribute
9891005
defaultMethods['%'].compile = function (data, buildState) {
990-
if (Array.isArray(data)) return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' % ')})`
991-
return `(${buildString(data, buildState)}).reduce((a,b) => (+precoerceNumber(a))%(+precoerceNumber(b)))`
1006+
if (Array.isArray(data)) {
1007+
if (data.length < 2) throw INVALID_ARGUMENTS
1008+
return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' % ')})`
1009+
}
1010+
return `assertSize(${buildString(data, buildState)}, 2).reduce((a,b) => (+precoerceNumber(a))%(+precoerceNumber(b)))`
9921011
}
9931012

9941013
// @ts-ignore Allow custom attribute
@@ -999,26 +1018,35 @@ defaultMethods.in.compile = function (data, buildState) {
9991018

10001019
// @ts-ignore Allow custom attribute
10011020
defaultMethods['-'].compile = function (data, buildState) {
1002-
if (Array.isArray(data)) return `${data.length === 1 ? '-' : ''}precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' - ')})`
1021+
if (Array.isArray(data)) {
1022+
if (data.length === 0) return '(-0)'
1023+
return `${data.length === 1 ? '-' : ''}precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' - ')})`
1024+
}
10031025
if (typeof data === 'string' || typeof data === 'number') return `(-${buildString(data, buildState)})`
1004-
return buildState.compile`(Array.isArray(prev = ${data}) ? prev.length === 1 ? -precoerceNumber(prev[0]) : prev.reduce((a,b) => (+precoerceNumber(a))-(+precoerceNumber(b))) : -precoerceNumber(prev))`
1026+
return buildState.compile`(Array.isArray(prev = ${data}) ? prev.length === 0 ? 0 : prev.length === 1 ? -precoerceNumber(prev[0]) : prev.reduce((a,b) => (+precoerceNumber(a))-(+precoerceNumber(b))) : -precoerceNumber(prev))`
10051027
}
10061028
// @ts-ignore Allow custom attribute
10071029
defaultMethods['/'].compile = function (data, buildState) {
10081030
if (Array.isArray(data)) {
1031+
if (data.length === 0) throw INVALID_ARGUMENTS
1032+
if (data.length === 1) data = [1, data[0]]
10091033
return `precoerceNumber(${data.map((i, x) => {
10101034
let res = numberCoercion(i, buildState)
10111035
if (x && res === '+0') precoerceNumber(NaN)
10121036
if (x) res = `precoerceNumber(${res} || NaN)`
10131037
return res
10141038
}).join(' / ')})`
10151039
}
1016-
return `(${buildString(data, buildState)}).reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b || NaN)))`
1040+
return `assertSize(prev = ${buildString(data, buildState)}, 1) && prev.length === 1 ? 1 / precoerceNumber(prev[0] || NaN) : prev.reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b || NaN)))`
10171041
}
10181042
// @ts-ignore Allow custom attribute
10191043
defaultMethods['*'].compile = function (data, buildState) {
1020-
if (Array.isArray(data)) return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' * ')})`
1021-
return `(${buildString(data, buildState)}).reduce((a,b) => (+precoerceNumber(a))*(+precoerceNumber(b)))`
1044+
if (Array.isArray(data)) {
1045+
// eslint-disable-next-line no-throw-literal
1046+
if (data.length === 0) throw INVALID_ARGUMENTS
1047+
return `precoerceNumber(${data.map(i => numberCoercion(i, buildState)).join(' * ')})`
1048+
}
1049+
return `assertSize(${buildString(data, buildState)}, 1).reduce((a,b) => (+precoerceNumber(a))*(+precoerceNumber(b)))`
10221050
}
10231051

10241052
// @ts-ignore Allow custom attribute

suites/divide.json

+39-6
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
{
7272
"description": "Divide with Single Operand, Direct (0)",
7373
"rule": { "/": 0 },
74-
"result": 0,
74+
"error": { "type": "NaN" },
7575
"data": null
7676
},
7777
{
@@ -92,10 +92,17 @@
9292
"result": 1,
9393
"data": null
9494
},
95+
{
96+
"description": "Divide Operator with Single Operand, Direct (2)",
97+
"rule": { "/": 2 },
98+
"result": 0.5,
99+
"decimal": true,
100+
"data": null
101+
},
95102
{
96103
"description": "Divide Operator with Single Operand, Direct (0)",
97104
"rule": { "/": 0 },
98-
"result": 0,
105+
"error": { "type": "NaN" },
99106
"data": null
100107
},
101108
{
@@ -114,7 +121,7 @@
114121
{
115122
"description": "Divide Operator with Single Operand, Direct (String 0)",
116123
"rule": { "/": "0" },
117-
"result": 0,
124+
"error": { "type": "NaN" },
118125
"data": null
119126
},
120127
{
@@ -126,19 +133,19 @@
126133
{
127134
"description": "Divide Operator with Single Operand, Direct (false)",
128135
"rule": { "/": false },
129-
"result": 0,
136+
"error": { "type": "NaN" },
130137
"data": null
131138
},
132139
{
133140
"description": "Divide Operator with Single Operand, Direct (Empty String)",
134141
"rule": { "/": "" },
135-
"result": 0,
142+
"error": { "type": "NaN" },
136143
"data": null
137144
},
138145
{
139146
"description": "Divide Operator with a Single Operand, Direct (null)",
140147
"rule": { "/": null },
141-
"result": 0,
148+
"error": { "type": "NaN" },
142149
"data": null
143150
},
144151
{
@@ -176,5 +183,31 @@
176183
"rule": { "/": [8, 2, 0] },
177184
"error": { "type": "NaN" },
178185
"data": null
186+
},
187+
{
188+
"description": "A division without any operands should throw invalid arguments",
189+
"rule": { "/": [] },
190+
"error": { "type": "Invalid Arguments" },
191+
"data": null
192+
},
193+
"# Not Included",
194+
{
195+
"description": "Divide with dynamic empty array",
196+
"rule": { "/": { "preserve": [] } },
197+
"error": { "type": "Invalid Arguments" },
198+
"data": null
199+
},
200+
{
201+
"description": "Divide with dynamic unary array",
202+
"rule": { "/": { "preserve": [2] } },
203+
"result": 0.5,
204+
"decimal": true,
205+
"data": null
206+
},
207+
{
208+
"description": "Divide with dynamic array",
209+
"rule": { "/": { "preserve": [8, 2] } },
210+
"result": 4,
211+
"data": null
179212
}
180213
]

suites/minus.json

+13
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,18 @@
126126
"rule": { "-": [[1], 1] },
127127
"error": { "type": "NaN" },
128128
"data": null
129+
},
130+
{
131+
"description": "Minus with no operands",
132+
"rule": { "-": [] },
133+
"result": 0,
134+
"data": null
135+
},
136+
"# Not Included",
137+
{
138+
"description": "Minus with dynamic empty array",
139+
"rule": { "-": { "preserve": [] }},
140+
"result": 0,
141+
"data": null
129142
}
130143
]

0 commit comments

Comments
 (0)