code.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import type {AnySchema, SchemaMap} from "../types"
  2. import type {SchemaCxt} from "../compile"
  3. import type {KeywordCxt} from "../compile/validate"
  4. import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen"
  5. import {alwaysValidSchema, Type} from "../compile/util"
  6. import N from "../compile/names"
  7. import {useFunc} from "../compile/util"
  8. export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void {
  9. const {gen, data, it} = cxt
  10. gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => {
  11. cxt.setParams({missingProperty: _`${prop}`}, true)
  12. cxt.error()
  13. })
  14. }
  15. export function checkMissingProp(
  16. {gen, data, it: {opts}}: KeywordCxt,
  17. properties: string[],
  18. missing: Name
  19. ): Code {
  20. return or(
  21. ...properties.map((prop) =>
  22. and(noPropertyInData(gen, data, prop, opts.ownProperties), _`${missing} = ${prop}`)
  23. )
  24. )
  25. }
  26. export function reportMissingProp(cxt: KeywordCxt, missing: Name): void {
  27. cxt.setParams({missingProperty: missing}, true)
  28. cxt.error()
  29. }
  30. export function hasPropFunc(gen: CodeGen): Name {
  31. return gen.scopeValue("func", {
  32. // eslint-disable-next-line @typescript-eslint/unbound-method
  33. ref: Object.prototype.hasOwnProperty,
  34. code: _`Object.prototype.hasOwnProperty`,
  35. })
  36. }
  37. export function isOwnProperty(gen: CodeGen, data: Name, property: Name | string): Code {
  38. return _`${hasPropFunc(gen)}.call(${data}, ${property})`
  39. }
  40. export function propertyInData(
  41. gen: CodeGen,
  42. data: Name,
  43. property: Name | string,
  44. ownProperties?: boolean
  45. ): Code {
  46. const cond = _`${data}${getProperty(property)} !== undefined`
  47. return ownProperties ? _`${cond} && ${isOwnProperty(gen, data, property)}` : cond
  48. }
  49. export function noPropertyInData(
  50. gen: CodeGen,
  51. data: Name,
  52. property: Name | string,
  53. ownProperties?: boolean
  54. ): Code {
  55. const cond = _`${data}${getProperty(property)} === undefined`
  56. return ownProperties ? or(cond, not(isOwnProperty(gen, data, property))) : cond
  57. }
  58. export function allSchemaProperties(schemaMap?: SchemaMap): string[] {
  59. return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : []
  60. }
  61. export function schemaProperties(it: SchemaCxt, schemaMap: SchemaMap): string[] {
  62. return allSchemaProperties(schemaMap).filter(
  63. (p) => !alwaysValidSchema(it, schemaMap[p] as AnySchema)
  64. )
  65. }
  66. export function callValidateCode(
  67. {schemaCode, data, it: {gen, topSchemaRef, schemaPath, errorPath}, it}: KeywordCxt,
  68. func: Code,
  69. context: Code,
  70. passSchema?: boolean
  71. ): Code {
  72. const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data
  73. const valCxt: [Name, Code | number][] = [
  74. [N.instancePath, strConcat(N.instancePath, errorPath)],
  75. [N.parentData, it.parentData],
  76. [N.parentDataProperty, it.parentDataProperty],
  77. [N.rootData, N.rootData],
  78. ]
  79. if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors])
  80. const args = _`${dataAndSchema}, ${gen.object(...valCxt)}`
  81. return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})`
  82. }
  83. const newRegExp = _`new RegExp`
  84. export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name {
  85. const u = opts.unicodeRegExp ? "u" : ""
  86. const {regExp} = opts.code
  87. const rx = regExp(pattern, u)
  88. return gen.scopeValue("pattern", {
  89. key: rx.toString(),
  90. ref: rx,
  91. code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`,
  92. })
  93. }
  94. export function validateArray(cxt: KeywordCxt): Name {
  95. const {gen, data, keyword, it} = cxt
  96. const valid = gen.name("valid")
  97. if (it.allErrors) {
  98. const validArr = gen.let("valid", true)
  99. validateItems(() => gen.assign(validArr, false))
  100. return validArr
  101. }
  102. gen.var(valid, true)
  103. validateItems(() => gen.break())
  104. return valid
  105. function validateItems(notValid: () => void): void {
  106. const len = gen.const("len", _`${data}.length`)
  107. gen.forRange("i", 0, len, (i) => {
  108. cxt.subschema(
  109. {
  110. keyword,
  111. dataProp: i,
  112. dataPropType: Type.Num,
  113. },
  114. valid
  115. )
  116. gen.if(not(valid), notValid)
  117. })
  118. }
  119. }
  120. export function validateUnion(cxt: KeywordCxt): void {
  121. const {gen, schema, keyword, it} = cxt
  122. /* istanbul ignore if */
  123. if (!Array.isArray(schema)) throw new Error("ajv implementation error")
  124. const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch))
  125. if (alwaysValid && !it.opts.unevaluated) return
  126. const valid = gen.let("valid", false)
  127. const schValid = gen.name("_valid")
  128. gen.block(() =>
  129. schema.forEach((_sch: AnySchema, i: number) => {
  130. const schCxt = cxt.subschema(
  131. {
  132. keyword,
  133. schemaProp: i,
  134. compositeRule: true,
  135. },
  136. schValid
  137. )
  138. gen.assign(valid, _`${valid} || ${schValid}`)
  139. const merged = cxt.mergeValidEvaluated(schCxt, schValid)
  140. // can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true)
  141. // or if all properties and items were evaluated (it.props === true && it.items === true)
  142. if (!merged) gen.if(not(valid))
  143. })
  144. )
  145. cxt.result(
  146. valid,
  147. () => cxt.reset(),
  148. () => cxt.error(true)
  149. )
  150. }