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

refactor: convert TS to new constraint setup #736

Merged
merged 5 commits into from
Apr 28, 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
285 changes: 61 additions & 224 deletions src/generators/typescript/TypeScriptConstrainer.ts
Original file line number Diff line number Diff line change
@@ -1,231 +1,68 @@
import { ConstrainedAnyModel, ConstrainedBooleanModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedTupleValueModel, ConstrainedArrayModel, ConstrainedUnionModel, ConstrainedEnumModel, ConstrainedDictionaryModel, ConstrainedEnumValueModel } from '../../models/ConstrainedMetaModel';
import { AnyModel, BooleanModel, FloatModel, IntegerModel, MetaModel, ObjectModel, ReferenceModel, StringModel, TupleModel, ArrayModel, UnionModel, EnumModel, DictionaryModel } from '../../models/MetaModel';
import { defaultPropertyKeyConstraints, PropertyKeyConstraintType } from './constrainer/PropertyKeyConstrainer';
import { defaultModelNameConstraints, ModelNameConstraintType } from './constrainer/ModelNameConstrainer';
import { defaultEnumKeyConstraints, EnumConstraintType } from './constrainer/EnumConstrainer';
import { ConstrainedUnionModel } from '../../models';
import { Logger } from '../../utils';
import { TypeMapping } from '../../helpers';
import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer';
import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer';
import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer';
import { TypeScriptRenderer } from './TypeScriptRenderer';

export interface TypeScriptConstraints {
enumKey: EnumConstraintType,
modelName: ModelNameConstraintType,
propertyKey: PropertyKeyConstraintType,
}

export const DefaultTypeScriptConstraints: TypeScriptConstraints = {
enumKey: defaultEnumKeyConstraints(),
modelName: defaultModelNameConstraints(),
propertyKey: defaultPropertyKeyConstraints()
};

type TypeMappingFunction<T extends ConstrainedMetaModel> = (model: T) => string;

type TypeMapping = {
Object?: TypeMappingFunction<ConstrainedObjectModel>,
Reference?: TypeMappingFunction<ConstrainedReferenceModel>,
Any?: TypeMappingFunction<ConstrainedAnyModel>,
Float?: TypeMappingFunction<ConstrainedFloatModel>,
Integer?: TypeMappingFunction<ConstrainedIntegerModel>,
String?: TypeMappingFunction<ConstrainedStringModel>,
Boolean?: TypeMappingFunction<ConstrainedBooleanModel>,
Tuple?: TypeMappingFunction<ConstrainedTupleModel>,
Array?: TypeMappingFunction<ConstrainedArrayModel>,
Enum?: TypeMappingFunction<ConstrainedEnumModel>,
Union?: TypeMappingFunction<ConstrainedUnionModel>,
Dictionary?: TypeMappingFunction<ConstrainedDictionaryModel>
}

