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
|
---|