massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / compile / jtd / serialize.ts
1 import type Ajv from "../../core"
2 import type {SchemaObject} from "../../types"
3 import {jtdForms, JTDForm, SchemaObjectMap} from "./types"
4 import {SchemaEnv, getCompilingSchema} from ".."
5 import {_, str, and, getProperty, CodeGen, Code, Name} from "../codegen"
6 import MissingRefError from "../ref_error"
7 import N from "../names"
8 import {isOwnProperty} from "../../vocabularies/code"
9 import {hasRef} from "../../vocabularies/jtd/ref"
10 import {useFunc} from "../util"
11 import quote from "../../runtime/quote"
12
13 const genSerialize: {[F in JTDForm]: (cxt: SerializeCxt) => void} = {
14   elements: serializeElements,
15   values: serializeValues,
16   discriminator: serializeDiscriminator,
17   properties: serializeProperties,
18   optionalProperties: serializeProperties,
19   enum: serializeString,
20   type: serializeType,
21   ref: serializeRef,
22 }
23
24 interface SerializeCxt {
25   readonly gen: CodeGen
26   readonly self: Ajv // current Ajv instance
27   readonly schemaEnv: SchemaEnv
28   readonly definitions: SchemaObjectMap
29   schema: SchemaObject
30   data: Code
31 }
32
33 export default function compileSerializer(
34   this: Ajv,
35   sch: SchemaEnv,
36   definitions: SchemaObjectMap
37 ): SchemaEnv {
38   const _sch = getCompilingSchema.call(this, sch)
39   if (_sch) return _sch
40   const {es5, lines} = this.opts.code
41   const {ownProperties} = this.opts
42   const gen = new CodeGen(this.scope, {es5, lines, ownProperties})
43   const serializeName = gen.scopeName("serialize")
44   const cxt: SerializeCxt = {
45     self: this,
46     gen,
47     schema: sch.schema as SchemaObject,
48     schemaEnv: sch,
49     definitions,
50     data: N.data,
51   }
52
53   let sourceCode: string | undefined
54   try {
55     this._compilations.add(sch)
56     sch.serializeName = serializeName
57     gen.func(serializeName, N.data, false, () => {
58       gen.let(N.json, str``)
59       serializeCode(cxt)
60       gen.return(N.json)
61     })
62     gen.optimize(this.opts.code.optimize)
63     const serializeFuncCode = gen.toString()
64     sourceCode = `${gen.scopeRefs(N.scope)}return ${serializeFuncCode}`
65     const makeSerialize = new Function(`${N.scope}`, sourceCode)
66     const serialize: (data: unknown) => string = makeSerialize(this.scope.get())
67     this.scope.value(serializeName, {ref: serialize})
68     sch.serialize = serialize
69   } catch (e) {
70     if (sourceCode) this.logger.error("Error compiling serializer, function code:", sourceCode)
71     delete sch.serialize
72     delete sch.serializeName
73     throw e
74   } finally {
75     this._compilations.delete(sch)
76   }
77   return sch
78 }
79
80 function serializeCode(cxt: SerializeCxt): void {
81   let form: JTDForm | undefined
82   for (const key of jtdForms) {
83     if (key in cxt.schema) {
84       form = key
85       break
86     }
87   }
88   serializeNullable(cxt, form ? genSerialize[form] : serializeEmpty)
89 }
90
91 function serializeNullable(cxt: SerializeCxt, serializeForm: (_cxt: SerializeCxt) => void): void {
92   const {gen, schema, data} = cxt
93   if (!schema.nullable) return serializeForm(cxt)
94   gen.if(
95     _`${data} === undefined || ${data} === null`,
96     () => gen.add(N.json, _`"null"`),
97     () => serializeForm(cxt)
98   )
99 }
100
101 function serializeElements(cxt: SerializeCxt): void {
102   const {gen, schema, data} = cxt
103   gen.add(N.json, str`[`)
104   const first = gen.let("first", true)
105   gen.forOf("el", data, (el) => {
106     addComma(cxt, first)
107     serializeCode({...cxt, schema: schema.elements, data: el})
108   })
109   gen.add(N.json, str`]`)
110 }
111
112 function serializeValues(cxt: SerializeCxt): void {
113   const {gen, schema, data} = cxt
114   gen.add(N.json, str`{`)
115   const first = gen.let("first", true)
116   gen.forIn("key", data, (key) => serializeKeyValue(cxt, key, schema.values, first))
117   gen.add(N.json, str`}`)
118 }
119
120 function serializeKeyValue(cxt: SerializeCxt, key: Name, schema: SchemaObject, first: Name): void {
121   const {gen, data} = cxt
122   addComma(cxt, first)
123   serializeString({...cxt, data: key})
124   gen.add(N.json, str`:`)
125   const value = gen.const("value", _`${data}${getProperty(key)}`)
126   serializeCode({...cxt, schema, data: value})
127 }
128
129 function serializeDiscriminator(cxt: SerializeCxt): void {
130   const {gen, schema, data} = cxt
131   const {discriminator} = schema
132   gen.add(N.json, str`{${JSON.stringify(discriminator)}:`)
133   const tag = gen.const("tag", _`${data}${getProperty(discriminator)}`)
134   serializeString({...cxt, data: tag})
135   gen.if(false)
136   for (const tagValue in schema.mapping) {
137     gen.elseIf(_`${tag} === ${tagValue}`)
138     const sch = schema.mapping[tagValue]
139     serializeSchemaProperties({...cxt, schema: sch}, discriminator)
140   }
141   gen.endIf()
142   gen.add(N.json, str`}`)
143 }
144
145 function serializeProperties(cxt: SerializeCxt): void {
146   const {gen} = cxt
147   gen.add(N.json, str`{`)
148   serializeSchemaProperties(cxt)
149   gen.add(N.json, str`}`)
150 }
151
152 function serializeSchemaProperties(cxt: SerializeCxt, discriminator?: string): void {
153   const {gen, schema, data} = cxt
154   const {properties, optionalProperties} = schema
155   const props = keys(properties)
156   const optProps = keys(optionalProperties)
157   const allProps = allProperties(props.concat(optProps))
158   let first = !discriminator
159   for (const key of props) {
160     serializeProperty(key, properties[key], keyValue(key))
161   }
162   for (const key of optProps) {
163     const value = keyValue(key)
164     gen.if(and(_`${value} !== undefined`, isOwnProperty(gen, data, key)), () =>
165       serializeProperty(key, optionalProperties[key], value)
166     )
167   }
168   if (schema.additionalProperties) {
169     gen.forIn("key", data, (key) =>
170       gen.if(isAdditional(key, allProps), () =>
171         serializeKeyValue(cxt, key, {}, gen.let("first", first))
172       )
173     )
174   }
175
176   function keys(ps?: SchemaObjectMap): string[] {
177     return ps ? Object.keys(ps) : []
178   }
179
180   function allProperties(ps: string[]): string[] {
181     if (discriminator) ps.push(discriminator)
182     if (new Set(ps).size !== ps.length) {
183       throw new Error("JTD: properties/optionalProperties/disciminator overlap")
184     }
185     return ps
186   }
187
188   function keyValue(key: string): Name {
189     return gen.const("value", _`${data}${getProperty(key)}`)
190   }
191
192   function serializeProperty(key: string, propSchema: SchemaObject, value: Name): void {
193     if (first) first = false
194     else gen.add(N.json, str`,`)
195     gen.add(N.json, str`${JSON.stringify(key)}:`)
196     serializeCode({...cxt, schema: propSchema, data: value})
197   }
198
199   function isAdditional(key: Name, ps: string[]): Code | true {
200     return ps.length ? and(...ps.map((p) => _`${key} !== ${p}`)) : true
201   }
202 }
203
204 function serializeType(cxt: SerializeCxt): void {
205   const {gen, schema, data} = cxt
206   switch (schema.type) {
207     case "boolean":
208       gen.add(N.json, _`${data} ? "true" : "false"`)
209       break
210     case "string":
211       serializeString(cxt)
212       break
213     case "timestamp":
214       gen.if(
215         _`${data} instanceof Date`,
216         () => gen.add(N.json, _`'"' + ${data}.toISOString() + '"'`),
217         () => serializeString(cxt)
218       )
219       break
220     default:
221       serializeNumber(cxt)
222   }
223 }
224
225 function serializeString({gen, data}: SerializeCxt): void {
226   gen.add(N.json, _`${useFunc(gen, quote)}(${data})`)
227 }
228
229 function serializeNumber({gen, data}: SerializeCxt): void {
230   gen.add(N.json, _`"" + ${data}`)
231 }
232
233 function serializeRef(cxt: SerializeCxt): void {
234   const {gen, self, data, definitions, schema, schemaEnv} = cxt
235   const {ref} = schema
236   const refSchema = definitions[ref]
237   if (!refSchema) throw new MissingRefError("", ref, `No definition ${ref}`)
238   if (!hasRef(refSchema)) return serializeCode({...cxt, schema: refSchema})
239   const {root} = schemaEnv
240   const sch = compileSerializer.call(self, new SchemaEnv({schema: refSchema, root}), definitions)
241   gen.add(N.json, _`${getSerialize(gen, sch)}(${data})`)
242 }
243
244 function getSerialize(gen: CodeGen, sch: SchemaEnv): Code {
245   return sch.serialize
246     ? gen.scopeValue("serialize", {ref: sch.serialize})
247     : _`${gen.scopeValue("wrapper", {ref: sch})}.serialize`
248 }
249
250 function serializeEmpty({gen, data}: SerializeCxt): void {
251   gen.add(N.json, _`JSON.stringify(${data})`)
252 }
253
254 function addComma({gen}: SerializeCxt, first: Name): void {
255   gen.if(
256     first,
257     () => gen.assign(first, false),
258     () => gen.add(N.json, str`,`)
259   )
260 }