Skip to content

Commit

Permalink
refactor: "required" and "uniqueItems" keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Aug 29, 2020
1 parent da9bed3 commit d940126
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 56 deletions.
4 changes: 2 additions & 2 deletions lib/compile/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ export default class CodeGen {
return this
}

break(): CodeGen {
this.code("break;")
break(label?: Code): CodeGen {
this.code(label ? _`break ${label};` : _`break;`)
return this
}

Expand Down
43 changes: 25 additions & 18 deletions lib/vocabularies/validation/required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ const def: CodeKeywordDefinition = {

function allErrorsMode(): void {
if (loopRequired) {
check$DataAnd(loopAllRequired)
if ($data) {
gen.if(_`${schemaCode} !== undefined`, () => {
gen.if(_`!Array.isArray(${schemaCode})`, () => cxt.error(), loopAllRequired)
})
} else {
loopAllRequired()
}
return
}
for (const prop of schema) {
Expand All @@ -30,27 +36,26 @@ const def: CodeKeywordDefinition = {
const missing = gen.let("missing")
if (loopRequired) {
const valid = gen.let("valid", true)
check$DataAnd(() => loopUntilMissing(missing, valid))
cxt.pass(valid)
if ($data) {
gen.if(_`${schemaCode} === undefined`)
gen.assign(valid, true)
gen.elseIf(_`!Array.isArray(${schemaCode})`)
cxt.error()
gen.assign(valid, false)
gen.else()
loopUntilMissing(missing, valid)
gen.endIf()
} else {
loopUntilMissing(missing, valid)
}
cxt.ok(valid)
} else {
gen.if(`${checkMissingProp(cxt, schema, missing)}`)
reportMissingProp(cxt, missing)
gen.else()
}
}

function check$DataAnd(loop: () => void): void {
if ($data) {
gen.if(
`${schemaCode} && !Array.isArray(${schemaCode})`,
() => cxt.error(),
() => gen.if(`${schemaCode} !== undefined`, loop)
)
} else {
loop()
}
}

function loopAllRequired(): void {
const prop = gen.name("prop")
cxt.setParams({missingProperty: prop})
Expand All @@ -62,9 +67,11 @@ const def: CodeKeywordDefinition = {
function loopUntilMissing(missing: Name, valid: Name): void {
cxt.setParams({missingProperty: missing})
gen.for(`${missing} of ${schemaCode}`, () => {
gen
.assign(valid, propertyInData(data, missing, it.opts.ownProperties))
.ifNot(valid, "break")
gen.assign(valid, propertyInData(data, missing, it.opts.ownProperties))
gen.ifNot(valid, () => {
cxt.error()
gen.break()
})
})
}
},
Expand Down
71 changes: 35 additions & 36 deletions lib/vocabularies/validation/uniqueItems.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {CodeKeywordDefinition} from "../../types"
import KeywordContext from "../../compile/context"
import {checkDataType, checkDataTypes} from "../../compile/util"
import {_, str} from "../../compile/codegen"
import {_, str, Name} from "../../compile/codegen"

const def: CodeKeywordDefinition = {
keyword: "uniqueItems",
Expand All @@ -11,29 +11,29 @@ const def: CodeKeywordDefinition = {
code(cxt: KeywordContext) {
const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt
if (it.opts.uniqueItems === false || !($data || schema)) return
const i = gen.let("i")
const j = gen.let("j")
const valid = gen.let("valid")
cxt.setParams({i, j})
const itemType = parentSchema.items?.type

// TODO refactor to have two open blocks? same as in required
if ($data) {
gen.if(`${schemaCode} === false || ${schemaCode} === undefined`, `${valid} = true`, () =>
gen.if(`typeof ${schemaCode} != "boolean"`, `${valid} = false`, validateUniqueItems)
)
gen.if(_`${schemaCode} === false || ${schemaCode} === undefined`)
gen.assign(valid, true)
gen.elseIf(_`typeof ${schemaCode} != "boolean"`)
cxt.error()
gen.assign(valid, false)
gen.else()
validateUniqueItems()
gen.endIf()
} else {
validateUniqueItems()
}

cxt.pass(valid)
cxt.ok(valid)

function validateUniqueItems() {
gen.code(
`${i} = ${data}.length;
${valid} = true;`
)
gen.if(`${i} > 1`, canOptimize() ? loopN : loopN2)
const i = gen.let("i", _`${data}.length`)
const j = gen.let("j")
cxt.setParams({i, j})
gen.assign(valid, true)
gen.if(`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j))
}

function canOptimize(): boolean {
Expand All @@ -42,47 +42,46 @@ const def: CodeKeywordDefinition = {
: itemType && itemType !== "object" && itemType !== "array"
}

function loopN(): void {
function loopN(i: Name, j: Name): void {
const item = gen.name("item")
const wrongType = (Array.isArray(itemType) ? checkDataTypes : checkDataType)(
itemType,
item,
it.opts.strictNumbers,
true
)
const indices = gen.const("indices", "{}")
const indices = gen.const("indices", _`{}`)
gen.for(_`;${i}--;`, () => {
gen.let(item, `${data}[${i}];`)
gen.if(wrongType, "continue")
if (Array.isArray(itemType)) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`)
gen
.if(
_`typeof ${indices}[${item}] == "number"`,
_`${valid} = false; ${j} = ${indices}[${item}]; break;`
)
.if(_`typeof ${indices}[${item}] == "number"`, () => {
gen.assign(j, _`${indices}[${item}]`)
cxt.error()
gen.assign(valid, false).break()
})
.code(_`${indices}[${item}] = ${i};`)
})
}

function loopN2(): void {
gen
.code(_`outer:`)
.for(_`;${i}--;`, () =>
gen.for(_`${j} = ${i}; ${j}--;`, () =>
gen.if(_`equal(${data}[${i}], ${data}[${j}])`, _`${valid} = false; break outer;`)
)
function loopN2(i: Name, j: Name): void {
gen.code(_`outer:`).for(_`;${i}--;`, () =>
gen.for(_`${j} = ${i}; ${j}--;`, () =>
gen.if(_`equal(${data}[${i}], ${data}[${j}])`, () => {
cxt.error()
gen.assign(valid, false).break(_`outer`)
})
)
)
}
},
error: {
message: ({$data, params: {i, j}}) => {
const msg = str`should NOT have duplicate items (items ## ${j} and ${i} are identical)`
return $data ? _`(${i} === undefined ? "uniqueItems must be boolean ($data)" : ${msg})` : msg
},
params: ({$data, params: {i, j}}) => {
const obj = _`{i: ${i}, j: ${j}}`
return $data ? _`(${i} === undefined ? {} : ${obj})` : obj
},
message: ({params: {i, j}}) =>
i
? str`should NOT have duplicate items (items ## ${j} and ${i} are identical)`
: str`uniqueItems must be boolean ($data)`,
params: ({params: {i, j}}) => (i ? _`{i: ${i}, j: ${j}}` : _`{}`),
},
}

Expand Down

0 comments on commit d940126

Please sign in to comment.