Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich tuples support #42

Merged
merged 10 commits into from
Aug 2, 2018
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ npm install --save ts-json-schema-generator
--path 'my/project/**.*.ts' \
--type 'My.Type.Full.Name' \
--expose 'export' \
--jsDoc 'extended' \
--strictTuples
--jsDoc 'extended'
```

## Options
Expand All @@ -44,9 +43,6 @@ npm install --save ts-json-schema-generator

-u, --unstable
Do not sort properties.

-s, --strictTuples
Do not allow additional items on tuples.
```


Expand Down
7 changes: 6 additions & 1 deletion factory/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { LiteralUnionTypeFormatter } from "../src/TypeFormatter/LiteralUnionType
import { NullTypeFormatter } from "../src/TypeFormatter/NullTypeFormatter";
import { NumberTypeFormatter } from "../src/TypeFormatter/NumberTypeFormatter";
import { ObjectTypeFormatter } from "../src/TypeFormatter/ObjectTypeFormatter";
import { OptionalTypeFormatter } from "../src/TypeFormatter/OptionalTypeFormatter";
import { PrimitiveUnionTypeFormatter } from "../src/TypeFormatter/PrimitiveUnionTypeFormatter";
import { ReferenceTypeFormatter } from "../src/TypeFormatter/ReferenceTypeFormatter";
import { RestTypeFormatter } from "../src/TypeFormatter/RestTypeFormatter";
import { StringTypeFormatter } from "../src/TypeFormatter/StringTypeFormatter";
import { TupleTypeFormatter } from "../src/TypeFormatter/TupleTypeFormatter";
import { UndefinedTypeFormatter } from "../src/TypeFormatter/UndefinedTypeFormatter";
Expand Down Expand Up @@ -50,8 +52,11 @@ export function createFormatter(config: Config): TypeFormatter {
.addTypeFormatter(new PrimitiveUnionTypeFormatter())
.addTypeFormatter(new LiteralUnionTypeFormatter())

.addTypeFormatter(new OptionalTypeFormatter(circularReferenceTypeFormatter))
.addTypeFormatter(new RestTypeFormatter(circularReferenceTypeFormatter))

.addTypeFormatter(new ArrayTypeFormatter(circularReferenceTypeFormatter))
.addTypeFormatter(new TupleTypeFormatter(circularReferenceTypeFormatter, config))
.addTypeFormatter(new TupleTypeFormatter(circularReferenceTypeFormatter))
.addTypeFormatter(new UnionTypeFormatter(circularReferenceTypeFormatter))
.addTypeFormatter(new IntersectionTypeFormatter(circularReferenceTypeFormatter));

Expand Down
4 changes: 4 additions & 0 deletions factory/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import { NullLiteralNodeParser } from "../src/NodeParser/NullLiteralNodeParser";
import { NumberLiteralNodeParser } from "../src/NodeParser/NumberLiteralNodeParser";
import { NumberTypeNodeParser } from "../src/NodeParser/NumberTypeNodeParser";
import { ObjectTypeNodeParser } from "../src/NodeParser/ObjectTypeNodeParser";
import { OptionalTypeNodeParser } from "../src/NodeParser/OptionalTypeNodeParser";
import { ParenthesizedNodeParser } from "../src/NodeParser/ParenthesizedNodeParser";
import { RestTypeNodeParser } from "../src/NodeParser/RestTypeNodeParser";
import { StringLiteralNodeParser } from "../src/NodeParser/StringLiteralNodeParser";
import { StringTypeNodeParser } from "../src/NodeParser/StringTypeNodeParser";
import { TupleNodeParser } from "../src/NodeParser/TupleNodeParser";
Expand Down Expand Up @@ -88,6 +90,8 @@ export function createParser(program: ts.Program, config: Config): NodeParser {
.addNodeParser(new UnionNodeParser(typeChecker, chainNodeParser))
.addNodeParser(new IntersectionNodeParser(typeChecker, chainNodeParser))
.addNodeParser(new TupleNodeParser(typeChecker, chainNodeParser))
.addNodeParser(new OptionalTypeNodeParser(chainNodeParser))
.addNodeParser(new RestTypeNodeParser(chainNodeParser))

.addNodeParser(new CallExpressionParser(typeChecker, chainNodeParser))

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"src"
],
"coverageDirectory": "./coverage/",
"collectCoverage": false
"collectCoverage": false,
"testEnvironment": "node"
}
}
20 changes: 20 additions & 0 deletions src/NodeParser/OptionalTypeNodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as ts from "typescript";
import { Context, NodeParser } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { OptionalType } from "../Type/OptionalType";

