[6a3a178] | 1 | import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
|
---|
| 2 | import type {KeywordCxt} from "../../compile/validate"
|
---|
| 3 | import {checkDataTypes, getSchemaTypes, DataType} from "../../compile/validate/dataType"
|
---|
| 4 | import {_, str, Name} from "../../compile/codegen"
|
---|
| 5 | import {useFunc} from "../../compile/util"
|
---|
| 6 | import equal from "../../runtime/equal"
|
---|
| 7 |
|
---|
| 8 | export type UniqueItemsError = ErrorObject<
|
---|
| 9 | "uniqueItems",
|
---|
| 10 | {i: number; j: number},
|
---|
| 11 | boolean | {$data: string}
|
---|
| 12 | >
|
---|
| 13 |
|
---|
| 14 | const error: KeywordErrorDefinition = {
|
---|
| 15 | message: ({params: {i, j}}) =>
|
---|
| 16 | str`must NOT have duplicate items (items ## ${j} and ${i} are identical)`,
|
---|
| 17 | params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`,
|
---|
| 18 | }
|
---|
| 19 |
|
---|
| 20 | const def: CodeKeywordDefinition = {
|
---|
| 21 | keyword: "uniqueItems",
|
---|
| 22 | type: "array",
|
---|
| 23 | schemaType: "boolean",
|
---|
| 24 | $data: true,
|
---|
| 25 | error,
|
---|
| 26 | code(cxt: KeywordCxt) {
|
---|
| 27 | const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt
|
---|
| 28 | if (!$data && !schema) return
|
---|
| 29 | const valid = gen.let("valid")
|
---|
| 30 | const itemTypes = parentSchema.items ? getSchemaTypes(parentSchema.items) : []
|
---|
| 31 | cxt.block$data(valid, validateUniqueItems, _`${schemaCode} === false`)
|
---|
| 32 | cxt.ok(valid)
|
---|
| 33 |
|
---|
| 34 | function validateUniqueItems(): void {
|
---|
| 35 | const i = gen.let("i", _`${data}.length`)
|
---|
| 36 | const j = gen.let("j")
|
---|
| 37 | cxt.setParams({i, j})
|
---|
| 38 | gen.assign(valid, true)
|
---|
| 39 | gen.if(_`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j))
|
---|
| 40 | }
|
---|
| 41 |
|
---|
| 42 | function canOptimize(): boolean {
|
---|
| 43 | return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array")
|
---|
| 44 | }
|
---|
| 45 |
|
---|
| 46 | function loopN(i: Name, j: Name): void {
|
---|
| 47 | const item = gen.name("item")
|
---|
| 48 | const wrongType = checkDataTypes(itemTypes, item, it.opts.strictNumbers, DataType.Wrong)
|
---|
| 49 | const indices = gen.const("indices", _`{}`)
|
---|
| 50 | gen.for(_`;${i}--;`, () => {
|
---|
| 51 | gen.let(item, _`${data}[${i}]`)
|
---|
| 52 | gen.if(wrongType, _`continue`)
|
---|
| 53 | if (itemTypes.length > 1) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`)
|
---|
| 54 | gen
|
---|
| 55 | .if(_`typeof ${indices}[${item}] == "number"`, () => {
|
---|
| 56 | gen.assign(j, _`${indices}[${item}]`)
|
---|
| 57 | cxt.error()
|
---|
| 58 | gen.assign(valid, false).break()
|
---|
| 59 | })
|
---|
| 60 | .code(_`${indices}[${item}] = ${i}`)
|
---|
| 61 | })
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | function loopN2(i: Name, j: Name): void {
|
---|
| 65 | const eql = useFunc(gen, equal)
|
---|
| 66 | const outer = gen.name("outer")
|
---|
| 67 | gen.label(outer).for(_`;${i}--;`, () =>
|
---|
| 68 | gen.for(_`${j} = ${i}; ${j}--;`, () =>
|
---|
| 69 | gen.if(_`${eql}(${data}[${i}], ${data}[${j}])`, () => {
|
---|
| 70 | cxt.error()
|
---|
| 71 | gen.assign(valid, false).break(outer)
|
---|
| 72 | })
|
---|
| 73 | )
|
---|
| 74 | )
|
---|
| 75 | }
|
---|
| 76 | },
|
---|
| 77 | }
|
---|
| 78 |
|
---|
| 79 | export default def
|
---|