From ecb167ff3a0eb528e15faf6ea194054ee2391d76 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Tue, 7 Jun 2022 14:11:18 +0200 Subject: [PATCH 1/2] Changed typescript and generator implementation --- src/generators/AbstractFileGenerator.ts | 4 +- src/generators/AbstractGenerator.ts | 62 ++--- src/generators/AbstractRenderer.ts | 11 +- .../typescript/TypeScriptConstrainer.ts | 4 +- .../typescript/TypeScriptFileGenerator.ts | 4 +- .../typescript/TypeScriptGenerator.ts | 167 ++++++-------- .../typescript/TypeScriptObjectRenderer.ts | 43 ++++ src/generators/typescript/TypeScriptPreset.ts | 20 +- .../typescript/TypeScriptRenderer.ts | 155 +------------ .../typescript/presets/CommonPreset.ts | 217 +++++++----------- .../typescript/presets/DescriptionPreset.ts | 14 +- .../typescript/presets/ExportKeywordPreset.ts | 20 +- .../presets/utils/ExampleFunction.ts | 80 +++---- .../typescript/renderers/ClassRenderer.ts | 82 ++----- .../typescript/renderers/EnumRenderer.ts | 67 +----- .../typescript/renderers/InterfaceRenderer.ts | 16 +- .../typescript/renderers/TypeRenderer.ts | 28 +-- src/models/CommonInputModel.ts | 9 - src/models/InputMetaModel.ts | 9 + src/models/OutputModel.ts | 12 +- src/models/index.ts | 3 +- 21 files changed, 371 insertions(+), 656 deletions(-) create mode 100644 src/generators/typescript/TypeScriptObjectRenderer.ts delete mode 100644 src/models/CommonInputModel.ts create mode 100644 src/models/InputMetaModel.ts diff --git a/src/generators/AbstractFileGenerator.ts b/src/generators/AbstractFileGenerator.ts index 4c22c99336..bcf179abf9 100644 --- a/src/generators/AbstractFileGenerator.ts +++ b/src/generators/AbstractFileGenerator.ts @@ -1,4 +1,4 @@ -import { CommonInputModel, OutputModel } from '../models'; +import { InputMetaModel, OutputModel } from '../models'; export type FileGenerator = (content: string, toFile: string) => Promise; @@ -6,5 +6,5 @@ export type FileGenerator = (content: string, toFile: string) => Promise; * Abstract file generator which each file generator much implement. */ export interface AbstractFileGenerator { - generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options: RenderCompleteModelOptions): Promise; + generateToFiles(input: Record | InputMetaModel, outputDirectory: string, options: RenderCompleteModelOptions): Promise; } diff --git a/src/generators/AbstractGenerator.ts b/src/generators/AbstractGenerator.ts index b74c99b45a..a06d3b3670 100644 --- a/src/generators/AbstractGenerator.ts +++ b/src/generators/AbstractGenerator.ts @@ -1,4 +1,4 @@ -import { CommonInputModel, CommonModel, OutputModel, Preset, Presets, RenderOutput, ProcessorOptions } from '../models'; +import { InputMetaModel, OutputModel, Preset, Presets, RenderOutput, ProcessorOptions, MetaModel, ConstrainedMetaModel } from '../models'; import { InputProcessor } from '../processors'; import { IndentationTypes } from '../helpers'; import { isPresetWithOptions } from '../utils'; @@ -9,35 +9,34 @@ export interface CommonGeneratorOptions

{ }; defaultPreset?: P; presets?: Presets