export class OptionalTypeNodeParser implements SubNodeParser {
public constructor(
private childNodeParser: NodeParser,
) {
}
public supportsNode(node: ts.OptionalTypeNode): boolean {
return node.kind === ts.SyntaxKind.OptionalType;
}
public createType(node: ts.OptionalTypeNode, context: Context): BaseType {
return new OptionalType(
this.childNodeParser.createType(node.type, context),
);
}
}
21 changes: 21 additions & 0 deletions src/NodeParser/RestTypeNodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as ts from "typescript";
import { Context, NodeParser } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { ArrayType } from "../Type/ArrayType";
import { BaseType } from "../Type/BaseType";
import { RestType } from "../Type/RestType";

export class RestTypeNodeParser implements SubNodeParser {
public constructor(
private childNodeParser: NodeParser,
) {
}
public supportsNode(node: ts.RestTypeNode): boolean {
return node.kind === ts.SyntaxKind.RestType;
}
public createType(node: ts.RestTypeNode, context: Context): BaseType {
return new RestType(
this.childNodeParser.createType(node.type, context) as ArrayType,
);
}
}
4 changes: 1 addition & 3 deletions src/Schema/Definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ export interface Definition {
items?: Definition | Definition[];
minItems?: number;
maxItems?: number;
additionalItems?: {
anyOf: Definition[],
};
additionalItems?: Definition;
enum?: (RawType | Definition)[];
default?: RawType | Object;
additionalProperties?: false | Definition;
Expand Down
17 changes: 17 additions & 0 deletions src/Type/OptionalType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BaseType } from "./BaseType";

export class OptionalType extends BaseType {
public constructor(
private item: BaseType,
) {
super();
}

public getId(): string {
return this.item.getId() + "?";
}

public getType(): BaseType {
return this.item;
}
}
18 changes: 18 additions & 0 deletions src/Type/RestType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ArrayType } from "./ArrayType";
import { BaseType } from "./BaseType";

export class RestType extends BaseType {
public constructor(
private item: ArrayType,
) {
super();
}

public getId(): string {
return "..." + this.item.getId();
}

public getType(): ArrayType {
return this.item;
}
}
22 changes: 22 additions & 0 deletions src/TypeFormatter/OptionalTypeFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { OptionalType } from "../Type/OptionalType";
import { TypeFormatter } from "../TypeFormatter";

export class OptionalTypeFormatter implements SubTypeFormatter {
public constructor(
private childTypeFormatter: TypeFormatter,
) {
}

public supportsType(type: OptionalType): boolean {
return type instanceof OptionalType;
}
public getDefinition(type: OptionalType): Definition {
return this.childTypeFormatter.getDefinition(type.getType());
}
public getChildren(type: OptionalType): BaseType[] {
return this.childTypeFormatter.getChildren(type.getType());
}
}
22 changes: 22 additions & 0 deletions src/TypeFormatter/RestTypeFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { RestType } from "../Type/RestType";
import { TypeFormatter } from "../TypeFormatter";

export class RestTypeFormatter implements SubTypeFormatter {
public constructor(
private childTypeFormatter: TypeFormatter,
) {
}

public supportsType(type: RestType): boolean {
return type instanceof RestType;
}
public getDefinition(type: RestType): Definition {
return this.childTypeFormatter.getDefinition(type.getType());
}
public getChildren(type: RestType): BaseType[] {
return this.childTypeFormatter.getChildren(type.getType());
}
}
29 changes: 21 additions & 8 deletions src/TypeFormatter/TupleTypeFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import { Config } from "../Config";
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { OptionalType } from "../Type/OptionalType";
import { RestType } from "../Type/RestType";
import { TupleType } from "../Type/TupleType";
import { TypeFormatter } from "../TypeFormatter";

