- Added feature to use union types

This commit is contained in:
Ferdi Koomen 2020-03-04 11:50:00 +01:00
parent 7c6a226dee
commit aa832a71dc
67 changed files with 727 additions and 526 deletions

View File

@ -54,10 +54,22 @@ openapi --input ./api/openapi.json --output ./dist
```javascript
const OpenAPI = require('openapi-typescript-codegen');
OpenAPI.generate(
'./api/openapi.json',
'./dist'
);
OpenAPI.generate({
input: './api/openapi.json',
output: './dist'
});
```
Or by providing the JSON directly:
```javascript
const OpenAPI = require('openapi-typescript-codegen');
const spec = require('./api/openapi.json');
OpenAPI.generate({
input: spec,
output: './dist'
});
```
## Features

View File

@ -12,15 +12,17 @@ program
.option('--output [value]', 'Output directory', './generated')
.option('--client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
.option('--useOptions', 'Use options vs arguments style functions', false)
.option('--useUnionTypes', 'Use inclusive union types', false)
.parse(process.argv);
const OpenAPI = require(path.resolve(__dirname, '../dist/index.js'));
if (OpenAPI) {
OpenAPI.generate(
program.input,
program.output,
program.client,
program.useOptions
);
OpenAPI.generate({
input: program.input,
output: program.output,
httpClient: program.client,
useOptions: program.useOptions,
useUnionTypes: program.useUnionTypes
});
}

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.1.17",
"version": "0.2.3",
"description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",

View File

@ -15,4 +15,6 @@ export interface Model extends Schema {
enum: Enum[];
enums: Model[];
properties: Model[];
extendedFrom?: string[];
extendedBy?: string[];
}

View File

@ -1,5 +1,6 @@
import { Model } from './Model';
export interface OperationResponse extends Model {
in: 'response' | 'header';
code: number;
}

View File

@ -1,5 +1,5 @@
export interface Schema {
isProperty: boolean;
isDefinition: boolean;
isReadOnly: boolean;
isRequired: boolean;
isNullable: boolean;

View File

@ -2,10 +2,18 @@ import * as OpenAPI from '.';
describe('index', () => {
it('parses v2 without issues', () => {
OpenAPI.generate('./test/mock/v2/spec.json', './test/result/v2/', OpenAPI.HttpClient.FETCH, false, false);
OpenAPI.generate({
input: './test/mock/v2/spec.json',
output: './test/result/v2/',
write: false,
});
});
it('parses v3 without issues', () => {
OpenAPI.generate('./test/mock/v3/spec.json', './test/result/v3/', OpenAPI.HttpClient.FETCH, false, false);
OpenAPI.generate({
input: './test/mock/v3/spec.json',
output: './test/result/v3/',
write: false,
});
});
});

View File

@ -1,9 +1,9 @@
import * as path from 'path';
import * as ts from 'typescript';
import { OpenApiVersion, getOpenApiVersion } from './utils/getOpenApiVersion';
import { getOpenApiSpec } from './utils/getOpenApiSpec';
import { isString } from './utils/isString';
import { parse as parseV2 } from './openApi/v2';
import { parse as parseV3 } from './openApi/v3';
import { postProcessClient } from './utils/postProcessClient';
import { readHandlebarsTemplates } from './utils/readHandlebarsTemplates';
import { writeClient } from './utils/writeClient';
@ -12,6 +12,15 @@ export enum HttpClient {
XHR = 'xhr',
}
export interface Options {
input: string | Record<string, any>;
output: string;
httpClient?: HttpClient;
useOptions?: boolean;
useUnionTypes?: boolean;
write?: boolean;
}
/**
* Generate the OpenAPI client. This method will read the OpenAPI specification and based on the
* given language it will generate the client, including the typed models, validation schemas,
@ -20,52 +29,38 @@ export enum HttpClient {
* @param output The relative location of the output directory.
* @param httpClient The selected httpClient (fetch or XHR).
* @param useOptions Use options or arguments functions.
* @param write Write the files to disk (true or false)
* @param useUnionTypes Use inclusive union types.
* @param write Write the files to disk (true or false).
*/
export function generate(input: string, output: string, httpClient: HttpClient = HttpClient.FETCH, useOptions: boolean = false, write: boolean = true): void {
const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);
export function generate({ input, output, httpClient = HttpClient.FETCH, useOptions = false, useUnionTypes = false, write = true }: Options): void {
try {
// Load the specification, read the OpenAPI version and load the
// handlebar templates for the given language
const openApi = getOpenApiSpec(inputPath);
const openApi = isString(input) ? getOpenApiSpec(input) : input;
const openApiVersion = getOpenApiVersion(openApi);
const templates = readHandlebarsTemplates();
switch (openApiVersion) {
case OpenApiVersion.V2:
const clientV2 = parseV2(openApi);
case OpenApiVersion.V2: {
const client = parseV2(openApi);
const clientFinal = postProcessClient(client, useUnionTypes);
if (write) {
writeClient(clientV2, httpClient, templates, outputPath, useOptions);
writeClient(clientFinal, templates, output, httpClient, useOptions);
}
break;
}
case OpenApiVersion.V3:
const clientV3 = parseV3(openApi);
case OpenApiVersion.V3: {
const client = parseV3(openApi);
const clientFinal = postProcessClient(client, useUnionTypes);
if (write) {
writeClient(clientV3, httpClient, templates, outputPath, useOptions);
writeClient(clientFinal, templates, output, httpClient, useOptions);
}
break;
}
}
} catch (e) {
console.error(e);
process.exit(1);
}
}
export function compile(dir: string): void {
const config = {
compilerOptions: {
target: 'esnext',
module: 'commonjs',
moduleResolution: 'node',
},
include: ['./index.ts'],
};
const configFile = ts.parseConfigFileTextToJson('tsconfig.json', JSON.stringify(config));
const configFileResult = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.resolve(process.cwd(), dir), undefined, 'tsconfig.json');
const compilerHost = ts.createCompilerHost(configFileResult.options);
const compiler = ts.createProgram(configFileResult.fileNames, configFileResult.options, compilerHost);
compiler.emit();
}

View File

@ -11,10 +11,10 @@ import { getServices } from './parser/getServices';
* @param openApi The OpenAPI spec that we have loaded from disk.
*/
export function parse(openApi: OpenApi): Client {
return {
version: getServiceVersion(openApi.info.version),
server: getServer(openApi),
models: getModels(openApi),
services: getServices(openApi),
};
const version = getServiceVersion(openApi.info.version);
const server = getServer(openApi);
const models = getModels(openApi);
const services = getServices(openApi);
return { version, server, models, services };
}

View File

@ -4,6 +4,12 @@ import { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension';
const KEY_ENUM_NAMES = 'x-enum-varnames';
const KEY_ENUM_DESCRIPTIONS = 'x-enum-descriptions';
/**
* Extend the enum with the x-enum properties. This adds the capability
* to use names and descriptions inside the generated enums.
* @param enumerators
* @param definition
*/
export function extendEnum(enumerators: Enum[], definition: WithEnumExtension): Enum[] {
const names = definition[KEY_ENUM_NAMES];
const descriptions = definition[KEY_ENUM_DESCRIPTIONS];

View File

@ -1,5 +1,10 @@
import { EOL } from 'os';
/**
* Cleanup comment and prefix multiline comments with "*",
* so they look a bit nicer when used in the generated code.
* @param comment
*/
export function getComment(comment?: string): string | null {
if (comment) {
return comment.replace(/\r?\n(.*)/g, (_, w) => `${EOL} * ${w.trim()}`);

View File

@ -9,7 +9,7 @@ import { getEnumFromDescription } from './getEnumFromDescription';
import { getModelProperties } from './getModelProperties';
import { getType } from './getType';
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty: boolean = false, name: string = ''): Model {
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, name: string = ''): Model {
const model: Model = {
name: name,
export: 'interface',
@ -18,7 +18,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
template: null,
link: null,
description: getComment(definition.description),
isProperty: isProperty,
isDefinition: isDefinition,
isReadOnly: definition.readOnly === true,
isNullable: false,
isRequired: false,
@ -86,7 +86,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
model.imports.push(...arrayItems.imports);
return model;
} else {
const arrayItems = getModel(openApi, definition.items, true);
const arrayItems = getModel(openApi, definition.items);
model.export = 'array';
model.type = arrayItems.type;
model.base = arrayItems.base;
@ -129,7 +129,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);
model.extends.push(parentRef.type);
model.extends.push(parentRef.base);
model.imports.push(parentRef.base);
}
if (parent.type === 'object' && parent.properties) {

View File

@ -21,7 +21,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema):
template: model.template,
link: null,
description: getComment(property.description),
isProperty: true,
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired === true,
isNullable: false,
@ -55,7 +55,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema):
template: model.template,
link: model.link,
description: getComment(property.description),
isProperty: true,
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired === true,
isNullable: false,

View File

@ -4,14 +4,14 @@ import { getModel } from './getModel';
import { getType } from './getType';
export function getModels(openApi: OpenApi): Model[] {
const models = new Map<string, Model>();
const models: Model[] = [];
for (const definitionName in openApi.definitions) {
if (openApi.definitions.hasOwnProperty(definitionName)) {
const definition = openApi.definitions[definitionName];
const definitionType = getType(definitionName);
const model = getModel(openApi, definition, false, definitionType.base);
models.set(definitionType.base, model);
const model = getModel(openApi, definition, true, definitionType.base);
models.push(model);
}
}
return Array.from(models.values());
return models;
}

View File

@ -22,7 +22,7 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
template: null,
link: null,
description: getComment(parameter.description),
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: parameter.required === true,
isNullable: false,

View File

@ -8,6 +8,7 @@ import { getType } from './getType';
export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse, responseCode: number): OperationResponse {
const operationResponse: OperationResponse = {
in: 'response',
name: '',
code: responseCode,
description: getComment(response.description)!,
@ -16,7 +17,7 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
base: PrimaryType.OBJECT,
template: null,
link: null,
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: false,
isNullable: false,

View File

@ -18,7 +18,8 @@ export function getOperationResponses(openApi: OpenApi, responses: OpenApiRespon
const responseCode = getOperationResponseCode(code);
if (responseCode) {
operationResponses.push(getOperationResponse(openApi, response, responseCode));
const operationResponse = getOperationResponse(openApi, response, responseCode);
operationResponses.push(operationResponse);
}
}
}

View File

@ -21,6 +21,7 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
if (!operationResults.length) {
operationResults.push({
in: 'response',
name: '',
code: 200,
description: '',
@ -29,7 +30,7 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
base: PrimaryType.OBJECT,
template: null,
link: null,
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: false,
isNullable: false,

View File

@ -27,13 +27,11 @@ export function getServices(openApi: OpenApi): Service[] {
// If we have already declared a service, then we should fetch that and
// append the new method to it. Otherwise we should create a new service object.
const service =
services.get(operation.service) ||
({
name: operation.service,
operations: [],
imports: [],
} as Service);
const service: Service = services.get(operation.service) || {
name: operation.service,
operations: [],
imports: [],
};
// Push the operation in the service
service.operations.push(operation);

View File

@ -26,7 +26,7 @@ export function getType(value?: string, template?: string): Type {
if (match1.type === PrimaryType.ARRAY) {
result.type = `${match2.type}[]`;
result.base = `${match2.type}`;
result.base = match2.type;
match1.imports = [];
} else if (match2.type) {
result.type = `${match1.type}<${match2.type}>`;

View File

@ -11,10 +11,10 @@ import { getServices } from './parser/getServices';
* @param openApi The OpenAPI spec that we have loaded from disk.
*/
export function parse(openApi: OpenApi): Client {
return {
version: getServiceVersion(openApi.info.version),
server: getServer(openApi),
models: getModels(openApi),
services: getServices(openApi),
};
const version = getServiceVersion(openApi.info.version);
const server = getServer(openApi);
const models = getModels(openApi);
const services = getServices(openApi);
return { version, server, models, services };
}

View File

@ -4,6 +4,12 @@ import { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension';
const KEY_ENUM_NAMES = 'x-enum-varnames';
const KEY_ENUM_DESCRIPTIONS = 'x-enum-descriptions';
/**
* Extend the enum with the x-enum properties. This adds the capability
* to use names and descriptions inside the generated enums.
* @param enumerators
* @param definition
*/
export function extendEnum(enumerators: Enum[], definition: WithEnumExtension): Enum[] {
const names = definition[KEY_ENUM_NAMES];
const descriptions = definition[KEY_ENUM_DESCRIPTIONS];

View File

@ -1,5 +1,10 @@
import { EOL } from 'os';
/**
* Cleanup comment and prefix multiline comments with "*",
* so they look a bit nicer when used in the generated code.
* @param comment
*/
export function getComment(comment?: string): string | null {
if (comment) {
return comment.replace(/\r?\n(.*)/g, (_, w) => `${EOL} * ${w.trim()}`);

View File

@ -10,7 +10,7 @@ import { getModelDefault } from './getModelDefault';
import { getModelProperties } from './getModelProperties';
import { getType } from './getType';
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty: boolean = false, name: string = ''): Model {
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, name: string = ''): Model {
const model: Model = {
name: name,
export: 'interface',
@ -19,7 +19,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
template: null,
link: null,
description: getComment(definition.description),
isProperty: isProperty,
isDefinition: isDefinition,
isReadOnly: definition.readOnly === true,
isNullable: definition.nullable === true,
isRequired: false,
@ -77,7 +77,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
model.default = getModelDefault(definition, model);
return model;
} else {
const arrayItems = getModel(openApi, definition.items, true);
const arrayItems = getModel(openApi, definition.items);
model.export = 'array';
model.type = arrayItems.type;
model.base = arrayItems.base;
@ -117,7 +117,10 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
if (definition.anyOf && definition.anyOf.length) {
model.export = 'generic';
const compositionTypes = definition.anyOf.filter(type => type.$ref).map(type => getType(type.$ref));
const composition = compositionTypes.map(type => type.type).join(' | ');
const composition = compositionTypes
.map(type => type.type)
.sort()
.join(' | ');
model.imports.push(...compositionTypes.map(type => type.base));
model.type = composition;
model.base = composition;
@ -127,7 +130,10 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
if (definition.oneOf && definition.oneOf.length) {
model.export = 'generic';
const compositionTypes = definition.oneOf.filter(type => type.$ref).map(type => getType(type.$ref));
const composition = compositionTypes.map(type => type.type).join(' | ');
const composition = compositionTypes
.map(type => type.type)
.sort()
.join(' | ');
model.imports.push(...compositionTypes.map(type => type.base));
model.type = composition;
model.base = composition;
@ -144,7 +150,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);
model.extends.push(parentRef.type);
model.extends.push(parentRef.base);
model.imports.push(parentRef.base);
}
if (parent.type === 'object' && parent.properties) {

View File

@ -21,7 +21,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema):
template: model.template,
link: null,
description: getComment(property.description),
isProperty: true,
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired === true,
isNullable: property.nullable === true,
@ -55,7 +55,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema):
template: model.template,
link: model.link,
description: getComment(property.description),
isProperty: true,
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired === true,
isNullable: property.nullable === true,

View File

@ -4,16 +4,16 @@ import { getModel } from './getModel';
import { getType } from './getType';
export function getModels(openApi: OpenApi): Model[] {
const models = new Map<string, Model>();
const models: Model[] = [];
if (openApi.components) {
for (const definitionName in openApi.components.schemas) {
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
const definition = openApi.components.schemas[definitionName];
const definitionType = getType(definitionName);
const model = getModel(openApi, definition, false, definitionType.base);
models.set(definitionType.base, model);
const model = getModel(openApi, definition, true, definitionType.base);
models.push(model);
}
}
}
return Array.from(models.values());
return models;
}

View File

@ -19,7 +19,7 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
template: null,
link: null,
description: getComment(parameter.description),
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: parameter.required === true,
isNullable: parameter.nullable === true,

View File

@ -19,7 +19,7 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
link: null,
description: getComment(parameter.description),
default: undefined,
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: parameter.required === true,
isNullable: parameter.nullable === true,

View File

@ -9,6 +9,7 @@ import { getType } from './getType';
export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse, responseCode: number): OperationResponse {
const operationResponse: OperationResponse = {
in: 'response',
name: '',
code: responseCode,
description: getComment(response.description)!,
@ -17,7 +18,7 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
base: PrimaryType.OBJECT,
template: null,
link: null,
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: false,
isNullable: false,

View File

@ -21,6 +21,7 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
if (!operationResults.length) {
operationResults.push({
in: 'response',
name: '',
code: 200,
description: '',
@ -29,7 +30,7 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
base: PrimaryType.OBJECT,
template: null,
link: null,
isProperty: false,
isDefinition: false,
isReadOnly: false,
isRequired: false,
isNullable: false,

View File

@ -3,9 +3,16 @@
/* eslint-disable */
/* prettier-ignore */
export namespace OpenAPI {
export let BASE = '{{{server}}}';
export let VERSION = '{{{version}}}';
export let CLIENT = '{{{httpClient}}}';
export let TOKEN = '';
interface Config {
BASE: string;
VERSION: string;
CLIENT: 'fetch' | 'xhr';
TOKEN: string;
}
export const OpenAPI: Config = {
BASE: '{{{server}}}',
VERSION: '{{{version}}}',
CLIENT: '{{{httpClient}}}',
TOKEN: '',
};

View File

@ -1,25 +0,0 @@
import { Model } from '../client/interfaces/Model';
export function exportModel(model: Model): Model {
return {
...model,
imports: model.imports
.filter(name => {
return model.name !== name;
})
.filter((name, index, arr) => {
return arr.indexOf(name) === index;
})
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB);
}),
enums: model.enums.filter((property, index, arr) => {
return arr.findIndex(item => item.name === property.name) === index;
}),
enum: model.enum.filter((enumerator, index, arr) => {
return arr.findIndex(item => item.name === enumerator.name) === index;
}),
};
}

View File

@ -1,29 +0,0 @@
import { Service } from '../client/interfaces/Service';
export function exportService(service: Service): Service {
const names = new Map<string, number>();
return {
...service,
imports: service.imports
.filter(name => {
return service.name !== name;
})
.filter((name, index, arr) => {
return arr.indexOf(name) === index;
})
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB);
}),
operations: service.operations.map(operation => {
const name = operation.name;
const index = names.get(name) || 0;
if (index > 0) {
operation.name = `${name}${index}`;
}
names.set(name, index + 1);
return operation;
}),
};
}

View File

@ -0,0 +1,23 @@
import { Client } from '../client/interfaces/Client';
import { Model } from '../client/interfaces/Model';
import { unique } from './unique';
/**
* Get the full list of models that are extended by the given model.
* This list is used when we have the flag "useUnionTypes" enabled.
* @param model
* @param client
*/
export function getExtendedByList(model: Model, client: Client): Model[] {
const extendedBy = client.models.filter(ref => {
const names = model.isDefinition ? [model.name] : model.base.split(' | ');
return names.find(name => {
return ref.extends.includes(name);
});
});
if (extendedBy.length) {
extendedBy.push(...extendedBy.flatMap(ref => getExtendedByList(ref, client)));
}
return extendedBy.filter(unique);
}

View File

@ -0,0 +1,23 @@
import { Client } from '../client/interfaces/Client';
import { Model } from '../client/interfaces/Model';
import { unique } from './unique';
/**
* Get the full list of models that are extended from the given model.
* This list is used when we have the flag "useUnionTypes" enabled.
* @param model
* @param client
*/
export function getExtendedFromList(model: Model, client: Client): Model[] {
const extendedFrom = client.models.filter(ref => {
const names = ref.isDefinition ? [ref.name] : ref.base.split(' | ');
return names.find(name => {
return model.extends.includes(name);
});
});
if (extendedFrom.length) {
extendedFrom.push(...extendedFrom.flatMap(ref => getExtendedFromList(ref, client)));
}
return extendedFrom.filter(unique);
}

View File

@ -12,7 +12,7 @@ describe('getModelNames', () => {
template: null,
link: null,
description: null,
isProperty: false,
isDefinition: true,
isReadOnly: false,
isRequired: false,
isNullable: false,
@ -30,7 +30,7 @@ describe('getModelNames', () => {
template: null,
link: null,
description: null,
isProperty: false,
isDefinition: true,
isReadOnly: false,
isRequired: false,
isNullable: false,
@ -48,7 +48,7 @@ describe('getModelNames', () => {
template: null,
link: null,
description: null,
isProperty: false,
isDefinition: true,
isReadOnly: false,
isRequired: false,
isNullable: false,

View File

@ -1,11 +1,6 @@
import { Model } from '../client/interfaces/Model';
import { sort } from './sort';
export function getModelNames(models: Model[]): string[] {
return models
.map(model => model.name)
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB, 'en');
});
return models.map(model => model.name).sort(sort);
}

View File

@ -21,25 +21,26 @@ function read(filePath: string): string {
* Load and parse te open api spec. If the file extension is ".yml" or ".yaml"
* we will try to parse the file as a YAML spec, otherwise we will fallback
* on parsing the file as JSON.
* @param filePath
* @param input
*/
export function getOpenApiSpec(filePath: string): any {
const content = read(filePath);
const extname = path.extname(filePath).toLowerCase();
export function getOpenApiSpec(input: string): any {
const file = path.resolve(process.cwd(), input);
const extname = path.extname(file).toLowerCase();
const content = read(file);
switch (extname) {
case '.yml':
case '.yaml':
try {
return yaml.safeLoad(content);
} catch (e) {
throw new Error(`Could not parse OpenApi YAML: "${filePath}"`);
throw new Error(`Could not parse OpenApi YAML: "${file}"`);
}
default:
try {
return JSON.parse(content);
} catch (e) {
throw new Error(`Could not parse OpenApi JSON: "${filePath}"`);
throw new Error(`Could not parse OpenApi JSON: "${file}"`);
}
}
}

View File

@ -1,11 +1,6 @@
import { Service } from '../client/interfaces/Service';
import { sort } from './sort';
export function getServiceNames(services: Service[]): string[] {
return services
.map(service => service.name)
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB, 'en');
});
return services.map(service => service.name).sort(sort);
}

3
src/utils/isString.ts Normal file
View File

@ -0,0 +1,3 @@
export function isString(val: any): val is string {
return typeof val === 'string';
}

View File

@ -0,0 +1,16 @@
import { Client } from '../client/interfaces/Client';
import { postProcessModel } from './postProcessModel';
import { postProcessService } from './postProcessService';
/**
* Post process client
* @param client Client object with all the models, services, etc.
* @param useUnionTypes Use inclusive union types.
*/
export function postProcessClient(client: Client, useUnionTypes: boolean): Client {
return {
...client,
models: client.models.map(model => postProcessModel(model, client, useUnionTypes)),
services: client.services.map(service => postProcessService(service, client, useUnionTypes)),
};
}

View File

@ -0,0 +1,24 @@
import { Client } from '../client/interfaces/Client';
import { Model } from '../client/interfaces/Model';
import { postProcessModelEnum } from './postProcessModelEnum';
import { postProcessModelEnums } from './postProcessModelEnums';
import { postProcessModelImports } from './postProcessModelImports';
import { postProcessUnionTypes } from './postProcessUnionTypes';
/**
* Post process the model. If needed this will convert types to union types,
* see the "useUnionTypes" flag in the documentation. Plus this will cleanup
* any double imports or enum values.
* @param model
* @param client
* @param useUnionTypes
*/
export function postProcessModel(model: Model, client: Client, useUnionTypes: boolean): Model {
const clone = postProcessUnionTypes(model, client, useUnionTypes);
return {
...clone,
imports: postProcessModelImports(clone),
enums: postProcessModelEnums(clone),
enum: postProcessModelEnum(clone),
};
}

View File

@ -0,0 +1,12 @@
import { Enum } from '../client/interfaces/Enum';
import { Model } from '../client/interfaces/Model';
/**
* Set unique enum values for the model
* @param model
*/
export function postProcessModelEnum(model: Model): Enum[] {
return model.enum.filter((property, index, arr) => {
return arr.findIndex(item => item.name === property.name) === index;
});
}

View File

@ -0,0 +1,11 @@
import { Model } from '../client/interfaces/Model';
/**
* Set unique enum values for the model
* @param model The model that is post-processed
*/
export function postProcessModelEnums(model: Model): Model[] {
return model.enums.filter((property, index, arr) => {
return arr.findIndex(item => item.name === property.name) === index;
});
}

View File

@ -0,0 +1,14 @@
import { Model } from '../client/interfaces/Model';
import { sort } from './sort';
import { unique } from './unique';
/**
* Set unique imports, sorted by name
* @param model The model that is post-processed
*/
export function postProcessModelImports(model: Model): string[] {
return model.imports
.filter(unique)
.sort(sort)
.filter(name => model.name !== name);
}

View File

@ -0,0 +1,14 @@
import { Client } from '../client/interfaces/Client';
import { Service } from '../client/interfaces/Service';
import { postProcessServiceImports } from './postProcessServiceImports';
import { postProcessServiceOperations } from './postProcessServiceOperations';
export function postProcessService(service: Service, client: Client, useUnionTypes: boolean): Service {
const clone = { ...service };
clone.operations = postProcessServiceOperations(clone, client, useUnionTypes);
clone.operations.forEach(operation => {
clone.imports.push(...operation.imports);
});
clone.imports = postProcessServiceImports(clone);
return clone;
}

View File

@ -0,0 +1,14 @@
import { Service } from '../client/interfaces/Service';
import { sort } from './sort';
import { unique } from './unique';
/**
* Set unique imports, sorted by name
* @param service
*/
export function postProcessServiceImports(service: Service): string[] {
return service.imports
.filter(unique)
.sort(sort)
.filter(name => service.name !== name);
}

View File

@ -0,0 +1,30 @@
import { Client } from '../client/interfaces/Client';
import { Operation } from '../client/interfaces/Operation';
import { Service } from '../client/interfaces/Service';
import { postProcessUnionTypes } from './postProcessUnionTypes';
export function postProcessServiceOperations(service: Service, client: Client, useUnionTypes: boolean = false): Operation[] {
const names = new Map<string, number>();
return service.operations.map(operation => {
const clone = { ...operation };
// Parse the service parameters and results, very similar to how we parse
// properties of models. These methods will extend the type if needed.
clone.parameters = clone.parameters.map(parameter => postProcessUnionTypes(parameter, client, useUnionTypes));
clone.results = clone.results.map(result => postProcessUnionTypes(result, client, useUnionTypes));
clone.imports.push(...clone.parameters.flatMap(parameter => parameter.imports));
clone.imports.push(...clone.results.flatMap(result => result.imports));
// Check of the operation name
let name = clone.name;
const index = names.get(name) || 0;
if (index > 0) {
clone.name = `${name}${index}`;
name = `${name}${index}`;
}
names.set(name, index + 1);
return clone;
});
}

View File

@ -0,0 +1,41 @@
import { Client } from '../client/interfaces/Client';
import { Model } from '../client/interfaces/Model';
import { getExtendedByList } from './getExtendedByList';
/**
* This post processor will convert types to union types. For more information
* please check the documentation. In a nutshell: By setting the "useUnionTypes"
* flag we will convert base types to a union of types that are extended from
* the base type.
* @param model
* @param client
* @param useUnionTypes
*/
export function postProcessUnionTypes<T extends Model>(model: T, client: Client, useUnionTypes: boolean): T {
const clone = { ...model };
if (useUnionTypes) {
// If this is not a root definition, then new need to check the base type
if (!clone.isDefinition) {
const extendedBy = getExtendedByList(clone, client);
const extendedByNames = extendedBy.map(m => m.name);
clone.base = [clone.base, ...extendedByNames].sort().join(' | ');
clone.imports = clone.imports.concat(...extendedByNames);
}
// In any case we need to check the properties of a model.
// When the types get extended, we also need to make sure we update the imports.
clone.properties = clone.properties.map(property => postProcessUnionTypes(property, client, useUnionTypes));
clone.properties.forEach(property => {
clone.imports.push(...property.imports);
});
// When the model has a link (in case of an Array or Dictionary),
// then we also process this linked model and again update the imports.
clone.link = clone.link ? postProcessUnionTypes(clone.link, client, useUnionTypes) : null;
if (clone.link) {
clone.imports.push(...clone.link.imports);
}
}
return clone;
}

View File

@ -12,6 +12,7 @@ export function readHandlebarsTemplate(filePath: string): Handlebars.TemplateDel
.readFileSync(filePath, 'utf8')
.toString()
.trim();
return Handlebars.compile(template, {
strict: true,
noEscape: true,

View File

@ -31,7 +31,9 @@ export function readHandlebarsTemplates(): Templates {
const partials = path.resolve(__dirname, `../../src/templates/partials`);
const partialsFiles = glob.sync('*.hbs', { cwd: partials });
partialsFiles.forEach(partial => {
Handlebars.registerPartial(path.basename(partial, '.hbs'), readHandlebarsTemplate(path.resolve(partials, partial)));
const templateName = path.basename(partial, '.hbs');
const template = readHandlebarsTemplate(path.resolve(partials, partial));
Handlebars.registerPartial(templateName, template);
});
return templates;

5
src/utils/sort.ts Normal file
View File

@ -0,0 +1,5 @@
export function sort(a: string, b: string): number {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB, 'en');
}

3
src/utils/unique.ts Normal file
View File

@ -0,0 +1,3 @@
export function unique<T>(val: T, index: number, arr: T[]): boolean {
return arr.indexOf(val) === index;
}

View File

@ -36,7 +36,7 @@ describe('writeClient', () => {
globSync.mockReturnValue([]);
writeClient(client, HttpClient.FETCH, templates, '/');
writeClient(client, templates, '/', HttpClient.FETCH, false);
expect(rimrafSync).toBeCalled();
expect(mkdirpSync).toBeCalled();

View File

@ -15,12 +15,13 @@ import { writeClientSettings } from './writeClientSettings';
/**
* Write our OpenAPI client, using the given templates at the given output path.
* @param client Client object with all the models, services, etc.
* @param httpClient The selected httpClient (fetch or XHR).
* @param templates Templates wrapper with all loaded Handlebars templates.
* @param outputPath Directory to write the generated files to.
* @param output Directory to write the generated files to.
* @param httpClient The selected httpClient (fetch or XHR).
* @param useOptions Use options or arguments functions.
*/
export function writeClient(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string, useOptions: boolean): void {
export function writeClient(client: Client, templates: Templates, output: string, httpClient: HttpClient, useOptions: boolean): void {
const outputPath = path.resolve(process.cwd(), output);
const outputPathCore = path.resolve(outputPath, 'core');
const outputPathModels = path.resolve(outputPath, 'models');
const outputPathSchemas = path.resolve(outputPath, 'schemas');
@ -30,7 +31,7 @@ export function writeClient(client: Client, httpClient: HttpClient, templates: T
try {
rimraf.sync(outputPath);
} catch (e) {
throw new Error(`Could not clean output directory`);
throw new Error('Could not clean output directory');
}
// Create new directories
@ -41,11 +42,11 @@ export function writeClient(client: Client, httpClient: HttpClient, templates: T
mkdirp.sync(outputPathSchemas);
mkdirp.sync(outputPathServices);
} catch (e) {
throw new Error(`Could not create output directories`);
throw new Error('Could not create output directories');
}
// Copy all support files
const supportFiles = path.resolve(__dirname, `../../src/templates/`);
const supportFiles = path.resolve(__dirname, '../../src/templates/');
const supportFilesList = glob.sync('**/*.ts', { cwd: supportFiles });
supportFilesList.forEach(file => {
fs.copyFileSync(
@ -58,6 +59,6 @@ export function writeClient(client: Client, httpClient: HttpClient, templates: T
writeClientModels(client.models, templates, outputPathModels);
writeClientSchemas(client.models, templates, outputPathSchemas);
writeClientServices(client.services, templates, outputPathServices, useOptions);
writeClientSettings(client, httpClient, templates, outputPathCore);
writeClientSettings(client, templates, outputPathCore, httpClient);
writeClientIndex(client, templates, outputPath);
}

View File

@ -9,25 +9,26 @@ const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeF
describe('writeClientModels', () => {
it('should write to filesystem', () => {
const models: Model[] = [];
models.push({
export: 'interface',
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
link: null,
description: null,
isProperty: false,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
extends: [],
enum: [],
enums: [],
properties: [],
});
const models: Model[] = [
{
export: 'interface',
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
link: null,
description: null,
isDefinition: true,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
extends: [],
enum: [],
enums: [],
properties: [],
},
];
const templates: Templates = {
index: () => 'dummy',

View File

@ -2,7 +2,6 @@ import * as fs from 'fs';
import * as path from 'path';
import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { exportModel } from './exportModel';
import { format } from './format';
/**
@ -14,8 +13,7 @@ import { format } from './format';
export function writeClientModels(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {
const file = path.resolve(outputPath, `${model.name}.ts`);
const templateData = exportModel(model);
const templateResult = templates.model(templateData);
const templateResult = templates.model(model);
fs.writeFileSync(file, format(templateResult));
});
}

View File

@ -9,25 +9,26 @@ const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeF
describe('writeClientModels', () => {
it('should write to filesystem', () => {
const models: Model[] = [];
models.push({
export: 'interface',
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
link: null,
description: null,
isProperty: false,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
extends: [],
enum: [],
enums: [],
properties: [],
});
const models: Model[] = [
{
export: 'interface',
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
link: null,
description: null,
isDefinition: true,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
extends: [],
enum: [],
enums: [],
properties: [],
},
];
const templates: Templates = {
index: () => 'dummy',

View File

@ -2,7 +2,6 @@ import * as fs from 'fs';
import * as path from 'path';
import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { exportModel } from './exportModel';
import { format } from './format';
/**
@ -14,8 +13,7 @@ import { format } from './format';
export function writeClientSchemas(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {
const file = path.resolve(outputPath, `$${model.name}.ts`);
const templateData = exportModel(model);
const templateResult = templates.schema(templateData);
const templateResult = templates.schema(model);
fs.writeFileSync(file, format(templateResult));
});
}

View File

@ -9,12 +9,13 @@ const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeF
describe('writeClientServices', () => {
it('should write to filesystem', () => {
const services: Service[] = [];
services.push({
name: 'Item',
operations: [],
imports: [],
});
const services: Service[] = [
{
name: 'Item',
operations: [],
imports: [],
},
];
const templates: Templates = {
index: () => 'dummy',
@ -24,7 +25,7 @@ describe('writeClientServices', () => {
settings: () => 'dummy',
};
writeClientServices(services, templates, '/');
writeClientServices(services, templates, '/', false);
expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy');
});

View File

@ -2,7 +2,6 @@ import * as fs from 'fs';
import * as path from 'path';
import { Service } from '../client/interfaces/Service';
import { Templates } from './readHandlebarsTemplates';
import { exportService } from './exportService';
import { format } from './format';
/**
@ -15,9 +14,8 @@ import { format } from './format';
export function writeClientServices(services: Service[], templates: Templates, outputPath: string, useOptions: boolean): void {
services.forEach(service => {
const file = path.resolve(outputPath, `${service.name}.ts`);
const templateData = exportService(service);
const templateResult = templates.service({
...templateData,
...service,
useOptions,
});
fs.writeFileSync(file, format(templateResult));

View File

@ -4,7 +4,14 @@ import { Client } from '../client/interfaces/Client';
import { HttpClient } from '../index';
import { Templates } from './readHandlebarsTemplates';
export function writeClientSettings(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string): void {
/**
* Generate OpenAPI configuration file "OpenAPI.ts"
* @param client Client object, containing, models, schemas and services.
* @param templates The loaded handlebar templates.
* @param outputPath Directory to write the generated files to.
* @param httpClient The selected httpClient (fetch or XHR).
*/
export function writeClientSettings(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient): void {
fs.writeFileSync(
path.resolve(outputPath, 'OpenAPI.ts'),
templates.settings({

View File

@ -67,12 +67,19 @@ exports[`generation v2 file(./test/result/v2/core/OpenAPI.ts): ./test/result/v2/
/* eslint-disable */
/* prettier-ignore */
export namespace OpenAPI {
export let BASE = 'http://localhost:8080/api';
export let VERSION = '9.0';
export let CLIENT = 'fetch';
export let TOKEN = '';
}"
interface Config {
BASE: string;
VERSION: string;
CLIENT: 'fetch' | 'xhr';
TOKEN: string;
}
export const OpenAPI: Config = {
BASE: 'http://localhost:8080/api',
VERSION: '9.0',
CLIENT: 'fetch',
TOKEN: '',
};"
`;
exports[`generation v2 file(./test/result/v2/core/RequestOptions.ts): ./test/result/v2/core/RequestOptions.ts 1`] = `
@ -520,12 +527,14 @@ exports[`generation v2 file(./test/result/v2/models/ArrayWithArray.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a simple array containing an array
*/
export type ArrayWithArray = Array<Array<ModelWithString>>;"
export type ArrayWithArray = Array<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>>;"
`;
exports[`generation v2 file(./test/result/v2/models/ArrayWithBooleans.ts): ./test/result/v2/models/ArrayWithBooleans.ts 1`] = `
@ -616,12 +625,14 @@ exports[`generation v2 file(./test/result/v2/models/DictionaryWithArray.ts): ./t
/* prettier-ignore */
import { Dictionary } from './Dictionary';
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a complex dictionary
*/
export type DictionaryWithArray = Dictionary<Array<ModelWithString>>;"
export type DictionaryWithArray = Dictionary<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>>;"
`;
exports[`generation v2 file(./test/result/v2/models/DictionaryWithDictionary.ts): ./test/result/v2/models/DictionaryWithDictionary.ts 1`] = `
@ -783,6 +794,7 @@ exports[`generation v2 file(./test/result/v2/models/ModelThatExtends.ts): ./test
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
@ -790,7 +802,7 @@ import { ModelWithString } from './ModelWithString';
*/
export interface ModelThatExtends extends ModelWithString {
propExtendsA?: string;
propExtendsB?: ModelWithString;
propExtendsB?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -809,7 +821,7 @@ import { ModelWithString } from './ModelWithString';
*/
export interface ModelThatExtendsExtends extends ModelWithString, ModelThatExtends {
propExtendsC?: string;
propExtendsD?: ModelWithString;
propExtendsD?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -820,13 +832,15 @@ exports[`generation v2 file(./test/result/v2/models/ModelWithArray.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with one property containing an array
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
prop?: Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>;
propWithFile?: Array<File>;
propWithNumber?: Array<number>;
}
@ -891,15 +905,17 @@ exports[`generation v2 file(./test/result/v2/models/ModelWithDuplicateImports.ts
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with duplicated imports
*/
export interface ModelWithDuplicateImports {
propA?: ModelWithString;
propB?: ModelWithString;
propC?: ModelWithString;
propA?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
propB?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
propC?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -910,13 +926,15 @@ exports[`generation v2 file(./test/result/v2/models/ModelWithDuplicateProperties
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with duplicated properties
*/
export interface ModelWithDuplicateProperties {
prop?: ModelWithString;
prop?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -1088,6 +1106,8 @@ exports[`generation v2 file(./test/result/v2/models/ModelWithProperties.ts): ./t
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
@ -1099,7 +1119,7 @@ export interface ModelWithProperties {
string?: string;
number?: number;
boolean?: boolean;
reference?: ModelWithString;
reference?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -1417,7 +1437,7 @@ export const $ModelThatExtends = {
type: 'string',
},
propExtendsB: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -1440,7 +1460,7 @@ export const $ModelThatExtendsExtends = {
type: 'string',
},
propExtendsD: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -1521,13 +1541,13 @@ exports[`generation v2 file(./test/result/v2/schemas/$ModelWithDuplicateImports.
export const $ModelWithDuplicateImports = {
properties: {
propA: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
propB: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
propC: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -1542,7 +1562,7 @@ exports[`generation v2 file(./test/result/v2/schemas/$ModelWithDuplicateProperti
export const $ModelWithDuplicateProperties = {
properties: {
prop: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -1709,7 +1729,7 @@ export const $ModelWithProperties = {
type: 'boolean',
},
reference: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -1817,6 +1837,8 @@ exports[`generation v2 file(./test/result/v2/services/ComplexService.ts): ./test
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from '../models/ModelThatExtends';
import { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends';
import { ModelWithString } from '../models/ModelWithString';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
@ -1838,8 +1860,8 @@ export class ComplexService {
},
},
},
parameterReference: ModelWithString,
): Promise<Array<ModelWithString>> {
parameterReference: ModelThatExtends | ModelThatExtendsExtends | ModelWithString,
): Promise<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>> {
const result = await __request({
method: 'get',
@ -1871,6 +1893,8 @@ exports[`generation v2 file(./test/result/v2/services/DefaultsService.ts): ./tes
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from '../models/ModelThatExtends';
import { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends';
import { ModelWithString } from '../models/ModelWithString';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
@ -1891,7 +1915,7 @@ export class DefaultsService {
parameterNumber: number = 123,
parameterBoolean: boolean = true,
parameterEnum: ('Success' | 'Warning' | 'Error') = 'Success',
parameterModel: ModelWithString = {
parameterModel: ModelThatExtends | ModelThatExtendsExtends | ModelWithString = {
\\"prop\\": \\"Hello World\\"
},
): Promise<void> {
@ -2024,7 +2048,7 @@ export class ResponseService {
* @result ModelWithString Message for default response
* @throws ApiError
*/
public static async callWithResponse(): Promise<ModelWithString> {
public static async callWithResponse(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> {
const result = await __request({
method: 'get',
@ -2040,7 +2064,7 @@ export class ResponseService {
* @result ModelWithString Message for default response
* @throws ApiError
*/
public static async callWithDuplicateResponses(): Promise<ModelWithString> {
public static async callWithDuplicateResponses(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> {
const result = await __request({
method: 'post',
@ -2066,7 +2090,7 @@ export class ResponseService {
* @result ModelThatExtendsExtends Message for 202 response
* @throws ApiError
*/
public static async callWithResponses(): Promise<ModelWithString | ModelThatExtends | ModelThatExtendsExtends> {
public static async callWithResponses(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString | ModelThatExtends | ModelThatExtendsExtends | ModelThatExtendsExtends> {
const result = await __request({
method: 'put',
@ -2337,12 +2361,19 @@ exports[`generation v3 file(./test/result/v3/core/OpenAPI.ts): ./test/result/v3/
/* eslint-disable */
/* prettier-ignore */
export namespace OpenAPI {
export let BASE = '/api';
export let VERSION = '1';
export let CLIENT = 'fetch';
export let TOKEN = '';
}"
interface Config {
BASE: string;
VERSION: string;
CLIENT: 'fetch' | 'xhr';
TOKEN: string;
}
export const OpenAPI: Config = {
BASE: '/api',
VERSION: '1',
CLIENT: 'fetch',
TOKEN: '',
};"
`;
exports[`generation v3 file(./test/result/v3/core/RequestOptions.ts): ./test/result/v3/core/RequestOptions.ts 1`] = `
@ -2796,12 +2827,14 @@ exports[`generation v3 file(./test/result/v3/models/ArrayWithArray.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a simple array containing an array
*/
export type ArrayWithArray = Array<Array<ModelWithString>>;"
export type ArrayWithArray = Array<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>>;"
`;
exports[`generation v3 file(./test/result/v3/models/ArrayWithBooleans.ts): ./test/result/v3/models/ArrayWithBooleans.ts 1`] = `
@ -2892,12 +2925,14 @@ exports[`generation v3 file(./test/result/v3/models/DictionaryWithArray.ts): ./t
/* prettier-ignore */
import { Dictionary } from './Dictionary';
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a complex dictionary
*/
export type DictionaryWithArray = Dictionary<Array<ModelWithString>>;"
export type DictionaryWithArray = Dictionary<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>>;"
`;
exports[`generation v3 file(./test/result/v3/models/DictionaryWithDictionary.ts): ./test/result/v3/models/DictionaryWithDictionary.ts 1`] = `
@ -3059,6 +3094,7 @@ exports[`generation v3 file(./test/result/v3/models/ModelThatExtends.ts): ./test
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
@ -3066,7 +3102,7 @@ import { ModelWithString } from './ModelWithString';
*/
export interface ModelThatExtends extends ModelWithString {
propExtendsA?: string;
propExtendsB?: ModelWithString;
propExtendsB?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -3085,7 +3121,7 @@ import { ModelWithString } from './ModelWithString';
*/
export interface ModelThatExtendsExtends extends ModelWithString, ModelThatExtends {
propExtendsC?: string;
propExtendsD?: ModelWithString;
propExtendsD?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -3096,6 +3132,8 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithAnyOf.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithArray } from './ModelWithArray';
import { ModelWithDictionary } from './ModelWithDictionary';
import { ModelWithEnum } from './ModelWithEnum';
@ -3105,7 +3143,7 @@ import { ModelWithString } from './ModelWithString';
* This is a model with one property with a 'any of' relationship
*/
export interface ModelWithAnyOf {
propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary;
propA?: ModelThatExtends | ModelThatExtendsExtends | ModelWithArray | ModelWithDictionary | ModelWithEnum | ModelWithString;
}
"
`;
@ -3116,13 +3154,15 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithArray.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with one property containing an array
*/
export interface ModelWithArray {
prop?: Array<ModelWithString>;
prop?: Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>;
propWithFile?: Array<File>;
propWithNumber?: Array<number>;
}
@ -3187,15 +3227,17 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithDuplicateImports.ts
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with duplicated imports
*/
export interface ModelWithDuplicateImports {
propA?: ModelWithString;
propB?: ModelWithString;
propC?: ModelWithString;
propA?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
propB?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
propC?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -3206,13 +3248,15 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithDuplicateProperties
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
* This is a model with duplicated properties
*/
export interface ModelWithDuplicateProperties {
prop?: ModelWithString;
prop?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -3366,6 +3410,8 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithOneOf.ts): ./test/r
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithArray } from './ModelWithArray';
import { ModelWithDictionary } from './ModelWithDictionary';
import { ModelWithEnum } from './ModelWithEnum';
@ -3375,7 +3421,7 @@ import { ModelWithString } from './ModelWithString';
* This is a model with one property with a 'one of' relationship
*/
export interface ModelWithOneOf {
propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary;
propA?: ModelThatExtends | ModelThatExtendsExtends | ModelWithArray | ModelWithDictionary | ModelWithEnum | ModelWithString;
}
"
`;
@ -3404,6 +3450,8 @@ exports[`generation v3 file(./test/result/v3/models/ModelWithProperties.ts): ./t
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from './ModelThatExtends';
import { ModelThatExtendsExtends } from './ModelThatExtendsExtends';
import { ModelWithString } from './ModelWithString';
/**
@ -3416,7 +3464,7 @@ export interface ModelWithProperties {
string?: string;
number?: number;
boolean?: boolean;
reference?: ModelWithString;
reference?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString;
}
"
`;
@ -3734,7 +3782,7 @@ export const $ModelThatExtends = {
type: 'string',
},
propExtendsB: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -3757,7 +3805,7 @@ export const $ModelThatExtendsExtends = {
type: 'string',
},
propExtendsD: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -3772,7 +3820,7 @@ exports[`generation v3 file(./test/result/v3/schemas/$ModelWithAnyOf.ts): ./test
export const $ModelWithAnyOf = {
properties: {
propA: {
type: 'ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithArray | ModelWithDictionary | ModelWithEnum | ModelWithString',
},
},
};"
@ -3853,13 +3901,13 @@ exports[`generation v3 file(./test/result/v3/schemas/$ModelWithDuplicateImports.
export const $ModelWithDuplicateImports = {
properties: {
propA: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
propB: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
propC: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -3874,7 +3922,7 @@ exports[`generation v3 file(./test/result/v3/schemas/$ModelWithDuplicateProperti
export const $ModelWithDuplicateProperties = {
properties: {
prop: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -4005,7 +4053,7 @@ exports[`generation v3 file(./test/result/v3/schemas/$ModelWithOneOf.ts): ./test
export const $ModelWithOneOf = {
properties: {
propA: {
type: 'ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithArray | ModelWithDictionary | ModelWithEnum | ModelWithString',
},
},
};"
@ -4064,7 +4112,7 @@ export const $ModelWithProperties = {
type: 'boolean',
},
reference: {
type: 'ModelWithString',
type: 'ModelThatExtends | ModelThatExtendsExtends | ModelWithString',
},
},
};"
@ -4172,6 +4220,8 @@ exports[`generation v3 file(./test/result/v3/services/ComplexService.ts): ./test
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from '../models/ModelThatExtends';
import { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends';
import { ModelWithArray } from '../models/ModelWithArray';
import { ModelWithDictionary } from '../models/ModelWithDictionary';
import { ModelWithEnum } from '../models/ModelWithEnum';
@ -4188,10 +4238,7 @@ export class ComplexService {
* @result ModelWithString Successful response
* @throws ApiError
*/
public static async complexTypes({
parameterObject,
parameterReference,
}: {
public static async complexTypes(
parameterObject: {
first?: {
second?: {
@ -4199,8 +4246,8 @@ export class ComplexService {
},
},
},
parameterReference: ModelWithString,
}): Promise<Array<ModelWithString>> {
parameterReference: ModelThatExtends | ModelThatExtendsExtends | ModelWithString,
): Promise<Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString>> {
const result = await __request({
method: 'get',
@ -4229,25 +4276,22 @@ export class ComplexService {
* @result ModelWithString Success
* @throws ApiError
*/
public static async complexParams({
id,
requestBody,
}: {
public static async complexParams(
id: number,
requestBody?: {
readonly key: string | null,
name: string | null,
enabled?: boolean,
readonly type: ('Monkey' | 'Horse' | 'Bird'),
listOfModels?: Array<ModelWithString> | null,
listOfModels?: Array<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> | null,
listOfStrings?: Array<string> | null,
parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary,
parameters: ModelThatExtends | ModelThatExtendsExtends | ModelWithArray | ModelWithDictionary | ModelWithEnum | ModelWithString,
readonly user?: {
readonly id?: number,
readonly name?: string | null,
},
},
}): Promise<ModelWithString> {
): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> {
const result = await __request({
method: 'put',
@ -4269,6 +4313,8 @@ exports[`generation v3 file(./test/result/v3/services/DefaultsService.ts): ./tes
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from '../models/ModelThatExtends';
import { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends';
import { ModelWithString } from '../models/ModelWithString';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
@ -4284,21 +4330,15 @@ export class DefaultsService {
* @param parameterModel This is a simple model
* @throws ApiError
*/
public static async callWithDefaultParameters({
parameterString = 'Hello World!',
parameterNumber = 123,
parameterBoolean = true,
parameterEnum = 'Success',
parameterModel = {
public static async callWithDefaultParameters(
parameterString: string | null = 'Hello World!',
parameterNumber: number | null = 123,
parameterBoolean: boolean | null = true,
parameterEnum: ('Success' | 'Warning' | 'Error') = 'Success',
parameterModel: ModelThatExtends | ModelThatExtendsExtends | ModelWithString | null = {
\\"prop\\": \\"Hello World\\"
},
}: {
parameterString: string | null,
parameterNumber: number | null,
parameterBoolean: boolean | null,
parameterEnum: ('Success' | 'Warning' | 'Error'),
parameterModel: ModelWithString | null,
}): Promise<void> {
): Promise<void> {
const result = await __request({
method: 'get',
@ -4363,6 +4403,8 @@ exports[`generation v3 file(./test/result/v3/services/ParametersService.ts): ./t
/* eslint-disable */
/* prettier-ignore */
import { ModelThatExtends } from '../models/ModelThatExtends';
import { ModelThatExtendsExtends } from '../models/ModelThatExtendsExtends';
import { ModelWithString } from '../models/ModelWithString';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
@ -4378,19 +4420,13 @@ export class ParametersService {
* @param requestBody This is the parameter that goes into the body
* @throws ApiError
*/
public static async callWithParameters({
parameterHeader,
parameterQuery,
parameterForm,
parameterCookie,
requestBody,
}: {
public static async callWithParameters(
parameterHeader: string | null,
parameterQuery: string | null,
parameterForm: string | null,
parameterCookie: string | null,
requestBody: ModelWithString | null,
}): Promise<void> {
requestBody: ModelThatExtends | ModelThatExtendsExtends | ModelWithString | null,
): Promise<void> {
const result = await __request({
method: 'get',
@ -4426,25 +4462,16 @@ export class ParametersService {
* @param parameterPath3 This is the parameter that goes into the path
* @throws ApiError
*/
public static async callWithWeirdParameterNames({
parameterHeader,
parameterQuery,
parameterForm,
parameterCookie,
requestBody,
parameterPath1,
parameterPath2,
parameterPath3,
}: {
public static async callWithWeirdParameterNames(
parameterHeader: string | null,
parameterQuery: string | null,
parameterForm: string | null,
parameterCookie: string | null,
requestBody: ModelWithString | null,
requestBody: ModelThatExtends | ModelThatExtendsExtends | ModelWithString | null,
parameterPath1?: string,
parameterPath2?: string,
parameterPath3?: string,
}): Promise<void> {
): Promise<void> {
const result = await __request({
method: 'get',
@ -4474,13 +4501,10 @@ export class ParametersService {
* @param parameter This is an optional parameter
* @throws ApiError
*/
public static async getCallWithOptionalParam({
requestBody,
parameter,
}: {
requestBody: ModelWithString,
public static async getCallWithOptionalParam(
requestBody: ModelThatExtends | ModelThatExtendsExtends | ModelWithString,
parameter?: string,
}): Promise<void> {
): Promise<void> {
const result = await __request({
method: 'get',
@ -4501,13 +4525,10 @@ export class ParametersService {
* @param requestBody This is an optional parameter
* @throws ApiError
*/
public static async postCallWithOptionalParam({
parameter,
requestBody,
}: {
public static async postCallWithOptionalParam(
parameter: string,
requestBody?: ModelWithString,
}): Promise<void> {
requestBody?: ModelThatExtends | ModelThatExtendsExtends | ModelWithString,
): Promise<void> {
const result = await __request({
method: 'post',
@ -4545,7 +4566,7 @@ export class ResponseService {
* @result ModelWithString
* @throws ApiError
*/
public static async callWithResponse(): Promise<ModelWithString> {
public static async callWithResponse(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> {
const result = await __request({
method: 'get',
@ -4561,7 +4582,7 @@ export class ResponseService {
* @result ModelWithString Message for default response
* @throws ApiError
*/
public static async callWithDuplicateResponses(): Promise<ModelWithString> {
public static async callWithDuplicateResponses(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString> {
const result = await __request({
method: 'post',
@ -4587,7 +4608,7 @@ export class ResponseService {
* @result ModelThatExtendsExtends Message for 202 response
* @throws ApiError
*/
public static async callWithResponses(): Promise<ModelWithString | ModelThatExtends | ModelThatExtendsExtends> {
public static async callWithResponses(): Promise<ModelThatExtends | ModelThatExtendsExtends | ModelWithString | ModelThatExtends | ModelThatExtendsExtends | ModelThatExtendsExtends> {
const result = await __request({
method: 'put',
@ -4757,25 +4778,16 @@ export class TypesService {
* @result any Response is a simple object
* @throws ApiError
*/
public static async types({
parameterNumber = 123,
parameterString = 'default',
parameterBoolean = true,
parameterObject = null,
parameterArray,
parameterDictionary,
parameterEnum,
id,
}: {
parameterNumber: number,
parameterString: string | null,
parameterBoolean: boolean | null,
parameterObject: any,
public static async types(
parameterNumber: number = 123,
parameterString: string | null = 'default',
parameterBoolean: boolean | null = true,
parameterObject: any = null,
parameterArray: Array<string> | null,
parameterDictionary: any,
parameterEnum: ('Success' | 'Warning' | 'Error') | null,
id?: number,
}): Promise<number | string | boolean | any> {
): Promise<number | string | boolean | any> {
const result = await __request({
method: 'get',
@ -4816,11 +4828,9 @@ export class UploadService {
* @result boolean
* @throws ApiError
*/
public static async uploadFile({
file,
}: {
public static async uploadFile(
file: File,
}): Promise<boolean> {
): Promise<boolean> {
const result = await __request({
method: 'post',

View File

@ -1,18 +1,38 @@
const path = require('path');
const ts = require('typescript');
const OpenAPI = require('../dist');
OpenAPI.generate(
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
false,
);
function compile(dir) {
const config = {
compilerOptions: {
target: 'esnext',
module: 'commonjs',
moduleResolution: 'node',
},
include: ['./index.ts'],
};
const configFile = ts.parseConfigFileTextToJson('tsconfig.json', JSON.stringify(config));
const configFileResult = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.resolve(process.cwd(), dir), undefined, 'tsconfig.json');
const compilerHost = ts.createCompilerHost(configFileResult.options);
const compiler = ts.createProgram(configFileResult.fileNames, configFileResult.options, compilerHost);
compiler.emit();
}
OpenAPI.generate(
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
true,
);
OpenAPI.generate({
input: './test/mock/v2/spec.json',
output: './test/result/v2/',
httpClient: OpenAPI.HttpClient.FETCH,
useOptions: false,
useUnionTypes: false,
});
OpenAPI.compile('./test/result/v2/');
OpenAPI.compile('./test/result/v3/');
OpenAPI.generate({
input: './test/mock/v3/spec.json',
output: './test/result/v3/',
httpClient: OpenAPI.HttpClient.FETCH,
useOptions: false,
useUnionTypes: false,
});
compile('./test/result/v2/');
compile('./test/result/v3/');

View File

@ -6,12 +6,12 @@ describe('generation', () => {
describe('v2', () => {
OpenAPI.generate(
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
false,
);
OpenAPI.generate({
input: './test/mock/v2/spec.json',
output: './test/result/v2/',
httpClient: OpenAPI.HttpClient.FETCH,
write: false,
});
test.each(glob
.sync('./test/result/v2/**/*.ts')
@ -24,12 +24,12 @@ describe('generation', () => {
describe('v3', () => {
OpenAPI.generate(
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
true,
);
OpenAPI.generate({
input: './test/mock/v3/spec.json',
output: './test/result/v3/',
httpClient: OpenAPI.HttpClient.FETCH,
write: false,
});
test.each(glob
.sync('./test/result/v3/**/*.ts')

View File

@ -1,62 +0,0 @@
{
"extends": [
"tslint:recommended",
"tslint-config-airbnb"
],
"rules": {
"align": false,
"import-name": false,
"ordered-imports": true,
"object-literal-sort-keys": false,
"no-boolean-literal-compare": false,
"no-increment-decrement": false,
"no-unused-variable": false,
"no-namespace": false,
"no-console": false,
"no-bitwise": false,
"no-var-requires": false,
"radix": false,
"member-access": true,
"max-line-length": [false],
"array-type": [true, "array"],
"arrow-parens": [true, "ban-single-arg-parens"],
"ter-arrow-parens": [true, "as-needed"],
"no-submodule-imports": [true],
"function-name": [true, {
"function-regex": "^[a-zA-Z$][\\w\\d]*$",
"method-regex": "^[a-z$][\\w\\d]*$",
"private-method-regex": "^[a-z$][\\w\\d]*$",
"protected-method-regex": "^[a-z$][\\w\\d]*$",
"static-method-regex": "^[a-z$][\\w\\d]*$"
}],
"interface-name": [
true,
"never-prefix"
],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-pascal-case",
"allow-leading-underscore"
],
"typedef": [
true,
"call-signature",
"parameter",
"property-declaration",
"member-variable-declaration"
],
"naming-convention": [true, {
"type": "enumMember",
"format": "UPPER_CASE"
}],
"ter-indent": [true, 4, {
"SwitchCase": 1
}],
"jsx-no-lambda": false,
"jsx-alignment": false,
"jsx-wrap-multiline": false,
"jsx-no-multiline-js": false
}
}

119
yarn.lock
View File

@ -85,22 +85,23 @@
semver "^5.5.0"
"@babel/helper-create-class-features-plugin@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.3.tgz#5b94be88c255f140fd2c10dd151e7f98f4bff397"
integrity sha512-qmp4pD7zeTxsv0JNecSBsEmG1ei2MqwJq4YQcK3ZWm/0t07QstWfvuV/vm3Qt5xNMFETn2SZqpMx2MQzbtq+KA==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0"
integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==
dependencies:
"@babel/helper-function-name" "^7.8.3"
"@babel/helper-member-expression-to-functions" "^7.8.3"
"@babel/helper-optimise-call-expression" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-split-export-declaration" "^7.8.3"
"@babel/helper-create-regexp-features-plugin@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79"
integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz#7fa040c97fb8aebe1247a5c645330c32d083066b"
integrity sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.8.3"
"@babel/helper-regex" "^7.8.3"
regexpu-core "^4.6.0"
@ -159,15 +160,16 @@
"@babel/types" "^7.8.3"
"@babel/helper-module-transforms@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz#d305e35d02bee720fbc2c3c3623aa0c316c01590"
integrity sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4"
integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==
dependencies:
"@babel/helper-module-imports" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-simple-access" "^7.8.3"
"@babel/helper-split-export-declaration" "^7.8.3"
"@babel/template" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/template" "^7.8.6"
"@babel/types" "^7.8.6"
lodash "^4.17.13"
"@babel/helper-optimise-call-expression@^7.8.3":
@ -200,17 +202,7 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/helper-replace-supers@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc"
integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.8.3"
"@babel/helper-optimise-call-expression" "^7.8.3"
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
"@babel/helper-replace-supers@^7.8.6":
"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==
@ -1153,7 +1145,7 @@ acorn-globals@^4.3.2:
acorn "^6.0.1"
acorn-walk "^6.0.1"
acorn-jsx@^5.1.0:
acorn-jsx@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
@ -1169,9 +1161,9 @@ acorn@^6.0.1:
integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==
acorn@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
version "7.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
agent-base@5:
version "5.1.1"
@ -1196,11 +1188,11 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
uri-js "^4.2.2"
ansi-escapes@^4.2.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
dependencies:
type-fest "^0.8.1"
type-fest "^0.11.0"
ansi-regex@^4.1.0:
version "4.1.0"
@ -1438,13 +1430,13 @@ browser-resolve@^1.11.3:
resolve "1.1.7"
browserslist@^4.8.3, browserslist@^4.8.5:
version "4.8.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.7.tgz#ec8301ff415e6a42c949d0e66b405eb539c532d0"
integrity sha512-gFOnZNYBHrEyUML0xr5NJ6edFaaKbTFX9S9kQHlYfCP0Rit/boRIz4G+Avq6/4haEKJXdGGUnoolx+5MWW2BoA==
version "4.9.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c"
integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==
dependencies:
caniuse-lite "^1.0.30001027"
electron-to-chromium "^1.3.349"
node-releases "^1.1.49"
caniuse-lite "^1.0.30001030"
electron-to-chromium "^1.3.363"
node-releases "^1.1.50"
bser@2.1.1:
version "2.1.1"
@ -1483,10 +1475,10 @@ camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001027:
version "1.0.30001030"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz#78076c4c6d67d3e41d6eb9399853fb27fe6e44ee"
integrity sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==
caniuse-lite@^1.0.30001030:
version "1.0.30001031"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001031.tgz#76f1bdd39e19567b855302f65102d9a8aaad5930"
integrity sha512-DpAP5a1NGRLgYfaNCaXIRyGARi+3tJA2quZXNNA1Du26VyVkqvy2tznNu5ANyN1Y5aX44QDotZSVSUSi2uMGjg==
capture-exit@^2.0.0:
version "2.0.0"
@ -1500,7 +1492,7 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2:
chalk@^2.0.0, chalk@^2.1.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -1811,10 +1803,10 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
electron-to-chromium@^1.3.349:
version "1.3.361"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.361.tgz#a820bf52da171c0024314745462cfe0dc944373e"
integrity sha512-OzSVjWpsRhJyr9PSAXkeloSe6e9viU2ToGt1wXlXFsGcxuI9vlsnalL+V/AM59Z2pEo3wRxIddtOGsT7Y6x/sQ==
electron-to-chromium@^1.3.363:
version "1.3.367"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.367.tgz#48abffcaa6591051b612ae70ddc657763ede2662"
integrity sha512-GCHQreWs4zhKA48FNXCjvpV4kTnKoLu2PSAfKX394g34NPvTs2pPh1+jzWitNwhmOYI8zIqt36ulRVRZUgqlfA==
emoji-regex@^7.0.1:
version "7.0.3"
@ -1961,12 +1953,12 @@ eslint@6.8.0, eslint@^6.2.2:
v8-compile-cache "^2.0.3"
espree@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==
version "6.2.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.0.tgz#349fef01a202bbab047748300deb37fa44da79d7"
integrity sha512-Xs8airJ7RQolnDIbLtRutmfvSsAe0xqMMAantCN/GMoqf81TFbeI1T7Jpd56qYu1uuh32dOG5W/X9uO+ghPXzA==
dependencies:
acorn "^7.1.0"
acorn-jsx "^5.1.0"
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.1.0"
esprima@^4.0.0, esprima@^4.0.1:
@ -2502,22 +2494,22 @@ inherits@2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inquirer@^7.0.0:
version "7.0.4"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==
version "7.0.5"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.5.tgz#fb95b238ba19966c1a1f55db53c3f0ce5c9e4275"
integrity sha512-6Z5cP+LAO0rzNE7xWjWtT84jxKa5ScLEGLgegPXeO3dGeU8lNe5Ii7SlXH6KVtLGlDuaEhsvsFjrjWjw8j5lFg==
dependencies:
ansi-escapes "^4.2.1"
chalk "^2.4.2"
chalk "^3.0.0"
cli-cursor "^3.1.0"
cli-width "^2.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.15"
mute-stream "0.0.8"
run-async "^2.2.0"
run-async "^2.4.0"
rxjs "^6.5.3"
string-width "^4.1.0"
strip-ansi "^5.1.0"
strip-ansi "^6.0.0"
through "^2.3.6"
invariant@^2.2.2, invariant@^2.2.4:
@ -3488,7 +3480,7 @@ node-notifier@^6.0.0:
shellwords "^0.1.1"
which "^1.3.1"
node-releases@^1.1.49:
node-releases@^1.1.50:
version "1.1.50"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.50.tgz#803c40d2c45db172d0410e4efec83aa8c6ad0592"
integrity sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==
@ -4018,7 +4010,7 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
run-async@^2.2.0:
run-async@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8"
integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==
@ -4520,9 +4512,9 @@ tr46@^1.0.1:
punycode "^2.1.0"
tslib@^1.8.1, tslib@^1.9.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.0.tgz#f1f3528301621a53220d58373ae510ff747a66bc"
integrity sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
tsutils@^3.17.1:
version "3.17.1"
@ -4555,6 +4547,11 @@ type-detect@4.0.8:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
type-fest@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
type-fest@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"