Skip to content

Commit

Permalink
Merge pull request #1362 from ajv-validator/fix-standalone
Browse files Browse the repository at this point in the history
Fix for standalone code generation creating duplicate functions
  • Loading branch information
epoberezkin committed Dec 19, 2020
2 parents 5fe4bc0 + 0b89a00 commit 832fee1
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 5 deletions.
2 changes: 1 addition & 1 deletion lib/compile/codegen/index.ts
Expand Up @@ -3,7 +3,7 @@ import {_, nil, _Code, Code, Name, UsedNames, CodeItem, addCodeArg, _CodeOrName}
import {Scope, varKinds} from "./scope"

export {_, str, strConcat, nil, getProperty, stringify, Name, Code} from "./code"
export {Scope, ScopeStore, ValueScope, ScopeValueSets, varKinds} from "./scope"
export {Scope, ScopeStore, ValueScope, ValueScopeName, ScopeValueSets, varKinds} from "./scope"

// type for expressions that can be safely inserted in code without quotes
export type SafeExpr = Code | number | boolean | null
Expand Down
4 changes: 2 additions & 2 deletions lib/compile/index.ts
Expand Up @@ -8,7 +8,7 @@ import type {
} from "../types"
import type Ajv from "../core"
import type {InstanceOptions} from "../core"
import {CodeGen, _, nil, stringify, Name, Code} from "./codegen"
import {CodeGen, _, nil, stringify, Name, Code, ValueScopeName} from "./codegen"
import {ValidationError} from "./error_classes"
import N from "./names"
import {LocalRefs, getFullPath, _getFullPath, inlineRef, normalizeId, resolveUrl} from "./resolve"
Expand Down Expand Up @@ -77,7 +77,7 @@ export class SchemaEnv implements SchemaEnvArgs {
readonly refs: SchemaRefs = {}
readonly dynamicAnchors: {[Ref in string]?: true} = {}
validate?: AnyValidateFunction
validateName?: Name
validateName?: ValueScopeName

constructor(env: SchemaEnvArgs) {
let schema: AnySchemaObject | undefined
Expand Down
4 changes: 4 additions & 0 deletions lib/standalone/index.ts
Expand Up @@ -51,6 +51,10 @@ export default function standaloneCode(

function validateCode(usedValues: ScopeValueSets, 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)

const scopeCode = ajv.scope.scopeCode(s.scopeValues, usedValues, refValidateCode)
const code = new _Code(`${scopeCode}${_n}${s.validateCode}`)
return s.evaluated ? _`${code}${s.validateName}.evaluated = ${s.evaluated};${_n}` : code
Expand Down
4 changes: 2 additions & 2 deletions lib/types/index.ts
@@ -1,4 +1,4 @@
import type {CodeGen, Code, Name, ScopeValueSets} from "../compile/codegen"
import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen"
import type {SchemaEnv, SchemaCxt, SchemaObjCxt} from "../compile"
import type {JSONType} from "../compile/rules"
import type KeywordCxt from "../compile/context"
Expand Down Expand Up @@ -30,7 +30,7 @@ export type AnySchema = Schema | AsyncSchema
export type SchemaMap = {[Key in string]?: AnySchema}

export interface SourceCode {
validateName: Name
validateName: ValueScopeName
validateCode: string
scopeValues: ScopeValueSets
evaluated?: Code
Expand Down
76 changes: 76 additions & 0 deletions spec/standalone.spec.ts
Expand Up @@ -85,6 +85,82 @@ describe("standalone code generation", () => {
}
})

describe("two refs to the same schema (issue #1361)", () => {
const userSchema = {
$id: "user.json",
type: "object",
properties: {
name: {type: "string"},
},
required: ["name"],
}

const infoSchema = {
$id: "info.json",
type: "object",
properties: {
author: {$ref: "user.json"},
contributors: {
type: "array",
items: {$ref: "user.json"},
},
},
required: ["author", "contributors"],
}

describe("all exports", () => {
it("should not have duplicate functions", () => {
const ajv = new _Ajv({
allErrors: true,
code: {optimize: false, source: true},
inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway
schemas: [userSchema, infoSchema],
})

const moduleCode = standaloneCode(ajv)
assertNoDuplicateFunctions(moduleCode)
const {"user.json": user, "info.json": info} = requireFromString(moduleCode)
testExports({user, info})
})
})

describe("named exports", () => {
it("should not have duplicate functions", () => {
const ajv = new _Ajv({
allErrors: true,
code: {optimize: false, source: true},
inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway
schemas: [userSchema, infoSchema],
})

const moduleCode = standaloneCode(ajv, {user: "user.json", info: "info.json"})
assertNoDuplicateFunctions(moduleCode)
testExports(requireFromString(moduleCode))
})
})

function assertNoDuplicateFunctions(code: string): void {
const funcs = code.match(/function\s+([a-z0-9_$]+)/gi)
assert(Array.isArray(funcs))
assert(funcs.length > 0)
assert.strictEqual(funcs.length, new Set(funcs).size, "should have no duplicates")
}

function testExports(validate: {[n: string]: AnyValidateFunction<unknown>}): void {
assert.strictEqual(validate.user({}), false)
assert.strictEqual(validate.user({name: "usr1"}), true)

assert.strictEqual(validate.info({}), false)
assert.strictEqual(
validate.info({
author: {name: "usr1"},
contributors: [{name: "usr2"}],
}),
true
)
}
})

it("should generate module code with a single export (ESM compatible)", () => {
const ajv = new _Ajv({code: {source: true}})
const v = ajv.compile({
Expand Down

0 comments on commit 832fee1

Please sign in to comment.