function constrainReferenceModel(constrainedName: string, metaModel: ReferenceModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedReferenceModel {
const constrainedRefModel = constrainMetaModel(metaModel.ref, typeMapping, constrainRules);
const constrainedModel = new ConstrainedReferenceModel(constrainedName, metaModel.originalInput, '', constrainedRefModel);
if (typeMapping.Reference !== undefined) {
constrainedModel.type = typeMapping.Reference(constrainedModel);
} else {
constrainedModel.type = constrainedModel.name;
}
return constrainedModel;
}
function constrainAnyModel(constrainedName: string, metaModel: AnyModel, typeMapping: TypeMapping): ConstrainedAnyModel {
const constrainedModel = new ConstrainedAnyModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Any !== undefined) {
constrainedModel.type = typeMapping.Any(constrainedModel);
} else {
constrainedModel.type = 'any';
}
return constrainedModel;
}
function constrainFloatModel(constrainedName: string, metaModel: FloatModel, typeMapping: TypeMapping): ConstrainedFloatModel {
const constrainedModel = new ConstrainedFloatModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Float !== undefined) {
constrainedModel.type = typeMapping.Float(constrainedModel);
} else {
constrainedModel.type = 'number';
}
return constrainedModel;
}
function constrainIntegerModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel {
const constrainedModel = new ConstrainedIntegerModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Integer !== undefined) {
constrainedModel.type = typeMapping.Integer(constrainedModel);
} else {
constrainedModel.type = 'integer';
}
return constrainedModel;
}
function constrainStringModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel {
const constrainedModel = new ConstrainedStringModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.String !== undefined) {
constrainedModel.type = typeMapping.String(constrainedModel);
} else {
constrainedModel.type = 'string';
}
return constrainedModel;
}
function constrainBooleanModel(constrainedName: string, metaModel: BooleanModel, typeMapping: TypeMapping): ConstrainedBooleanModel {
const constrainedModel = new ConstrainedBooleanModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Boolean !== undefined) {
constrainedModel.type = typeMapping.Boolean(constrainedModel);
} else {
constrainedModel.type = 'boolean';
}
return constrainedModel;
}
function constrainTupleModel(constrainedName: string, metaModel: TupleModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedTupleModel {
const constrainedTupleModels = metaModel.tuple.map((tupleValue) => {
const tupleType = constrainMetaModel(tupleValue.value, typeMapping, constrainRules);
return new ConstrainedTupleValueModel(tupleValue.index, tupleType);
});
const constrainedModel = new ConstrainedTupleModel(constrainedName, metaModel.originalInput, '', constrainedTupleModels);
if (typeMapping.Tuple !== undefined) {
constrainedModel.type = typeMapping.Tuple(constrainedModel);
} else {
const tupleTypes = constrainedTupleModels.map((constrainedType) => {
export const TypeScriptDefaultTypeMapping: TypeMapping<TypeScriptRenderer> = {
Object ({constrainedModel}): string {
return constrainedModel.name;
},
Reference ({constrainedModel}): string {
return constrainedModel.name;
},
Any (): string {
return 'any';
},
Float (): string {
return 'number';
},
Integer (): string {
return 'integer';
},
String (): string {
return 'string';
},
Boolean (): string {
return 'boolean';
},
Tuple ({constrainedModel}): string {
const tupleTypes = constrainedModel.tuple.map((constrainedType) => {
return constrainedType.value.type;
});
const tupleType = `[${tupleTypes.join(', ')}]`;
constrainedModel.type = tupleType;
}
return constrainedModel;
}
function constrainArrayModel(constrainedName: string, metaModel: ArrayModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedArrayModel {
const constrainedValueModel = constrainMetaModel(metaModel.valueModel, typeMapping, constrainRules);
const constrainedModel = new ConstrainedArrayModel(constrainedName, metaModel.originalInput, '', constrainedValueModel);
if (typeMapping.Array !== undefined) {
constrainedModel.type = typeMapping.Array(constrainedModel);
} else {
constrainedModel.type = `${constrainedValueModel.type}[]`;
}
return constrainedModel;
}
function constrainUnionModel(constrainedName: string, metaModel: UnionModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedUnionModel {
const constrainedUnionModels = metaModel.union.map((unionValue) => {
return constrainMetaModel(unionValue, typeMapping, constrainRules);
});
const constrainedModel = new ConstrainedUnionModel(constrainedName, metaModel.originalInput, '', constrainedUnionModels);
if (typeMapping.Union !== undefined) {
constrainedModel.type = typeMapping.Union(constrainedModel);
} else {
constrainedModel.type = constrainedUnionModels.join(' | ');
}
return constrainedModel;
}
function constrainDictionaryModel(constrainedName: string, metaModel: DictionaryModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedDictionaryModel {
let keyModel;
//There is some restrictions on what can be used as keys for dictionaries.
if (metaModel.key instanceof UnionModel) {
Logger.error('Key for dictionary is not allowed to be union type, falling back to any model.');
const anyModel = new AnyModel(metaModel.key.name, metaModel.key.originalInput);
keyModel = constrainMetaModel(anyModel, typeMapping, constrainRules);
} else {
keyModel = constrainMetaModel(metaModel.key, typeMapping, constrainRules);
}
const valueModel = constrainMetaModel(metaModel.value, typeMapping, constrainRules);
const constrainedModel = new ConstrainedDictionaryModel(constrainedName, metaModel.originalInput, '', keyModel, valueModel, metaModel.serializationType);
if (typeMapping.Dictionary !== undefined) {
constrainedModel.type = typeMapping.Dictionary(constrainedModel);
} else {
const type = `{ [name: ${keyModel.type}]: ${valueModel.type} }`;
constrainedModel.type = type;
}
return constrainedModel;
}

function constrainObjectModel(constrainedName: string, objectModel: ObjectModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedObjectModel {
const constrainedObjectModel = new ConstrainedObjectModel(constrainedName, objectModel.originalInput, '', {});
for (const [propertyKey, propertyMetaModel] of Object.entries(objectModel.properties)) {
const constrainedPropertyName = constrainRules.propertyKey({propertyKey, constrainedObjectModel, objectModel});
const constrainedProperty = constrainMetaModel(propertyMetaModel, typeMapping, constrainRules);
constrainedObjectModel.properties[String(constrainedPropertyName)] = constrainedProperty;
}
if (typeMapping.Object !== undefined) {
constrainedObjectModel.type = typeMapping.Object(constrainedObjectModel);
} else {
constrainedObjectModel.type = constrainedName;
}
return constrainedObjectModel;
}

export function ConstrainEnumModel(constrainedName: string, enumModel: EnumModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedEnumModel {
const constrainedModel = new ConstrainedEnumModel(constrainedName, enumModel.originalInput, '', []);

for (const enumValue of enumModel.values) {
const constrainedEnumKey = constrainRules.enumKey({enumKey: String(enumValue.key), enumModel, constrainedEnumModel: constrainedModel});
let normalizedEnumValue;
switch (typeof enumValue.value) {
case 'string':
case 'boolean':
normalizedEnumValue = `"${enumValue.value}"`;
break;
case 'bigint':
case 'number': {
normalizedEnumValue = enumValue.value;
break;
}
case 'object': {
normalizedEnumValue = `'${JSON.stringify(enumValue.value)}'`;
break;
}
default: {
normalizedEnumValue = String(enumValue.value);
}
return `[${tupleTypes.join(', ')}]`;
},
Array ({constrainedModel}): string {
return `${constrainedModel.valueModel.type}[]`;
},
Enum ({constrainedModel}): string {
return constrainedModel.name;
},
Union ({constrainedModel}): string {
const unionTypes = constrainedModel.union.map((unionModel) => {
return unionModel.type;
});
return unionTypes.join(' | ');
},
Dictionary ({constrainedModel}): string {
let keyType;
//There is some restrictions on what can be used as keys for dictionaries.
if (constrainedModel.key instanceof ConstrainedUnionModel) {
Logger.error('Key for dictionary is not allowed to be union type, falling back to any model.');
keyType = 'any';
} else {
keyType = constrainedModel.key.type;
}
const constrainedEnumValueModel = new ConstrainedEnumValueModel(constrainedEnumKey, normalizedEnumValue);
constrainedModel.values.push(constrainedEnumValueModel);
}
if (typeMapping.Enum !== undefined) {
constrainedModel.type = typeMapping.Enum(constrainedModel);
} else {
constrainedModel.type = constrainedName;
}
return constrainedModel;
}

export function constrainMetaModel(metaModel: MetaModel, typeMapping: TypeMapping, constrainRules: TypeScriptConstraints): ConstrainedMetaModel {
const constrainedName = constrainRules.modelName({modelName: metaModel.name});

if (metaModel instanceof ObjectModel) {
return constrainObjectModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof ReferenceModel) {
return constrainReferenceModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof AnyModel) {
return constrainAnyModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof FloatModel) {
return constrainFloatModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof IntegerModel) {
return constrainIntegerModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof StringModel) {
return constrainStringModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof BooleanModel) {
return constrainBooleanModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof TupleModel) {
return constrainTupleModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof ArrayModel) {
return constrainArrayModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof UnionModel) {
return constrainUnionModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof EnumModel) {
return ConstrainEnumModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof DictionaryModel) {
return constrainDictionaryModel(constrainedName, metaModel, typeMapping, constrainRules);
return `{ [name: ${keyType}]: ${constrainedModel.value.type} }`;
}
throw new Error('Could not constrain model');
}
};

export const TypeScriptDefaultConstraints = {
enumKey: defaultEnumKeyConstraints(),
enumValue: defaultEnumValueConstraints(),
modelName: defaultModelNameConstraints(),
propertyKey: defaultPropertyKeyConstraints()
};
16 changes: 12 additions & 4 deletions src/generators/typescript/TypeScriptGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ import {
defaultGeneratorOptions
} from '../AbstractGenerator';
import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
import { TypeHelpers, ModelKind, CommonNamingConvention, CommonNamingConventionImplementation } from '../../helpers';
import { TypeHelpers, ModelKind, CommonNamingConvention, CommonNamingConventionImplementation, TypeMapping, Constraints } from '../../helpers';
import { TS_EXPORT_KEYWORD_PRESET } from './presets';
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<TypeScriptPreset> {
renderTypes?: boolean;
modelType?: 'class' | 'interface';
enumType?: 'enum' | 'union';
namingConvention?: CommonNamingConvention;
typeMapping: TypeMapping<TypeScriptRenderer>;
constraints: Constraints
}
export interface TypeScriptRenderCompleteModelOptions {
moduleSystem?: 'ESM' | 'CJS';
Expand All @@ -34,13 +38,17 @@ export class TypeScriptGenerator extends AbstractGenerator<TypeScriptOptions,Typ
modelType: 'class',
enumType: 'enum',
defaultPreset: TS_DEFAULT_PRESET,
namingConvention: CommonNamingConventionImplementation
namingConvention: CommonNamingConventionImplementation,
typeMapping: TypeScriptDefaultTypeMapping,
constraints: TypeScriptDefaultConstraints
};

constructor(
options: TypeScriptOptions = TypeScriptGenerator.defaultOptions,
options: Partial<TypeScriptOptions> = TypeScriptGenerator.defaultOptions,
) {
super('TypeScript', TypeScriptGenerator.defaultOptions, options);
const mergedOptions = {...TypeScriptGenerator.defaultOptions, ...options};

super('TypeScript', TypeScriptGenerator.defaultOptions, mergedOptions);
}

/**
Expand Down
Loading