From 0a963cae8dc4d6e93760191eecc1b8b37ba78ab7 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Wed, 14 Feb 2024 02:33:14 +0200 Subject: [PATCH] fix: avoid unrelated schemaOverrides when building definition for single type (#589) --- .../schema.MyOtherObjectWithOverride.json | 14 +++++++ .../schema.program.json | 40 +++++++++++++++++++ test/schema.test.ts | 37 +++++++++++++++++ typescript-json-schema.ts | 28 ++++++++----- 4 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 test/programs/no-unrelated-definitions/schema.MyOtherObjectWithOverride.json create mode 100644 test/programs/no-unrelated-definitions/schema.program.json diff --git a/test/programs/no-unrelated-definitions/schema.MyOtherObjectWithOverride.json b/test/programs/no-unrelated-definitions/schema.MyOtherObjectWithOverride.json new file mode 100644 index 00000000..a4aa2220 --- /dev/null +++ b/test/programs/no-unrelated-definitions/schema.MyOtherObjectWithOverride.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "SomeOtherDefinition": { + "type": "string" + } + }, + "properties": { + "sub": { + "$ref": "#/definitions/SomeOtherDefinition" + } + }, + "type": "object" +} diff --git a/test/programs/no-unrelated-definitions/schema.program.json b/test/programs/no-unrelated-definitions/schema.program.json new file mode 100644 index 00000000..4eb968c7 --- /dev/null +++ b/test/programs/no-unrelated-definitions/schema.program.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyObject": { + "properties": { + "sub": { + "$ref": "#/definitions/SomeDefinition" + } + }, + "type": "object" + }, + "MyOtherObject": { + "properties": { + "sub": { + "$ref": "#/definitions/SomeOtherDefinition" + } + }, + "type": "object" + }, + "SomeDefinition": { + "properties": { + "is": { + "type": "string" + } + }, + "type": "object" + }, + "SomeOtherDefinition": { + "properties": { + "is": { + "type": "string" + } + }, + "type": "object" + }, + "UnrelatedDefinition": { + "type": "string" + } + } +} diff --git a/test/schema.test.ts b/test/schema.test.ts index 247eef78..da96a97e 100644 --- a/test/schema.test.ts +++ b/test/schema.test.ts @@ -534,6 +534,43 @@ describe("when reusing a generator", () => { assert.deepEqual(actualSchemaObject, expectedSchemaObject, `The schema for ${symbolName} is not as expected`); }); }); + + it("should not add unrelated schemaOverrides to schemas", () => { + const testProgramPath = BASE + "no-unrelated-definitions/"; + const program = TJS.programFromConfig(resolve(testProgramPath + "tsconfig.json")); + const generator = TJS.buildGenerator(program); + + const schemaOverride: TJS.Definition = { type: "string" }; + generator?.setSchemaOverride("SomeOtherDefinition", schemaOverride); + + [ + { symbolName: "MyObject", schemaName: "MyObject" }, + { symbolName: "MyOtherObject", schemaName: "MyOtherObjectWithOverride" }, + ].forEach(({ symbolName, schemaName }) => { + const expectedSchemaString = readFileSync(`${testProgramPath}schema.${schemaName}.json`, "utf8"); + const expectedSchemaObject = JSON.parse(expectedSchemaString); + + const actualSchemaObject = generator?.getSchemaForSymbol(symbolName); + + assert.deepEqual(actualSchemaObject, expectedSchemaObject, `The schema for ${symbolName} is not as expected`); + }); + }); + + it("should include all schemaOverrides when generating program schemas", () => { + const testProgramPath = BASE + "no-unrelated-definitions/"; + const program = TJS.programFromConfig(resolve(`${testProgramPath}tsconfig.json`)); + const generator = TJS.buildGenerator(program)!; + + const schemaOverride: TJS.Definition = { type: "string" }; + generator.setSchemaOverride("UnrelatedDefinition", schemaOverride); + + const expectedSchemaString = readFileSync(`${testProgramPath}schema.program.json`, "utf8"); + const expectedSchemaObject = JSON.parse(expectedSchemaString); + + const actualSchemaObject = TJS.generateSchema(program, "*", {}, undefined, generator); + + assert.deepEqual(actualSchemaObject, expectedSchemaObject, `The schema for whole program is not as expected`); + }); }); describe("satisfies keyword - ignore from a \"satisfies\" and build by rally type", () => { diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index 5e43a031..c423a112 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -534,15 +534,17 @@ export class JsonSchemaGenerator { return false; } - private resetSchemaSpecificProperties() { + private resetSchemaSpecificProperties(includeAllOverrides: boolean = false) { this.reffedDefinitions = {}; this.typeIdsByName = {}; this.typeNamesById = {}; // restore schema overrides - this.schemaOverrides.forEach((value, key) => { - this.reffedDefinitions[key] = value; - }); + if (includeAllOverrides) { + this.schemaOverrides.forEach((value, key) => { + this.reffedDefinitions[key] = value; + }); + } } /** @@ -1410,8 +1412,12 @@ export class JsonSchemaGenerator { } // Create the actual definition only if is an inline definition, or - // if it will be a $ref and it is not yet created - if (!asRef || !this.reffedDefinitions[fullTypeName]) { + // if it will be a $ref and it is not yet created. + // Prioritise overrides. + const overrideDefinition = this.schemaOverrides.get(fullTypeName); + if (overrideDefinition) { + this.reffedDefinitions[fullTypeName] = overrideDefinition; + } else if (!asRef || !this.reffedDefinitions[fullTypeName]) { if (asRef) { // must be here to prevent recursivity problems let reffedDefinition: Definition; @@ -1511,12 +1517,12 @@ export class JsonSchemaGenerator { this.schemaOverrides.set(symbolName, schema); } - public getSchemaForSymbol(symbolName: string, includeReffedDefinitions: boolean = true): Definition { + public getSchemaForSymbol(symbolName: string, includeReffedDefinitions: boolean = true, includeAllOverrides: boolean = false): Definition { if (!this.allSymbols[symbolName]) { throw new Error(`type ${symbolName} not found`); } - this.resetSchemaSpecificProperties(); + this.resetSchemaSpecificProperties(includeAllOverrides); const def = this.getTypeDefinition( this.allSymbols[symbolName], @@ -1538,13 +1544,13 @@ export class JsonSchemaGenerator { return def; } - public getSchemaForSymbols(symbolNames: string[], includeReffedDefinitions: boolean = true): Definition { + public getSchemaForSymbols(symbolNames: string[], includeReffedDefinitions: boolean = true, includeAllOverrides: boolean = false): Definition { const root = { $schema: "http://json-schema.org/draft-07/schema#", definitions: {}, }; - this.resetSchemaSpecificProperties(); + this.resetSchemaSpecificProperties(includeAllOverrides); const id = this.args.id; @@ -1742,7 +1748,7 @@ export function generateSchema( if (fullTypeName === "*") { // All types in file(s) - return generator.getSchemaForSymbols(generator.getMainFileSymbols(program, onlyIncludeFiles)); + return generator.getSchemaForSymbols(generator.getMainFileSymbols(program, onlyIncludeFiles), true, true); } else if (args.uniqueNames) { // Find the hashed type name to use as the root object const matchingSymbols = generator.getSymbols(fullTypeName);