Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: refactored go generator #771

Merged
merged 4 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/generators/go/GoConstrainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GoRenderer> = {
export const GoDefaultTypeMapping: TypeMapping<GoOptions> = {
Object ({constrainedModel}): string {
return constrainedModel.name;
},
Expand Down
4 changes: 2 additions & 2 deletions src/generators/go/GoFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string, unknown> | CommonInputModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise<OutputModel[]> {
public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise<OutputModel[]> {
let generatedModels = await this.generateCompleteModels(input, options);
generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
for (const outputModel of generatedModels) {
Expand Down
100 changes: 36 additions & 64 deletions src/generators/go/GoGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GoPreset> {
namingConvention?: GoNamingConvention;
typeMapping: TypeMapping<GoRenderer>;
typeMapping: TypeMapping<GoOptions>;
constraints: Constraints
}

Expand All @@ -65,33 +27,45 @@ export class GoGenerator extends AbstractGenerator<GoOptions, GoRenderCompleteMo
static defaultOptions: GoOptions = {
...defaultGeneratorOptions,
defaultPreset: GO_DEFAULT_PRESET,
namingConvention: GoNamingConventionImplementation,
typeMapping: GoDefaultTypeMapping,
constraints: GoDefaultConstraints
};
constructor(
options: Partial<GoOptions> = 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<RenderOutput> {
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<RenderOutput> {
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: [] }));
}

Expand All @@ -102,7 +76,7 @@ export class GoGenerator extends AbstractGenerator<GoOptions, GoRenderCompleteMo
* @param inputModel
* @param options
*/
async renderCompleteModel(model: CommonModel, inputModel: CommonInputModel, options: GoRenderCompleteModelOptions): Promise<RenderOutput> {
async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: GoRenderCompleteModelOptions): Promise<RenderOutput> {
const outputModel = await this.render(model, inputModel);
let importCode = '';
if (outputModel.dependencies.length > 0) {
Expand All @@ -118,19 +92,17 @@ ${outputModel.result}`;
return RenderOutput.toRenderOutput({ result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies });
}

async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise<RenderOutput> {
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<RenderOutput> {
async renderStruct(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise<RenderOutput> {
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 });
}
}
25 changes: 10 additions & 15 deletions src/generators/go/GoPreset.ts
Original file line number Diff line number Diff line change
@@ -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<R extends AbstractRenderer, O extends object = any> extends CommonPreset<R, O> {
field?: (args: PresetArgs<R, O> & FieldArgs) => Promise<string> | string;
export interface StructPreset<R extends AbstractRenderer, O> extends CommonPreset<R, O, ConstrainedObjectModel> {
field?: (args: PresetArgs<R, O, ConstrainedObjectModel> & FieldArgs) => Promise<string> | string;
}
export type StructPresetType<O> = StructPreset<StructRenderer, O>;
export type EnumPresetType<O> = EnumPreset<EnumRenderer, O>;

export type GoPreset<O extends object = any> = Preset<{
struct: StructPreset<StructRenderer, O>;
enum: EnumPreset<EnumRenderer, O>
export type GoPreset<O = GoOptions> = Preset<{
struct: StructPresetType<O>;
enum: EnumPresetType<O>
}>;

export const GO_DEFAULT_PRESET: GoPreset = {
Expand Down
103 changes: 4 additions & 99 deletions src/generators/go/GoRenderer.ts
Original file line number Diff line number Diff line change
@@ -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<GoOptions> {
export abstract class GoRenderer<RendererModelType extends ConstrainedMetaModel> extends AbstractRenderer<GoOptions, GoGenerator, RendererModelType> {
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<string> {
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<string> {
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{}';
}
}
}
Loading