Skip to content

Commit

Permalink
Translate intersection into union if needed
Browse files Browse the repository at this point in the history
  • Loading branch information
Klaus Reimer authored and domoritz committed Jun 4, 2019
1 parent 9f9c355 commit d9b9a15
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
36 changes: 34 additions & 2 deletions src/NodeParser/IntersectionNodeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Context, NodeParser } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { IntersectionType } from "../Type/IntersectionType";
import { UnionType } from "../Type/UnionType";
import { derefType } from "../Utils/derefType";
import { referenceHidden } from "../Utils/isHidden";

export class IntersectionNodeParser implements SubNodeParser {
Expand All @@ -15,12 +17,42 @@ export class IntersectionNodeParser implements SubNodeParser {
public supportsNode(node: ts.IntersectionTypeNode): boolean {
return node.kind === ts.SyntaxKind.IntersectionType;
}

public createType(node: ts.IntersectionTypeNode, context: Context): BaseType {
const hidden = referenceHidden(this.typeChecker);
return new IntersectionType(
return this.translate(new IntersectionType(
node.types
.filter((subnode) => !hidden(subnode))
.map((subnode) => this.childNodeParser.createType(subnode, context)),
);
));
}

/**
* Translates the given intersection type into a union type if necessary so `A & (B | C)` becomes
* `(A & B) | (A & C)`. If no translation is needed then the original intersection type is returned.
*
* @param intersection - The intersection type to translate.
* @return Either the union type into which the intersection type was translated or the original intersection type
* if no translation is needed.
*/
private translate(intersection: IntersectionType): IntersectionType | UnionType {
const unions = intersection.getTypes().map(type => {
const derefed = derefType(type);
return derefed instanceof UnionType ? derefed.getTypes() : [ type ];
});
const result: IntersectionType[] = [];
function process(i: number, types: BaseType[] = []) {
for (const type of unions[i]) {
const currentTypes = types.slice();
currentTypes.push(type);
if (i < unions.length - 1) {
process(i + 1, currentTypes);
} else {
result.push(new IntersectionType(currentTypes));
}
}
}
process(0);
return result.length > 1 ? new UnionType(result) : intersection;
}
}
2 changes: 1 addition & 1 deletion test/valid-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe("valid-data", () => {
it("type-union", assertSchema("type-union", "TypeUnion"));
it("type-union-tagged", assertSchema("type-union-tagged", "Shape"));
it("type-intersection", assertSchema("type-intersection", "MyObject"));
it("type-intersection-union", assertSchema("type-intersection", "MyObject"));
it("type-intersection-union", assertSchema("type-intersection-union", "MyObject"));
it("type-intersection-additional-props", assertSchema("type-intersection-additional-props", "MyObject"));

it("type-typeof", assertSchema("type-typeof", "MyType"));
Expand Down
21 changes: 21 additions & 0 deletions test/valid-data/type-intersection-union/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface A {
a: number;
}

export interface B {
b: number;
}

export interface C {
c: number;
}

export interface D {
d: number;
}

export interface E {
e: number;
}

export type MyObject = (B | C) & A & (D | E);
90 changes: 90 additions & 0 deletions test/valid-data/type-intersection-union/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"$ref": "#/definitions/MyObject",
"$schema": "http://json-schema.org/draft-06/schema#",
"definitions": {
"MyObject": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
},
"d": {
"type": "number"
}
},
"required": [
"a",
"b",
"d"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
},
"e": {
"type": "number"
}
},
"required": [
"a",
"b",
"e"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"c": {
"type": "number"
},
"d": {
"type": "number"
}
},
"required": [
"a",
"c",
"d"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"c": {
"type": "number"
},
"e": {
"type": "number"
}
},
"required": [
"a",
"c",
"e"
],
"type": "object"
}
]
}
}
}

0 comments on commit d9b9a15

Please sign in to comment.