.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / compile / validate / dataType.ts
1 import type {
2   KeywordErrorDefinition,
3   KeywordErrorCxt,
4   ErrorObject,
5   AnySchemaObject,
6 } from "../../types"
7 import type {SchemaObjCxt} from ".."
8 import {isJSONType, JSONType} from "../rules"
9 import {schemaHasRulesForType} from "./applicability"
10 import {reportError} from "../errors"
11 import {_, nil, and, not, operators, Code, Name} from "../codegen"
12 import {toHash, schemaRefOrVal} from "../util"
13
14 export enum DataType {
15   Correct,
16   Wrong,
17 }
18
19 export function getSchemaTypes(schema: AnySchemaObject): JSONType[] {
20   const types = getJSONTypes(schema.type)
21   const hasNull = types.includes("null")
22   if (hasNull) {
23     if (schema.nullable === false) throw new Error("type: null contradicts nullable: false")
24   } else {
25     if (!types.length && schema.nullable !== undefined) {
26       throw new Error('"nullable" cannot be used without "type"')
27     }
28     if (schema.nullable === true) types.push("null")
29   }
30   return types
31 }
32
33 export function getJSONTypes(ts: unknown | unknown[]): JSONType[] {
34   const types: unknown[] = Array.isArray(ts) ? ts : ts ? [ts] : []
35   if (types.every(isJSONType)) return types
36   throw new Error("type must be JSONType or JSONType[]: " + types.join(","))
37 }
38
39 export function coerceAndCheckDataType(it: SchemaObjCxt, types: JSONType[]): boolean {
40   const {gen, data, opts} = it
41   const coerceTo = coerceToTypes(types, opts.coerceTypes)
42   const checkTypes =
43     types.length > 0 &&
44     !(coerceTo.length === 0 && types.length === 1 && schemaHasRulesForType(it, types[0]))
45   if (checkTypes) {
46     const wrongType = checkDataTypes(types, data, opts.strictNumbers, DataType.Wrong)
47     gen.if(wrongType, () => {
48       if (coerceTo.length) coerceData(it, types, coerceTo)
49       else reportTypeError(it)
50     })
51   }
52   return checkTypes
53 }
54
55 const COERCIBLE: Set<JSONType> = new Set(["string", "number", "integer", "boolean", "null"])
56 function coerceToTypes(types: JSONType[], coerceTypes?: boolean | "array"): JSONType[] {
57   return coerceTypes
58     ? types.filter((t) => COERCIBLE.has(t) || (coerceTypes === "array" && t === "array"))
59     : []
60 }
61
62 function coerceData(it: SchemaObjCxt, types: JSONType[], coerceTo: JSONType[]): void {
63   const {gen, data, opts} = it
64   const dataType = gen.let("dataType", _`typeof ${data}`)
65   const coerced = gen.let("coerced", _`undefined`)
66   if (opts.coerceTypes === "array") {
67     gen.if(_`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () =>
68       gen
69         .assign(data, _`${data}[0]`)
70         .assign(dataType, _`typeof ${data}`)
71         .if(checkDataTypes(types, data, opts.strictNumbers), () => gen.assign(coerced, data))
72     )
73   }
74   gen.if(_`${coerced} !== undefined`)
75   for (const t of coerceTo) {
76     if (COERCIBLE.has(t) || (t === "array" && opts.coerceTypes === "array")) {
77       coerceSpecificType(t)
78     }
79   }
80   gen.else()
81   reportTypeError(it)
82   gen.endIf()
83
84   gen.if(_`${coerced} !== undefined`, () => {
85     gen.assign(data, coerced)
86     assignParentData(it, coerced)
87   })
88
89   function coerceSpecificType(t: string): void {
90     switch (t) {
91       case "string":
92         gen
93           .elseIf(_`${dataType} == "number" || ${dataType} == "boolean"`)
94           .assign(coerced, _`"" + ${data}`)
95           .elseIf(_`${data} === null`)
96           .assign(coerced, _`""`)
97         return
98       case "number":
99         gen
100           .elseIf(
101             _`${dataType} == "boolean" || ${data} === null
102               || (${dataType} == "string" && ${data} && ${data} == +${data})`
103           )
104           .assign(coerced, _`+${data}`)
105         return
106       case "integer":
107         gen
108           .elseIf(
109             _`${dataType} === "boolean" || ${data} === null
110               || (${dataType} === "string" && ${data} && ${data} == +${data} && !(${data} % 1))`
111           )
112           .assign(coerced, _`+${data}`)
113         return
114       case "boolean":
115         gen
116           .elseIf(_`${data} === "false" || ${data} === 0 || ${data} === null`)
117           .assign(coerced, false)
118           .elseIf(_`${data} === "true" || ${data} === 1`)
119           .assign(coerced, true)
120         return
121       case "null":
122         gen.elseIf(_`${data} === "" || ${data} === 0 || ${data} === false`)
123         gen.assign(coerced, null)
124         return
125
126       case "array":
127         gen
128           .elseIf(
129             _`${dataType} === "string" || ${dataType} === "number"
130               || ${dataType} === "boolean" || ${data} === null`
131           )
132           .assign(coerced, _`[${data}]`)
133     }
134   }
135 }
136
137 function assignParentData({gen, parentData, parentDataProperty}: SchemaObjCxt, expr: Name): void {
138   // TODO use gen.property
139   gen.if(_`${parentData} !== undefined`, () =>
140     gen.assign(_`${parentData}[${parentDataProperty}]`, expr)
141   )
142 }
143
144 export function checkDataType(
145   dataType: JSONType,
146   data: Name,
147   strictNums?: boolean | "log",
148   correct = DataType.Correct
149 ): Code {
150   const EQ = correct === DataType.Correct ? operators.EQ : operators.NEQ
151   let cond: Code
152   switch (dataType) {
153     case "null":
154       return _`${data} ${EQ} null`
155     case "array":
156       cond = _`Array.isArray(${data})`
157       break
158     case "object":
159       cond = _`${data} && typeof ${data} == "object" && !Array.isArray(${data})`
160       break
161     case "integer":
162       cond = numCond(_`!(${data} % 1) && !isNaN(${data})`)
163       break
164     case "number":
165       cond = numCond()
166       break
167     default:
168       return _`typeof ${data} ${EQ} ${dataType}`
169   }
170   return correct === DataType.Correct ? cond : not(cond)
171
172   function numCond(_cond: Code = nil): Code {
173     return and(_`typeof ${data} == "number"`, _cond, strictNums ? _`isFinite(${data})` : nil)
174   }
175 }
176
177 export function checkDataTypes(
178   dataTypes: JSONType[],
179   data: Name,
180   strictNums?: boolean | "log",
181   correct?: DataType
182 ): Code {
183   if (dataTypes.length === 1) {
184     return checkDataType(dataTypes[0], data, strictNums, correct)
185   }
186   let cond: Code
187   const types = toHash(dataTypes)
188   if (types.array && types.object) {
189     const notObj = _`typeof ${data} != "object"`
190     cond = types.null ? notObj : _`!${data} || ${notObj}`
191     delete types.null
192     delete types.array
193     delete types.object
194   } else {
195     cond = nil
196   }
197   if (types.number) delete types.integer
198   for (const t in types) cond = and(cond, checkDataType(t as JSONType, data, strictNums, correct))
199   return cond
200 }
201
202 export type TypeError = ErrorObject<"type", {type: string}>
203
204 const typeError: KeywordErrorDefinition = {
205   message: ({schema}) => `must be ${schema}`,
206   params: ({schema, schemaValue}) =>
207     typeof schema == "string" ? _`{type: ${schema}}` : _`{type: ${schemaValue}}`,
208 }
209
210 export function reportTypeError(it: SchemaObjCxt): void {
211   const cxt = getTypeErrorContext(it)
212   reportError(cxt, typeError)
213 }
214
215 function getTypeErrorContext(it: SchemaObjCxt): KeywordErrorCxt {
216   const {gen, data, schema} = it
217   const schemaCode = schemaRefOrVal(it, schema, "type")
218   return {
219     gen,
220     keyword: "type",
221     data,
222     schema: schema.type,
223     schemaCode,
224     schemaValue: schemaCode,
225     parentSchema: schema,
226     params: {},
227     it,
228   }
229 }