Skip to content

Commit

Permalink
fix: duplicate functions in standalone code for mutually recursive sc…
Browse files Browse the repository at this point in the history
…hemas, fixes #1361
  • Loading branch information
epoberezkin committed Feb 1, 2021
1 parent 59d637b commit 9cdd77d
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 15 deletions.
18 changes: 14 additions & 4 deletions lib/compile/codegen/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ export type ScopeValueSets = {
[Prefix in string]?: Set<ValueScopeName>
}

export enum UsedValueState {
Started,
Completed,
}

export type UsedScopeValues = {
[Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined>
}

export const varKinds = {
const: new Name("const"),
let: new Name("let"),
Expand Down Expand Up @@ -161,7 +170,7 @@ export class ValueScope extends Scope {

scopeCode(
values: ScopeValues | ScopeValueSets = this._values,
usedValues?: ScopeValueSets,
usedValues?: UsedScopeValues,
getCode?: (n: ValueScopeName) => Code | undefined
): Code {
return this._reduceValues(
Expand All @@ -178,17 +187,17 @@ export class ValueScope extends Scope {
private _reduceValues(
values: ScopeValues | ScopeValueSets,
valueCode: (n: ValueScopeName) => Code | undefined,
usedValues: ScopeValueSets = {},
usedValues: UsedScopeValues = {},
getCode?: (n: ValueScopeName) => Code | undefined
): Code {
let code: Code = nil
for (const prefix in values) {
const vs = values[prefix]
if (!vs) continue
const nameSet = (usedValues[prefix] = usedValues[prefix] || new Set())
const nameSet = (usedValues[prefix] = usedValues[prefix] || new Map())
vs.forEach((name: ValueScopeName) => {
if (nameSet.has(name)) return
nameSet.add(name)
nameSet.set(name, UsedValueState.Started)
let c = valueCode(name)
if (c) {
const def = this.opts.es5 ? varKinds.var : varKinds.const
Expand All @@ -198,6 +207,7 @@ export class ValueScope extends Scope {
} else {
throw new ValueError(name)
}
nameSet.set(name, UsedValueState.Completed)
})
}
return code
Expand Down
32 changes: 22 additions & 10 deletions lib/standalone/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type AjvCore from "../core"
import type {AnyValidateFunction, SourceCode} from "../types"
import type {SchemaEnv} from "../compile"
import {ScopeValueSets, ValueScopeName, varKinds} from "../compile/codegen/scope"
import {_, _Code, Code, getProperty} from "../compile/codegen/code"
import {UsedScopeValues, UsedValueState, ValueScopeName, varKinds} from "../compile/codegen/scope"
import {_, nil, _Code, Code, getProperty} from "../compile/codegen/code"

export default function standaloneCode(
ajv: AjvCore,
Expand All @@ -27,7 +27,7 @@ export default function standaloneCode(
}

function funcExportCode(source?: SourceCode): string {
const usedValues: ScopeValueSets = {}
const usedValues: UsedScopeValues = {}
const n = source?.validateName
const vCode = validateCode(usedValues, source)
return `"use strict";${_n}module.exports = ${n};${_n}module.exports.default = ${n};${_n}${vCode}`
Expand All @@ -37,7 +37,7 @@ export default function standaloneCode(
schemas: {[K in string]?: T},
getValidateFunc: (schOrId: T) => AnyValidateFunction | undefined
): string {
const usedValues: ScopeValueSets = {}
const usedValues: UsedScopeValues = {}
let code = _`"use strict";`
for (const name in schemas) {
const v = getValidateFunc(schemas[name] as T)
Expand All @@ -49,11 +49,10 @@ export default function standaloneCode(
return `${code}`
}

function validateCode(usedValues: ScopeValueSets, s?: SourceCode): Code {
function validateCode(usedValues: UsedScopeValues, s?: SourceCode): Code {
if (!s) throw new Error('moduleCode: function does not have "source" property')
const {prefix} = s.validateName
const nameSet = (usedValues[prefix] = usedValues[prefix] || new Set())
nameSet.add(s.validateName)
if (usedState(s.validateName) === UsedValueState.Completed) return nil
setUsedState(s.validateName, UsedValueState.Started)

const scopeCode = ajv.scope.scopeCode(s.scopeValues, usedValues, refValidateCode)
const code = new _Code(`${scopeCode}${_n}${s.validateCode}`)
Expand All @@ -66,11 +65,24 @@ export default function standaloneCode(
return validateCode(usedValues, v.source)
} else if ((n.prefix === "root" || n.prefix === "wrapper") && typeof vRef == "object") {
const {validate, validateName} = vRef as SchemaEnv
const vCode = validateCode(usedValues, validate?.source)
if (!validateName) throw new Error("ajv internal error")
const def = ajv.opts.code.es5 ? varKinds.var : varKinds.const
return _`${def} ${n} = {validate: ${validateName}};${_n}${vCode}`
const wrapper = _`${def} ${n} = {validate: ${validateName}};`
if (usedState(validateName) === UsedValueState.Started) return wrapper
const vCode = validateCode(usedValues, validate?.source)
return _`${wrapper}${_n}${vCode}`
}
return undefined
}

function usedState(name: ValueScopeName): UsedValueState | undefined {
return usedValues[name.prefix]?.get(name)
}

function setUsedState(name: ValueScopeName, state: UsedValueState): void {
const {prefix} = name
const names = (usedValues[prefix] = usedValues[prefix] || new Map())
names.set(name, state)
}
}
}
2 changes: 1 addition & 1 deletion spec/standalone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe("standalone code generation", () => {
})
})

describe.skip("mutually recursive schemas", () => {
describe("mutually recursive schemas", () => {
const userSchema = {
$id: "user.json",
type: "object",
Expand Down

0 comments on commit 9cdd77d

Please sign in to comment.