diff --git a/src/NodeParser/EnumNodeParser.ts b/src/NodeParser/EnumNodeParser.ts index 3e9d2dac5..622950a78 100644 --- a/src/NodeParser/EnumNodeParser.ts +++ b/src/NodeParser/EnumNodeParser.ts @@ -19,7 +19,8 @@ export class EnumNodeParser implements SubNodeParser { `enum-${getKey(node, context)}`, members .filter((member: ts.EnumMember) => !isNodeHidden(member)) - .map((member, index) => this.getMemberValue(member, index)) + .map((member, index) => this.getMemberValue(member, index)), + node.getSourceFile().fileName ); } diff --git a/src/NodeParser/InterfaceAndClassNodeParser.ts b/src/NodeParser/InterfaceAndClassNodeParser.ts index 0da128469..7828ed1b8 100644 --- a/src/NodeParser/InterfaceAndClassNodeParser.ts +++ b/src/NodeParser/InterfaceAndClassNodeParser.ts @@ -5,10 +5,12 @@ import { ArrayType } from "../Type/ArrayType"; import { BaseType } from "../Type/BaseType"; import { ObjectProperty, ObjectType } from "../Type/ObjectType"; import { ReferenceType } from "../Type/ReferenceType"; +import { hasJsDocTag } from "../Utils/hasJsDocTag"; import { isNodeHidden } from "../Utils/isHidden"; import { isPublic, isStatic } from "../Utils/modifiers"; import { getKey } from "../Utils/nodeKey"; import { notUndefined } from "../Utils/notUndefined"; +import { localSymbolAtNode } from "../Utils/symbolAtNode"; export class InterfaceAndClassNodeParser implements SubNodeParser { public constructor( @@ -64,7 +66,13 @@ export class InterfaceAndClassNodeParser implements SubNodeParser { } } - return new ObjectType(id, this.getBaseTypes(node, context), properties, additionalProperties); + return new ObjectType( + id, + this.getBaseTypes(node, context), + properties, + additionalProperties, + node.getSourceFile().fileName + ); } /** @@ -162,6 +170,14 @@ export class InterfaceAndClassNodeParser implements SubNodeParser { private getTypeId(node: ts.Node, context: Context): string { const nodeType = ts.isInterfaceDeclaration(node) ? "interface" : "class"; - return `${nodeType}-${getKey(node, context)}`; + return `${this.isExportType(node) ? "def-" : ""}${nodeType}-${getKey(node, context)}`; + } + + private isExportType(node: ts.Node): boolean { + if (hasJsDocTag(node, "internal")) { + return false; + } + const localSymbol = localSymbolAtNode(node); + return localSymbol ? "exportSymbol" in localSymbol : false; } } diff --git a/src/NodeParser/MappedTypeNodeParser.ts b/src/NodeParser/MappedTypeNodeParser.ts index 1251e0fdb..fd7f9ccb4 100644 --- a/src/NodeParser/MappedTypeNodeParser.ts +++ b/src/NodeParser/MappedTypeNodeParser.ts @@ -35,20 +35,33 @@ export class MappedTypeNodeParser implements SubNodeParser { id, [], this.getProperties(node, keyListType, context), - this.getAdditionalProperties(node, keyListType, context) + this.getAdditionalProperties(node, keyListType, context), + node.getSourceFile().fileName ); } else if (keyListType instanceof LiteralType) { // Key type resolves to single known property - return new ObjectType(id, [], this.getProperties(node, new UnionType([keyListType]), context), false); + return new ObjectType( + id, + [], + this.getProperties(node, new UnionType([keyListType]), context), + false, + node.getSourceFile().fileName + ); } else if (keyListType instanceof StringType || keyListType instanceof SymbolType) { // Key type widens to `string` const type = this.childNodeParser.createType(node.type!, context); - return type === undefined ? undefined : new ObjectType(id, [], [], type); + return type === undefined ? undefined : new ObjectType(id, [], [], type, node.getSourceFile().fileName); } else if (keyListType instanceof NumberType) { const type = this.childNodeParser.createType(node.type!, this.createSubContext(node, keyListType, context)); return type === undefined ? undefined : new ArrayType(type); } else if (keyListType instanceof EnumType) { - return new ObjectType(id, [], this.getValues(node, keyListType, context), false); + return new ObjectType( + id, + [], + this.getValues(node, keyListType, context), + false, + node.getSourceFile().fileName + ); } else { throw new LogicError( // eslint-disable-next-line max-len diff --git a/src/NodeParser/ObjectLiteralExpressionNodeParser.ts b/src/NodeParser/ObjectLiteralExpressionNodeParser.ts index b62d9951c..2855b8128 100644 --- a/src/NodeParser/ObjectLiteralExpressionNodeParser.ts +++ b/src/NodeParser/ObjectLiteralExpressionNodeParser.ts @@ -24,7 +24,13 @@ export class ObjectLiteralExpressionNodeParser implements SubNodeParser { ) ); - return new ObjectType(`object-${getKey(node, context)}`, [], properties, false); + return new ObjectType( + `object-${getKey(node, context)}`, + [], + properties, + false, + node.getSourceFile().fileName + ); } // TODO: implement this? diff --git a/src/NodeParser/ObjectTypeNodeParser.ts b/src/NodeParser/ObjectTypeNodeParser.ts index 1621043d0..b774ee257 100644 --- a/src/NodeParser/ObjectTypeNodeParser.ts +++ b/src/NodeParser/ObjectTypeNodeParser.ts @@ -11,6 +11,6 @@ export class ObjectTypeNodeParser implements SubNodeParser { } public createType(node: ts.KeywordTypeNode, context: Context): BaseType { - return new ObjectType(`object-${getKey(node, context)}`, [], [], true); + return new ObjectType(`object-${getKey(node, context)}`, [], [], true, node.getSourceFile().fileName); } } diff --git a/src/NodeParser/TypeAliasNodeParser.ts b/src/NodeParser/TypeAliasNodeParser.ts index b99e4a788..4821d6a9d 100644 --- a/src/NodeParser/TypeAliasNodeParser.ts +++ b/src/NodeParser/TypeAliasNodeParser.ts @@ -41,7 +41,7 @@ export class TypeAliasNodeParser implements SubNodeParser { if (type === undefined) { return undefined; } - return new AliasType(id, type); + return new AliasType(id, type, node.getSourceFile().fileName); } private getTypeId(node: ts.TypeAliasDeclaration, context: Context): string { diff --git a/src/NodeParser/TypeLiteralNodeParser.ts b/src/NodeParser/TypeLiteralNodeParser.ts index 1bfcd8f9c..ac0e75fd5 100644 --- a/src/NodeParser/TypeLiteralNodeParser.ts +++ b/src/NodeParser/TypeLiteralNodeParser.ts @@ -26,7 +26,13 @@ export class TypeLiteralNodeParser implements SubNodeParser { return undefined; } - return new ObjectType(id, [], properties, this.getAdditionalProperties(node, context)); + return new ObjectType( + id, + [], + properties, + this.getAdditionalProperties(node, context), + node.getSourceFile().fileName + ); } private getProperties(node: ts.TypeLiteralNode, context: Context): ObjectProperty[] | undefined { diff --git a/src/NodeParser/TypeofNodeParser.ts b/src/NodeParser/TypeofNodeParser.ts index f341257e8..b9730b339 100644 --- a/src/NodeParser/TypeofNodeParser.ts +++ b/src/NodeParser/TypeofNodeParser.ts @@ -61,6 +61,6 @@ export class TypeofNodeParser implements SubNodeParser { return new ObjectProperty(name, type, true); }); - return new ObjectType(id, [], properties, false); + return new ObjectType(id, [], properties, false, node.getSourceFile().fileName); } } diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index 6df7a4260..25fbb193e 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -12,6 +12,9 @@ import { notUndefined } from "./Utils/notUndefined"; import { removeUnreachable } from "./Utils/removeUnreachable"; import { Config } from "./Config"; import { hasJsDocTag } from "./Utils/hasJsDocTag"; +import { JSONSchema7, JSONSchema7Definition } from "json-schema"; +import { unambiguousName } from "./Utils/unambiguousName"; +import { resolveIdRefs } from "./Utils/resolveIdRefs"; export class SchemaGenerator { public constructor( @@ -34,19 +37,23 @@ export class SchemaGenerator { .filter(notUndefined); const rootTypeDefinition = rootTypes.length === 1 ? this.getRootTypeDefinition(rootTypes[0]) : undefined; const definitions: StringMap = {}; - rootTypes.forEach((rootType) => this.appendRootChildDefinitions(rootType, definitions)); + const idNameMap = new Map(); + rootTypes.forEach((rootType) => this.appendRootChildDefinitions(rootType, definitions, idNameMap)); const reachableDefinitions = removeUnreachable(rootTypeDefinition, definitions); - return { + // create schema - all $ref's use getId(). + const schema: JSONSchema7Definition = { ...(this.config?.schemaId ? { $id: this.config.schemaId } : {}), $schema: "http://json-schema.org/draft-07/schema#", ...(rootTypeDefinition ?? {}), definitions: reachableDefinitions, }; + // Finally, replace all getId() by their equivalent names. + return resolveIdRefs(schema, idNameMap, this.config?.encodeRefs ?? true) as JSONSchema7; } - protected getRootNodes(fullName: string | undefined) { + protected getRootNodes(fullName: string | undefined): ts.Node[] { if (fullName && fullName !== "*") { return [this.findNamedNode(fullName)]; } else { @@ -81,9 +88,12 @@ export class SchemaGenerator { protected getRootTypeDefinition(rootType: BaseType): Definition { return this.typeFormatter.getDefinition(rootType); } - protected appendRootChildDefinitions(rootType: BaseType, childDefinitions: StringMap): void { + protected appendRootChildDefinitions( + rootType: BaseType, + childDefinitions: StringMap, + idNameMap: Map + ): void { const seen = new Set(); - const children = this.typeFormatter .getChildren(rootType) .filter((child): child is DefinitionType => child instanceof DefinitionType) @@ -95,29 +105,27 @@ export class SchemaGenerator { return false; }); - const ids = new Map(); + const duplicates: StringMap> = {}; for (const child of children) { const name = child.getName(); - const previousId = ids.get(name); - // remove def prefix from ids to avoid false alarms - // FIXME: we probably shouldn't be doing this as there is probably something wrong with the deduplication - const childId = child.getId().replace(/def-/g, ""); - - if (previousId && childId !== previousId) { - throw new Error(`Type "${name}" has multiple definitions.`); - } - ids.set(name, childId); + duplicates[name] = duplicates[name] ?? new Set(); + duplicates[name].add(child); } children.reduce((definitions, child) => { - const name = child.getName(); - if (!(name in definitions)) { - definitions[name] = this.typeFormatter.getDefinition(child.getType()); + const id = child.getId().replace(/^def-/, ""); + if (!(id in definitions)) { + const name = unambiguousName(child, child === rootType, [...duplicates[child.getName()]]); + // Record the schema against the ID, allowing steps like removeUnreachable to work + definitions[id] = this.typeFormatter.getDefinition(child.getType()); + // Create a record of id->name mapping. This is used in the final step + // to resolve id -> name before delivering the schema to caller. + idNameMap.set(id, name); } return definitions; }, childDefinitions); } - protected partitionFiles() { + protected partitionFiles(): { projectFiles: ts.SourceFile[]; externalFiles: ts.SourceFile[] } { const projectFiles = new Array(); const externalFiles = new Array(); @@ -132,7 +140,7 @@ export class SchemaGenerator { sourceFiles: readonly ts.SourceFile[], typeChecker: ts.TypeChecker, types: Map - ) { + ): void { for (const sourceFile of sourceFiles) { this.inspectNode(sourceFile, typeChecker, types); } diff --git a/src/Type/AliasType.ts b/src/Type/AliasType.ts index 50f62bd12..a27c1d198 100644 --- a/src/Type/AliasType.ts +++ b/src/Type/AliasType.ts @@ -1,7 +1,7 @@ import { BaseType } from "./BaseType"; export class AliasType extends BaseType { - public constructor(private id: string, private type: BaseType) { + public constructor(private id: string, private type: BaseType, private srcFileName: string) { super(); } @@ -12,4 +12,8 @@ export class AliasType extends BaseType { public getType(): BaseType { return this.type; } + + public getSrcFileName(): string { + return this.srcFileName; + } } diff --git a/src/Type/BaseType.ts b/src/Type/BaseType.ts index 7b2fd8cbe..e1a5516e9 100644 --- a/src/Type/BaseType.ts +++ b/src/Type/BaseType.ts @@ -7,4 +7,12 @@ export abstract class BaseType { public getName(): string { return this.getId(); } + + /** + * Provide a base class implementation. Will only be exported for entities + * exposed in a schema - Alias|Enum|Class|Interface. + */ + public getSrcFileName(): string | null { + return null; + } } diff --git a/src/Type/DefinitionType.ts b/src/Type/DefinitionType.ts index 89b25940f..689960b50 100644 --- a/src/Type/DefinitionType.ts +++ b/src/Type/DefinitionType.ts @@ -16,4 +16,8 @@ export class DefinitionType extends BaseType { public getType(): BaseType { return this.type; } + + public getSrcFileName(): string | null { + return this.type.getSrcFileName(); + } } diff --git a/src/Type/EnumType.ts b/src/Type/EnumType.ts index 242d7fb05..912d34270 100644 --- a/src/Type/EnumType.ts +++ b/src/Type/EnumType.ts @@ -7,7 +7,7 @@ export type EnumValue = string | boolean | number | null; export class EnumType extends BaseType { private types: BaseType[]; - public constructor(private id: string, private values: readonly EnumValue[]) { + public constructor(private id: string, private values: readonly EnumValue[], private srcFileName: string) { super(); this.types = values.map((value) => (value == null ? new NullType() : new LiteralType(value))); } @@ -23,4 +23,8 @@ export class EnumType extends BaseType { public getTypes(): BaseType[] { return this.types; } + + public getSrcFileName(): string { + return this.srcFileName; + } } diff --git a/src/Type/ObjectType.ts b/src/Type/ObjectType.ts index 4c581885f..636ecba0f 100644 --- a/src/Type/ObjectType.ts +++ b/src/Type/ObjectType.ts @@ -20,7 +20,8 @@ export class ObjectType extends BaseType { private id: string, private baseTypes: readonly BaseType[], private properties: readonly ObjectProperty[], - private additionalProperties: BaseType | boolean + private additionalProperties: BaseType | boolean, + private srcFileName: string ) { super(); } @@ -38,4 +39,7 @@ export class ObjectType extends BaseType { public getAdditionalProperties(): BaseType | boolean { return this.additionalProperties; } + public getSrcFileName(): string { + return this.srcFileName; + } } diff --git a/src/Type/ReferenceType.ts b/src/Type/ReferenceType.ts index 4d0cc5496..67f1e181e 100644 --- a/src/Type/ReferenceType.ts +++ b/src/Type/ReferenceType.ts @@ -7,6 +7,8 @@ export class ReferenceType extends BaseType { private name: string | null = null; + private srcFileName: string | null = null; + public getId(): string { if (this.id == null) { throw new Error("Reference type ID not set yet"); @@ -41,4 +43,11 @@ export class ReferenceType extends BaseType { this.setId(type.getId()); this.setName(type.getName()); } + + public getSrcFileName(): string { + if (!this.srcFileName) { + throw new Error("Reference srcFileName not set yet"); + } + return this.srcFileName; + } } diff --git a/src/TypeFormatter/DefinitionTypeFormatter.ts b/src/TypeFormatter/DefinitionTypeFormatter.ts index 8fe8151dd..824b8dfeb 100644 --- a/src/TypeFormatter/DefinitionTypeFormatter.ts +++ b/src/TypeFormatter/DefinitionTypeFormatter.ts @@ -12,7 +12,7 @@ export class DefinitionTypeFormatter implements SubTypeFormatter { return type instanceof DefinitionType; } public getDefinition(type: DefinitionType): Definition { - const ref = type.getName(); + const ref = type.getId().replace(/^def-/, ""); return { $ref: `#/definitions/${this.encodeRefs ? encodeURIComponent(ref) : ref}` }; } public getChildren(type: DefinitionType): BaseType[] { diff --git a/src/TypeFormatter/ReferenceTypeFormatter.ts b/src/TypeFormatter/ReferenceTypeFormatter.ts index 42871b63d..1e819b1cb 100644 --- a/src/TypeFormatter/ReferenceTypeFormatter.ts +++ b/src/TypeFormatter/ReferenceTypeFormatter.ts @@ -12,7 +12,7 @@ export class ReferenceTypeFormatter implements SubTypeFormatter { return type instanceof ReferenceType; } public getDefinition(type: ReferenceType): Definition { - const ref = type.getName(); + const ref = type.getId().replace(/^def-/, ""); return { $ref: `#/definitions/${this.encodeRefs ? encodeURIComponent(ref) : ref}` }; } public getChildren(type: ReferenceType): BaseType[] { diff --git a/src/Utils/isAssignableTo.ts b/src/Utils/isAssignableTo.ts index a8d7fa949..af1fe579d 100644 --- a/src/Utils/isAssignableTo.ts +++ b/src/Utils/isAssignableTo.ts @@ -37,7 +37,15 @@ function combineIntersectingTypes(intersection: IntersectionType): BaseType[] { if (objectTypes.length === 1) { combined.push(objectTypes[0]); } else if (objectTypes.length > 1) { - combined.push(new ObjectType(`combined-objects-${intersection.getId()}`, objectTypes, [], false)); + combined.push( + new ObjectType( + `combined-objects-${intersection.getId()}`, + objectTypes, + [], + false, + `non-terminal-intersection` + ) + ); } return combined; } diff --git a/src/Utils/removeUnreachable.ts b/src/Utils/removeUnreachable.ts index 1619fc3f9..be29b9ad8 100644 --- a/src/Utils/removeUnreachable.ts +++ b/src/Utils/removeUnreachable.ts @@ -8,7 +8,7 @@ function addReachable( definitions: StringMap, reachable: Set ) { - if (isBoolean(definition)) { + if (!definition || isBoolean(definition)) { return; } diff --git a/src/Utils/resolveIdRefs.ts b/src/Utils/resolveIdRefs.ts new file mode 100644 index 000000000..dabdc1315 --- /dev/null +++ b/src/Utils/resolveIdRefs.ts @@ -0,0 +1,57 @@ +import { JSONSchema7Definition } from "json-schema"; +import { StringMap } from "./StringMap"; + +/** + * Resolve all `#/definitions/...` and `$ref` in schema with appropriate disambiguated names + */ +export function resolveIdRefs( + schema: JSONSchema7Definition, + idNameMap: Map, + encodeRefs: boolean +): JSONSchema7Definition { + if (!schema || typeof schema === "boolean") { + return schema; + } + const { $ref, allOf, oneOf, anyOf, not, properties, items, definitions, additionalProperties, ...rest } = schema; + const result: JSONSchema7Definition = { ...rest }; + if ($ref) { + // THE Money Shot. + const id = encodeRefs ? decodeURIComponent($ref.slice(14)) : $ref.slice(14); + const name = idNameMap.get(id); + result.$ref = `#/definitions/${encodeRefs ? encodeURIComponent(name!) : name}`; + } + if (definitions) { + result.definitions = Object.entries(definitions).reduce((acc, [prop, value]) => { + const name = idNameMap.get(prop)!; + acc[name] = resolveIdRefs(value, idNameMap, encodeRefs); + return acc; + }, {} as StringMap); + } + if (properties) { + result.properties = Object.entries(properties).reduce((acc, [prop, value]) => { + acc[prop] = resolveIdRefs(value, idNameMap, encodeRefs); + return acc; + }, {} as StringMap); + } + if (additionalProperties || additionalProperties === false) { + result.additionalProperties = resolveIdRefs(additionalProperties, idNameMap, encodeRefs); + } + if (items) { + result.items = Array.isArray(items) + ? items.map((el) => resolveIdRefs(el, idNameMap, encodeRefs)) + : resolveIdRefs(items, idNameMap, encodeRefs); + } + if (allOf) { + result.allOf = allOf.map((el) => resolveIdRefs(el, idNameMap, encodeRefs)); + } + if (anyOf) { + result.anyOf = anyOf.map((el) => resolveIdRefs(el, idNameMap, encodeRefs)); + } + if (oneOf) { + result.oneOf = oneOf.map((el) => resolveIdRefs(el, idNameMap, encodeRefs)); + } + if (not) { + result.not = resolveIdRefs(not, idNameMap, encodeRefs); + } + return result; +} diff --git a/src/Utils/unambiguousName.ts b/src/Utils/unambiguousName.ts new file mode 100644 index 000000000..bec44211b --- /dev/null +++ b/src/Utils/unambiguousName.ts @@ -0,0 +1,59 @@ +import { DefinitionType } from "../Type/DefinitionType"; + +/** + * Given a set of paths, will strip the common-prefix, and return an array of remaining substrings. + * Also removes any file extensions, and replaces any '/' with '-' in the path. + * Each return value can be used as a name prefix for disambiguation. + * The returned prefixes array maintains input order. + */ +function getCommonPrefixes(paths: string[]) { + // clone before sorting to maintain input order. + const sorted = [...paths].sort(); + const shortest = sorted[0]; + const second = sorted[sorted.length - 1]; + const maxPrefix = shortest.length; + let pos = 0; + let path_pos = 0; + while (pos < maxPrefix && shortest.charAt(pos) === second.charAt(pos)) { + if (shortest.charAt(pos) === "/") { + path_pos = pos; + } + pos++; + } + return paths.map((p) => + p + .substr(path_pos + 1) + .replace(/\//g, "__") + .replace(/\.[^.]+$/, "") + ); +} + +export function unambiguousName(child: DefinitionType, isRoot: boolean, peers: DefinitionType[]): string { + if (peers.length === 1 || isRoot) { + return child.getName(); + } else if (child.getType().getSrcFileName()) { + let index = -1; + + // filter unique peers to be those that have srcFileNames. + // Intermediate Types - AnnotationTypes, UnionTypes, do not have sourceFileNames + const uniques = peers.filter((peer) => peer.getType().getSrcFileName()); + if (uniques.length === 1) { + return uniques[0].getName(); + } + const srcPaths = uniques.map((peer: DefinitionType, count) => { + index = child === peer ? count : index; + return peer.getType().getSrcFileName()!; + }); + const prefixes = getCommonPrefixes(srcPaths); + return `${prefixes[index]}-${child.getName()}`; + } else { + // intermediate type. + const name = child.getName(); + // TODO: Perhaps we should maintain a name-id map, and throw a duplicate error on collision. + if (name === undefined) { + // this might be unreachable code. + throw new Error(`Unable to disambiguate types`); + } + return name; + } +} diff --git a/test/invalid-data.test.ts b/test/invalid-data.test.ts index 8abcf3f53..5e775067b 100644 --- a/test/invalid-data.test.ts +++ b/test/invalid-data.test.ts @@ -32,5 +32,4 @@ describe("invalid-data", () => { // TODO: template recursive it("script-empty", assertSchema("script-empty", "MyType", `No root type "MyType" found`)); - it("duplicates", assertSchema("duplicates", "MyType", `Type "A" has multiple definitions.`)); }); diff --git a/test/valid-data-other.test.ts b/test/valid-data-other.test.ts index 07f2d2e51..1e43397d0 100644 --- a/test/valid-data-other.test.ts +++ b/test/valid-data-other.test.ts @@ -64,5 +64,8 @@ describe("valid-data-other", () => { it("array-min-max-items", assertValidSchema("array-min-max-items", "MyType")); it("array-min-max-items-optional", assertValidSchema("array-min-max-items-optional", "MyType")); it("array-max-items-optional", assertValidSchema("array-max-items-optional", "MyType")); + it("duplicates", assertValidSchema("duplicates", "MyType")); + it("duplicates-composition", assertValidSchema("duplicates-composition", "MyObject")); + it("duplicates-inheritance", assertValidSchema("duplicates-inheritance", "MyObject")); it("shorthand-array", assertValidSchema("shorthand-array", "MyType")); }); diff --git a/test/valid-data/duplicates-composition/componentA.ts b/test/valid-data/duplicates-composition/componentA.ts new file mode 100644 index 000000000..bd5ff59fb --- /dev/null +++ b/test/valid-data/duplicates-composition/componentA.ts @@ -0,0 +1,3 @@ +export interface MyObject { + a: string; +} diff --git a/test/valid-data/duplicates-composition/componentB.ts b/test/valid-data/duplicates-composition/componentB.ts new file mode 100644 index 000000000..5837991cc --- /dev/null +++ b/test/valid-data/duplicates-composition/componentB.ts @@ -0,0 +1,3 @@ +export interface MyObject { + b: string; +} diff --git a/test/valid-data/duplicates-composition/main.ts b/test/valid-data/duplicates-composition/main.ts new file mode 100644 index 000000000..241cd330e --- /dev/null +++ b/test/valid-data/duplicates-composition/main.ts @@ -0,0 +1,7 @@ +import * as A from "./componentA"; +import * as B from "./componentB"; + +export interface MyObject { + a: A.MyObject; + b: B.MyObject; +} diff --git a/test/valid-data/duplicates-composition/schema.json b/test/valid-data/duplicates-composition/schema.json new file mode 100644 index 000000000..6e4486ec9 --- /dev/null +++ b/test/valid-data/duplicates-composition/schema.json @@ -0,0 +1,46 @@ +{ + "$ref": "#/definitions/MyObject", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyObject": { + "additionalProperties": false, + "properties": { + "a": { + "$ref": "#/definitions/componentA-MyObject" + }, + "b": { + "$ref": "#/definitions/componentB-MyObject" + } + }, + "required": [ + "a", + "b" + ], + "type": "object" + }, + "componentA-MyObject": { + "additionalProperties": false, + "properties": { + "a": { + "type": "string" + } + }, + "required": [ + "a" + ], + "type": "object" + }, + "componentB-MyObject": { + "additionalProperties": false, + "properties": { + "b": { + "type": "string" + } + }, + "required": [ + "b" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/duplicates-inheritance/base.ts b/test/valid-data/duplicates-inheritance/base.ts new file mode 100644 index 000000000..bd5ff59fb --- /dev/null +++ b/test/valid-data/duplicates-inheritance/base.ts @@ -0,0 +1,3 @@ +export interface MyObject { + a: string; +} diff --git a/test/valid-data/duplicates-inheritance/intermediate.ts b/test/valid-data/duplicates-inheritance/intermediate.ts new file mode 100644 index 000000000..8315188da --- /dev/null +++ b/test/valid-data/duplicates-inheritance/intermediate.ts @@ -0,0 +1,5 @@ +import * as Base from "./base"; + +export interface MyObject extends Base.MyObject { + b: string; +} diff --git a/test/valid-data/duplicates-inheritance/main.ts b/test/valid-data/duplicates-inheritance/main.ts new file mode 100644 index 000000000..00ad875b7 --- /dev/null +++ b/test/valid-data/duplicates-inheritance/main.ts @@ -0,0 +1,5 @@ +import * as Intermediate from "./intermediate"; + +export interface MyObject extends Intermediate.MyObject { + c: string; +} diff --git a/test/valid-data/duplicates-inheritance/schema.json b/test/valid-data/duplicates-inheritance/schema.json new file mode 100644 index 000000000..01ebc1a36 --- /dev/null +++ b/test/valid-data/duplicates-inheritance/schema.json @@ -0,0 +1,26 @@ +{ + "$ref": "#/definitions/MyObject", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyObject": { + "additionalProperties": false, + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + }, + "c": { + "type": "string" + } + }, + "required": [ + "a", + "b", + "c" + ], + "type": "object" + } + } +} diff --git a/test/invalid-data/duplicates/import1.ts b/test/valid-data/duplicates/import1.ts similarity index 100% rename from test/invalid-data/duplicates/import1.ts rename to test/valid-data/duplicates/import1.ts diff --git a/test/invalid-data/duplicates/import2.ts b/test/valid-data/duplicates/import2.ts similarity index 100% rename from test/invalid-data/duplicates/import2.ts rename to test/valid-data/duplicates/import2.ts diff --git a/test/invalid-data/duplicates/main.ts b/test/valid-data/duplicates/main.ts similarity index 100% rename from test/invalid-data/duplicates/main.ts rename to test/valid-data/duplicates/main.ts diff --git a/test/valid-data/duplicates/schema.json b/test/valid-data/duplicates/schema.json new file mode 100644 index 000000000..aa2b526de --- /dev/null +++ b/test/valid-data/duplicates/schema.json @@ -0,0 +1,22 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "anyOf": [ + { + "$ref": "#/definitions/import1-A" + }, + { + "$ref": "#/definitions/import2-A" + } + ] + }, + "import1-A": { + "type": "number" + }, + "import2-A": { + "type": "string" + } + } +} diff --git a/test/valid-data/type-aliases-recursive-anonymous/schema.json b/test/valid-data/type-aliases-recursive-anonymous/schema.json index 10db739d1..a465b36cf 100644 --- a/test/valid-data/type-aliases-recursive-anonymous/schema.json +++ b/test/valid-data/type-aliases-recursive-anonymous/schema.json @@ -1,38 +1,38 @@ { - "$ref": "#/definitions/MyAlias", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyAlias": { - "additionalProperties": false, - "properties": { - "alias": { - "$ref": "#/definitions/MyAlias" + "$ref": "#/definitions/MyAlias", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyAlias": { + "additionalProperties": false, + "properties": { + "alias": { + "$ref": "#/definitions/MyAlias" + }, + "self": { + "$ref": "#/definitions/interface-425636228-0-62-425636228-0-96" + } }, - "self": { - "$ref": "#/definitions/interface-425636228-0-62-425636228-0-96" - } + "required": [ + "alias", + "self" + ], + "type": "object" }, - "required": [ - "alias", - "self" - ], - "type": "object" - }, - "interface-425636228-0-62-425636228-0-96": { - "additionalProperties": false, - "properties": { - "alias": { - "$ref": "#/definitions/MyAlias" + "interface-425636228-0-62-425636228-0-96": { + "additionalProperties": false, + "properties": { + "alias": { + "$ref": "#/definitions/MyAlias" + }, + "self": { + "$ref": "#/definitions/interface-425636228-0-62-425636228-0-96" + } }, - "self": { - "$ref": "#/definitions/interface-425636228-0-62-425636228-0-96" - } - }, - "required": [ - "alias", - "self" - ], - "type": "object" + "required": [ + "alias", + "self" + ], + "type": "object" + } } } -} diff --git a/test/valid-data/type-mapped-exclude/schema.json b/test/valid-data/type-mapped-exclude/schema.json index 104764de4..0daf24649 100644 --- a/test/valid-data/type-mapped-exclude/schema.json +++ b/test/valid-data/type-mapped-exclude/schema.json @@ -1,23 +1,23 @@ { - "$ref": "#/definitions/MyObject", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyObject": { - "additionalProperties": false, - "properties": { - "bar": { - "description": "Bar", - "type": "string" - }, - "foo": { - "description": "Foo", - "type": "string" + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/MyObject", + "definitions": { + "MyObject": { + "type": "object", + "required": [ + "bar" + ], + "properties": { + "foo": { + "type": "string", + "description": "Foo" + }, + "bar": { + "type": "string", + "description": "Bar" + } + }, + "additionalProperties": false } - }, - "required": [ - "bar" - ], - "type": "object" } - } }