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

feat: add JSDoc style comment support to Typescript generator #621

Merged
merged 14 commits into from
Mar 9, 2022
8 changes: 7 additions & 1 deletion docs/languages/TypeScript.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ There are special use-cases that each language supports; this document pertains
- [Generate un/marshal functions for classes](#generate-unmarshal-functions-for-classes)
- [Generate example data function](#generate-example-data-function)
- [Rendering complete models to a specific module system](#rendering-complete-models-to-a-specific-module-system)
- [Rendering comments from description and example fields](#rendering-comments-from-description-and-example-fields)

<!-- tocstop -->

Expand Down Expand Up @@ -58,4 +59,9 @@ In some cases you might need to render the complete models to a specific module

Check out this [example for a live demonstration how to generate the complete TypeScript models to use ESM module system](../../examples/typescript-use-esm).

Check out this [example for a live demonstration how to generate the complete TypeScript models to use CJS module system](../../examples/typescript-use-cjs).
Check out this [example for a live demonstration how to generate the complete TypeScript models to use CJS module system](../../examples/typescript-use-cjs).

## Rendering comments from description and example fields
You can use the `TS_DESCRIPTION_PRESET` to generate JSDoc style comments from description and example fields in your model.

See [this example](../../examples/typescript-generate-comments) for how this can be used.
2 changes: 2 additions & 0 deletions docs/presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Each language has different model types, which results in different implementabl

Below is a custom preset written for TypeScript language, which adds a description to each interface's property and to the model itself as a JavaScript comment.

You can find the full preset at [typescript/presets/DescriptionPreset.ts](../src/generators/typescript/presets/DescriptionPreset.ts)

```ts
import { TypeScriptGenerator } from '@asyncapi/modelina';

Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ This directory contains a series of self-contained examples that you can use as
- [generate-typescript-models](./generate-typescript-models) - A basic example to generate TypeScript data models
- [typescript-interface](./typescript-interface) - A basic TypeScript generator that outputs interfaces.
- [typescript-enum-type](./typescript-enum-type) - A basic example of how to use Modelina can output different types of enums in TypeScript.
- [typescript-generate-marshalling](./typescript-generate-marshalling) - A basic example of how to use the un/marshalling functionality of the typescript class.
- [typescript-generate-example](./typescript-generate-example) - A basic example of how to use Modelina and output a TypeScript class with an example function.
- [typescript-generate-marshalling](./typescript-generate-marshalling) - A basic example of how to use the un/marshalling functionality of the typescript class.
- [typescript-generate-comments](./typescript-generate-comments) - A basic example of how to generate TypeScript interfaces with comments from description and example fields.
- [typescript-use-esm](./typescript-use-esm) - A basic example that generate the models to use ESM module system.
- [typescript-use-cjs](./typescript-use-cjs) - A basic example that generate the models to use CJS module system.
- [indentation-type-and-size](./indentation-type-and-size) - This example shows how to change the indentation type and size of the generated model.
Expand Down
17 changes: 17 additions & 0 deletions examples/typescript-generate-comments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TypeScript Data Models with comment generation functionality

A basic example of how to generate comments from descriptions with the TypeScript generator.

## How to run this example

Run this example using:

```sh
npm i && npm run start
```

If you are on Windows, use the `start:windows` script instead:

```sh
npm i && npm run start:windows
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Should be able to generate ts data model with comments and should log expected output to console 1`] = `
Array [
Array [
"/**
* Main Description
*/
interface Test {
stringProp: string;
/**
* Description
* @example Example
*/
numberProp?: number;
objectProp?: NestedTest;
additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;
}",
],
Array [
"/**
* @example Example 1, Example 2
*/
interface NestedTest {
stringProp?: string;
additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;
}",
],
Array [
"/**
* Main Description
*/
interface Test {
stringProp: string;
/**
* Description
* @example Example
*/
numberProp?: number;
objectProp?: NestedTest;
additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;
}",
],
Array [
"/**
* @example Example 1, Example 2
*/
interface NestedTest {
stringProp?: string;
additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;
}",
],
]
`;
13 changes: 13 additions & 0 deletions examples/typescript-generate-comments/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 generate ts data model with comments', () => {
afterAll(() => {
jest.restoreAllMocks();
});
test('and should log expected output to console', async () => {
await generate();
expect(spy.mock.calls.length).toEqual(4);
expect(spy.mock.calls).toMatchSnapshot();
});
});
38 changes: 38 additions & 0 deletions examples/typescript-generate-comments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {TS_DESCRIPTION_PRESET, TypeScriptGenerator} from '../../src';

const generator = new TypeScriptGenerator({
modelType: 'interface',
presets: [
TS_DESCRIPTION_PRESET,
]
});
const jsonSchemaDraft7 = {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'Test',
type: 'object',
additionalProperties: true,
required: ['string prop'],
description: 'Main Description',
properties: {
'string prop': { type: 'string' },
numberProp: {
type: 'number',
description: 'Description',
examples: 'Example',
},
objectProp: {
type: 'object',
$id: 'NestedTest',
properties: { stringProp: { type: 'string' } },
examples: ['Example 1', 'Example 2'],
},
},
};

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/typescript-generate-comments/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/typescript-generate-comments/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"example_name": "typescript-generate-comments"
},
"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"
}
}
62 changes: 62 additions & 0 deletions src/generators/typescript/presets/DescriptionPreset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { CommonModel } from '../../../models';
import { TypeScriptPreset } from '../TypeScriptPreset';
import { TypeScriptRenderer } from '../TypeScriptRenderer';

const renderDescription = ({
renderer,
content,
item,
}: {
renderer: TypeScriptRenderer;
content: string;
item: CommonModel;
}): string => {
const desc = item.getFromOriginalInput('description')?.trim();
const examples = item.getFromOriginalInput('examples');
const formattedExamples = `@example ${
examples?.join ? examples.join(', ') : examples
}`;

if (desc || examples) {
const doc = renderer.renderComments(
`${desc || ''}\n${examples ? formattedExamples : ''}`.trim()
);
return `${doc}\n${content}`;
}

return content;
};

/**
* Preset which adds descriptions
*
* @implements {TypeScriptPreset}
*/
export const TS_DESCRIPTION_PRESET: TypeScriptPreset = {
class: {
self({ renderer, model, content }) {
return renderDescription({ renderer, content, item: model });
},
property({ renderer, property, content }) {
return renderDescription({ renderer, content, item: property });
}
},
interface: {
self({ renderer, model, content }) {
return renderDescription({ renderer, content, item: model });
},
property({ renderer, property, content }) {
return renderDescription({ renderer, content, item: property });
}
},
type: {
self({ renderer, model, content }) {
return renderDescription({ renderer, content, item: model });
},
},
enum: {
self({ renderer, model, content }) {
return renderDescription({ renderer, content, item: model });
},
},
};
1 change: 1 addition & 0 deletions src/generators/typescript/presets/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './CommonPreset';
export * from './DescriptionPreset';
47 changes: 47 additions & 0 deletions test/generators/typescript/preset/DescriptionPreset.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {TS_DESCRIPTION_PRESET, TypeScriptGenerator} from '../../../../src';

const doc = {
$id: 'Test',
type: 'object',
additionalProperties: true,
required: ['string prop'],
description: 'Main Description',
properties: {
'string prop': { type: 'string' },
numberProp: {
type: 'number',
description: 'Description',
examples: 'Example',
},
objectProp: {
type: 'object',
$id: 'NestedTest',
properties: { stringProp: { type: 'string' } },
examples: ['Example 1', 'Example 2'],
},
},
};

describe('Description generation', () => {
let generator: TypeScriptGenerator;
beforeEach(() => {
generator = new TypeScriptGenerator({
presets: [TS_DESCRIPTION_PRESET],
});
});

test('should render example function for model', async () => {
const inputModel = await generator.process(doc);
const testModel = inputModel.models['Test'];
const nestedTestModel = inputModel.models['NestedTest'];

const testClass = await generator.renderClass(testModel, inputModel);
const nestedTestClass = await generator.renderClass(
nestedTestModel,
inputModel
);

expect(testClass.result).toMatchSnapshot();
expect(nestedTestClass.result).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Description generation should render example function for model 1`] = `
"/**
* Main Description
*/
class Test {
private _stringProp: string;
/**
* Description
* @example Example
*/
private _numberProp?: number;
private _objectProp?: NestedTest;
private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;

constructor(input: {
stringProp: string,
numberProp?: number,
objectProp?: NestedTest,
}) {
this._stringProp = input.stringProp;
this._numberProp = input.numberProp;
this._objectProp = input.objectProp;
}

get stringProp(): string { return this._stringProp; }
set stringProp(stringProp: string) { this._stringProp = stringProp; }

get numberProp(): number | undefined { return this._numberProp; }
set numberProp(numberProp: number | undefined) { this._numberProp = numberProp; }

get objectProp(): NestedTest | undefined { return this._objectProp; }
set objectProp(objectProp: NestedTest | undefined) { this._objectProp = objectProp; }

get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null> | undefined { return this._additionalProperties; }
set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null> | undefined) { this._additionalProperties = additionalProperties; }
}"
`;

exports[`Description generation should render example function for model 2`] = `
"/**
* @example Example 1, Example 2
*/
class NestedTest {
private _stringProp?: string;
private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null>;

constructor(input: {
stringProp?: string,
}) {
this._stringProp = input.stringProp;
}

get stringProp(): string | undefined { return this._stringProp; }
set stringProp(stringProp: string | undefined) { this._stringProp = stringProp; }

get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null> | undefined { return this._additionalProperties; }
set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null> | undefined) { this._additionalProperties = additionalProperties; }
}"
`;