From e446893f5bc1711fa782839eb760ca2c04c1cbfe Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 19 Dec 2020 12:33:19 +0000 Subject: [PATCH 1/3] test: duplicate function in standalone code, it should fail but it does not (#1361) --- spec/standalone.spec.ts | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/spec/standalone.spec.ts b/spec/standalone.spec.ts index 052af065f..dc078d6d0 100644 --- a/spec/standalone.spec.ts +++ b/spec/standalone.spec.ts @@ -85,6 +85,56 @@ describe("standalone code generation", () => { } }) + describe.only("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) + console.log(moduleCode) + const {"user.json": validateUser, "info.json": validateInfo} = requireFromString(moduleCode) + assert.strictEqual(validateUser({}), false) + assert.strictEqual(validateUser({name: "usr1"}), true) + + assert.strictEqual(validateInfo({}), false) + assert.strictEqual( + validateInfo({ + 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({ From eae2d5d47426e8b4c355e6613fcf2c3f5e61eec2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 19 Dec 2020 12:50:47 +0000 Subject: [PATCH 2/3] test: failing test for standalone code (duplicate functions, #1361) --- spec/standalone.spec.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/standalone.spec.ts b/spec/standalone.spec.ts index dc078d6d0..c2a160fe6 100644 --- a/spec/standalone.spec.ts +++ b/spec/standalone.spec.ts @@ -85,7 +85,7 @@ describe("standalone code generation", () => { } }) - describe.only("two refs to the same schema (issue #1361)", () => { + describe.skip("two refs to the same schema (issue #1361)", () => { const userSchema = { $id: "user.json", type: "object", @@ -118,7 +118,8 @@ describe("standalone code generation", () => { }) const moduleCode = standaloneCode(ajv) - console.log(moduleCode) + assertNoDuplicateFunctions(moduleCode) + const {"user.json": validateUser, "info.json": validateInfo} = requireFromString(moduleCode) assert.strictEqual(validateUser({}), false) assert.strictEqual(validateUser({name: "usr1"}), true) @@ -133,6 +134,13 @@ describe("standalone code generation", () => { ) }) }) + + 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") + } }) it("should generate module code with a single export (ESM compatible)", () => { From 0b89a00fc36d9e618c79b464bf4efff08cc26eb9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 19 Dec 2020 13:19:43 +0000 Subject: [PATCH 3/3] fix: standalone code generation creating duplicate functions (closes #1361) --- lib/compile/codegen/index.ts | 2 +- lib/compile/index.ts | 4 ++-- lib/standalone/index.ts | 4 ++++ lib/types/index.ts | 4 ++-- spec/standalone.spec.ts | 44 +++++++++++++++++++++++++----------- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/lib/compile/codegen/index.ts b/lib/compile/codegen/index.ts index 63bf5df65..0957ca9a7 100644 --- a/lib/compile/codegen/index.ts +++ b/lib/compile/codegen/index.ts @@ -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 diff --git a/lib/compile/index.ts b/lib/compile/index.ts index 05db0977f..94e2dfdbc 100644 --- a/lib/compile/index.ts +++ b/lib/compile/index.ts @@ -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" @@ -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 diff --git a/lib/standalone/index.ts b/lib/standalone/index.ts index 6df1abf6f..27a4bca1b 100644 --- a/lib/standalone/index.ts +++ b/lib/standalone/index.ts @@ -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 diff --git a/lib/types/index.ts b/lib/types/index.ts index ef58a636e..e91f122b9 100644 --- a/lib/types/index.ts +++ b/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" @@ -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 diff --git a/spec/standalone.spec.ts b/spec/standalone.spec.ts index c2a160fe6..f9c60953f 100644 --- a/spec/standalone.spec.ts +++ b/spec/standalone.spec.ts @@ -85,7 +85,7 @@ describe("standalone code generation", () => { } }) - describe.skip("two refs to the same schema (issue #1361)", () => { + describe("two refs to the same schema (issue #1361)", () => { const userSchema = { $id: "user.json", type: "object", @@ -119,19 +119,23 @@ describe("standalone code generation", () => { const moduleCode = standaloneCode(ajv) assertNoDuplicateFunctions(moduleCode) + const {"user.json": user, "info.json": info} = requireFromString(moduleCode) + testExports({user, info}) + }) + }) - const {"user.json": validateUser, "info.json": validateInfo} = requireFromString(moduleCode) - assert.strictEqual(validateUser({}), false) - assert.strictEqual(validateUser({name: "usr1"}), true) - - assert.strictEqual(validateInfo({}), false) - assert.strictEqual( - validateInfo({ - author: {name: "usr1"}, - contributors: [{name: "usr2"}], - }), - true - ) + 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)) }) }) @@ -141,6 +145,20 @@ describe("standalone code generation", () => { assert(funcs.length > 0) assert.strictEqual(funcs.length, new Set(funcs).size, "should have no duplicates") } + + function testExports(validate: {[n: string]: AnyValidateFunction}): 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)", () => {