.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / core.ts
1 export {
2   Format,
3   FormatDefinition,
4   AsyncFormatDefinition,
5   KeywordDefinition,
6   KeywordErrorDefinition,
7   CodeKeywordDefinition,
8   MacroKeywordDefinition,
9   FuncKeywordDefinition,
10   Vocabulary,
11   Schema,
12   SchemaObject,
13   AnySchemaObject,
14   AsyncSchema,
15   AnySchema,
16   ValidateFunction,
17   AsyncValidateFunction,
18   AnyValidateFunction,
19   ErrorObject,
20   ErrorNoParams,
21 } from "./types"
22
23 export {SchemaCxt, SchemaObjCxt} from "./compile"
24 export interface Plugin<Opts> {
25   (ajv: Ajv, options?: Opts): Ajv
26   [prop: string]: any
27 }
28
29 export {KeywordCxt} from "./compile/validate"
30 export {DefinedError} from "./vocabularies/errors"
31 export {JSONType} from "./compile/rules"
32 export {JSONSchemaType} from "./types/json-schema"
33 export {JTDSchemaType} from "./types/jtd-schema"
34 export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"
35
36 import type {
37   Schema,
38   AnySchema,
39   AnySchemaObject,
40   SchemaObject,
41   AsyncSchema,
42   Vocabulary,
43   KeywordDefinition,
44   AddedKeywordDefinition,
45   AnyValidateFunction,
46   ValidateFunction,
47   AsyncValidateFunction,
48   ErrorObject,
49   Format,
50   AddedFormat,
51 } from "./types"
52 import type {JSONSchemaType} from "./types/json-schema"
53 import type {JTDSchemaType} from "./types/jtd-schema"
54 import ValidationError from "./runtime/validation_error"
55 import MissingRefError from "./compile/ref_error"
56 import {getRules, ValidationRules, Rule, RuleGroup, JSONType} from "./compile/rules"
57 import {SchemaEnv, compileSchema, resolveSchema} from "./compile"
58 import {Code, ValueScope} from "./compile/codegen"
59 import {normalizeId, getSchemaRefs} from "./compile/resolve"
60 import {getJSONTypes} from "./compile/validate/dataType"
61 import {eachItem} from "./compile/util"
62
63 import * as $dataRefSchema from "./refs/data.json"
64
65 const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"]
66 const EXT_SCOPE_NAMES = new Set([
67   "validate",
68   "serialize",
69   "parse",
70   "wrapper",
71   "root",
72   "schema",
73   "keyword",
74   "pattern",
75   "formats",
76   "validate$data",
77   "func",
78   "obj",
79   "Error",
80 ])
81
82 export type Options = CurrentOptions & DeprecatedOptions
83
84 export interface CurrentOptions {
85   // strict mode options (NEW)
86   strict?: boolean | "log"
87   strictSchema?: boolean | "log"
88   strictNumbers?: boolean | "log"
89   strictTypes?: boolean | "log"
90   strictTuples?: boolean | "log"
91   strictRequired?: boolean | "log"
92   allowMatchingProperties?: boolean // disables a strict mode restriction
93   allowUnionTypes?: boolean
94   validateFormats?: boolean
95   // validation and reporting options:
96   $data?: boolean
97   allErrors?: boolean
98   verbose?: boolean
99   discriminator?: boolean
100   $comment?:
101     | true
102     | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown)
103   formats?: {[Name in string]?: Format}
104   keywords?: Vocabulary
105   schemas?: AnySchema[] | {[Key in string]?: AnySchema}
106   logger?: Logger | false
107   loadSchema?: (uri: string) => Promise<AnySchemaObject>
108   // options to modify validated data:
109   removeAdditional?: boolean | "all" | "failing"
110   useDefaults?: boolean | "empty"
111   coerceTypes?: boolean | "array"
112   // advanced options:
113   next?: boolean // NEW
114   unevaluated?: boolean // NEW
115   dynamicRef?: boolean // NEW
116   jtd?: boolean // NEW
117   meta?: SchemaObject | boolean
118   defaultMeta?: string | AnySchemaObject
119   validateSchema?: boolean | "log"
120   addUsedSchema?: boolean
121   inlineRefs?: boolean | number
122   passContext?: boolean
123   loopRequired?: number
124   loopEnum?: number // NEW
125   ownProperties?: boolean
126   multipleOfPrecision?: number
127   messages?: boolean
128   code?: CodeOptions // NEW
129 }
130
131 export interface CodeOptions {
132   es5?: boolean
133   lines?: boolean
134   optimize?: boolean | number
135   formats?: Code // code to require (or construct) map of available formats - for standalone code
136   source?: boolean
137   process?: (code: string, schema?: SchemaEnv) => string
138 }
139
140 interface InstanceCodeOptions extends CodeOptions {
141   optimize: number
142 }
143
144 interface DeprecatedOptions {
145   /** @deprecated */
146   ignoreKeywordsWithRef?: boolean
147   /** @deprecated */
148   jsPropertySyntax?: boolean // added instead of jsonPointers
149   /** @deprecated */
150   unicode?: boolean
151 }
152
153 interface RemovedOptions {
154   format?: boolean
155   errorDataPath?: "object" | "property"
156   nullable?: boolean // "nullable" keyword is supported by default
157   jsonPointers?: boolean
158   extendRefs?: true | "ignore" | "fail"
159   missingRefs?: true | "ignore" | "fail"
160   processCode?: (code: string, schema?: SchemaEnv) => string
161   sourceCode?: boolean
162   schemaId?: string
163   strictDefaults?: boolean
164   strictKeywords?: boolean
165   uniqueItems?: boolean
166   unknownFormats?: true | string[] | "ignore"
167   cache?: any
168   serialize?: (schema: AnySchema) => unknown
169   ajvErrors?: boolean
170 }
171
172 type OptionsInfo<T extends RemovedOptions | DeprecatedOptions> = {
173   [K in keyof T]-?: string | undefined
174 }
175
176 const removedOptions: OptionsInfo<RemovedOptions> = {
177   errorDataPath: "",
178   format: "`validateFormats: false` can be used instead.",
179   nullable: '"nullable" keyword is supported by default.',
180   jsonPointers: "Deprecated jsPropertySyntax can be used instead.",
181   extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.",
182   missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.",
183   processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`",
184   sourceCode: "Use option `code: {source: true}`",
185   schemaId: "JSON Schema draft-04 is not supported in Ajv v7/8.",
186   strictDefaults: "It is default now, see option `strict`.",
187   strictKeywords: "It is default now, see option `strict`.",
188   uniqueItems: '"uniqueItems" keyword is always validated.',
189   unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",
190   cache: "Map is used as cache, schema object as key.",
191   serialize: "Map is used as cache, schema object as key.",
192   ajvErrors: "It is default now, see option `strict`.",
193 }
194
195 const deprecatedOptions: OptionsInfo<DeprecatedOptions> = {
196   ignoreKeywordsWithRef: "",
197   jsPropertySyntax: "",
198   unicode: '"minLength"/"maxLength" account for unicode characters by default.',
199 }
200
201 type RequiredInstanceOptions = {
202   [K in
203     | "strictSchema"
204     | "strictNumbers"
205     | "strictTypes"
206     | "strictTuples"
207     | "strictRequired"
208     | "inlineRefs"
209     | "loopRequired"
210     | "loopEnum"
211     | "meta"
212     | "messages"
213     | "addUsedSchema"
214     | "validateSchema"
215     | "validateFormats"]: NonNullable<Options[K]>
216 } & {code: InstanceCodeOptions}
217
218 export type InstanceOptions = Options & RequiredInstanceOptions
219
220 const MAX_EXPRESSION = 200
221
222 // eslint-disable-next-line complexity
223 function requiredOptions(o: Options): RequiredInstanceOptions {
224   const s = o.strict
225   const _optz = o.code?.optimize
226   const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0
227   return {
228     strictSchema: o.strictSchema ?? s ?? true,
229     strictNumbers: o.strictNumbers ?? s ?? true,
230     strictTypes: o.strictTypes ?? s ?? "log",
231     strictTuples: o.strictTuples ?? s ?? "log",
232     strictRequired: o.strictRequired ?? s ?? false,
233     code: o.code ? {...o.code, optimize} : {optimize},
234     loopRequired: o.loopRequired ?? MAX_EXPRESSION,
235     loopEnum: o.loopEnum ?? MAX_EXPRESSION,
236     meta: o.meta ?? true,
237     messages: o.messages ?? true,
238     inlineRefs: o.inlineRefs ?? true,
239     addUsedSchema: o.addUsedSchema ?? true,
240     validateSchema: o.validateSchema ?? true,
241     validateFormats: o.validateFormats ?? true,
242   }
243 }
244
245 export interface Logger {
246   log(...args: unknown[]): unknown
247   warn(...args: unknown[]): unknown
248   error(...args: unknown[]): unknown
249 }
250
251 export default class Ajv {
252   opts: InstanceOptions
253   errors?: ErrorObject[] | null // errors from the last validation
254   logger: Logger
255   // shared external scope values for compiled functions
256   readonly scope: ValueScope
257   readonly schemas: {[Key in string]?: SchemaEnv} = {}
258   readonly refs: {[Ref in string]?: SchemaEnv | string} = {}
259   readonly formats: {[Name in string]?: AddedFormat} = {}
260   readonly RULES: ValidationRules
261   readonly _compilations: Set<SchemaEnv> = new Set()
262   private readonly _loading: {[Ref in string]?: Promise<AnySchemaObject>} = {}
263   private readonly _cache: Map<AnySchema, SchemaEnv> = new Map()
264   private readonly _metaOpts: InstanceOptions
265
266   static ValidationError = ValidationError
267   static MissingRefError = MissingRefError
268
269   constructor(opts: Options = {}) {
270     opts = this.opts = {...opts, ...requiredOptions(opts)}
271     const {es5, lines} = this.opts.code
272     this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines})
273     this.logger = getLogger(opts.logger)
274     const formatOpt = opts.validateFormats
275     opts.validateFormats = false
276
277     this.RULES = getRules()
278     checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED")
279     checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn")
280     this._metaOpts = getMetaSchemaOptions.call(this)
281
282     if (opts.formats) addInitialFormats.call(this)
283     this._addVocabularies()
284     this._addDefaultMetaSchema()
285     if (opts.keywords) addInitialKeywords.call(this, opts.keywords)
286     if (typeof opts.meta == "object") this.addMetaSchema(opts.meta)
287     addInitialSchemas.call(this)
288     opts.validateFormats = formatOpt
289   }
290
291   _addVocabularies(): void {
292     this.addKeyword("$async")
293   }
294
295   _addDefaultMetaSchema(): void {
296     const {$data, meta} = this.opts
297     if (meta && $data) this.addMetaSchema($dataRefSchema, $dataRefSchema.$id, false)
298   }
299
300   defaultMeta(): string | AnySchemaObject | undefined {
301     const {meta} = this.opts
302     return (this.opts.defaultMeta = typeof meta == "object" ? meta.$id || meta : undefined)
303   }
304
305   // Validate data using schema
306   // AnySchema will be compiled and cached using schema itself as a key for Map
307   validate(schema: Schema | string, data: unknown): boolean
308   validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise<unknown>
309   validate<T>(schema: Schema | JSONSchemaType<T> | string, data: unknown): data is T
310   // Separated for type inference to work
311   // eslint-disable-next-line @typescript-eslint/unified-signatures
312   validate<T>(schema: JTDSchemaType<T>, data: unknown): data is T
313   validate<T>(schema: AsyncSchema, data: unknown | T): Promise<T>
314   validate<T>(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise<T>
315   validate<T>(
316     schemaKeyRef: AnySchema | string, // key, ref or schema object
317     data: unknown | T // to be validated
318   ): boolean | Promise<T> {
319     let v: AnyValidateFunction | undefined
320     if (typeof schemaKeyRef == "string") {
321       v = this.getSchema<T>(schemaKeyRef)
322       if (!v) throw new Error(`no schema with key or ref "${schemaKeyRef}"`)
323     } else {
324       v = this.compile<T>(schemaKeyRef)
325     }
326
327     const valid = v(data)
328     if (!("$async" in v)) this.errors = v.errors
329     return valid
330   }
331
332   // Create validation function for passed schema
333   // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords.
334   compile<T = unknown>(schema: Schema | JSONSchemaType<T>, _meta?: boolean): ValidateFunction<T>
335   // Separated for type inference to work
336   // eslint-disable-next-line @typescript-eslint/unified-signatures
337   compile<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): ValidateFunction<T>
338   compile<T = unknown>(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction<T>
339   compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T>
340   compile<T = unknown>(schema: AnySchema, _meta?: boolean): AnyValidateFunction<T> {
341     const sch = this._addSchema(schema, _meta)
342     return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T>
343   }
344
345   // Creates validating function for passed schema with asynchronous loading of missing schemas.
346   // `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
347   // TODO allow passing schema URI
348   // meta - optional true to compile meta-schema
349   compileAsync<T = unknown>(
350     schema: SchemaObject | JSONSchemaType<T>,
351     _meta?: boolean
352   ): Promise<ValidateFunction<T>>
353   // Separated for type inference to work
354   // eslint-disable-next-line @typescript-eslint/unified-signatures
355   compileAsync<T = unknown>(schema: JTDSchemaType<T>, _meta?: boolean): Promise<ValidateFunction<T>>
356   compileAsync<T = unknown>(schema: AsyncSchema, meta?: boolean): Promise<AsyncValidateFunction<T>>
357   // eslint-disable-next-line @typescript-eslint/unified-signatures
358   compileAsync<T = unknown>(
359     schema: AnySchemaObject,
360     meta?: boolean
361   ): Promise<AnyValidateFunction<T>>
362   compileAsync<T = unknown>(
363     schema: AnySchemaObject,
364     meta?: boolean
365   ): Promise<AnyValidateFunction<T>> {
366     if (typeof this.opts.loadSchema != "function") {
367       throw new Error("options.loadSchema should be a function")
368     }
369     const {loadSchema} = this.opts
370     return runCompileAsync.call(this, schema, meta)
371
372     async function runCompileAsync(
373       this: Ajv,
374       _schema: AnySchemaObject,
375       _meta?: boolean
376     ): Promise<AnyValidateFunction> {
377       await loadMetaSchema.call(this, _schema.$schema)
378       const sch = this._addSchema(_schema, _meta)
379       return sch.validate || _compileAsync.call(this, sch)
380     }
381
382     async function loadMetaSchema(this: Ajv, $ref?: string): Promise<void> {
383       if ($ref && !this.getSchema($ref)) {
384         await runCompileAsync.call(this, {$ref}, true)
385       }
386     }
387
388     async function _compileAsync(this: Ajv, sch: SchemaEnv): Promise<AnyValidateFunction> {
389       try {
390         return this._compileSchemaEnv(sch)
391       } catch (e) {
392         if (!(e instanceof MissingRefError)) throw e
393         checkLoaded.call(this, e)
394         await loadMissingSchema.call(this, e.missingSchema)
395         return _compileAsync.call(this, sch)
396       }
397     }
398
399     function checkLoaded(this: Ajv, {missingSchema: ref, missingRef}: MissingRefError): void {
400       if (this.refs[ref]) {
401         throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`)
402       }
403     }
404
405     async function loadMissingSchema(this: Ajv, ref: string): Promise<void> {
406       const _schema = await _loadSchema.call(this, ref)
407       if (!this.refs[ref]) await loadMetaSchema.call(this, _schema.$schema)
408       if (!this.refs[ref]) this.addSchema(_schema, ref, meta)
409     }
410
411     async function _loadSchema(this: Ajv, ref: string): Promise<AnySchemaObject> {
412       const p = this._loading[ref]
413       if (p) return p
414       try {
415         return await (this._loading[ref] = loadSchema(ref))
416       } finally {
417         delete this._loading[ref]
418       }
419     }
420   }
421
422   // Adds schema to the instance
423   addSchema(
424     schema: AnySchema | AnySchema[], // If array is passed, `key` will be ignored
425     key?: string, // Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
426     _meta?: boolean, // true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
427     _validateSchema = this.opts.validateSchema // false to skip schema validation. Used internally, option validateSchema should be used instead.
428   ): Ajv {
429     if (Array.isArray(schema)) {
430       for (const sch of schema) this.addSchema(sch, undefined, _meta, _validateSchema)
431       return this
432     }
433     let id: string | undefined
434     if (typeof schema === "object") {
435       id = schema.$id
436       if (id !== undefined && typeof id != "string") throw new Error("schema $id must be string")
437     }
438     key = normalizeId(key || id)
439     this._checkUnique(key)
440     this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true)
441     return this
442   }
443
444   // Add schema that will be used to validate other schemas
445   // options in META_IGNORE_OPTIONS are alway set to false
446   addMetaSchema(
447     schema: AnySchemaObject,
448     key?: string, // schema key
449     _validateSchema = this.opts.validateSchema // false to skip schema validation, can be used to override validateSchema option for meta-schema
450   ): Ajv {
451     this.addSchema(schema, key, true, _validateSchema)
452     return this
453   }
454
455   //  Validate schema against its meta-schema
456   validateSchema(schema: AnySchema, throwOrLogError?: boolean): boolean | Promise<unknown> {
457     if (typeof schema == "boolean") return true
458     let $schema: string | AnySchemaObject | undefined
459     $schema = schema.$schema
460     if ($schema !== undefined && typeof $schema != "string") {
461       throw new Error("$schema must be a string")
462     }
463     $schema = $schema || this.opts.defaultMeta || this.defaultMeta()
464     if (!$schema) {
465       this.logger.warn("meta-schema not available")
466       this.errors = null
467       return true
468     }
469     const valid = this.validate($schema, schema)
470     if (!valid && throwOrLogError) {
471       const message = "schema is invalid: " + this.errorsText()
472       if (this.opts.validateSchema === "log") this.logger.error(message)
473       else throw new Error(message)
474     }
475     return valid
476   }
477
478   // Get compiled schema by `key` or `ref`.
479   // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id)
480   getSchema<T = unknown>(keyRef: string): AnyValidateFunction<T> | undefined {
481     let sch
482     while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch
483     if (sch === undefined) {
484       const root = new SchemaEnv({schema: {}})
485       sch = resolveSchema.call(this, root, keyRef)
486       if (!sch) return
487       this.refs[keyRef] = sch
488     }
489     return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction<T> | undefined
490   }
491
492   // Remove cached schema(s).
493   // If no parameter is passed all schemas but meta-schemas are removed.
494   // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
495   // Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
496   removeSchema(schemaKeyRef?: AnySchema | string | RegExp): Ajv {
497     if (schemaKeyRef instanceof RegExp) {
498       this._removeAllSchemas(this.schemas, schemaKeyRef)
499       this._removeAllSchemas(this.refs, schemaKeyRef)
500       return this
501     }
502     switch (typeof schemaKeyRef) {
503       case "undefined":
504         this._removeAllSchemas(this.schemas)
505         this._removeAllSchemas(this.refs)
506         this._cache.clear()
507         return this
508       case "string": {
509         const sch = getSchEnv.call(this, schemaKeyRef)
510         if (typeof sch == "object") this._cache.delete(sch.schema)
511         delete this.schemas[schemaKeyRef]
512         delete this.refs[schemaKeyRef]
513         return this
514       }
515       case "object": {
516         const cacheKey = schemaKeyRef
517         this._cache.delete(cacheKey)
518         let id = schemaKeyRef.$id
519         if (id) {
520           id = normalizeId(id)
521           delete this.schemas[id]
522           delete this.refs[id]
523         }
524         return this
525       }
526       default:
527         throw new Error("ajv.removeSchema: invalid parameter")
528     }
529   }
530
531   // add "vocabulary" - a collection of keywords
532   addVocabulary(definitions: Vocabulary): Ajv {
533     for (const def of definitions) this.addKeyword(def)
534     return this
535   }
536
537   addKeyword(
538     kwdOrDef: string | KeywordDefinition,
539     def?: KeywordDefinition // deprecated
540   ): Ajv {
541     let keyword: string | string[]
542     if (typeof kwdOrDef == "string") {
543       keyword = kwdOrDef
544       if (typeof def == "object") {
545         this.logger.warn("these parameters are deprecated, see docs for addKeyword")
546         def.keyword = keyword
547       }
548     } else if (typeof kwdOrDef == "object" && def === undefined) {
549       def = kwdOrDef
550       keyword = def.keyword
551       if (Array.isArray(keyword) && !keyword.length) {
552         throw new Error("addKeywords: keyword must be string or non-empty array")
553       }
554     } else {
555       throw new Error("invalid addKeywords parameters")
556     }
557
558     checkKeyword.call(this, keyword, def)
559     if (!def) {
560       eachItem(keyword, (kwd) => addRule.call(this, kwd))
561       return this
562     }
563     keywordMetaschema.call(this, def)
564     const definition: AddedKeywordDefinition = {
565       ...def,
566       type: getJSONTypes(def.type),
567       schemaType: getJSONTypes(def.schemaType),
568     }
569     eachItem(
570       keyword,
571       definition.type.length === 0
572         ? (k) => addRule.call(this, k, definition)
573         : (k) => definition.type.forEach((t) => addRule.call(this, k, definition, t))
574     )
575     return this
576   }
577
578   getKeyword(keyword: string): AddedKeywordDefinition | boolean {
579     const rule = this.RULES.all[keyword]
580     return typeof rule == "object" ? rule.definition : !!rule
581   }
582
583   // Remove keyword
584   removeKeyword(keyword: string): Ajv {
585     // TODO return type should be Ajv
586     const {RULES} = this
587     delete RULES.keywords[keyword]
588     delete RULES.all[keyword]
589     for (const group of RULES.rules) {
590       const i = group.rules.findIndex((rule) => rule.keyword === keyword)
591       if (i >= 0) group.rules.splice(i, 1)
592     }
593     return this
594   }
595
596   // Add format
597   addFormat(name: string, format: Format): Ajv {
598     if (typeof format == "string") format = new RegExp(format)
599     this.formats[name] = format
600     return this
601   }
602
603   errorsText(
604     errors: ErrorObject[] | null | undefined = this.errors, // optional array of validation errors
605     {separator = ", ", dataVar = "data"}: ErrorsTextOptions = {} // optional options with properties `separator` and `dataVar`
606   ): string {
607     if (!errors || errors.length === 0) return "No errors"
608     return errors
609       .map((e) => `${dataVar}${e.instancePath} ${e.message}`)
610       .reduce((text, msg) => text + separator + msg)
611   }
612
613   $dataMetaSchema(metaSchema: AnySchemaObject, keywordsJsonPointers: string[]): AnySchemaObject {
614     const rules = this.RULES.all
615     metaSchema = JSON.parse(JSON.stringify(metaSchema))
616     for (const jsonPointer of keywordsJsonPointers) {
617       const segments = jsonPointer.split("/").slice(1) // first segment is an empty string
618       let keywords = metaSchema
619       for (const seg of segments) keywords = keywords[seg] as AnySchemaObject
620
621       for (const key in rules) {
622         const rule = rules[key]
623         if (typeof rule != "object") continue
624         const {$data} = rule.definition
625         const schema = keywords[key] as AnySchemaObject | undefined
626         if ($data && schema) keywords[key] = schemaOrData(schema)
627       }
628     }
629
630     return metaSchema
631   }
632
633   private _removeAllSchemas(schemas: {[Ref in string]?: SchemaEnv | string}, regex?: RegExp): void {
634     for (const keyRef in schemas) {
635       const sch = schemas[keyRef]
636       if (!regex || regex.test(keyRef)) {
637         if (typeof sch == "string") {
638           delete schemas[keyRef]
639         } else if (sch && !sch.meta) {
640           this._cache.delete(sch.schema)
641           delete schemas[keyRef]
642         }
643       }
644     }
645   }
646
647   _addSchema(
648     schema: AnySchema,
649     meta?: boolean,
650     baseId?: string,
651     validateSchema = this.opts.validateSchema,
652     addSchema = this.opts.addUsedSchema
653   ): SchemaEnv {
654     let id: string | undefined
655     if (typeof schema == "object") {
656       id = schema.$id
657     } else {
658       if (this.opts.jtd) throw new Error("schema must be object")
659       else if (typeof schema != "boolean") throw new Error("schema must be object or boolean")
660     }
661     let sch = this._cache.get(schema)
662     if (sch !== undefined) return sch
663
664     const localRefs = getSchemaRefs.call(this, schema)
665     baseId = normalizeId(id || baseId)
666     sch = new SchemaEnv({schema, meta, baseId, localRefs})
667     this._cache.set(sch.schema, sch)
668     if (addSchema && !baseId.startsWith("#")) {
669       // TODO atm it is allowed to overwrite schemas without id (instead of not adding them)
670       if (baseId) this._checkUnique(baseId)
671       this.refs[baseId] = sch
672     }
673     if (validateSchema) this.validateSchema(schema, true)
674     return sch
675   }
676
677   private _checkUnique(id: string): void {
678     if (this.schemas[id] || this.refs[id]) {
679       throw new Error(`schema with key or id "${id}" already exists`)
680     }
681   }
682
683   private _compileSchemaEnv(sch: SchemaEnv): AnyValidateFunction {
684     if (sch.meta) this._compileMetaSchema(sch)
685     else compileSchema.call(this, sch)
686
687     /* istanbul ignore if */
688     if (!sch.validate) throw new Error("ajv implementation error")
689     return sch.validate
690   }
691
692   private _compileMetaSchema(sch: SchemaEnv): void {
693     const currentOpts = this.opts
694     this.opts = this._metaOpts
695     try {
696       compileSchema.call(this, sch)
697     } finally {
698       this.opts = currentOpts
699     }
700   }
701 }
702
703 export interface ErrorsTextOptions {
704   separator?: string
705   dataVar?: string
706 }
707
708 function checkOptions(
709   this: Ajv,
710   checkOpts: OptionsInfo<RemovedOptions | DeprecatedOptions>,
711   options: Options & RemovedOptions,
712   msg: string,
713   log: "warn" | "error" = "error"
714 ): void {
715   for (const key in checkOpts) {
716     const opt = key as keyof typeof checkOpts
717     if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`)
718   }
719 }
720
721 function getSchEnv(this: Ajv, keyRef: string): SchemaEnv | string | undefined {
722   keyRef = normalizeId(keyRef) // TODO tests fail without this line
723   return this.schemas[keyRef] || this.refs[keyRef]
724 }
725
726 function addInitialSchemas(this: Ajv): void {
727   const optsSchemas = this.opts.schemas
728   if (!optsSchemas) return
729   if (Array.isArray(optsSchemas)) this.addSchema(optsSchemas)
730   else for (const key in optsSchemas) this.addSchema(optsSchemas[key] as AnySchema, key)
731 }
732
733 function addInitialFormats(this: Ajv): void {
734   for (const name in this.opts.formats) {
735     const format = this.opts.formats[name]
736     if (format) this.addFormat(name, format)
737   }
738 }
739
740 function addInitialKeywords(
741   this: Ajv,
742   defs: Vocabulary | {[K in string]?: KeywordDefinition}
743 ): void {
744   if (Array.isArray(defs)) {
745     this.addVocabulary(defs)
746     return
747   }
748   this.logger.warn("keywords option as map is deprecated, pass array")
749   for (const keyword in defs) {
750     const def = defs[keyword] as KeywordDefinition
751     if (!def.keyword) def.keyword = keyword
752     this.addKeyword(def)
753   }
754 }
755
756 function getMetaSchemaOptions(this: Ajv): InstanceOptions {
757   const metaOpts = {...this.opts}
758   for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt]
759   return metaOpts
760 }
761
762 const noLogs = {log() {}, warn() {}, error() {}}
763
764 function getLogger(logger?: Partial<Logger> | false): Logger {
765   if (logger === false) return noLogs
766   if (logger === undefined) return console
767   if (logger.log && logger.warn && logger.error) return logger as Logger
768   throw new Error("logger must implement log, warn and error methods")
769 }
770
771 const KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i
772
773 function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void {
774   const {RULES} = this
775   eachItem(keyword, (kwd) => {
776     if (RULES.keywords[kwd]) throw new Error(`Keyword ${kwd} is already defined`)
777     if (!KEYWORD_NAME.test(kwd)) throw new Error(`Keyword ${kwd} has invalid name`)
778   })
779   if (!def) return
780   if (def.$data && !("code" in def || "validate" in def)) {
781     throw new Error('$data keyword must have "code" or "validate" function')
782   }
783 }
784
785 function addRule(
786   this: Ajv,
787   keyword: string,
788   definition?: AddedKeywordDefinition,
789   dataType?: JSONType
790 ): void {
791   const post = definition?.post
792   if (dataType && post) throw new Error('keyword with "post" flag cannot have "type"')
793   const {RULES} = this
794   let ruleGroup = post ? RULES.post : RULES.rules.find(({type: t}) => t === dataType)
795   if (!ruleGroup) {
796     ruleGroup = {type: dataType, rules: []}
797     RULES.rules.push(ruleGroup)
798   }
799   RULES.keywords[keyword] = true
800   if (!definition) return
801
802   const rule: Rule = {
803     keyword,
804     definition: {
805       ...definition,
806       type: getJSONTypes(definition.type),
807       schemaType: getJSONTypes(definition.schemaType),
808     },
809   }
810   if (definition.before) addBeforeRule.call(this, ruleGroup, rule, definition.before)
811   else ruleGroup.rules.push(rule)
812   RULES.all[keyword] = rule
813   definition.implements?.forEach((kwd) => this.addKeyword(kwd))
814 }
815
816 function addBeforeRule(this: Ajv, ruleGroup: RuleGroup, rule: Rule, before: string): void {
817   const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before)
818   if (i >= 0) {
819     ruleGroup.rules.splice(i, 0, rule)
820   } else {
821     ruleGroup.rules.push(rule)
822     this.logger.warn(`rule ${before} is not defined`)
823   }
824 }
825
826 function keywordMetaschema(this: Ajv, def: KeywordDefinition): void {
827   let {metaSchema} = def
828   if (metaSchema === undefined) return
829   if (def.$data && this.opts.$data) metaSchema = schemaOrData(metaSchema)
830   def.validateSchema = this.compile(metaSchema, true)
831 }
832
833 const $dataRef = {
834   $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#",
835 }
836
837 function schemaOrData(schema: AnySchema): AnySchemaObject {
838   return {anyOf: [schema, $dataRef]}
839 }