export class TupleTypeFormatter implements SubTypeFormatter {
public constructor(
private childTypeFormatter: TypeFormatter,
private config: Config,
) {
}

public supportsType(type: TupleType): boolean {
return type instanceof TupleType;
}
public getDefinition(type: TupleType): Definition {
const tupleDefinitions = type.getTypes().map((item) => this.childTypeFormatter.getDefinition(item));
const addAdditionalItems = tupleDefinitions.length > 1 && !this.config.strictTuples;
const additionalItems = {additionalItems: {anyOf: tupleDefinitions}};
const subTypes = type.getTypes();

const requiredElements = subTypes.filter(t => !(t instanceof OptionalType) && !(t instanceof RestType));
const optionalElements = subTypes.filter(t => t instanceof OptionalType) as OptionalType[];
const restElements = subTypes.filter(t => t instanceof RestType) as RestType[];

const requiredDefinitions = requiredElements.map((item) => this.childTypeFormatter.getDefinition(item));
const optionalDefinitions = optionalElements.map((item) => this.childTypeFormatter.getDefinition(item));
const itemsTotal = requiredDefinitions.length + optionalDefinitions.length;

const restType = restElements.length ? restElements[0].getType().getItem() : undefined;
const restDefinition = restType ? this.childTypeFormatter.getDefinition(restType) : undefined;

return {
type: "array",
items: tupleDefinitions,
minItems: tupleDefinitions.length,
...(addAdditionalItems ? additionalItems : {maxItems: tupleDefinitions.length}),
minItems: requiredDefinitions.length,
...(itemsTotal ? { items: requiredDefinitions.concat(optionalDefinitions) } : {}), // with items
...(!itemsTotal && restDefinition ? { items: restDefinition } : {}), // with only rest param
...(!itemsTotal && !restDefinition ? { maxItems: 0 } : {}), // empty
...(restDefinition && itemsTotal ? { additionalItems: restDefinition } : {}), // with items and rest
...(!restDefinition && itemsTotal ? { maxItems: itemsTotal } : {}), // without rest
};
}
public getChildren(type: TupleType): BaseType[] {
Expand Down
15 changes: 0 additions & 15 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,4 @@ describe("config", () => {

assertSchema("jsdoc-hide", {type: "MyObject", expose: "export", topRef: true, jsDoc: "extended"});
assertSchema("jsdoc-inheritance", {type: "MyObject", expose: "export", topRef: true, jsDoc: "extended"});

assertSchema("strict-tuples-true", {
type: "MyObject",
expose: "export",
topRef: true,
jsDoc: "none",
strictTuples: true,
});
assertSchema("strict-tuples-false", {
type: "MyObject",
expose: "export",
topRef: true,
jsDoc: "none",
strictTuples: false,
});
});
3 changes: 0 additions & 3 deletions test/config/strict-tuples-false/main.ts

This file was deleted.

37 changes: 0 additions & 37 deletions test/config/strict-tuples-false/schema.json

This file was deleted.

3 changes: 0 additions & 3 deletions test/config/strict-tuples-true/main.ts

This file was deleted.

28 changes: 0 additions & 28 deletions test/config/strict-tuples-true/schema.json

This file was deleted.

7 changes: 6 additions & 1 deletion test/valid-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,17 @@ describe("valid-data", () => {
assertSchema("type-aliases-object", "MyAlias");
assertSchema("type-aliases-mixed", "MyObject");
assertSchema("type-aliases-union", "MyUnion");
assertSchema("type-aliases-tuple", "MyTuple");
assertSchema("type-aliases-anonymous", "MyObject");
assertSchema("type-aliases-local-namespace", "MyObject");
assertSchema("type-aliases-recursive-anonymous", "MyAlias");
assertSchema("type-aliases-recursive-export", "MyObject");

assertSchema("type-aliases-tuple", "MyTuple");
assertSchema("type-aliases-tuple-empty", "MyTuple");
assertSchema("type-aliases-tuple-optional-items", "MyTuple");
assertSchema("type-aliases-tuple-rest", "MyTuple");
assertSchema("type-aliases-tuple-only-rest", "MyTuple");

assertSchema("type-maps", "MyObject");
assertSchema("type-primitives", "MyObject");
assertSchema("type-union", "TypeUnion");
Expand Down
1 change: 1 addition & 0 deletions test/valid-data/type-aliases-tuple-empty/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MyTuple = [];
11 changes: 11 additions & 0 deletions test/valid-data/type-aliases-tuple-empty/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$ref": "#/definitions/MyTuple",
"$schema": "http://json-schema.org/draft-06/schema#",
"definitions": {
"MyTuple": {
"maxItems": 0,
"minItems": 0,
"type": "array"
}
}
}
1 change: 1 addition & 0 deletions test/valid-data/type-aliases-tuple-only-rest/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MyTuple = [...string[]];
Loading