massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / compile / resolve.ts
1 import type {AnySchema, AnySchemaObject} from "../types"
2 import type Ajv from "../ajv"
3 import {eachItem} from "./util"
4 import * as equal from "fast-deep-equal"
5 import * as traverse from "json-schema-traverse"
6 import * as URI from "uri-js"
7
8 // the hash of local references inside the schema (created by getSchemaRefs), used for inline resolution
9 export type LocalRefs = {[Ref in string]?: AnySchemaObject}
10
11 // TODO refactor to use keyword definitions
12 const SIMPLE_INLINED = new Set([
13   "type",
14   "format",
15   "pattern",
16   "maxLength",
17   "minLength",
18   "maxProperties",
19   "minProperties",
20   "maxItems",
21   "minItems",
22   "maximum",
23   "minimum",
24   "uniqueItems",
25   "multipleOf",
26   "required",
27   "enum",
28   "const",
29 ])
30
31 export function inlineRef(schema: AnySchema, limit: boolean | number = true): boolean {
32   if (typeof schema == "boolean") return true
33   if (limit === true) return !hasRef(schema)
34   if (!limit) return false
35   return countKeys(schema) <= limit
36 }
37
38 const REF_KEYWORDS = new Set([
39   "$ref",
40   "$recursiveRef",
41   "$recursiveAnchor",
42   "$dynamicRef",
43   "$dynamicAnchor",
44 ])
45
46 function hasRef(schema: AnySchemaObject): boolean {
47   for (const key in schema) {
48     if (REF_KEYWORDS.has(key)) return true
49     const sch = schema[key]
50     if (Array.isArray(sch) && sch.some(hasRef)) return true
51     if (typeof sch == "object" && hasRef(sch)) return true
52   }
53   return false
54 }
55
56 function countKeys(schema: AnySchemaObject): number {
57   let count = 0
58   for (const key in schema) {
59     if (key === "$ref") return Infinity
60     count++
61     if (SIMPLE_INLINED.has(key)) continue
62     if (typeof schema[key] == "object") {
63       eachItem(schema[key], (sch) => (count += countKeys(sch)))
64     }
65     if (count === Infinity) return Infinity
66   }
67   return count
68 }
69
70 export function getFullPath(id = "", normalize?: boolean): string {
71   if (normalize !== false) id = normalizeId(id)
72   const p = URI.parse(id)
73   return _getFullPath(p)
74 }
75
76 export function _getFullPath(p: URI.URIComponents): string {
77   return URI.serialize(p).split("#")[0] + "#"
78 }
79
80 const TRAILING_SLASH_HASH = /#\/?$/
81 export function normalizeId(id: string | undefined): string {
82   return id ? id.replace(TRAILING_SLASH_HASH, "") : ""
83 }
84
85 export function resolveUrl(baseId: string, id: string): string {
86   id = normalizeId(id)
87   return URI.resolve(baseId, id)
88 }
89
90 const ANCHOR = /^[a-z_][-a-z0-9._]*$/i
91
92 export function getSchemaRefs(this: Ajv, schema: AnySchema, baseId: string): LocalRefs {
93   if (typeof schema == "boolean") return {}
94   const {schemaId} = this.opts
95   const schId = normalizeId(schema[schemaId] || baseId)
96   const baseIds: {[JsonPtr in string]?: string} = {"": schId}
97   const pathPrefix = getFullPath(schId, false)
98   const localRefs: LocalRefs = {}
99   const schemaRefs: Set<string> = new Set()
100
101   traverse(schema, {allKeys: true}, (sch, jsonPtr, _, parentJsonPtr) => {
102     if (parentJsonPtr === undefined) return
103     const fullPath = pathPrefix + jsonPtr
104     let baseId = baseIds[parentJsonPtr]
105     if (typeof sch[schemaId] == "string") baseId = addRef.call(this, sch[schemaId])
106     addAnchor.call(this, sch.$anchor)
107     addAnchor.call(this, sch.$dynamicAnchor)
108     baseIds[jsonPtr] = baseId
109
110     function addRef(this: Ajv, ref: string): string {
111       ref = normalizeId(baseId ? URI.resolve(baseId, ref) : ref)
112       if (schemaRefs.has(ref)) throw ambiguos(ref)
113       schemaRefs.add(ref)
114       let schOrRef = this.refs[ref]
115       if (typeof schOrRef == "string") schOrRef = this.refs[schOrRef]
116       if (typeof schOrRef == "object") {
117         checkAmbiguosRef(sch, schOrRef.schema, ref)
118       } else if (ref !== normalizeId(fullPath)) {
119         if (ref[0] === "#") {
120           checkAmbiguosRef(sch, localRefs[ref], ref)
121           localRefs[ref] = sch
122         } else {
123           this.refs[ref] = fullPath
124         }
125       }
126       return ref
127     }
128
129     function addAnchor(this: Ajv, anchor: unknown): void {
130       if (typeof anchor == "string") {
131         if (!ANCHOR.test(anchor)) throw new Error(`invalid anchor "${anchor}"`)
132         addRef.call(this, `#${anchor}`)
133       }
134     }
135   })
136
137   return localRefs
138
139   function checkAmbiguosRef(sch1: AnySchema, sch2: AnySchema | undefined, ref: string): void {
140     if (sch2 !== undefined && !equal(sch1, sch2)) throw ambiguos(ref)
141   }
142
143   function ambiguos(ref: string): Error {
144     return new Error(`reference "${ref}" resolves to more than one schema`)
145   }
146 }