Skip to content

Commit

Permalink
feat:: configurable formatter (#565)
Browse files Browse the repository at this point in the history
Co-authored-by: Dominik Moritz <domoritz@gmail.com>
  • Loading branch information
fwal and domoritz committed Nov 10, 2020
1 parent 0e33245 commit a3479e0
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 5 deletions.
63 changes: 63 additions & 0 deletions README.md
Expand Up @@ -51,6 +51,69 @@ fs.writeFile(output_path, schemaString, (err) => {

Run the schema generator via `node main.js`.

### Custom formatting

Extending the built-in formatting is possible by creating a custom formatter and adding it to the main formatter:

1. First we create a formatter, in this case for formatting function types:

```ts
// my-function-formatter.ts
import { BaseType, Definition, FunctionType, SubTypeFormatter } from 'ts-json-schema-generator';

export class MyFunctionTypeFormatter implements SubTypeFormatter {

public supportsType(type: FunctionType): boolean {
return type instanceof FunctionType;
}

public getDefinition(_type: FunctionType): Definition {
// Return a custom schema for the function property.
return {
type: "object",
properties: {
isFunction: {
type: "boolean",
const: true,
},
},
};
}

public getChildren(_type: FunctionType): BaseType[] {
return [];
}
}
```

2. Then we add the formatter as a child to the core formatter using the augmentation callback:

```ts
import { createProgram, createParser, SchemaGenerator, createFormatter } from 'ts-json-schema-generator';
import { MyFunctionTypeFormatter } from './my-function-formatter.ts';
import fs from 'fs'

const config = {
path: "path/to/source/file",
tsconfig: "path/to/tsconfig.json",
type: "*", // Or <type-name> if you want to generate schema for that one type only
};

// We configure the formatter an add our custom formatter to it.
const formatter = createFormatter(config, fmt => {
fmt.addTypeFormatter(new MyFunctionTypeFormatter());
});

const program = createProgram(config);
const generator = new SchemaGenerator(program, parser, formatter, config);
const schema = generator.createSchema(config.type);

const schemaString = JSON.stringify(schema, null, 2);
fs.writeFile(output_path, schemaString, (err) => {
if (err) throw err;
});
```

## Options

```
Expand Down
9 changes: 8 additions & 1 deletion factory/formatter.ts
Expand Up @@ -25,8 +25,11 @@ import { UndefinedTypeFormatter } from "../src/TypeFormatter/UndefinedTypeFormat
import { UnionTypeFormatter } from "../src/TypeFormatter/UnionTypeFormatter";
import { UnknownTypeFormatter } from "../src/TypeFormatter/UnknownTypeFormatter";
import { VoidTypeFormatter } from "../src/TypeFormatter/VoidTypeFormatter";
import { MutableTypeFormatter } from "../src/MutableTypeFormatter";

export function createFormatter(config: Config): TypeFormatter {
export type FormatterAugmentor = (formatter: MutableTypeFormatter) => void;

export function createFormatter(config: Config, augmentor?: FormatterAugmentor): TypeFormatter {
const chainTypeFormatter = new ChainTypeFormatter([]);
const circularReferenceTypeFormatter = new CircularReferenceTypeFormatter(chainTypeFormatter);

Expand Down Expand Up @@ -62,5 +65,9 @@ export function createFormatter(config: Config): TypeFormatter {
.addTypeFormatter(new UnionTypeFormatter(circularReferenceTypeFormatter))
.addTypeFormatter(new IntersectionTypeFormatter(circularReferenceTypeFormatter));

if (augmentor) {
augmentor(chainTypeFormatter);
}

return circularReferenceTypeFormatter;
}
2 changes: 2 additions & 0 deletions index.ts
Expand Up @@ -35,6 +35,7 @@ export * from "./src/Type/AliasType";
export * from "./src/Type/ReferenceType";
export * from "./src/Type/DefinitionType";
export * from "./src/Type/AnnotatedType";
export * from "./src/Type/FunctionType";

export * from "./src/AnnotationsReader";
export * from "./src/AnnotationsReader/BasicAnnotationsReader";
Expand All @@ -43,6 +44,7 @@ export * from "./src/AnnotationsReader/ExtendedAnnotationsReader";
export * from "./src/TypeFormatter";
export * from "./src/SubTypeFormatter";
export * from "./src/ChainTypeFormatter";
export * from "./src/MutableTypeFormatter";
export * from "./src/CircularReferenceTypeFormatter";
export * from "./src/TypeFormatter/AnyTypeFormatter";
export * from "./src/TypeFormatter/UnknownTypeFormatter";
Expand Down
3 changes: 2 additions & 1 deletion src/ChainTypeFormatter.ts
@@ -1,9 +1,10 @@
import { UnknownTypeError } from "./Error/UnknownTypeError";
import { MutableTypeFormatter } from "./MutableTypeFormatter";
import { Definition } from "./Schema/Definition";
import { SubTypeFormatter } from "./SubTypeFormatter";
import { BaseType } from "./Type/BaseType";

export class ChainTypeFormatter implements SubTypeFormatter {
export class ChainTypeFormatter implements SubTypeFormatter, MutableTypeFormatter {
public constructor(private typeFormatters: SubTypeFormatter[]) {}

public addTypeFormatter(typeFormatter: SubTypeFormatter): this {
Expand Down
5 changes: 5 additions & 0 deletions src/MutableTypeFormatter.ts
@@ -0,0 +1,5 @@
import { SubTypeFormatter } from "./SubTypeFormatter";

export interface MutableTypeFormatter {
addTypeFormatter(formatter: SubTypeFormatter): MutableTypeFormatter;
}
47 changes: 44 additions & 3 deletions test/config.test.ts
Expand Up @@ -3,15 +3,24 @@ import { readFileSync } from "fs";
import { resolve } from "path";
import ts from "typescript";

import { createFormatter } from "../factory/formatter";
import { createFormatter, FormatterAugmentor } from "../factory/formatter";
import { createParser } from "../factory/parser";
import { createProgram } from "../factory/program";
import { Config, DEFAULT_CONFIG } from "../src/Config";
import { Definition } from "../src/Schema/Definition";
import { SchemaGenerator } from "../src/SchemaGenerator";
import { SubTypeFormatter } from "../src/SubTypeFormatter";
import { BaseType } from "../src/Type/BaseType";
import { FunctionType } from "../src/Type/FunctionType";

const basePath = "test/config";

function assertSchema(name: string, userConfig: Config & { type: string }, tsconfig?: boolean) {
function assertSchema(
name: string,
userConfig: Config & { type: string },
tsconfig?: boolean,
augmentor?: FormatterAugmentor
) {
return () => {
const config: Config = {
...DEFAULT_CONFIG,
Expand All @@ -28,7 +37,7 @@ function assertSchema(name: string, userConfig: Config & { type: string }, tscon
const generator: SchemaGenerator = new SchemaGenerator(
program,
createParser(program, config),
createFormatter(config),
createFormatter(config, augmentor),
config
);

Expand All @@ -51,6 +60,26 @@ function assertSchema(name: string, userConfig: Config & { type: string }, tscon
};
}

export class ExampleFunctionTypeFormatter implements SubTypeFormatter {
public supportsType(type: FunctionType): boolean {
return type instanceof FunctionType;
}
public getDefinition(_type: FunctionType): Definition {
return {
type: "object",
properties: {
isFunction: {
type: "boolean",
const: true,
},
},
};
}
public getChildren(_type: FunctionType): BaseType[] {
return [];
}
}

describe("config", () => {
it(
"expose-all-topref-true",
Expand Down Expand Up @@ -248,4 +277,16 @@ describe("config", () => {
additionalProperties: true,
})
);

it(
"custom-formatter-configuration",
assertSchema(
"custom-formatter-configuration",
{
type: "MyObject",
},
false,
(formatter) => formatter.addTypeFormatter(new ExampleFunctionTypeFormatter())
)
);
});
39 changes: 39 additions & 0 deletions test/config/custom-formatter-configuration/main.ts
@@ -0,0 +1,39 @@
export interface ExportInterface {
exportValue: string;
}
export type ExportAlias = ExportInterface;

interface PrivateInterface {
privateValue: string;
}
type PrivateAlias = PrivateInterface;

interface MixedInterface {
mixedValue: ExportAlias;
}
export type MixedAlias = PrivateInterface;


export type PublicAnonymousTypeLiteral = {
publicValue: string;
};

type PrivateAnonymousTypeLiteral = {
privateValue: string;
};

export interface MyObject {
exportInterface: ExportInterface;
exportAlias: ExportAlias;

privateInterface: PrivateInterface;
privateAlias: PrivateAlias;

mixedInterface: MixedInterface;
mixedAlias: MixedAlias;

publicAnonymousTypeLiteral: PublicAnonymousTypeLiteral;
privateAnonymousTypeLiteral: PrivateAnonymousTypeLiteral;

exportedFunction: () => void;
}
131 changes: 131 additions & 0 deletions test/config/custom-formatter-configuration/schema.json
@@ -0,0 +1,131 @@
{
"$ref": "#/definitions/MyObject",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ExportInterface": {
"type": "object",
"properties": {
"exportValue": {
"type": "string"
}
},
"required": [
"exportValue"
],
"additionalProperties": false
},
"ExportAlias": {
"$ref": "#/definitions/ExportInterface"
},
"MixedAlias": {
"type": "object",
"properties": {
"privateValue": {
"type": "string"
}
},
"required": [
"privateValue"
],
"additionalProperties": false
},
"PublicAnonymousTypeLiteral": {
"type": "object",
"properties": {
"publicValue": {
"type": "string"
}
},
"required": [
"publicValue"
],
"additionalProperties": false
},
"MyObject": {
"type": "object",
"properties": {
"exportInterface": {
"$ref": "#/definitions/ExportInterface"
},
"exportAlias": {
"$ref": "#/definitions/ExportAlias"
},
"privateInterface": {
"type": "object",
"properties": {
"privateValue": {
"type": "string"
}
},
"required": [
"privateValue"
],
"additionalProperties": false
},
"privateAlias": {
"type": "object",
"properties": {
"privateValue": {
"type": "string"
}
},
"required": [
"privateValue"
],
"additionalProperties": false
},
"mixedInterface": {
"type": "object",
"properties": {
"mixedValue": {
"$ref": "#/definitions/ExportAlias"
}
},
"required": [
"mixedValue"
],
"additionalProperties": false
},
"mixedAlias": {
"$ref": "#/definitions/MixedAlias"
},
"publicAnonymousTypeLiteral": {
"$ref": "#/definitions/PublicAnonymousTypeLiteral"
},
"privateAnonymousTypeLiteral": {
"type": "object",
"properties": {
"privateValue": {
"type": "string"
}
},
"required": [
"privateValue"
],
"additionalProperties": false
},
"exportedFunction": {
"type": "object",
"properties": {
"isFunction": {
"type": "boolean",
"const": true
}
}
}
},
"required": [
"exportInterface",
"exportAlias",
"privateInterface",
"privateAlias",
"mixedInterface",
"mixedAlias",
"publicAnonymousTypeLiteral",
"privateAnonymousTypeLiteral",
"exportedFunction"
],
"additionalProperties": false
}
}
}

0 comments on commit a3479e0

Please sign in to comment.