Skip to content

Commit d470b3b

Browse files
schanidomoritz
authored andcommitted
Fix recursive intersection. Fixes YousefED#198
1 parent 21225ed commit d470b3b

File tree

4 files changed

+95
-13
lines changed

4 files changed

+95
-13
lines changed

Diff for: test/programs/type-intersection-recursive/main.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
interface ChildFoo {
2+
}
3+
4+
interface Foo {
5+
readonly childFoos: Foo & ChildFoo;
6+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"definitions": {
4+
"ChildFoo": {
5+
"type": "object"
6+
},
7+
"Foo": {
8+
"properties": {
9+
"childFoos": {
10+
"allOf": [
11+
{
12+
"$ref": "#/definitions/Foo"
13+
},
14+
{
15+
"$ref": "#/definitions/ChildFoo"
16+
}
17+
]
18+
}
19+
},
20+
"required": [
21+
"childFoos"
22+
],
23+
"type": "object"
24+
}
25+
}
26+
}

Diff for: test/schema.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ describe("schema", () => {
136136
});
137137
assertSchema("type-union-tagged", "Shape");
138138
assertSchema("type-aliases-union-namespace", "MyModel");
139+
assertSchema("type-intersection-recursive", "*");
139140
});
140141

141142
describe("annotations", () => {

Diff for: typescript-json-schema.ts

+62-13
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,51 @@ export class JsonSchemaGenerator {
537537
return definition;
538538
}
539539

540+
private getIntersectionDefinition(intersectionType: ts.IntersectionType, tc: ts.TypeChecker, definition: Definition) {
541+
const simpleTypes: string[] = [];
542+
const schemas: Definition[] = [];
543+
544+
const addSimpleType = (type: string) => {
545+
if (simpleTypes.indexOf(type) === -1) {
546+
simpleTypes.push(type);
547+
}
548+
};
549+
550+
for (let i = 0; i < intersectionType.types.length; ++i) {
551+
const def = this.getTypeDefinition(intersectionType.types[i], tc);
552+
if (def.type === "undefined") {
553+
console.error("Undefined in intersection makes no sense.");
554+
} else {
555+
const keys = Object.keys(def);
556+
if (keys.length === 1 && keys[0] === "type") {
557+
if (typeof def.type !== "string") {
558+
console.error("Expected only a simple type.");
559+
} else {
560+
addSimpleType(def.type);
561+
}
562+
} else {
563+
schemas.push(def);
564+
}
565+
}
566+
}
567+
568+
if (simpleTypes.length > 0) {
569+
schemas.push({ type: simpleTypes.length === 1 ? simpleTypes[0] : simpleTypes });
570+
}
571+
572+
if (schemas.length === 1) {
573+
for (let k in schemas[0]) {
574+
if (schemas[0].hasOwnProperty(k)) {
575+
definition[k] = schemas[0][k];
576+
}
577+
}
578+
} else {
579+
definition.allOf = schemas;
580+
}
581+
return definition;
582+
}
583+
584+
540585
private getClassDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: Definition): Definition {
541586
const node = clazzType.getSymbol()!.getDeclarations()![0];
542587
if (this.args.typeOfKeyword && node.kind === ts.SyntaxKind.FunctionType) {
@@ -797,22 +842,26 @@ export class JsonSchemaGenerator {
797842
if (typ.flags & ts.TypeFlags.Union) {
798843
this.getUnionDefinition(typ as ts.UnionType, prop!, tc, unionModifier, definition);
799844
} else if (typ.flags & ts.TypeFlags.Intersection) {
800-
// extend object instead of using allOf because allOf does not work well with additional properties. See #107
801845
if (this.args.noExtraProps) {
802-
definition.additionalProperties = false;
803-
}
804-
805-
const types = (<ts.IntersectionType> typ).types;
806-
for (let i = 0; i < types.length; ++i) {
807-
const other = this.getTypeDefinition(types[i], tc, false);
808-
definition.type = other.type; // should always be object
809-
definition.properties = extend(definition.properties || {}, other.properties);
810-
if (Object.keys(other.default || {}).length > 0) {
811-
definition.default = extend(definition.default || {}, other.default);
846+
// extend object instead of using allOf because allOf does not work well with additional properties. See #107
847+
if (this.args.noExtraProps) {
848+
definition.additionalProperties = false;
812849
}
813-
if (other.required) {
814-
definition.required = unique((definition.required || []).concat(other.required)).sort();
850+
851+
const types = (<ts.IntersectionType> typ).types;
852+
for (let i = 0; i < types.length; ++i) {
853+
const other = this.getTypeDefinition(types[i], tc, false);
854+
definition.type = other.type; // should always be object
855+
definition.properties = extend(definition.properties || {}, other.properties);
856+
if (Object.keys(other.default || {}).length > 0) {
857+
definition.default = extend(definition.default || {}, other.default);
858+
}
859+
if (other.required) {
860+
definition.required = unique((definition.required || []).concat(other.required)).sort();
861+
}
815862
}
863+
} else {
864+
this.getIntersectionDefinition(typ as ts.IntersectionType, tc, definition);
816865
}
817866
} else if (isRawType) {
818867
if (pairedSymbol) {

0 commit comments

Comments
 (0)