; - processorOptions?: ProcessorOptions; + processorOptions?: ProcessorOptions } export const defaultGeneratorOptions: CommonGeneratorOptions = { indentation: { type: IndentationTypes.SPACES, size: 2, - }, + } }; /** * Abstract generator which must be implemented by each language */ -export abstract class AbstractGenerator { - protected options: Options; - +export abstract class AbstractGenerator< + Options extends CommonGeneratorOptions = CommonGeneratorOptions, + RenderCompleteModelOptions = any> { constructor( public readonly languageName: string, - defaultOptions?: Options, - passedOptions?: Options, - ) { - this.options = this.mergeOptions(defaultOptions, passedOptions); - } + public readonly options: Options + ) { } - public abstract render(model: CommonModel, inputModel: CommonInputModel): Promise; - public abstract renderCompleteModel(model: CommonModel, inputModel: CommonInputModel, options: RenderCompleteModelOptions): Promise; + public abstract render(model: MetaModel, inputModel: InputMetaModel): Promise; + public abstract renderCompleteModel(model: MetaModel, inputModel: InputMetaModel, options: RenderCompleteModelOptions): Promise; + public abstract constrainToMetaModel(model: MetaModel): ConstrainedMetaModel; + public abstract splitMetaModel(model: MetaModel): MetaModel[]; - public process(input: Record): Promise { - return InputProcessor.processor.process(input, this.options.processorOptions); + public async process(input: Record): Promise { + return await InputProcessor.processor.process(input, this.options.processorOptions); } /** @@ -48,15 +47,16 @@ export abstract class AbstractGenerator | CommonInputModel, options: RenderCompleteModelOptions): Promise { + public async generateCompleteModels(input: Record | InputMetaModel, options: RenderCompleteModelOptions): Promise { const inputModel = await this.processInput(input); const renders = Object.values(inputModel.models).map(async (model) => { - const renderedOutput = await this.renderCompleteModel(model, inputModel, options); + const constrainedModel = this.constrainToMetaModel(model); + const renderedOutput = await this.renderCompleteModel(constrainedModel, inputModel, options); return OutputModel.toOutputModel({ result: renderedOutput.result, modelName: renderedOutput.renderedName, dependencies: renderedOutput.dependencies, - model, + model: constrainedModel, inputModel }); }); @@ -68,15 +68,16 @@ export abstract class AbstractGenerator | CommonInputModel): Promise { + public async generate(input: Record | InputMetaModel): Promise { const inputModel = await this.processInput(input); const renders = Object.values(inputModel.models).map(async (model) => { - const renderedOutput = await this.render(model, inputModel); + const constrainedModel = this.constrainToMetaModel(model); + const renderedOutput = await this.render(constrainedModel, inputModel); return OutputModel.toOutputModel({ result: renderedOutput.result, modelName: renderedOutput.renderedName, dependencies: renderedOutput.dependencies, - model, + model: constrainedModel, inputModel }); }); @@ -84,15 +85,24 @@ export abstract class AbstractGenerator | CommonInputModel): Promise { - if (input instanceof CommonInputModel) { - return Promise.resolve(input); + private async processInput(input: Record | InputMetaModel): Promise { + const rawInputModel = input instanceof InputMetaModel ? input : await this.process(input); + + //Split out the models based on the language specific requirements of which models is rendered separately + const splitOutModels: {[key: string]: MetaModel} = {}; + for (const model of Object.values(rawInputModel.models)) { + const splitModels = this.splitMetaModel(model); + for (const splitModel of splitModels) { + splitOutModels[splitModel.name] = splitModel; + } } - return this.process(input); + rawInputModel.models = splitOutModels; + return rawInputModel; } protected getPresets(presetType: string): Array<[Preset, unknown]> { diff --git a/src/generators/AbstractRenderer.ts b/src/generators/AbstractRenderer.ts index a25963781a..143bc7aa67 100644 --- a/src/generators/AbstractRenderer.ts +++ b/src/generators/AbstractRenderer.ts @@ -1,5 +1,5 @@ import { AbstractGenerator, CommonGeneratorOptions } from './AbstractGenerator'; -import { CommonModel, CommonInputModel, Preset } from '../models'; +import { ConstrainedMetaModel, InputMetaModel, Preset } from '../models'; import { FormatHelpers, IndentationTypes } from '../helpers'; /** @@ -7,14 +7,15 @@ import { FormatHelpers, IndentationTypes } from '../helpers'; */ export abstract class AbstractRenderer< O extends CommonGeneratorOptions = CommonGeneratorOptions, - G extends AbstractGenerator = AbstractGenerator + G extends AbstractGenerator = AbstractGenerator, + RendererModelType extends ConstrainedMetaModel = ConstrainedMetaModel > { constructor( - readonly options: O, + protected readonly options: O, readonly generator: G, protected readonly presets: Array<[Preset, unknown]>, - protected readonly model: CommonModel, - protected readonly inputModel: CommonInputModel, + protected readonly model: RendererModelType, + protected readonly inputModel: InputMetaModel, public dependencies: string[] = [] ) {} diff --git a/src/generators/typescript/TypeScriptConstrainer.ts b/src/generators/typescript/TypeScriptConstrainer.ts index bb473d2fdd..3c841e587f 100644 --- a/src/generators/typescript/TypeScriptConstrainer.ts +++ b/src/generators/typescript/TypeScriptConstrainer.ts @@ -4,9 +4,9 @@ import { TypeMapping } from '../../helpers'; import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; -import { TypeScriptRenderer } from './TypeScriptRenderer'; +import { TypeScriptOptions } from './TypeScriptGenerator'; -export const TypeScriptDefaultTypeMapping: TypeMapping = { +export const TypeScriptDefaultTypeMapping: TypeMapping = { Object ({constrainedModel}): string { return constrainedModel.name; }, diff --git a/src/generators/typescript/TypeScriptFileGenerator.ts b/src/generators/typescript/TypeScriptFileGenerator.ts index c594f3f511..183e73c0b0 100644 --- a/src/generators/typescript/TypeScriptFileGenerator.ts +++ b/src/generators/typescript/TypeScriptFileGenerator.ts @@ -1,5 +1,5 @@ import { TypeScriptGenerator, TypeScriptRenderCompleteModelOptions } from './'; -import { CommonInputModel, OutputModel } from '../../models'; +import { InputMetaModel, OutputModel } from '../../models'; import * as path from 'path'; import { AbstractFileGenerator } from '../AbstractFileGenerator'; import { FileHelpers } from '../../helpers'; @@ -12,7 +12,7 @@ export class TypeScriptFileGenerator extends TypeScriptGenerator implements Abst * @param outputDirectory where you want the models generated to * @param options */ - public async generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options?: TypeScriptRenderCompleteModelOptions): Promise { + public async generateToFiles(input: Record | InputMetaModel, outputDirectory: string, options?: TypeScriptRenderCompleteModelOptions): Promise { let generatedModels = await this.generateCompleteModels(input, options || {}); //Filter anything out that have not been successfully generated generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== ''; }); diff --git a/src/generators/typescript/TypeScriptGenerator.ts b/src/generators/typescript/TypeScriptGenerator.ts index 93d203f8b8..7e30d83133 100644 --- a/src/generators/typescript/TypeScriptGenerator.ts +++ b/src/generators/typescript/TypeScriptGenerator.ts @@ -1,31 +1,28 @@ -import { hasPreset } from '../../helpers/PresetHelpers'; -import { - AbstractGenerator, +import { + AbstractGenerator, CommonGeneratorOptions, defaultGeneratorOptions } from '../AbstractGenerator'; -import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { TypeHelpers, ModelKind, CommonNamingConvention, CommonNamingConventionImplementation, TypeMapping, Constraints } from '../../helpers'; -import { TS_EXPORT_KEYWORD_PRESET } from './presets'; +import { ConstrainedEnumModel, ConstrainedMetaModel, ConstrainedObjectModel, InputMetaModel, MetaModel, RenderOutput } from '../../models'; +import { CommonNamingConvention, CommonNamingConventionImplementation, constrainMetaModel, Constraints, split, TypeMapping } from '../../helpers'; import { TypeScriptPreset, TS_DEFAULT_PRESET } from './TypeScriptPreset'; import { ClassRenderer } from './renderers/ClassRenderer'; import { InterfaceRenderer } from './renderers/InterfaceRenderer'; import { EnumRenderer } from './renderers/EnumRenderer'; import { TypeRenderer } from './renderers/TypeRenderer'; -import { TypeScriptRenderer } from './TypeScriptRenderer'; import { TypeScriptDefaultConstraints, TypeScriptDefaultTypeMapping } from './TypeScriptConstrainer'; export interface TypeScriptOptions extends CommonGeneratorOptions { - renderTypes?: boolean; - modelType?: 'class' | 'interface'; - enumType?: 'enum' | 'union'; - namingConvention?: CommonNamingConvention; - typeMapping: TypeMapping; - constraints: Constraints + renderTypes: boolean; + modelType: 'class' | 'interface'; + enumType: 'enum' | 'union'; + namingConvention: CommonNamingConvention; + typeMapping: TypeMapping; + constraints: Constraints; } + export interface TypeScriptRenderCompleteModelOptions { moduleSystem?: 'ESM' | 'CJS'; - exportType?: 'default' | 'named'; } /** @@ -46,9 +43,29 @@ export class TypeScriptGenerator extends AbstractGenerator = TypeScriptGenerator.defaultOptions, ) { - const mergedOptions = {...TypeScriptGenerator.defaultOptions, ...options}; + const realizedOptions = {...TypeScriptGenerator.defaultOptions, ...options}; + super('TypeScript', realizedOptions); + } + + splitMetaModel(model: MetaModel): MetaModel[] { + //These are the models that we have separate renderers for + const metaModelsToSplit = { + splitEnum: true, + splitObject: true + }; + return split(model, metaModelsToSplit); + } - super('TypeScript', TypeScriptGenerator.defaultOptions, mergedOptions); + constrainToMetaModel(model: MetaModel): ConstrainedMetaModel { + return constrainMetaModel( + this.options.typeMapping, + this.options.constraints, + { + metaModel: model, + options: this.options, + constrainedName: '' //This is just a placeholder, it will be constrained within the function + } + ); } /** @@ -58,116 +75,72 @@ export class TypeScriptGenerator extends AbstractGenerator { - // Shallow copy presets so that we can restore it once we are done - const originalPresets = [...(this.options.presets ? this.options.presets : [])]; - - // Add preset that adds the `export` keyword if it hasn't already been added - if ( - moduleSystem === 'ESM' && - exportType === 'named' && - !hasPreset(originalPresets, TS_EXPORT_KEYWORD_PRESET) - ) { - this.options.presets = [TS_EXPORT_KEYWORD_PRESET, ...originalPresets]; - } - + async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: TypeScriptRenderCompleteModelOptions): Promise { const outputModel = await this.render(model, inputModel); - let modelDependencies = model.getNearestDependencies(); - //Ensure model dependencies have their rendered name - modelDependencies = modelDependencies.map((dependencyModelName) => { - return this.options.namingConvention?.type ? this.options.namingConvention.type(dependencyModelName, { inputModel, model: inputModel.models[String(dependencyModelName)] }) : dependencyModelName; - }); - //Filter out any dependencies that is recursive to itself - modelDependencies = modelDependencies.filter((dependencyModelName) => { - return dependencyModelName !== outputModel.renderedName; - }); - + const modelDependencies = model.getNearestDependencies(); //Create the correct dependency imports - modelDependencies = modelDependencies.map( - (dependencyName) => { - const dependencyObject = - exportType === 'named' ? `{${dependencyName}}` : dependencyName; - - return moduleSystem === 'CJS' - ? `const ${dependencyObject} = require('./${dependencyName}');` - : `import ${dependencyObject} from './${dependencyName}';`; + const modelDependencyImports = modelDependencies.map((formattedDependencyModelName) => { + if (options.moduleSystem === 'CJS') { + return `const ${formattedDependencyModelName} = require('./${formattedDependencyModelName}');`; } - ); + return `import ${formattedDependencyModelName} from './${formattedDependencyModelName}';`; + }); - //Ensure we expose the model correctly, based on the module system and export type - const cjsExport = - exportType === 'default' - ? `module.exports = ${outputModel.renderedName};` - : `exports.${outputModel.renderedName} = ${outputModel.renderedName};`; - const esmExport = - exportType === 'default' - ? `export default ${outputModel.renderedName};\n` - : ''; - const modelCode = `${outputModel.result}\n${moduleSystem === 'CJS' ? cjsExport : esmExport}`; + //Ensure we expose the model correctly, based on the module system + let modelCode = `${outputModel.result} +export default ${outputModel.renderedName}; +`; + if (options.moduleSystem === 'CJS') { + modelCode = `${outputModel.result} +module.exports = ${outputModel.renderedName};`; + } - const outputContent = `${[...modelDependencies, ...outputModel.dependencies].join('\n')} + const outputContent = `${[...modelDependencyImports, ...outputModel.dependencies].join('\n')} ${modelCode}`; - - // Restore presets array from original copy - this.options.presets = originalPresets; - return RenderOutput.toRenderOutput({ result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies }); } - render(model: CommonModel, inputModel: CommonInputModel): Promise { - const kind = TypeHelpers.extractKind(model); - switch (kind) { - case ModelKind.OBJECT: { - return this.renderModelType(model, inputModel); - } - case ModelKind.ENUM: { + render(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise { + if (model instanceof ConstrainedObjectModel) { + if (this.options.modelType === 'interface') { + return this.renderInterface(model, inputModel); + } + return this.renderClass(model, inputModel); + } else if (model instanceof ConstrainedEnumModel) { if (this.options.enumType === 'union') { return this.renderType(model, inputModel); } return this.renderEnum(model, inputModel); - } - default: return this.renderType(model, inputModel); - } + } + return this.renderType(model, inputModel); } - async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise { - const presets = this.getPresets('class'); + async renderClass(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise { + const presets = this.getPresets('class'); const renderer = new ClassRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } - async renderInterface(model: CommonModel, inputModel: CommonInputModel): Promise { - const presets = this.getPresets('interface'); + async renderInterface(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise { + const presets = this.getPresets('interface'); const renderer = new InterfaceRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } - async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise { - const presets = this.getPresets('enum'); + async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise { + const presets = this.getPresets('enum'); const renderer = new EnumRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } - async renderType(model: CommonModel, inputModel: CommonInputModel): Promise { - const presets = this.getPresets('type'); + async renderType(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise { + const presets = this.getPresets('type'); const renderer = new TypeRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); - } - - private renderModelType(model: CommonModel, inputModel: CommonInputModel): Promise { - const modelType = this.options.modelType; - if (modelType === 'interface') { - return this.renderInterface(model, inputModel); - } - return this.renderClass(model, inputModel); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } } diff --git a/src/generators/typescript/TypeScriptObjectRenderer.ts b/src/generators/typescript/TypeScriptObjectRenderer.ts new file mode 100644 index 0000000000..5a744e03f5 --- /dev/null +++ b/src/generators/typescript/TypeScriptObjectRenderer.ts @@ -0,0 +1,43 @@ +import { TypeScriptGenerator, TypeScriptOptions } from './TypeScriptGenerator'; +import { ConstrainedObjectModel, ConstrainedObjectPropertyModel, InputMetaModel, Preset } from '../../models'; +import { TypeScriptRenderer } from './TypeScriptRenderer'; + +/** + * Common renderer for TypeScript types + * + * @extends AbstractRenderer + */ +export abstract class TypeScriptObjectRenderer extends TypeScriptRenderer { + constructor( + options: TypeScriptOptions, + generator: TypeScriptGenerator, + presets: Array<[Preset, unknown]>, + model: ConstrainedObjectModel, + inputModel: InputMetaModel, + ) { + super(options, generator, presets, model, inputModel); + } + + /** + * Render all the properties for the model by calling the property preset per property. + */ + async renderProperties(): Promise { + const properties = this.model.properties || {}; + const content: string[] = []; + + for (const property of Object.values(properties)) { + const rendererProperty = await this.runPropertyPreset(property); + content.push(rendererProperty); + } + + return this.renderBlock(content); + } + + renderProperty(property: ConstrainedObjectPropertyModel): string { + return `${property.propertyName}: ${property.property.type};`; + } + + runPropertyPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('property', { property }); + } +} diff --git a/src/generators/typescript/TypeScriptPreset.ts b/src/generators/typescript/TypeScriptPreset.ts index 0c7bff54c8..aaba0cd1a5 100644 --- a/src/generators/typescript/TypeScriptPreset.ts +++ b/src/generators/typescript/TypeScriptPreset.ts @@ -1,18 +1,20 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import { Preset, ClassPreset, InterfacePreset, EnumPreset, CommonPreset } from '../../models'; - +import { Preset, ClassPreset, InterfacePreset, EnumPreset, CommonPreset, ConstrainedMetaModel } from '../../models'; import { ClassRenderer, TS_DEFAULT_CLASS_PRESET } from './renderers/ClassRenderer'; import { InterfaceRenderer, TS_DEFAULT_INTERFACE_PRESET } from './renderers/InterfaceRenderer'; import { EnumRenderer, TS_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer'; import { TypeRenderer, TS_DEFAULT_TYPE_PRESET } from './renderers/TypeRenderer'; +import { TypeScriptOptions } from './TypeScriptGenerator'; -export type TypePreset = CommonPreset +export type ClassPresetType = ClassPreset; +export type InterfacePresetType = InterfacePreset; +export type EnumPresetType = EnumPreset; +export type TypePresetType = CommonPreset -export type TypeScriptPreset = Preset<{ - class: ClassPreset; - interface: InterfacePreset; - enum: EnumPreset; - type: TypePreset; +export type TypeScriptPreset = Preset<{ + class: ClassPresetType; + interface: InterfacePresetType; + enum: EnumPresetType; + type: TypePresetType; }>; export const TS_DEFAULT_PRESET: TypeScriptPreset = { diff --git a/src/generators/typescript/TypeScriptRenderer.ts b/src/generators/typescript/TypeScriptRenderer.ts index 33344ecdd6..e9de26d1ac 100644 --- a/src/generators/typescript/TypeScriptRenderer.ts +++ b/src/generators/typescript/TypeScriptRenderer.ts @@ -1,122 +1,24 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { TypeScriptGenerator, TypeScriptOptions } from './TypeScriptGenerator'; - import { FormatHelpers } from '../../helpers'; -import { CommonModel, CommonInputModel, Preset, PropertyType } from '../../models'; -import { DefaultPropertyNames, getUniquePropertyName } from '../../helpers/NameHelpers'; -import { isReservedTypeScriptKeyword } from './Constants'; +import { ConstrainedMetaModel, InputMetaModel, Preset } from '../../models'; /** * Common renderer for TypeScript types * * @extends AbstractRenderer */ -export abstract class TypeScriptRenderer extends AbstractRenderer { +export abstract class TypeScriptRenderer extends AbstractRenderer { constructor( options: TypeScriptOptions, generator: TypeScriptGenerator, presets: Array<[Preset, unknown]>, - model: CommonModel, - inputModel: CommonInputModel, + model: RendererModelType, + inputModel: InputMetaModel, ) { super(options, generator, presets, model, inputModel); } - /** - * Renders the name of a type based on provided generator option naming convention type function. - * - * This is used to render names of models (example TS class) and then later used if that class is referenced from other models. - * - * @param name - * @param model - */ - nameType(name: string | undefined, model?: CommonModel): string { - return this.options?.namingConvention?.type - ? this.options.namingConvention.type(name, { model: model || this.model, inputModel: this.inputModel, reservedKeywordCallback: isReservedTypeScriptKeyword }) - : name || ''; - } - - /** - * Renders the name of a property based on provided generator option naming convention property function. - * - * @param propertyName - * @param property - */ - nameProperty(propertyName: string | undefined, property?: CommonModel): string { - return this.options?.namingConvention?.property - ? this.options.namingConvention.property(propertyName, { model: this.model, inputModel: this.inputModel, property, reservedKeywordCallback: isReservedTypeScriptKeyword }) - : propertyName || ''; - } - - renderType(model: CommonModel | CommonModel[]): string { - if (Array.isArray(model)) { - return model.map(t => this.renderType(t)).join(' | '); - } - if (model.enum !== undefined) { - return model.enum.map(value => typeof value === 'string' ? `"${value}"` : value).join(' | '); - } - if (model.$ref !== undefined) { - return this.nameType(model.$ref); - } - if (Array.isArray(model.type)) { - return [... new Set(model.type.map(t => this.toTsType(t, model)))].join(' | '); - } - return this.toTsType(model.type, model); - } - - /** - * JSON Schema types to TS - * - * @param type - * @param model - */ - toTsType(type: string | undefined, model: CommonModel): string { - switch (type) { - case 'null': - return 'null'; - case 'object': - return 'object'; - case 'string': - return 'string'; - case 'integer': - case 'number': - return 'number'; - case 'boolean': - return 'boolean'; - case 'array': { - //Check and see if it should be rendered as tuples or array - if (Array.isArray(model.items)) { - const types = model.items.map((item) => { - return this.renderType(item); - }); - const additionalTypes = model.additionalItems ? `, ...(${this.renderType(model.additionalItems)})[]` : ''; - return `[${types.join(', ')}${additionalTypes}]`; - } - const arrayType = model.items ? this.renderType(model.items) : 'unknown'; - return `Array<${arrayType}>`; - } - default: return 'any'; - } - } - - renderTypeSignature(type: CommonModel | CommonModel[], { - isRequired = true, - orUndefined = false, - }: { - isRequired?: boolean; - orUndefined?: boolean; - } = {}): string { - if (this.options.renderTypes === false) { - return ''; - } - - const annotation = isRequired ? ':' : '?:'; - let t = this.renderType(type); - t = orUndefined ? `${t} | undefined` : t; - - return `${annotation} ${t}`; - } - renderComments(lines: string | string[]): string { lines = FormatHelpers.breakLines(lines); const renderedLines = lines.map(line => ` * ${line}`).join('\n'); @@ -124,53 +26,4 @@ export abstract class TypeScriptRenderer extends AbstractRenderer { - const properties = this.model.properties || {}; - const content: string[] = []; - - for (const [propertyName, property] of Object.entries(properties)) { - const rendererProperty = await this.runPropertyPreset(propertyName, property); - content.push(rendererProperty); - } - - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); - content.push(additionalProperty); - } - - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const renderedPatternProperty = await this.runPropertyPreset(propertyName, patternModel, PropertyType.patternProperties); - content.push(renderedPatternProperty); - } - } - - return this.renderBlock(content); - } - - renderProperty(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): string { - const formattedPropertyName = this.nameProperty(propertyName, property); - let signature: string; - switch (type) { - case PropertyType.property: - signature = this.renderTypeSignature(property, { isRequired: this.model.isRequired(propertyName) }); - return `${formattedPropertyName}${signature};`; - case PropertyType.additionalProperty: - case PropertyType.patternProperties: - signature = this.renderType(property); - return `${formattedPropertyName}?: Map;`; - default: - return ''; - } - } - - runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('property', { propertyName, property, type }); - } } diff --git a/src/generators/typescript/presets/CommonPreset.ts b/src/generators/typescript/presets/CommonPreset.ts index 3512eba5bf..792f1f6eff 100644 --- a/src/generators/typescript/presets/CommonPreset.ts +++ b/src/generators/typescript/presets/CommonPreset.ts @@ -1,8 +1,8 @@ -import { TypeScriptRenderer } from '../TypeScriptRenderer'; + import { TypeScriptPreset } from '../TypeScriptPreset'; -import { getUniquePropertyName, DefaultPropertyNames, TypeHelpers, ModelKind } from '../../../helpers'; -import { CommonInputModel, CommonModel } from '../../../models'; +import { ConstrainedObjectModel, ConstrainedDictionaryModel, ConstrainedReferenceModel, ConstrainedMetaModel, ConstrainedEnumModel } from '../../../models'; import renderExampleFunction from './utils/ExampleFunction'; +import { ClassRenderer } from '../renderers/ClassRenderer'; export interface TypeScriptCommonPresetOptions { marshalling: boolean; @@ -12,180 +12,135 @@ export interface TypeScriptCommonPresetOptions { function realizePropertyFactory(prop: string) { return `$\{typeof ${prop} === 'number' || typeof ${prop} === 'boolean' ? ${prop} : JSON.stringify(${prop})}`; } -function renderMarshalProperty(modelInstanceVariable: string, model: CommonModel, inputModel: CommonInputModel) { - if (model.$ref) { - const resolvedModel = inputModel.models[model.$ref]; - const propertyModelKind = TypeHelpers.extractKind(resolvedModel); - //Referenced enums only need standard marshalling, so lets filter those away - if (propertyModelKind !== ModelKind.ENUM) { - return `$\{${modelInstanceVariable}.marshal()}`; - } + +function renderMarshalProperty(modelInstanceVariable: string, model: ConstrainedMetaModel) { + if (model instanceof ConstrainedReferenceModel && !(model.ref instanceof ConstrainedEnumModel)) { + return `$\{${model.type}.marshal()}`; } return realizePropertyFactory(modelInstanceVariable); } -function renderMarshalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { +function renderMarshalProperties(model: ConstrainedObjectModel) { const properties = model.properties || {}; const propertyKeys = [...Object.entries(properties)]; - const marshalProperties = propertyKeys.map(([prop, propModel]) => { - const formattedPropertyName = renderer.nameProperty(prop, propModel); - const modelInstanceVariable = `this.${formattedPropertyName}`; - const propMarshalCode = renderMarshalProperty(modelInstanceVariable, propModel, inputModel); + + //These are a bit special as 'unwrap' dictionary models means they have to be unwrapped within the JSON object. + const unwrapDictionaryProperties = []; + const normalProperties = []; + for (const entry of propertyKeys) { + if (entry[1] instanceof ConstrainedDictionaryModel && entry[1].serializationType === 'unwrap') { + unwrapDictionaryProperties.push(entry); + } else { + normalProperties.push(entry); + } + } + + const marshalNormalProperties = normalProperties.map(([prop, propModel]) => { + const modelInstanceVariable = `this.${prop}`; + const propMarshalCode = renderMarshalProperty(modelInstanceVariable, propModel.property); const marshalCode = `json += \`"${prop}": ${propMarshalCode},\`;`; return `if(${modelInstanceVariable} !== undefined) { ${marshalCode} }`; }); - return marshalProperties.join('\n'); -} - -function renderMarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { - let marshalPatternProperties = ''; - if (model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(model.patternProperties)) { - let patternPropertyName = getUniquePropertyName(model, `${pattern}${DefaultPropertyNames.patternProperties}`); - patternPropertyName = renderer.nameProperty(patternPropertyName, patternModel); - const modelInstanceVariable = 'value'; - const patternPropertyMarshalCode = renderMarshalProperty(modelInstanceVariable, patternModel, inputModel); - const marshalCode = `json += \`"$\{key}": ${patternPropertyMarshalCode},\`;`; - marshalPatternProperties += `if(this.${patternPropertyName} !== undefined) { - for (const [key, value] of this.${patternPropertyName}.entries()) { - //Only render pattern properties which are not already a property - if(Object.keys(this).includes(String(key))) continue; - ${marshalCode} - } -}`; - } - } - return marshalPatternProperties; -} -function renderMarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { - let marshalAdditionalProperties = ''; - if (model.additionalProperties !== undefined) { - let additionalPropertyName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties); - additionalPropertyName = renderer.nameProperty(additionalPropertyName, model.additionalProperties); + const marshalUnwrapDictionaryProperties = unwrapDictionaryProperties.map(([prop, propModel]) => { const modelInstanceVariable = 'value'; - const patternPropertyMarshalCode = renderMarshalProperty(modelInstanceVariable, model.additionalProperties, inputModel); + const patternPropertyMarshalCode = renderMarshalProperty(modelInstanceVariable, propModel.property); const marshalCode = `json += \`"$\{key}": ${patternPropertyMarshalCode},\`;`; - marshalAdditionalProperties = `if(this.${additionalPropertyName} !== undefined) { - for (const [key, value] of this.${additionalPropertyName}.entries()) { - //Only render additionalProperties which are not already a property - if(Object.keys(this).includes(String(key))) continue; + return `if(this.${prop} !== undefined) { +for (const [key, value] of this.${prop}.entries()) { + //Only unwrap those who are not already a property in the JSON object + if(Object.keys(this).includes(String(key))) continue; ${marshalCode} } }`; - } - return marshalAdditionalProperties; + }); + + return ` +${marshalNormalProperties.join('\n')} +${marshalUnwrapDictionaryProperties.join('\n')} +`; } /** * Render `marshal` function based on model */ -function renderMarshal({ renderer, model, inputModel }: { - renderer: TypeScriptRenderer, - model: CommonModel, - inputModel: CommonInputModel +function renderMarshal({ renderer, model }: { + renderer: ClassRenderer, + model: ConstrainedObjectModel }): string { return `public marshal() : string { let json = '{' -${renderer.indent(renderMarshalProperties(model, renderer, inputModel))} -${renderer.indent(renderMarshalPatternProperties(model, renderer, inputModel))} -${renderer.indent(renderMarshalAdditionalProperties(model, renderer, inputModel))} - +${renderer.indent(renderMarshalProperties(model))} //Remove potential last comma return \`$\{json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}\`; }`; -} +} -function renderUnmarshalProperty(modelInstanceVariable: string, model: CommonModel, inputModel: CommonInputModel, renderer: TypeScriptRenderer) { - if (model.$ref) { - const resolvedModel = inputModel.models[model.$ref]; - const propertyModelKind = TypeHelpers.extractKind(resolvedModel); - //Referenced enums only need standard marshalling, so lets filter those away - if (propertyModelKind !== ModelKind.ENUM) { - return `${renderer.nameType(model.$ref)}.unmarshal(${modelInstanceVariable})`; - } +function renderUnmarshalProperty(modelInstanceVariable: string, model: ConstrainedMetaModel) { + if (model instanceof ConstrainedReferenceModel && !(model.ref instanceof ConstrainedEnumModel)) { + return `$\{${model.type}.marshal()}`; } return `${modelInstanceVariable}`; } -function renderUnmarshalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { +function renderUnmarshalProperties(model: ConstrainedObjectModel) { const properties = model.properties || {}; const propertyKeys = [...Object.entries(properties)]; - const unmarshalProperties = propertyKeys.map(([prop, propModel]) => { - const formattedPropertyName = renderer.nameProperty(prop, propModel); + const propertyNames = propertyKeys.map(([name,]) => {return name;}); + //These are a bit special as 'unwrap' dictionary models means they have to be unwrapped within the JSON object. + const unwrapDictionaryProperties = []; + const normalProperties = []; + for (const entry of propertyKeys) { + if (entry[1] instanceof ConstrainedDictionaryModel && entry[1].serializationType === 'unwrap') { + unwrapDictionaryProperties.push(entry); + } else { + normalProperties.push(entry); + } + } + + const unmarshalNormalProperties = normalProperties.map(([prop, propModel]) => { const modelInstanceVariable = `obj["${prop}"]`; - const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, propModel, inputModel, renderer); + const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, propModel.property); return `if (${modelInstanceVariable} !== undefined) { - instance.${formattedPropertyName} = ${unmarshalCode}; + instance.${prop} = ${unmarshalCode}; }`; }); - return unmarshalProperties.join('\n'); -} - -function renderUnmarshalPatternProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { - let unmarshalPatternProperties = ''; - let setPatternPropertiesMap = ''; - if (model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(model.patternProperties)) { - let patternPropertyName = getUniquePropertyName(model, `${pattern}${DefaultPropertyNames.patternProperties}`); - patternPropertyName = renderer.nameProperty(patternPropertyName, patternModel); - const modelInstanceVariable = 'value as any'; - const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, patternModel, inputModel, renderer); - setPatternPropertiesMap += `if (instance.${patternPropertyName} === undefined) {instance.${patternPropertyName} = new Map();}\n`; - unmarshalPatternProperties += `//Check all pattern properties -if (key.match(new RegExp('${pattern}'))) { - instance.${patternPropertyName}.set(key, ${unmarshalCode}); - continue; -}`; - } - } - return { unmarshalPatternProperties, setPatternPropertiesMap }; -} -function renderUnmarshalAdditionalProperties(model: CommonModel, renderer: TypeScriptRenderer, inputModel: CommonInputModel) { - let unmarshalAdditionalProperties = ''; - let setAdditionalPropertiesMap = ''; - if (model.additionalProperties !== undefined) { - let additionalPropertyName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties); - additionalPropertyName = renderer.nameProperty(additionalPropertyName, model.additionalProperties); + const setDictionaryProperties = []; + const unmarshalDictionaryProperties = []; + for (const [prop, propModel] of unwrapDictionaryProperties) { const modelInstanceVariable = 'value as any'; - const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, model.additionalProperties, inputModel, renderer); - setAdditionalPropertiesMap = `if (instance.${additionalPropertyName} === undefined) {instance.${additionalPropertyName} = new Map();}`; - unmarshalAdditionalProperties = `instance.${additionalPropertyName}.set(key, ${unmarshalCode});`; + const unmarshalCode = renderUnmarshalProperty(modelInstanceVariable, propModel.property); + setDictionaryProperties.push(`if (instance.${prop} === undefined) {instance.${prop} = new Map();}`); + unmarshalDictionaryProperties.push(`instance.${prop}.set(key, ${unmarshalCode});`); } - return { unmarshalAdditionalProperties, setAdditionalPropertiesMap }; + + return ` +${unmarshalNormalProperties.join('\n')} + +${setDictionaryProperties.join('\n')} +for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![${propertyNames.join(',')}].includes(key);}))) { + ${unmarshalDictionaryProperties.join('\n')} +} +`; } /** * Render `unmarshal` function based on model */ -function renderUnmarshal({ renderer, model, inputModel }: { - renderer: TypeScriptRenderer, - model: CommonModel, - inputModel: CommonInputModel +function renderUnmarshal({ renderer, model }: { + renderer: ClassRenderer, + model: ConstrainedObjectModel }): string { - const properties = model.properties || {}; - const { unmarshalPatternProperties, setPatternPropertiesMap } = renderUnmarshalPatternProperties(model, renderer, inputModel); - const { unmarshalAdditionalProperties, setAdditionalPropertiesMap } = renderUnmarshalAdditionalProperties(model, renderer, inputModel); - const unmarshalProperties = renderUnmarshalProperties(model, renderer, inputModel); - const formattedModelName = renderer.nameType(model.$id); - const propertyNames = Object.keys(properties).map((prop => `"${prop}"`)); - return `public static unmarshal(json: string | object): ${formattedModelName} { + const unmarshalProperties = renderUnmarshalProperties(model); + return `public static unmarshal(json: string | object): ${model.type} { const obj = typeof json === "object" ? json : JSON.parse(json); - const instance = new ${formattedModelName}({} as any); + const instance = new ${model.type}({} as any); ${renderer.indent(unmarshalProperties)} - - //Not part of core properties - ${setPatternPropertiesMap} - ${setAdditionalPropertiesMap} - for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![${propertyNames}].includes(key);}))) { -${renderer.indent(unmarshalPatternProperties, 4)} -${renderer.indent(unmarshalAdditionalProperties, 4)} - } return instance; }`; -} +} /** * Preset which adds `marshal`, `unmarshal`, `example` functions to class. @@ -194,19 +149,19 @@ ${renderer.indent(unmarshalAdditionalProperties, 4)} */ export const TS_COMMON_PRESET: TypeScriptPreset = { class: { - additionalContent({ renderer, model, content, options, inputModel }) { + additionalContent({ renderer, model, content, options }) { options = options || {}; const blocks: string[] = []; - + if (options.marshalling === true) { - blocks.push(renderMarshal({ renderer, model, inputModel })); - blocks.push(renderUnmarshal({ renderer, model, inputModel })); + blocks.push(renderMarshal({ renderer, model })); + blocks.push(renderUnmarshal({ renderer, model })); } if (options.example === true) { blocks.push(renderExampleFunction({ renderer, model })); } - + return renderer.renderBlock([content, ...blocks], 2); }, } diff --git a/src/generators/typescript/presets/DescriptionPreset.ts b/src/generators/typescript/presets/DescriptionPreset.ts index d3c112036f..6077aa12e2 100644 --- a/src/generators/typescript/presets/DescriptionPreset.ts +++ b/src/generators/typescript/presets/DescriptionPreset.ts @@ -1,4 +1,4 @@ -import { CommonModel } from '../../../models'; +import { ConstrainedMetaModel } from '../../../models'; import { TypeScriptPreset } from '../TypeScriptPreset'; import { TypeScriptRenderer } from '../TypeScriptRenderer'; @@ -7,12 +7,12 @@ const renderDescription = ({ content, item, }: { - renderer: TypeScriptRenderer; + renderer: TypeScriptRenderer; content: string; - item: CommonModel; + item: ConstrainedMetaModel; }): string => { - const desc = item.getFromOriginalInput('description')?.trim(); - const examples = item.getFromOriginalInput('examples'); + const desc = item.originalInput.description?.trim(); + const examples = item.originalInput.examples; const formattedExamples = `@example ${ examples?.join ? examples.join(', ') : examples }`; @@ -38,7 +38,7 @@ export const TS_DESCRIPTION_PRESET: TypeScriptPreset = { return renderDescription({ renderer, content, item: model }); }, property({ renderer, property, content }) { - return renderDescription({ renderer, content, item: property }); + return renderDescription({ renderer, content, item: property.property }); } }, interface: { @@ -46,7 +46,7 @@ export const TS_DESCRIPTION_PRESET: TypeScriptPreset = { return renderDescription({ renderer, content, item: model }); }, property({ renderer, property, content }) { - return renderDescription({ renderer, content, item: property }); + return renderDescription({ renderer, content, item: property.property }); } }, type: { diff --git a/src/generators/typescript/presets/ExportKeywordPreset.ts b/src/generators/typescript/presets/ExportKeywordPreset.ts index bfe227ea42..98529aee73 100644 --- a/src/generators/typescript/presets/ExportKeywordPreset.ts +++ b/src/generators/typescript/presets/ExportKeywordPreset.ts @@ -1,13 +1,9 @@ -import { CommonModel } from '../../../models'; import { TypeScriptPreset } from '../TypeScriptPreset'; -import { TypeScriptRenderer } from '../TypeScriptRenderer'; const renderWithExportKeyword = ({ content, }: { - renderer: TypeScriptRenderer; content: string; - item: CommonModel; }): string => `export ${content}`; /** @@ -17,23 +13,23 @@ const renderWithExportKeyword = ({ */ export const TS_EXPORT_KEYWORD_PRESET: TypeScriptPreset = { class: { - self({ renderer, model, content }) { - return renderWithExportKeyword({ renderer, content, item: model }); + self({ content }) { + return renderWithExportKeyword({ content }); }, }, interface: { - self({ renderer, model, content }) { - return renderWithExportKeyword({ renderer, content, item: model }); + self({ content }) { + return renderWithExportKeyword({ content }); }, }, type: { - self({ renderer, model, content }) { - return renderWithExportKeyword({ renderer, content, item: model }); + self({ content }) { + return renderWithExportKeyword({ content }); }, }, enum: { - self({ renderer, model, content }) { - return renderWithExportKeyword({ renderer, content, item: model }); + self({ content }) { + return renderWithExportKeyword({ content }); }, }, }; diff --git a/src/generators/typescript/presets/utils/ExampleFunction.ts b/src/generators/typescript/presets/utils/ExampleFunction.ts index 0cf6d0f2a4..af6ba65823 100644 --- a/src/generators/typescript/presets/utils/ExampleFunction.ts +++ b/src/generators/typescript/presets/utils/ExampleFunction.ts @@ -1,5 +1,5 @@ -import { TypeScriptRenderer } from '../../TypeScriptRenderer'; -import { CommonModel } from '../../../../models'; +import { ConstrainedArrayModel, ConstrainedBooleanModel, ConstrainedEnumModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedUnionModel } from '../../../../models'; +import { ClassRenderer } from '../../renderers/ClassRenderer'; /** * Inferring first acceptable value from the model. @@ -7,72 +7,52 @@ import { CommonModel } from '../../../../models'; * @param model * @param renderer */ -export function renderValueFromModel(model: CommonModel, renderer: TypeScriptRenderer): string | undefined { - if (Array.isArray(model.enum) && model.enum.length > 0) { - return JSON.stringify(model.enum[0]); - } - if (model.$ref !== undefined) { - return `${renderer.nameType(model.$ref)}.example()`; - } - if (Array.isArray(model.type)) { - if (model.type.length > 0) { - return renderValueFromType(model.type[0], model, renderer); - } - return undefined; - } - return renderValueFromType(model.type, model, renderer); -} - -export function renderValueFromType(modelType: string | undefined, model: CommonModel, renderer: TypeScriptRenderer): string | undefined { - if (modelType === undefined) { - return undefined; - } - switch (modelType) { - case 'string': +export function renderValueFromModel(model: ConstrainedMetaModel, renderer: ClassRenderer): string | undefined { + if (model instanceof ConstrainedEnumModel && model.values.length > 0) { + //Greedy example + return model.values[0].value; + } else if (model instanceof ConstrainedReferenceModel) { + return `${model.type}.example()`; + } else if (model instanceof ConstrainedUnionModel && model.union.length > 0) { + //Greedy example + return renderValueFromModel(model.union[0], renderer); + } else if (model instanceof ConstrainedArrayModel) { + const arrayType = renderValueFromModel(model.valueModel, renderer); + return `[${arrayType}]`; + } else if (model instanceof ConstrainedTupleModel && model.tuple.length > 0) { + const values = model.tuple.map((tupleModel) => { + return renderValueFromModel(tupleModel.value, renderer); + }); + return `[${values.join(',')}]`; + } else if (model instanceof ConstrainedStringModel) { return '"string"'; - case 'integer': - case 'number': + } else if (model instanceof ConstrainedIntegerModel || model instanceof ConstrainedFloatModel) { return '0'; - case 'boolean': + } else if (model instanceof ConstrainedBooleanModel) { return 'true'; - case 'array': { - if (model.items === undefined) { - return '[]'; - } - //Check and see if it should be rendered as tuples - if (Array.isArray(model.items)) { - const arrayValues = model.items.map((item) => { - return renderValueFromModel(item, renderer); - }); - return `[${arrayValues.join(', ')}]`; - } - const arrayType = renderValueFromModel(model.items, renderer); - return `[${arrayType}]`; - } - } + } return undefined; } + /** * Render `example` function based on model properties. */ export default function renderExampleFunction({ renderer, model }: { - renderer: TypeScriptRenderer, - model: CommonModel, + renderer: ClassRenderer, + model: ConstrainedObjectModel, }): string { const properties = model.properties || {}; const setProperties = []; for (const [propertyName, property] of Object.entries(properties)) { - const formattedPropertyName = renderer.nameProperty(propertyName, property); - const potentialRenderedValue = renderValueFromModel(property, renderer); + const potentialRenderedValue = renderValueFromModel(property.property, renderer); if (potentialRenderedValue === undefined) { //Unable to determine example value, skip property. continue; } - setProperties.push(` instance.${formattedPropertyName} = ${potentialRenderedValue};`); + setProperties.push(` instance.${propertyName} = ${potentialRenderedValue};`); } - const formattedModelName = renderer.nameType(model.$id); - return `public static example(): ${formattedModelName} { - const instance = new ${formattedModelName}({} as any); + return `public static example(): ${model.type} { + const instance = new ${model.type}({} as any); ${(setProperties.join('\n'))} return instance; }`; diff --git a/src/generators/typescript/renderers/ClassRenderer.ts b/src/generators/typescript/renderers/ClassRenderer.ts index a3f992007a..b6f5ebccfa 100644 --- a/src/generators/typescript/renderers/ClassRenderer.ts +++ b/src/generators/typescript/renderers/ClassRenderer.ts @@ -1,13 +1,14 @@ -import { TypeScriptRenderer } from '../TypeScriptRenderer'; -import { CommonModel, ClassPreset, PropertyType } from '../../../models'; -import { getUniquePropertyName, DefaultPropertyNames } from '../../../helpers'; +import { ConstrainedObjectPropertyModel } from '../../../models'; +import { TypeScriptOptions } from '../TypeScriptGenerator'; +import { TypeScriptObjectRenderer } from '../TypeScriptObjectRenderer'; +import { ClassPresetType } from '../TypeScriptPreset'; /** * Renderer for TypeScript's `class` type * * @extends TypeScriptRenderer */ -export class ClassRenderer extends TypeScriptRenderer { +export class ClassRenderer extends TypeScriptObjectRenderer { public async defaultSelf(): Promise { const content = [ await this.renderProperties(), @@ -16,8 +17,7 @@ export class ClassRenderer extends TypeScriptRenderer { await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - return `class ${formattedName} { + return `class ${this.model.name} { ${this.indent(this.renderBlock(content, 2))} }`; } @@ -27,54 +27,38 @@ ${this.indent(this.renderBlock(content, 2))} } async renderAccessors(): Promise { - const properties = this.model.properties || {}; + const properties = this.model.properties; const content: string[] = []; - for (const [propertyName, property] of Object.entries(properties)) { - const getter = await this.runGetterPreset(propertyName, property); - const setter = await this.runSetterPreset(propertyName, property); + for (const property of Object.values(properties)) { + const getter = await this.runGetterPreset(property); + const setter = await this.runSetterPreset(property); content.push(this.renderBlock([getter, setter])); } - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); - const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); - content.push(this.renderBlock([getter, setter])); - } - - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const getter = await this.runGetterPreset(propertyName, patternModel, PropertyType.patternProperties); - const setter = await this.runSetterPreset(propertyName, patternModel, PropertyType.patternProperties); - content.push(this.renderBlock([getter, setter])); - } - } return this.renderBlock(content, 2); } - runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('getter', { propertyName, property, type }); + runGetterPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('getter', { property }); } - runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('setter', { propertyName, property, type }); + runSetterPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('setter', { property }); } } -export const TS_DEFAULT_CLASS_PRESET: ClassPreset = { +export const TS_DEFAULT_CLASS_PRESET: ClassPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, ctor({ renderer, model }) : string { const properties = model.properties || {}; - const assignments = Object.entries(properties).map(([propertyName, property]) => { - propertyName = renderer.nameProperty(propertyName, property); + const assignments = Object.keys(properties).map((propertyName) => { return `this._${propertyName} = input.${propertyName};`; }); - const ctorProperties = Object.entries(properties).map(([propertyName, property]) => { - return renderer.renderProperty(propertyName, property).replace(';', ','); + const ctorProperties = Object.values(properties).map((property) => { + return renderer.renderProperty(property).replace(';', ','); }); return `constructor(input: { @@ -83,31 +67,13 @@ ${renderer.indent(renderer.renderBlock(ctorProperties))} ${renderer.indent(renderer.renderBlock(assignments))} }`; }, - property({ renderer, propertyName, property, type }): string { - return `private _${renderer.renderProperty(propertyName, property, type)}`; + property({ renderer, property }): string { + return `private _${renderer.renderProperty(property)}`; }, - getter({ renderer, model, propertyName, property, type }): string { - const isRequired = model.isRequired(propertyName); - propertyName = renderer.nameProperty(propertyName, property); - let signature = ''; - if (type === PropertyType.property) { - signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); - } else if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { - const mapType = renderer.renderType(property); - signature = `: Map | undefined`; - } - return `get ${propertyName}()${signature} { return this._${propertyName}; }`; + getter({ property }): string { + return `get ${property.propertyName}()${property.property.type} { return this._${property.propertyName}; }`; }, - setter({ renderer, model, propertyName, property, type }): string { - const isRequired = model.isRequired(propertyName); - propertyName = renderer.nameProperty(propertyName, property); - let signature = ''; - if (type === PropertyType.property) { - signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); - } else if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { - const mapType = renderer.renderType(property); - signature = `: Map | undefined`; - } - return `set ${propertyName}(${propertyName}${signature}) { this._${propertyName} = ${propertyName}; }`; + setter({ property }): string { + return `set ${property.propertyName}(${property.propertyName}${property.property.type}) { this._${property.propertyName} = ${property.propertyName}; }`; }, }; diff --git a/src/generators/typescript/renderers/EnumRenderer.ts b/src/generators/typescript/renderers/EnumRenderer.ts index 024cc41a57..4ecec568a5 100644 --- a/src/generators/typescript/renderers/EnumRenderer.ts +++ b/src/generators/typescript/renderers/EnumRenderer.ts @@ -1,28 +1,27 @@ import { TypeScriptRenderer } from '../TypeScriptRenderer'; - -import { EnumPreset } from '../../../models'; -import { FormatHelpers } from '../../../helpers'; +import { ConstrainedEnumModel } from '../../../models'; +import { EnumPresetType } from '../TypeScriptPreset'; +import { TypeScriptOptions } from '../TypeScriptGenerator'; /** * Renderer for TypeScript's `enum` type * * @extends TypeScriptRenderer */ -export class EnumRenderer extends TypeScriptRenderer { +export class EnumRenderer extends TypeScriptRenderer { async defaultSelf(): Promise { const content = [ await this.renderItems(), await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - return `enum ${formattedName} { + return `enum ${this.model.name} { ${this.indent(this.renderBlock(content, 2))} }`; } async renderItems(): Promise { - const enums = this.model.enum || []; + const enums = this.model.values || []; const items: string[] = []; for (const item of enums) { @@ -36,61 +35,13 @@ ${this.indent(this.renderBlock(content, 2))} runItemPreset(item: any): Promise { return this.runPreset('item', { item }); } - - normalizeKey(value: any): any { - let key; - switch (typeof value) { - case 'bigint': - case 'number': { - key = `number_${value}`; - break; - } - case 'object': { - key = JSON.stringify(value); - break; - } - default: { - key = FormatHelpers.replaceSpecialCharacters(String(value), { exclude: [' ','_'], separator: '_' }); - //Ensure no special char can be the beginning letter - if (!(/^[a-zA-Z]+$/).test(key.charAt(0))) { - key = `String_${key}`; - } - } - } - return FormatHelpers.toConstantCase(key); - } - - normalizeValue(value: any): any { - let normalizedValue; - switch (typeof value) { - case 'string': - case 'boolean': - normalizedValue = `"${value}"`; - break; - case 'bigint': - case 'number': { - normalizedValue = value; - break; - } - case 'object': { - normalizedValue = `'${JSON.stringify(value)}'`; - break; - } - default: { - normalizedValue = String(value); - } - } - return normalizedValue; - } } -export const TS_DEFAULT_ENUM_PRESET: EnumPreset = { +export const TS_DEFAULT_ENUM_PRESET: EnumPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, - item({ item, renderer }): string { - const key = renderer.normalizeKey(item); - const value = renderer.normalizeValue(item); - return `${key} = ${value},`; + item({ item }): string { + return `${item.key} = ${item.value},`; } }; diff --git a/src/generators/typescript/renderers/InterfaceRenderer.ts b/src/generators/typescript/renderers/InterfaceRenderer.ts index 53099afdf3..08ad403e83 100644 --- a/src/generators/typescript/renderers/InterfaceRenderer.ts +++ b/src/generators/typescript/renderers/InterfaceRenderer.ts @@ -1,30 +1,30 @@ -import { TypeScriptRenderer } from '../TypeScriptRenderer'; -import { InterfacePreset } from '../../../models'; +import { TypeScriptOptions } from '../TypeScriptGenerator'; +import { TypeScriptObjectRenderer } from '../TypeScriptObjectRenderer'; +import { InterfacePresetType } from '../TypeScriptPreset'; /** * Renderer for TypeScript's `interface` type * * @extends TypeScriptRenderer */ -export class InterfaceRenderer extends TypeScriptRenderer { +export class InterfaceRenderer extends TypeScriptObjectRenderer { async defaultSelf(): Promise { const content = [ await this.renderProperties(), await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - return `interface ${formattedName} { + return `interface ${this.model.name} { ${this.indent(this.renderBlock(content, 2))} }`; } } -export const TS_DEFAULT_INTERFACE_PRESET: InterfacePreset = { +export const TS_DEFAULT_INTERFACE_PRESET: InterfacePresetType = { self({ renderer }) { return renderer.defaultSelf(); }, - property({ renderer, propertyName, property, type }) { - return renderer.renderProperty(propertyName, property, type); + property({ renderer, property }) { + return renderer.renderProperty(property); } }; diff --git a/src/generators/typescript/renderers/TypeRenderer.ts b/src/generators/typescript/renderers/TypeRenderer.ts index e0f81df7d5..17019f9ba7 100644 --- a/src/generators/typescript/renderers/TypeRenderer.ts +++ b/src/generators/typescript/renderers/TypeRenderer.ts @@ -1,34 +1,20 @@ import { TypeScriptRenderer } from '../TypeScriptRenderer'; -import { TypePreset } from '../TypeScriptPreset'; -import { TypeHelpers, ModelKind } from '../../../helpers'; +import { TypePresetType } from '../TypeScriptPreset'; +import { ConstrainedMetaModel } from '../../../models'; +import { TypeScriptOptions } from '../TypeScriptGenerator'; /** * Renderer for TypeScript's `type` type * * @extends TypeScriptRenderer */ -export class TypeRenderer extends TypeScriptRenderer { - async defaultSelf(): Promise { - const body = await this.renderTypeBody(); - const formattedName = this.nameType(this.model.$id); - return `type ${formattedName} = ${body};`; - } - - renderTypeBody(): Promise { - const kind = TypeHelpers.extractKind(this.model); - if (kind === ModelKind.ENUM) { - return Promise.resolve(this.renderEnum()); - } - return Promise.resolve(this.renderType(this.model)); - } - - renderEnum(): string { - const enums = this.model.enum || []; - return enums.map(t => typeof t === 'string' ? `"${t}"` : t).join(' | '); +export class TypeRenderer extends TypeScriptRenderer { + defaultSelf(): string { + return `type ${this.model.name} = ${this.model.type};`; } } -export const TS_DEFAULT_TYPE_PRESET: TypePreset = { +export const TS_DEFAULT_TYPE_PRESET: TypePresetType = { self({ renderer }) { return renderer.defaultSelf(); }, diff --git a/src/models/CommonInputModel.ts b/src/models/CommonInputModel.ts deleted file mode 100644 index 035644bd11..0000000000 --- a/src/models/CommonInputModel.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CommonModel } from './CommonModel'; - -/** - * This class is the wrapper for simplified models and the rest of the context needed for further generate typed models. - */ -export class CommonInputModel { - models: {[key: string]: CommonModel} = {}; - originalInput: any = {}; -} diff --git a/src/models/InputMetaModel.ts b/src/models/InputMetaModel.ts new file mode 100644 index 0000000000..bb00586f67 --- /dev/null +++ b/src/models/InputMetaModel.ts @@ -0,0 +1,9 @@ +import { MetaModel } from './MetaModel'; + +/** + * Since each input processor can create multiple meta models this is a wrapper to a MetaModel to make that possible. + */ +export class InputMetaModel { + models: {[key: string]: MetaModel} = {}; + originalInput: any = {}; +} diff --git a/src/models/OutputModel.ts b/src/models/OutputModel.ts index 748e9197d5..5c0cc8077e 100644 --- a/src/models/OutputModel.ts +++ b/src/models/OutputModel.ts @@ -1,11 +1,11 @@ -import { CommonInputModel } from './CommonInputModel'; -import { CommonModel } from './CommonModel'; +import { InputMetaModel } from './InputMetaModel'; +import { ConstrainedMetaModel } from './ConstrainedMetaModel'; export interface ToOutputModelArg { result: string; - model: CommonModel; + model: ConstrainedMetaModel; modelName: string; - inputModel: CommonInputModel; + inputModel: InputMetaModel; dependencies: string[]; } @@ -15,9 +15,9 @@ export interface ToOutputModelArg { export class OutputModel { constructor( public readonly result: string, - public readonly model: CommonModel, + public readonly model: ConstrainedMetaModel, public readonly modelName: string, - public readonly inputModel: CommonInputModel, + public readonly inputModel: InputMetaModel, public readonly dependencies: string[] ) {} diff --git a/src/models/index.ts b/src/models/index.ts index 77c57843b3..555d5d5352 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,3 @@ -export * from './CommonInputModel'; export * from './CommonModel'; export * from './RenderOutput'; export * from './OutputModel'; @@ -12,4 +11,4 @@ export * from './SwaggerV2Schema'; export * from './OpenapiV3Schema'; export * from './MetaModel'; export * from './ConstrainedMetaModel'; - +export * from './InputMetaModel'; From 798303b5fe8e2517e2efdfd28a1c2c0eb1861898 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Wed, 15 Jun 2022 14:03:48 +0200 Subject: [PATCH 2/2] Converted go --- src/generators/go/GoConstrainer.ts | 4 +- src/generators/go/GoFileGenerator.ts | 4 +- src/generators/go/GoGenerator.ts | 100 ++++++----------- src/generators/go/GoPreset.ts | 25 ++--- src/generators/go/GoRenderer.ts | 103 +----------------- src/generators/go/renderers/EnumRenderer.ts | 28 ++--- src/generators/go/renderers/StructRenderer.ts | 37 ++++--- 7 files changed, 88 insertions(+), 213 deletions(-) diff --git a/src/generators/go/GoConstrainer.ts b/src/generators/go/GoConstrainer.ts index 72b5d8ee64..e4b471bb7d 100644 --- a/src/generators/go/GoConstrainer.ts +++ b/src/generators/go/GoConstrainer.ts @@ -2,9 +2,9 @@ import { TypeMapping } from '../../helpers'; import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; -import { GoRenderer } from './GoRenderer'; +import { GoOptions } from './GoGenerator'; -export const GoDefaultTypeMapping: TypeMapping = { +export const GoDefaultTypeMapping: TypeMapping = { Object ({constrainedModel}): string { return constrainedModel.name; }, diff --git a/src/generators/go/GoFileGenerator.ts b/src/generators/go/GoFileGenerator.ts index e0ef44aae8..ba519e7870 100644 --- a/src/generators/go/GoFileGenerator.ts +++ b/src/generators/go/GoFileGenerator.ts @@ -1,5 +1,5 @@ import { GoGenerator, GoRenderCompleteModelOptions } from './GoGenerator'; -import { CommonInputModel, OutputModel } from '../../models'; +import { InputMetaModel, OutputModel } from '../../models'; import * as path from 'path'; import { AbstractFileGenerator } from '../AbstractFileGenerator'; import { FileHelpers } from '../../helpers'; @@ -12,7 +12,7 @@ export class GoFileGenerator extends GoGenerator implements AbstractFileGenerato * @param outputDirectory where you want the models generated to * @param options */ - public async generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise { + public async generateToFiles(input: Record | InputMetaModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise { let generatedModels = await this.generateCompleteModels(input, options); generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; }); for (const outputModel of generatedModels) { diff --git a/src/generators/go/GoGenerator.ts b/src/generators/go/GoGenerator.ts index 0dba394172..b8209b724c 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -3,54 +3,16 @@ import { CommonGeneratorOptions, defaultGeneratorOptions } from '../AbstractGenerator'; -import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { TypeHelpers, ModelKind, FormatHelpers, Constraints, TypeMapping } from '../../helpers'; +import { InputMetaModel, RenderOutput, ConstrainedObjectModel, ConstrainedEnumModel, ConstrainedMetaModel, MetaModel } from '../../models'; +import { constrainMetaModel, Constraints, split, TypeMapping } from '../../helpers'; import { GoPreset, GO_DEFAULT_PRESET } from './GoPreset'; import { StructRenderer } from './renderers/StructRenderer'; import { EnumRenderer } from './renderers/EnumRenderer'; -import { pascalCaseTransformMerge } from 'change-case'; import { Logger } from '../../utils/LoggingInterface'; -import { isReservedGoKeyword } from './Constants'; import { GoDefaultConstraints, GoDefaultTypeMapping } from './GoConstrainer'; -import { GoRenderer } from './GoRenderer'; -/** - * The Go naming convention type - */ -export type GoNamingConvention = { - type?: (name: string | undefined, ctx: { model: CommonModel, inputModel: CommonInputModel, reservedKeywordCallback?: (name: string) => boolean }) => string; - field?: (name: string | undefined, ctx: { model: CommonModel, inputModel: CommonInputModel, field?: CommonModel, reservedKeywordCallback?: (name: string) => boolean }) => string; -}; - -/** - * A GoNamingConvention implementation for Go - */ -export const GoNamingConventionImplementation: GoNamingConvention = { - type: (name: string | undefined, ctx) => { - if (!name) { return ''; } - let formattedName = FormatHelpers.toPascalCase(name, { transform: pascalCaseTransformMerge }); - if (ctx.reservedKeywordCallback !== undefined && ctx.reservedKeywordCallback(formattedName)) { - formattedName = FormatHelpers.toPascalCase(`reserved_${formattedName}`); - } - return formattedName; - }, - // eslint-disable-next-line sonarjs/no-identical-functions - field: (name: string | undefined, ctx) => { - if (!name) { return ''; } - let formattedName = FormatHelpers.toPascalCase(name, { transform: pascalCaseTransformMerge }); - if (ctx.reservedKeywordCallback !== undefined && ctx.reservedKeywordCallback(formattedName)) { - formattedName = FormatHelpers.toPascalCase(`reserved_${formattedName}`); - if (Object.keys(ctx.model.properties || {}).includes(formattedName)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return GoNamingConventionImplementation.field!(`reserved_${formattedName}`, ctx); - } - } - return formattedName; - } -}; export interface GoOptions extends CommonGeneratorOptions { - namingConvention?: GoNamingConvention; - typeMapping: TypeMapping; + typeMapping: TypeMapping; constraints: Constraints } @@ -65,33 +27,45 @@ export class GoGenerator extends AbstractGenerator = GoGenerator.defaultOptions, ) { - const mergedOptions = {...GoGenerator.defaultOptions, ...options}; + const realizedOptions = {...GoGenerator.defaultOptions, ...options}; - super('Go', GoGenerator.defaultOptions, mergedOptions); + super('Go', realizedOptions); } - reservedGoKeyword(name: string): boolean { - return isReservedGoKeyword(name); + + splitMetaModel(model: MetaModel): MetaModel[] { + //These are the models that we have separate renderers for + const metaModelsToSplit = { + splitEnum: true, + splitObject: true + }; + return split(model, metaModelsToSplit); } - render(model: CommonModel, inputModel: CommonInputModel): Promise { - const kind = TypeHelpers.extractKind(model); - switch (kind) { - case ModelKind.UNION: - // We don't support union in Go generator, however, if union is an object, we render it as a struct. - if (!model.type?.includes('object')) { break; } - return this.renderStruct(model, inputModel); - case ModelKind.OBJECT: + + constrainToMetaModel(model: MetaModel): ConstrainedMetaModel { + return constrainMetaModel( + this.options.typeMapping, + this.options.constraints, + { + metaModel: model, + options: this.options, + constrainedName: '' //This is just a placeholder, it will be constrained within the function + } + ); + } + + render(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise { + if (model instanceof ConstrainedObjectModel) { return this.renderStruct(model, inputModel); - case ModelKind.ENUM: + } else if (model instanceof ConstrainedEnumModel) { return this.renderEnum(model, inputModel); - } - Logger.warn(`Go generator, cannot generate this type of model, ${model.$id}`); + } + Logger.warn(`Go generator, cannot generate this type of model, ${model.name}`); return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] })); } @@ -102,7 +76,7 @@ export class GoGenerator extends AbstractGenerator { + async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: GoRenderCompleteModelOptions): Promise { const outputModel = await this.render(model, inputModel); let importCode = ''; if (outputModel.dependencies.length > 0) { @@ -118,19 +92,17 @@ ${outputModel.result}`; return RenderOutput.toRenderOutput({ result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies }); } - async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('enum'); const renderer = new EnumRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({ result, renderedName, dependencies: renderer.dependencies }); + return RenderOutput.toRenderOutput({ result, renderedName: model.name, dependencies: renderer.dependencies }); } - async renderStruct(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderStruct(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('struct'); const renderer = new StructRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({ result, renderedName, dependencies: renderer.dependencies }); + return RenderOutput.toRenderOutput({ result, renderedName: model.name, dependencies: renderer.dependencies }); } } diff --git a/src/generators/go/GoPreset.ts b/src/generators/go/GoPreset.ts index 0b0fc6cca4..859494b041 100644 --- a/src/generators/go/GoPreset.ts +++ b/src/generators/go/GoPreset.ts @@ -1,27 +1,22 @@ -/* eslint-disable @typescript-eslint/ban-types */ import { AbstractRenderer } from '../AbstractRenderer'; -import { Preset, CommonModel, CommonPreset, PresetArgs, EnumPreset } from '../../models'; +import { Preset, CommonPreset, PresetArgs, EnumPreset, ConstrainedObjectModel, ConstrainedObjectPropertyModel } from '../../models'; import { StructRenderer, GO_DEFAULT_STRUCT_PRESET } from './renderers/StructRenderer'; import { EnumRenderer, GO_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer'; +import { GoOptions } from './GoGenerator'; -export enum FieldType { - field, - additionalProperty, - patternProperties -} export interface FieldArgs { - fieldName: string; - field: CommonModel; - type: FieldType; + field: ConstrainedObjectPropertyModel; } -export interface StructPreset extends CommonPreset { - field?: (args: PresetArgs & FieldArgs) => Promise | string; +export interface StructPreset extends CommonPreset { + field?: (args: PresetArgs & FieldArgs) => Promise | string; } +export type StructPresetType = StructPreset; +export type EnumPresetType = EnumPreset; -export type GoPreset = Preset<{ - struct: StructPreset; - enum: EnumPreset +export type GoPreset = Preset<{ + struct: StructPresetType; + enum: EnumPresetType }>; export const GO_DEFAULT_PRESET: GoPreset = { diff --git a/src/generators/go/GoRenderer.ts b/src/generators/go/GoRenderer.ts index 1c01de1daf..738b5205f8 100644 --- a/src/generators/go/GoRenderer.ts +++ b/src/generators/go/GoRenderer.ts @@ -1,121 +1,26 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { GoGenerator, GoOptions } from './GoGenerator'; -import { CommonModel, CommonInputModel, Preset } from '../../models'; +import { InputMetaModel, Preset, ConstrainedMetaModel } from '../../models'; import { FormatHelpers } from '../../helpers/FormatHelpers'; -import { DefaultPropertyNames, getUniquePropertyName } from '../../helpers'; -import { FieldType } from './GoPreset'; -import { isReservedGoKeyword } from './Constants'; /** * Common renderer for Go types * * @extends AbstractRenderer */ -export abstract class GoRenderer extends AbstractRenderer { +export abstract class GoRenderer extends AbstractRenderer { constructor( options: GoOptions, generator: GoGenerator, presets: Array<[Preset, unknown]>, - model: CommonModel, - inputModel: CommonInputModel, + model: RendererModelType, + inputModel: InputMetaModel, ) { super(options, generator, presets, model, inputModel); } - async renderFields(): Promise { - const fields = this.model.properties || {}; - const content: string[] = []; - - for (const [fieldName, field] of Object.entries(fields)) { - const renderField = await this.runFieldPreset(fieldName, field); - content.push(renderField); - } - - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const additionalProperty = await this.runFieldPreset(propertyName, this.model.additionalProperties, FieldType.additionalProperty); - content.push(additionalProperty); - } - - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const renderedPatternProperty = await this.runFieldPreset(propertyName, patternModel, FieldType.patternProperties); - content.push(renderedPatternProperty); - } - } - return this.renderBlock(content); - } - - /** - * Renders the name of a type based on provided generator option naming convention type function. - * - * This is used to render names of models and then later used if that class is referenced from other models. - * - * @param name - * @param model - */ - nameType(name: string | undefined, model?: CommonModel): string { - return this.options?.namingConvention?.type - ? this.options.namingConvention.type(name, { model: model || this.model, inputModel: this.inputModel, reservedKeywordCallback: isReservedGoKeyword }) - : name || ''; - } - - /** - * Renders the name of a field based on provided generator option naming convention field function. - * - * @param fieldName - * @param field - */ - nameField(fieldName: string | undefined, field?: CommonModel): string { - return this.options?.namingConvention?.field - ? this.options.namingConvention.field(fieldName, { model: this.model, inputModel: this.inputModel, field, reservedKeywordCallback: isReservedGoKeyword }) - : fieldName || ''; - } - - runFieldPreset(fieldName: string, field: CommonModel, type: FieldType = FieldType.field): Promise { - return this.runPreset('field', { fieldName, field, type }); - } - - renderType(model: CommonModel): string { - if (model.$ref !== undefined) { - const formattedRef = this.nameType(model.$ref); - return `*${formattedRef}`; - } - - if (Array.isArray(model.type)) { - return model.type.length > 1 ? '[]interface{}' : `[]${this.toGoType(model.type[0], model)}`; - } - - return this.toGoType(model.type, model); - } - renderComments(lines: string | string[]): string { lines = FormatHelpers.breakLines(lines); return lines.map(line => `// ${line}`).join('\n'); } - - /* eslint-disable sonarjs/no-duplicate-string */ - toGoType(type: string | undefined, model: CommonModel): string { - switch (type) { - case 'string': - return 'string'; - case 'integer': - return 'int'; - case 'number': - return 'float64'; - case 'boolean': - return 'bool'; - case 'object': - return 'interface{}'; - case 'array': { - if (Array.isArray(model.items)) { - return model.items.length > 1 ? '[]interface{}' : `[]${this.renderType(model.items[0])}`; - } - const arrayType = model.items ? this.renderType(model.items) : 'interface{}'; - return `[]${arrayType}`; - } - default: return 'interface{}'; - } - } } diff --git a/src/generators/go/renderers/EnumRenderer.ts b/src/generators/go/renderers/EnumRenderer.ts index c7fc3af93f..dcc3bddd8d 100644 --- a/src/generators/go/renderers/EnumRenderer.ts +++ b/src/generators/go/renderers/EnumRenderer.ts @@ -1,41 +1,33 @@ import { GoRenderer } from '../GoRenderer'; -import { EnumPreset, CommonModel } from '../../../models'; +import { ConstrainedEnumModel } from '../../../models'; import { FormatHelpers } from '../../../helpers'; +import { EnumPresetType } from '../GoPreset'; +import { GoOptions } from '../GoGenerator'; /** * Renderer for Go's `enum` type * * @extends GoRenderer */ -export class EnumRenderer extends GoRenderer { +export class EnumRenderer extends GoRenderer { public defaultSelf(): string { - const formattedName = this.nameType(this.model.$id); - const type = this.enumType(this.model); - const doc = formattedName && this.renderCommentForEnumType(formattedName, type); + const doc = this.renderCommentForEnumType(this.model.name, this.model.type); // eslint-disable-next-line sonarjs/no-duplicate-string - if (type === 'interface{}') { + if (this.model.type === 'interface{}') { return `${doc} -type ${formattedName} ${type}`; +type ${this.model.name} ${this.model.type}`; } - const enumValues = this.renderConstValuesForEnumType(formattedName, type, this.model.enum); + const enumValues = this.renderConstValuesForEnumType(this.model.name, this.model.type, this.model.values.map((enumValue) => enumValue.value)); return `${doc} -type ${formattedName} ${type} +type ${this.model.name} ${this.model.type} const ( ${this.indent(this.renderBlock(enumValues))} )`; } - enumType(model: CommonModel): string { - if (this.model.type === undefined || Array.isArray(this.model.type)) { - return 'interface{}'; - } - - return this.toGoType(this.model.type, model); - } - renderCommentForEnumType(name: string, type: string): string { const globalType = type === 'interface{}' ? 'mixed types' : type; return this.renderComments(`${name} represents an enum of ${globalType}.`); @@ -61,7 +53,7 @@ ${this.indent(this.renderBlock(enumValues))} } } -export const GO_DEFAULT_ENUM_PRESET: EnumPreset = { +export const GO_DEFAULT_ENUM_PRESET: EnumPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, diff --git a/src/generators/go/renderers/StructRenderer.ts b/src/generators/go/renderers/StructRenderer.ts index e8fb19799e..6688a349b5 100644 --- a/src/generators/go/renderers/StructRenderer.ts +++ b/src/generators/go/renderers/StructRenderer.ts @@ -1,38 +1,49 @@ import { GoRenderer } from '../GoRenderer'; -import { FieldType, StructPreset } from '../GoPreset'; +import { StructPresetType } from '../GoPreset'; +import { ConstrainedObjectModel, ConstrainedObjectPropertyModel } from 'models'; +import { GoOptions } from '../GoGenerator'; /** * Renderer for Go's `struct` type * * @extends GoRenderer */ -export class StructRenderer extends GoRenderer { +export class StructRenderer extends GoRenderer { public async defaultSelf(): Promise { const content = [ await this.renderFields(), await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - const doc = this.renderComments(`${formattedName} represents a ${formattedName} model.`); + const doc = this.renderComments(`${this.model.name} represents a ${this.model.name} model.`); return `${doc} -type ${formattedName} struct { +type ${this.model.name} struct { ${this.indent(this.renderBlock(content, 2))} }`; } + + async renderFields(): Promise { + const fields = this.model.properties || {}; + const content: string[] = []; + + for (const field of Object.values(fields)) { + const renderField = await this.runFieldPreset(field); + content.push(renderField); + } + return this.renderBlock(content); + } + + runFieldPreset(field: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('field', { field }); + } } -export const GO_DEFAULT_STRUCT_PRESET: StructPreset = { +export const GO_DEFAULT_STRUCT_PRESET: StructPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, - field({ fieldName, field, renderer, type }) { - fieldName = renderer.nameField(fieldName, field); - let fieldType = renderer.renderType(field); - if (type === FieldType.additionalProperty || type === FieldType.patternProperties) { - fieldType = `map[string]${fieldType}`; - } - return `${ fieldName } ${ fieldType }`; + field({ field }) { + return `${ field } ${ field.property.type }`; }, };