Skip to content

Commit

Permalink
init dart generator v0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
rmasarovic committed Feb 23, 2022
1 parent 810d607 commit 8ca20db
Show file tree
Hide file tree
Showing 17 changed files with 663 additions and 0 deletions.
11 changes: 11 additions & 0 deletions examples/generate-dart-models/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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();
}",
]
`;
13 changes: 13 additions & 0 deletions examples/generate-dart-models/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
22 changes: 22 additions & 0 deletions examples/generate-dart-models/index.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
const models = await generator.generate(jsonSchemaDraft7);
for (const model of models) {
console.log(model.result);
}
}
generate();
10 changes: 10 additions & 0 deletions examples/generate-dart-models/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions examples/generate-dart-models/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
69 changes: 69 additions & 0 deletions src/generators/dart/Constants.ts
Original file line number Diff line number Diff line change
@@ -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);
}
24 changes: 24 additions & 0 deletions src/generators/dart/DartFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -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<DartRenderCompleteModelOptions> {
/**
* 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<string, unknown> | CommonInputModel, outputDirectory: string, options: DartRenderCompleteModelOptions): Promise<OutputModel[]> {
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;
}
}
104 changes: 104 additions & 0 deletions src/generators/dart/DartGenerator.ts
Original file line number Diff line number Diff line change
@@ -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<DartPreset> {
collectionType?: 'List';
namingConvention?: CommonNamingConvention;
}

export interface DartRenderCompleteModelOptions {
packageName: string;
}

export class DartGenerator extends AbstractGenerator<DartOptions, DartRenderCompleteModelOptions> {
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<RenderOutput> {
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<RenderOutput> {
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<RenderOutput> {
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<RenderOutput> {
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});
}
}
13 changes: 13 additions & 0 deletions src/generators/dart/DartPreset.ts
Original file line number Diff line number Diff line change
@@ -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<ClassRenderer>;
enum: EnumPreset<EnumRenderer>;
}>;

export const DART_DEFAULT_PRESET: DartPreset = {
class: DART_DEFAULT_CLASS_PRESET,
enum: DART_DEFAULT_ENUM_PRESET,
};
Loading

0 comments on commit 8ca20db

Please sign in to comment.