From c4f2a55146054eed74557e9e5ff0978a2a20e7b9 Mon Sep 17 00:00:00 2001 From: rmasarovic Date: Wed, 23 Feb 2022 20:43:00 +0100 Subject: [PATCH] feat: add dart generator. --- .../__snapshots__/index.spec.ts.snap | 11 ++ examples/generate-dart-models/index.spec.ts | 13 ++ examples/generate-dart-models/index.ts | 22 +++ .../generate-dart-models/package-lock.json | 10 ++ examples/generate-dart-models/package.json | 12 ++ src/generators/dart/Constants.ts | 69 ++++++++ src/generators/dart/DartFileGenerator.ts | 24 +++ src/generators/dart/DartGenerator.ts | 104 +++++++++++ src/generators/dart/DartPreset.ts | 13 ++ src/generators/dart/DartRenderer.ts | 167 ++++++++++++++++++ src/generators/dart/index.ts | 5 + .../dart/presets/JsonSerializablePreset.ts | 34 ++++ src/generators/dart/presets/index.ts | 2 + .../dart/renderers/ClassRenderer.ts | 82 +++++++++ src/generators/dart/renderers/EnumRenderer.ts | 87 +++++++++ src/generators/index.ts | 1 + src/helpers/FormatHelpers.ts | 7 + 17 files changed, 663 insertions(+) create mode 100644 examples/generate-dart-models/__snapshots__/index.spec.ts.snap create mode 100644 examples/generate-dart-models/index.spec.ts create mode 100644 examples/generate-dart-models/index.ts create mode 100644 examples/generate-dart-models/package-lock.json create mode 100644 examples/generate-dart-models/package.json create mode 100644 src/generators/dart/Constants.ts create mode 100644 src/generators/dart/DartFileGenerator.ts create mode 100644 src/generators/dart/DartGenerator.ts create mode 100644 src/generators/dart/DartPreset.ts create mode 100644 src/generators/dart/DartRenderer.ts create mode 100644 src/generators/dart/index.ts create mode 100644 src/generators/dart/presets/JsonSerializablePreset.ts create mode 100644 src/generators/dart/presets/index.ts create mode 100644 src/generators/dart/renderers/ClassRenderer.ts create mode 100644 src/generators/dart/renderers/EnumRenderer.ts diff --git a/examples/generate-dart-models/__snapshots__/index.spec.ts.snap b/examples/generate-dart-models/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..5f0c97ece6 --- /dev/null +++ b/examples/generate-dart-models/__snapshots__/index.spec.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should be able to render Dart Models and should log expected output to console 1`] = ` +Array [ + "class Root { + Object? email; + + Root(); +}", +] +`; diff --git a/examples/generate-dart-models/index.spec.ts b/examples/generate-dart-models/index.spec.ts new file mode 100644 index 0000000000..04d73e2656 --- /dev/null +++ b/examples/generate-dart-models/index.spec.ts @@ -0,0 +1,13 @@ +const spy = jest.spyOn(global.console, 'log').mockImplementation(() => { return; }); +import {generate} from './index'; + +describe('Should be able to render Dart Models', () => { + afterAll(() => { + jest.restoreAllMocks(); + }); + test('and should log expected output to console', async () => { + await generate(); + expect(spy.mock.calls.length).toEqual(2); + expect(spy.mock.calls[1]).toMatchSnapshot(); + }); +}); diff --git a/examples/generate-dart-models/index.ts b/examples/generate-dart-models/index.ts new file mode 100644 index 0000000000..a99ca0d90b --- /dev/null +++ b/examples/generate-dart-models/index.ts @@ -0,0 +1,22 @@ +import {DART_JSON_PRESET, DartGenerator} from '../../src'; + +const generator = new DartGenerator(); +const jsonSchemaDraft7 = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + additionalProperties: false, + properties: { + email: { + type: 'string', + format: 'email' + } + } +};; + +export async function generate() : Promise { + const models = await generator.generate(jsonSchemaDraft7); + for (const model of models) { + console.log(model.result); + } +} +generate(); diff --git a/examples/generate-dart-models/package-lock.json b/examples/generate-dart-models/package-lock.json new file mode 100644 index 0000000000..d097d2e80d --- /dev/null +++ b/examples/generate-dart-models/package-lock.json @@ -0,0 +1,10 @@ +{ + "name": "generate-dart-models", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "hasInstallScript": true + } + } +} diff --git a/examples/generate-dart-models/package.json b/examples/generate-dart-models/package.json new file mode 100644 index 0000000000..15936b78b6 --- /dev/null +++ b/examples/generate-dart-models/package.json @@ -0,0 +1,12 @@ +{ + "config": { + "example_name": "generate-dart-models" + }, + "scripts": { + "install": "cd ../.. && npm i", + "start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts", + "start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts", + "test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts", + "test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts" + } +} diff --git a/src/generators/dart/Constants.ts b/src/generators/dart/Constants.ts new file mode 100644 index 0000000000..691fbe8837 --- /dev/null +++ b/src/generators/dart/Constants.ts @@ -0,0 +1,69 @@ +export const RESERVED_DART_KEYWORDS = [ + 'abstract', + 'as', + 'assert', + 'async', + 'await', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'covariant', + 'default', + 'deferred', + 'do', + 'dynamic', + 'else', + 'enum', + 'export', + 'extends', + 'extension', + 'external', + 'factory', + 'false', + 'final', + 'for', + 'Function', + 'get', + 'get', + 'hide', + 'if', + 'implements', + 'import', + 'in', + 'interface', + 'is', + 'late', + 'library', + 'mixin', + 'new', + 'null', + 'on', + 'operator', + 'part', + 'required', + 'rethrow', + 'return', + 'set', + 'show', + 'static', + 'super', + 'switch', + 'sync', + 'this', + 'throw', + 'true', + 'try', + 'typedef', + 'var', + 'void', + 'while', + 'with', + 'yield', +]; + +export function isReservedDartKeyword(word: string): boolean { + return RESERVED_DART_KEYWORDS.includes(word); +} diff --git a/src/generators/dart/DartFileGenerator.ts b/src/generators/dart/DartFileGenerator.ts new file mode 100644 index 0000000000..594750705d --- /dev/null +++ b/src/generators/dart/DartFileGenerator.ts @@ -0,0 +1,24 @@ +import { DartGenerator, DartRenderCompleteModelOptions } from './'; +import { CommonInputModel, OutputModel } from '../../models'; +import * as path from 'path'; +import { AbstractFileGenerator } from '../AbstractFileGenerator'; +import { FileHelpers } from '../../helpers'; + +export class DartFileGenerator extends DartGenerator implements AbstractFileGenerator { + /** + * Generates all the models to an output directory each model with their own separate files. + * + * @param input + * @param outputDirectory where you want the models generated to + * @param options + */ + public async generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options: DartRenderCompleteModelOptions): Promise { + let generatedModels = await this.generateCompleteModels(input, options); + generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; }); + for (const outputModel of generatedModels) { + const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.dart`); + await FileHelpers.writerToFileSystem(outputModel.result, filePath); + } + return generatedModels; + } +} diff --git a/src/generators/dart/DartGenerator.ts b/src/generators/dart/DartGenerator.ts new file mode 100644 index 0000000000..1b13b95cf8 --- /dev/null +++ b/src/generators/dart/DartGenerator.ts @@ -0,0 +1,104 @@ +import { + AbstractGenerator, + CommonGeneratorOptions, + defaultGeneratorOptions +} from '../AbstractGenerator'; +import {CommonModel, CommonInputModel, RenderOutput} from '../../models'; +import {CommonNamingConvention, CommonNamingConventionImplementation, ModelKind, TypeHelpers} from '../../helpers'; +import {DartPreset, DART_DEFAULT_PRESET} from './DartPreset'; +import {ClassRenderer} from './renderers/ClassRenderer'; +import {EnumRenderer} from './renderers/EnumRenderer'; +import {isReservedDartKeyword} from './Constants'; +import {Logger} from '../../'; +import {FormatHelpers} from '../../helpers/FormatHelpers'; + +export interface DartOptions extends CommonGeneratorOptions { + collectionType?: 'List'; + namingConvention?: CommonNamingConvention; +} + +export interface DartRenderCompleteModelOptions { + packageName: string; +} + +export class DartGenerator extends AbstractGenerator { + static defaultOptions: DartOptions = { + ...defaultGeneratorOptions, + defaultPreset: DART_DEFAULT_PRESET, + collectionType: 'List', + namingConvention: CommonNamingConventionImplementation + }; + + constructor( + options: DartOptions = DartGenerator.defaultOptions, + ) { + super('Dart', DartGenerator.defaultOptions, options); + } + + /** + * Render a scattered model, where the source code and library and model dependencies are separated. + * + * @param model + * @param inputModel + */ + render(model: CommonModel, inputModel: CommonInputModel): Promise { + const kind = TypeHelpers.extractKind(model); + // We don't support union in Dart generator, however, if union is an object, we render it as a class. + if (kind === ModelKind.OBJECT || (kind === ModelKind.UNION && model.type?.includes('object'))) { + return this.renderClass(model, inputModel); + } else if (kind === ModelKind.ENUM) { + return this.renderEnum(model, inputModel); + } + Logger.warn(`Dart generator, cannot generate this type of model, ${model.$id}`); + return Promise.resolve(RenderOutput.toRenderOutput({result: '', renderedName: '', dependencies: []})); + } + + /** + * Render a complete model result where the model code, library and model dependencies are all bundled appropriately. + * + * For Dart you need to specify which package the model is placed under. + * + * @param model + * @param inputModel + * @param options used to render the full output + */ + async renderCompleteModel(model: CommonModel, inputModel: CommonInputModel, options: DartRenderCompleteModelOptions): Promise { + if (isReservedDartKeyword(options.packageName)) { + throw new Error(`You cannot use reserved Dart keyword (${options.packageName}) as package name, please use another.`); + } + + const outputModel = await this.render(model, inputModel); + const modelDependencies = model.getNearestDependencies().map((dependencyModelName) => { + const formattedDependencyModelName = this.options.namingConvention?.type ? this.options.namingConvention.type(dependencyModelName, { + inputModel, + model: inputModel.models[String(dependencyModelName)], + reservedKeywordCallback: isReservedDartKeyword + }) : dependencyModelName; + return `import 'package:${options.packageName}/${FormatHelpers.snakeCase(formattedDependencyModelName)}.dart';`; + }); + const outputContent = `${modelDependencies.join('\n')} + ${outputModel.dependencies.join('\n')} + ${outputModel.result}`; + return RenderOutput.toRenderOutput({ + result: outputContent, + renderedName: outputModel.renderedName, + dependencies: outputModel.dependencies + }); + } + + async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise { + const presets = this.getPresets('class'); + const renderer = new ClassRenderer(this.options, this, presets, model, inputModel); + const result = await renderer.runSelfPreset(); + const renderedName = FormatHelpers.snakeCase(renderer.nameType(model.$id, model)); + return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + } + + async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise { + const presets = this.getPresets('enum'); + const renderer = new EnumRenderer(this.options, this, presets, model, inputModel); + const result = await renderer.runSelfPreset(); + const renderedName = FormatHelpers.snakeCase(renderer.nameType(model.$id, model)); + return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + } +} diff --git a/src/generators/dart/DartPreset.ts b/src/generators/dart/DartPreset.ts new file mode 100644 index 0000000000..6d0860927d --- /dev/null +++ b/src/generators/dart/DartPreset.ts @@ -0,0 +1,13 @@ +import { Preset, ClassPreset, EnumPreset } from '../../models'; +import { ClassRenderer, DART_DEFAULT_CLASS_PRESET } from './renderers/ClassRenderer'; +import { EnumRenderer, DART_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer'; + +export type DartPreset = Preset<{ + class: ClassPreset; + enum: EnumPreset; +}>; + +export const DART_DEFAULT_PRESET: DartPreset = { + class: DART_DEFAULT_CLASS_PRESET, + enum: DART_DEFAULT_ENUM_PRESET, +}; diff --git a/src/generators/dart/DartRenderer.ts b/src/generators/dart/DartRenderer.ts new file mode 100644 index 0000000000..83b8b17fbf --- /dev/null +++ b/src/generators/dart/DartRenderer.ts @@ -0,0 +1,167 @@ +import { AbstractRenderer } from '../AbstractRenderer'; +import { DartGenerator, DartOptions } from './DartGenerator'; +import { CommonModel, CommonInputModel, Preset } from '../../models'; +import { FormatHelpers, ModelKind, TypeHelpers } from '../../helpers'; +import { isReservedDartKeyword } from './Constants'; + +/** + * Common renderer for Dart types + * + * @extends AbstractRenderer + */ +export abstract class DartRenderer extends AbstractRenderer { + constructor( + options: DartOptions, + generator: DartGenerator, + presets: Array<[Preset, unknown]>, + model: CommonModel, + inputModel: CommonInputModel, + ) { + 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 and then later used if it 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: isReservedDartKeyword }) + : 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: isReservedDartKeyword }) + : propertyName || ''; + } + + /** + * Renders model(s) to Dart type(s). + * + * @param model + */ + renderType(model: CommonModel | CommonModel[]): string { + if (Array.isArray(model) || Array.isArray(model.type)) { + return 'Object'; // fallback + } + if (model.$ref !== undefined) { + return this.nameType(model.$ref, model); + } + const kind = TypeHelpers.extractKind(model); + if ( + kind === ModelKind.PRIMITIVE || + kind === ModelKind.ARRAY + ) { + const format = model.getFromOriginalInput('format'); + return this.toClassType(this.toDartType(format || model.type, model)); + } + return this.nameType(model.$id, model); + } + + /** + * Returns the Dart corresponding type from CommonModel type or JSON schema format + * @param type + * @param model + */ + toDartType(type: string | undefined, model: CommonModel): string { + switch (type) { + case 'integer': + case 'int32': + case 'long': + case 'int64': + return 'int'; + case 'boolean': + return 'bool'; + case 'date': + return 'LocalDate'; + case 'time': + return 'OffsetTime'; + case 'dateTime': + case 'date-time': + return 'OffsetDateTime?'; + case 'string': + case 'password': + case 'byte': + return 'String'; + case 'float': + case 'double': + case 'number': + return 'double'; + case 'binary': + return 'byte[]'; + case 'array': { + let arrayItemModel = model.items; + //Since Dart dont support tuples, lets make sure that we combine the tuple types to find the appropriate array type + if (Array.isArray(model.items)) { + arrayItemModel = model.items.reduce((prevModel, currentModel) => { + return CommonModel.mergeCommonModels(CommonModel.toCommonModel(prevModel), CommonModel.toCommonModel(currentModel), {}); + }); + //If tuples and additionalItems make sure to find the appropriate type by merging all the tuples and additionalItems model together to find the combined type. + if (model.additionalItems !== undefined) { + arrayItemModel = CommonModel.mergeCommonModels(arrayItemModel, model.additionalItems, {}); + } + } + const newType = arrayItemModel ? this.renderType(arrayItemModel) : 'Object'; + if (this.options.collectionType && this.options.collectionType === 'List') { + return `List<${newType}>`; + } + return `${newType}[]`; + } + default: + return 'Object'; + } + } + + toClassType(type: string): string { + switch (type) { + case 'int': + return 'int'; + case 'long': + return 'long'; + case 'boolean': + return 'bool'; + case 'float': + case 'double': + return 'double'; + default: + return `${type}`; + } + } + + renderComments(lines: string | string[]): string { + lines = FormatHelpers.breakLines(lines); + const newLiteral = lines.map(line => ` * ${line}`).join('\n'); + return `/** +${newLiteral} + */`; + } + + renderAnnotation(annotationName: string, value?: any | Record): string { + const name = `@${FormatHelpers.upperFirst(annotationName)}`; + let values = undefined; + if (value !== undefined) { + if (typeof value === 'object') { + values = Object.entries(value || {}).map(([paramName, newValue]) => { + if (paramName && newValue !== undefined) { + return `${paramName}=${newValue}`; + } + return newValue; + }).filter(v => v !== undefined).join(', '); + } else { + values = value; + } + } + return values !== undefined ? `${name}(${values})` : name; + } +} diff --git a/src/generators/dart/index.ts b/src/generators/dart/index.ts new file mode 100644 index 0000000000..4c10bb3c57 --- /dev/null +++ b/src/generators/dart/index.ts @@ -0,0 +1,5 @@ +export * from './DartGenerator'; +export * from './DartFileGenerator'; +export { DART_DEFAULT_PRESET } from './DartPreset'; +export type { DartPreset } from './DartPreset'; +export * from './presets'; diff --git a/src/generators/dart/presets/JsonSerializablePreset.ts b/src/generators/dart/presets/JsonSerializablePreset.ts new file mode 100644 index 0000000000..d545982e62 --- /dev/null +++ b/src/generators/dart/presets/JsonSerializablePreset.ts @@ -0,0 +1,34 @@ +import {DartPreset} from '../DartPreset'; +import {FormatHelpers} from '../../../helpers/FormatHelpers'; + +/** + * Preset which adds `json_serializable` related annotations to class's property getters. + * + * @implements {DartPreset} + */ +export const DART_JSON_PRESET: DartPreset = { + class: { + self({renderer, model, content}) { + renderer.addDependency('import \'package:json_annotation/json_annotation.dart\';'); + const formattedModelName = renderer.nameType(model.$id); + const snakeformattedModelName = FormatHelpers.snakeCase(formattedModelName); + renderer.addDependency(`part '${snakeformattedModelName}.g.dart';`); + renderer.addDependency('@JsonSerializable()'); + return content; + }, + additionalContent({renderer, model}) { + const formattedModelName = renderer.nameType(model.$id); + return `factory ${formattedModelName}.fromJson(Map json) => _$${formattedModelName}FromJson(json); +Map toJson() => _$${formattedModelName}ToJson(this);`; + } + }, enum: { + self({renderer, model, content}) { + renderer.addDependency('import \'package:json_annotation/json_annotation.dart\';'); + const formattedModelName = renderer.nameType(model.$id); + const snakeformattedModelName = FormatHelpers.snakeCase(formattedModelName); + renderer.addDependency(`part '${snakeformattedModelName}.g.dart';`); + renderer.addDependency('@JsonEnum(alwaysCreate:true)'); + return content; + }, + } +}; diff --git a/src/generators/dart/presets/index.ts b/src/generators/dart/presets/index.ts new file mode 100644 index 0000000000..82b477a887 --- /dev/null +++ b/src/generators/dart/presets/index.ts @@ -0,0 +1,2 @@ +export * from './JsonSerializablePreset'; + diff --git a/src/generators/dart/renderers/ClassRenderer.ts b/src/generators/dart/renderers/ClassRenderer.ts new file mode 100644 index 0000000000..3630aa8588 --- /dev/null +++ b/src/generators/dart/renderers/ClassRenderer.ts @@ -0,0 +1,82 @@ +import {DartRenderer} from '../DartRenderer'; +import {CommonModel, ClassPreset, PropertyType} from '../../../models'; +import {DefaultPropertyNames, getUniquePropertyName} from '../../../helpers'; + +/** + * Renderer for Dart's `class` type + * + * @extends DartRenderer + */ +export class ClassRenderer extends DartRenderer { + async defaultSelf(): Promise { + const content = [ + await this.renderProperties(), + await this.runCtorPreset(), + await this.renderAccessors(), + await this.runAdditionalContentPreset(), + ]; + + const formattedName = this.nameType(`${this.model.$id}`); + return `class ${formattedName} { +${this.indent(this.renderBlock(content, 2))} +}`; + } + + runCtorPreset(): Promise { + return this.runPreset('ctor'); + } + + /** + * Render all the properties for the class. + */ + async renderProperties(): Promise { + 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.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); + } + + runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('property', {propertyName, property, type}); + } + + /** + * Render all the accessors for the properties + */ + + // eslint-disable-next-line require-await + async renderAccessors(): Promise { + const content: string[] = []; + return this.renderBlock(content, 2); + } +} + +export const DART_DEFAULT_CLASS_PRESET: ClassPreset = { + self({renderer}) { + return renderer.defaultSelf(); + }, + property({renderer, propertyName, property, type}) { + propertyName = renderer.nameProperty(propertyName, property); + let propertyType = renderer.renderType(property); + if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { + propertyType = `Map`; + } + return `${propertyType}? ${propertyName};`; + }, + ctor({renderer,model}) { + return `${renderer.nameType(model.$id)}();`; + } +}; diff --git a/src/generators/dart/renderers/EnumRenderer.ts b/src/generators/dart/renderers/EnumRenderer.ts new file mode 100644 index 0000000000..f92f8a8837 --- /dev/null +++ b/src/generators/dart/renderers/EnumRenderer.ts @@ -0,0 +1,87 @@ +import {DartRenderer} from '../DartRenderer'; +import {EnumPreset} from '../../../models'; +import {FormatHelpers} from '../../../helpers'; + +/** + * Renderer for Dart's `enum` type + * + * @extends DartRenderer + */ +export class EnumRenderer extends DartRenderer { + async defaultSelf(): Promise { + const content = [ + await this.renderItems(), + ]; + const formattedName = this.nameType(this.model.$id); + return `enum ${formattedName} { +${this.indent(this.renderBlock(content, 2))} +}`; + } + + async renderItems(): Promise { + const enums = this.model.enum || []; + const items: string[] = []; + + for (const value of enums) { + const renderedItem = await this.runItemPreset(value); + items.push(renderedItem); + } + + const content = items.join(', '); + return `${content}`; + } + + normalizeKey(value: any): string { + let key; + switch (typeof value) { + case 'bigint': + case 'number': { + key = 'number_${value}'; + break; + } + case 'boolean': { + key = `boolean_${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): string { + if (typeof value === 'number') { + return `NUMBER_${value}`; + } + if (typeof value === 'string') { + return `${value}`; + } + if (typeof value === 'object') { + return `${JSON.stringify(value).replace(/"/g, '\\"')}`; + } + return String(value); + } + + runItemPreset(item: any): Promise { + return this.runPreset('item', {item}); + } +} + +export const DART_DEFAULT_ENUM_PRESET: EnumPreset = { + self({renderer}) { + return renderer.defaultSelf(); + }, + item({renderer, item}) { + const value = renderer.normalizeValue(item); + return `${value}`; + }, +}; diff --git a/src/generators/index.ts b/src/generators/index.ts index 1fc1501500..57fcdf36d8 100644 --- a/src/generators/index.ts +++ b/src/generators/index.ts @@ -1,6 +1,7 @@ export * from './AbstractGenerator'; export * from './AbstractRenderer'; export * from './java'; +export * from './dart'; export * from './csharp'; export * from './javascript'; export * from './typescript'; diff --git a/src/helpers/FormatHelpers.ts b/src/helpers/FormatHelpers.ts index 26232ba93a..c2370c97ba 100644 --- a/src/helpers/FormatHelpers.ts +++ b/src/helpers/FormatHelpers.ts @@ -171,4 +171,11 @@ export class FormatHelpers { } return renderedExamples; } + + static snakeCase(renderName: string): string { + return renderName.replace(/\W+/g, ' ') + .split(/ |\B(?=[A-Z])/) + .map(word => word.toLowerCase()) + .join('_'); + } }