index.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import type AjvCore from "../core"
  2. import type {AnyValidateFunction, SourceCode} from "../types"
  3. import type {SchemaEnv} from "../compile"
  4. import {UsedScopeValues, UsedValueState, ValueScopeName, varKinds} from "../compile/codegen/scope"
  5. import {_, nil, _Code, Code, getProperty, getEsmExportName} from "../compile/codegen/code"
  6. function standaloneCode(
  7. ajv: AjvCore,
  8. refsOrFunc?: {[K in string]?: string} | AnyValidateFunction
  9. ): string {
  10. if (!ajv.opts.code.source) {
  11. throw new Error("moduleCode: ajv instance must have code.source option")
  12. }
  13. const {_n} = ajv.scope.opts
  14. return typeof refsOrFunc == "function"
  15. ? funcExportCode(refsOrFunc.source)
  16. : refsOrFunc !== undefined
  17. ? multiExportsCode<string>(refsOrFunc, getValidate)
  18. : multiExportsCode<SchemaEnv>(ajv.schemas, (sch) =>
  19. sch.meta ? undefined : ajv.compile(sch.schema)
  20. )
  21. function getValidate(id: string): AnyValidateFunction {
  22. const v = ajv.getSchema(id)
  23. if (!v) throw new Error(`moduleCode: no schema with id ${id}`)
  24. return v
  25. }
  26. function funcExportCode(source?: SourceCode): string {
  27. const usedValues: UsedScopeValues = {}
  28. const n = source?.validateName
  29. const vCode = validateCode(usedValues, source)
  30. if (ajv.opts.code.esm) {
  31. // Always do named export as `validate` rather than the variable `n` which is `validateXX` for known export value
  32. return `"use strict";${_n}export const validate = ${n};${_n}export default ${n};${_n}${vCode}`
  33. }
  34. return `"use strict";${_n}module.exports = ${n};${_n}module.exports.default = ${n};${_n}${vCode}`
  35. }
  36. function multiExportsCode<T extends SchemaEnv | string>(
  37. schemas: {[K in string]?: T},
  38. getValidateFunc: (schOrId: T) => AnyValidateFunction | undefined
  39. ): string {
  40. const usedValues: UsedScopeValues = {}
  41. let code = _`"use strict";`
  42. for (const name in schemas) {
  43. const v = getValidateFunc(schemas[name] as T)
  44. if (v) {
  45. const vCode = validateCode(usedValues, v.source)
  46. const exportSyntax = ajv.opts.code.esm
  47. ? _`export const ${getEsmExportName(name)}`
  48. : _`exports${getProperty(name)}`
  49. code = _`${code}${_n}${exportSyntax} = ${v.source?.validateName};${_n}${vCode}`
  50. }
  51. }
  52. return `${code}`
  53. }
  54. function validateCode(usedValues: UsedScopeValues, s?: SourceCode): Code {
  55. if (!s) throw new Error('moduleCode: function does not have "source" property')
  56. if (usedState(s.validateName) === UsedValueState.Completed) return nil
  57. setUsedState(s.validateName, UsedValueState.Started)
  58. const scopeCode = ajv.scope.scopeCode(s.scopeValues, usedValues, refValidateCode)
  59. const code = new _Code(`${scopeCode}${_n}${s.validateCode}`)
  60. return s.evaluated ? _`${code}${s.validateName}.evaluated = ${s.evaluated};${_n}` : code
  61. function refValidateCode(n: ValueScopeName): Code | undefined {
  62. const vRef = n.value?.ref
  63. if (n.prefix === "validate" && typeof vRef == "function") {
  64. const v = vRef as AnyValidateFunction
  65. return validateCode(usedValues, v.source)
  66. } else if ((n.prefix === "root" || n.prefix === "wrapper") && typeof vRef == "object") {
  67. const {validate, validateName} = vRef as SchemaEnv
  68. if (!validateName) throw new Error("ajv internal error")
  69. const def = ajv.opts.code.es5 ? varKinds.var : varKinds.const
  70. const wrapper = _`${def} ${n} = {validate: ${validateName}};`
  71. if (usedState(validateName) === UsedValueState.Started) return wrapper
  72. const vCode = validateCode(usedValues, validate?.source)
  73. return _`${wrapper}${_n}${vCode}`
  74. }
  75. return undefined
  76. }
  77. function usedState(name: ValueScopeName): UsedValueState | undefined {
  78. return usedValues[name.prefix]?.get(name)
  79. }
  80. function setUsedState(name: ValueScopeName, state: UsedValueState): void {
  81. const {prefix} = name
  82. const names = (usedValues[prefix] = usedValues[prefix] || new Map())
  83. names.set(name, state)
  84. }
  85. }
  86. }
  87. module.exports = exports = standaloneCode
  88. Object.defineProperty(exports, "__esModule", {value: true})
  89. export default standaloneCode