Skip to content

Commit d9bae0d

Browse files
committedAug 20, 2020
Merge branch 'qbcbyb-descriptionFromCommentInPlugin'
2 parents 5de276c + b2daad6 commit d9bae0d

13 files changed

+346
-31
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/.idea
66
/.awcache
77
/.vscode
8+
.history
89

910
# misc
1011
npm-debug.log

‎lib/plugin/compiler-plugin.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const before = (options?: Record<string, any>, program?: ts.Program) => {
1717
return modelClassVisitor.visit(sf, ctx, program, options);
1818
}
1919
if (isFilenameMatched(options.controllerFileNameSuffix, sf.fileName)) {
20-
return controllerClassVisitor.visit(sf, ctx, program);
20+
return controllerClassVisitor.visit(sf, ctx, program, options);
2121
}
2222
return sf;
2323
};

‎lib/plugin/merge-options.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ export interface PluginOptions {
44
dtoFileNameSuffix?: string | string[];
55
controllerFileNameSuffix?: string | string[];
66
classValidatorShim?: boolean;
7+
dtoKeyOfComment?: string;
8+
controllerKeyOfComment?: string;
9+
introspectComments?: boolean;
710
}
811

912
const defaultOptions: PluginOptions = {
1013
dtoFileNameSuffix: ['.dto.ts', '.entity.ts'],
1114
controllerFileNameSuffix: ['.controller.ts'],
12-
classValidatorShim: true
15+
classValidatorShim: true,
16+
dtoKeyOfComment: 'description',
17+
controllerKeyOfComment: 'description',
18+
introspectComments: false
1319
};
1420

1521
export const mergePluginOptions = (

‎lib/plugin/utils/ast-utils.ts

+48
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import {
22
CallExpression,
3+
CommentRange,
34
Decorator,
5+
getLeadingCommentRanges,
6+
getTrailingCommentRanges,
47
Identifier,
58
LeftHandSideExpression,
69
Node,
710
ObjectFlags,
811
ObjectType,
912
PropertyAccessExpression,
13+
SourceFile,
1014
SyntaxKind,
1115
Type,
1216
TypeChecker,
@@ -98,6 +102,50 @@ export function getDefaultTypeFormatFlags(enclosingNode: Node) {
98102
return formatFlags;
99103
}
100104

105+
export function getMainCommentAndExamplesOfNode(
106+
node: Node,
107+
sourceFile: SourceFile,
108+
includeExamples?: boolean
109+
): [string, string[]] {
110+
const sourceText = sourceFile.getFullText();
111+
const replaceRegex = /^ *\** *@.*$|^ *\/\*+ *|^ *\/\/+.*|^ *\/+ *|^ *\*+ *| +$| *\**\/ *$/gim;
112+
113+
const commentResult = [];
114+
const examplesResult = [];
115+
const introspectCommentsAndExamples = (comments?: CommentRange[]) =>
116+
comments?.forEach((comment) => {
117+
const commentSource = sourceText.substring(comment.pos, comment.end);
118+
const oneComment = commentSource.replace(replaceRegex, '').trim();
119+
if (oneComment) {
120+
commentResult.push(oneComment);
121+
}
122+
if (includeExamples) {
123+
const regexOfExample = /@example *['"]?([^ ]+?)['"]? *$/gim;
124+
let execResult: RegExpExecArray;
125+
while (
126+
(execResult = regexOfExample.exec(commentSource)) &&
127+
execResult.length > 1
128+
) {
129+
examplesResult.push(execResult[1]);
130+
}
131+
}
132+
});
133+
134+
const leadingCommentRanges = getLeadingCommentRanges(
135+
sourceText,
136+
node.getFullStart()
137+
);
138+
introspectCommentsAndExamples(leadingCommentRanges);
139+
if (!commentResult.length) {
140+
const trailingCommentRanges = getTrailingCommentRanges(
141+
sourceText,
142+
node.getFullStart()
143+
);
144+
introspectCommentsAndExamples(trailingCommentRanges);
145+
}
146+
return [commentResult.join('\n'), examplesResult];
147+
}
148+
101149
export function getDecoratorArguments(decorator: Decorator) {
102150
const callExpression = decorator.expression;
103151
return (callExpression && (callExpression as CallExpression).arguments) || [];

‎lib/plugin/visitors/controller-class.visitor.ts

+93-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { compact, head } from 'lodash';
22
import * as ts from 'typescript';
3-
import { ApiResponse } from '../../decorators';
3+
import { ApiOperation, ApiResponse } from '../../decorators';
4+
import { PluginOptions } from '../merge-options';
45
import { OPENAPI_NAMESPACE } from '../plugin-constants';
5-
import { getDecoratorArguments } from '../utils/ast-utils';
6+
import {
7+
getDecoratorArguments,
8+
getMainCommentAndExamplesOfNode
9+
} from '../utils/ast-utils';
610
import {
711
getDecoratorOrUndefinedByNames,
812
getTypeReferenceAsString,
@@ -15,14 +19,25 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
1519
visit(
1620
sourceFile: ts.SourceFile,
1721
ctx: ts.TransformationContext,
18-
program: ts.Program
22+
program: ts.Program,
23+
options: PluginOptions
1924
) {
2025
const typeChecker = program.getTypeChecker();
2126
sourceFile = this.updateImports(sourceFile);
2227

2328
const visitNode = (node: ts.Node): ts.Node => {
2429
if (ts.isMethodDeclaration(node)) {
25-
return this.addDecoratorToNode(node, typeChecker, sourceFile.fileName);
30+
try {
31+
return this.addDecoratorToNode(
32+
node,
33+
typeChecker,
34+
options,
35+
sourceFile.fileName,
36+
sourceFile
37+
);
38+
} catch {
39+
return node;
40+
}
2641
}
2742
return ts.visitEachChild(node, visitNode, ctx);
2843
};
@@ -32,17 +47,23 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
3247
addDecoratorToNode(
3348
compilerNode: ts.MethodDeclaration,
3449
typeChecker: ts.TypeChecker,
35-
hostFilename: string
50+
options: PluginOptions,
51+
hostFilename: string,
52+
sourceFile: ts.SourceFile
3653
): ts.MethodDeclaration {
3754
const node = ts.getMutableClone(compilerNode);
38-
if (!node.decorators) {
39-
return compilerNode;
40-
}
41-
const { pos, end } = node.decorators;
55+
const nodeArray = node.decorators || ts.createNodeArray();
56+
const { pos, end } = nodeArray;
4257

4358
node.decorators = Object.assign(
4459
[
45-
...node.decorators,
60+
...this.createApiOperationDecorator(
61+
node,
62+
nodeArray,
63+
options,
64+
sourceFile
65+
),
66+
...nodeArray,
4667
ts.createDecorator(
4768
ts.createCall(
4869
ts.createIdentifier(`${OPENAPI_NAMESPACE}.${ApiResponse.name}`),
@@ -63,6 +84,68 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
6384
return node;
6485
}
6586

87+
createApiOperationDecorator(
88+
node: ts.MethodDeclaration,
89+
nodeArray: ts.NodeArray<ts.Decorator>,
90+
options: PluginOptions,
91+
sourceFile: ts.SourceFile
92+
) {
93+
if (!options.introspectComments) {
94+
return [];
95+
}
96+
const keyToGenerate = options.controllerKeyOfComment;
97+
const apiOperationDecorator = getDecoratorOrUndefinedByNames(
98+
[ApiOperation.name],
99+
nodeArray
100+
);
101+
const apiOperationExpr: ts.ObjectLiteralExpression | undefined =
102+
apiOperationDecorator &&
103+
head(getDecoratorArguments(apiOperationDecorator));
104+
const apiOperationExprProperties =
105+
apiOperationExpr &&
106+
(apiOperationExpr.properties as ts.NodeArray<ts.PropertyAssignment>);
107+
108+
if (
109+
!apiOperationDecorator ||
110+
!apiOperationExpr ||
111+
!apiOperationExprProperties ||
112+
!hasPropertyKey(keyToGenerate, apiOperationExprProperties)
113+
) {
114+
const [extractedComments] = getMainCommentAndExamplesOfNode(
115+
node,
116+
sourceFile
117+
);
118+
if (!extractedComments) {
119+
// Node does not have any comments
120+
return [];
121+
}
122+
const properties = [
123+
ts.createPropertyAssignment(
124+
keyToGenerate,
125+
ts.createLiteral(extractedComments)
126+
),
127+
...(apiOperationExprProperties ?? ts.createNodeArray())
128+
];
129+
const apiOperationDecoratorArguments: ts.NodeArray<ts.Expression> = ts.createNodeArray(
130+
[ts.createObjectLiteral(compact(properties))]
131+
);
132+
if (apiOperationDecorator) {
133+
(apiOperationDecorator.expression as ts.CallExpression).arguments = apiOperationDecoratorArguments;
134+
} else {
135+
return [
136+
ts.createDecorator(
137+
ts.createCall(
138+
ts.createIdentifier(`${OPENAPI_NAMESPACE}.${ApiOperation.name}`),
139+
undefined,
140+
apiOperationDecoratorArguments
141+
)
142+
)
143+
];
144+
}
145+
}
146+
return [];
147+
}
148+
66149
createDecoratorObjectLiteralExpr(
67150
node: ts.MethodDeclaration,
68151
typeChecker: ts.TypeChecker,

‎lib/plugin/visitors/model-class.visitor.ts

+69-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import * as ts from 'typescript';
33
import { ApiHideProperty } from '../../decorators';
44
import { PluginOptions } from '../merge-options';
55
import { METADATA_FACTORY_NAME } from '../plugin-constants';
6-
import { getDecoratorArguments, getText, isEnum } from '../utils/ast-utils';
6+
import {
7+
getDecoratorArguments,
8+
getMainCommentAndExamplesOfNode,
9+
getText,
10+
isEnum
11+
} from '../utils/ast-utils';
712
import {
813
extractTypeArgumentIfArray,
914
getDecoratorOrUndefinedByNames,
@@ -39,8 +44,10 @@ export class ModelClassVisitor extends AbstractFileVisitor {
3944
if (hidePropertyDecorator) {
4045
return node;
4146
}
47+
4248
const isPropertyStatic = (node.modifiers || []).some(
43-
(modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword
49+
(modifier: ts.Modifier) =>
50+
modifier.kind === ts.SyntaxKind.StaticKeyword
4451
);
4552
if (isPropertyStatic) {
4653
return node;
@@ -117,7 +124,8 @@ export class ModelClassVisitor extends AbstractFileVisitor {
117124
typeChecker,
118125
ts.createNodeArray(),
119126
options,
120-
hostFilename
127+
hostFilename,
128+
sourceFile
121129
);
122130
this.addClassMetadata(
123131
compilerNode,
@@ -134,9 +142,11 @@ export class ModelClassVisitor extends AbstractFileVisitor {
134142
ts.PropertyAssignment
135143
> = ts.createNodeArray(),
136144
options: PluginOptions = {},
137-
hostFilename = ''
145+
hostFilename = '',
146+
sourceFile?: ts.SourceFile
138147
): ts.ObjectLiteralExpression {
139148
const isRequired = !node.questionToken;
149+
140150
let properties = [
141151
...existingProperties,
142152
!hasPropertyKey('required', existingProperties) &&
@@ -147,6 +157,12 @@ export class ModelClassVisitor extends AbstractFileVisitor {
147157
existingProperties,
148158
hostFilename
149159
),
160+
...this.createDescriptionAndExamplePropertyAssigments(
161+
node,
162+
existingProperties,
163+
options,
164+
sourceFile
165+
),
150166
this.createDefaultPropertyAssignment(node, existingProperties),
151167
this.createEnumPropertyAssignment(
152168
node,
@@ -417,4 +433,53 @@ export class ModelClassVisitor extends AbstractFileVisitor {
417433
}
418434
metadata[propertyName] = objectLiteral;
419435
}
436+
437+
createDescriptionAndExamplePropertyAssigments(
438+
node: ts.PropertyDeclaration | ts.PropertySignature,
439+
existingProperties: ts.NodeArray<
440+
ts.PropertyAssignment
441+
> = ts.createNodeArray(),
442+
options: PluginOptions = {},
443+
sourceFile?: ts.SourceFile
444+
): ts.PropertyAssignment[] {
445+
if (!options.introspectComments || !sourceFile) {
446+
return [];
447+
}
448+
const propertyAssignments = [];
449+
const [comments, examples] = getMainCommentAndExamplesOfNode(
450+
node,
451+
sourceFile,
452+
true
453+
);
454+
455+
const keyOfComment = options.dtoKeyOfComment;
456+
if (!hasPropertyKey(keyOfComment, existingProperties) && comments) {
457+
const descriptionPropertyAssignment = ts.createPropertyAssignment(
458+
keyOfComment,
459+
ts.createLiteral(comments)
460+
);
461+
propertyAssignments.push(descriptionPropertyAssignment);
462+
}
463+
464+
const hasExampleOrExamplesKey =
465+
hasPropertyKey('example', existingProperties) ||
466+
hasPropertyKey('examples', existingProperties);
467+
468+
if (!hasExampleOrExamplesKey && examples.length) {
469+
if (examples.length === 1) {
470+
const examplePropertyAssignment = ts.createPropertyAssignment(
471+
'example',
472+
ts.createLiteral(examples[0])
473+
);
474+
propertyAssignments.push(examplePropertyAssignment);
475+
} else {
476+
const examplesPropertyAssignment = ts.createPropertyAssignment(
477+
'examples',
478+
ts.createArrayLiteral(examples.map((e) => ts.createLiteral(e)))
479+
);
480+
propertyAssignments.push(examplesPropertyAssignment);
481+
}
482+
}
483+
return propertyAssignments;
484+
}
420485
}

‎test/plugin/controller-class-visitor.spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ describe('Controller methods', () => {
1919
const result = ts.transpileModule(appControllerText, {
2020
compilerOptions: options,
2121
fileName: filename,
22-
transformers: { before: [before({}, fakeProgram)] }
22+
transformers: {
23+
before: [
24+
before(
25+
{ controllerKeyOfComment: 'summary', introspectComments: true },
26+
fakeProgram
27+
)
28+
]
29+
}
2330
});
2431
expect(result.outputText).toEqual(appControllerTextTranspiled);
2532
});

‎test/plugin/fixtures/app.controller.ts

+47
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,35 @@
11
export const appControllerText = `import { Controller, Post, HttpStatus } from '@nestjs/common';
2+
import { ApiOperation } from '@nestjs/swagger';
23
34
class Cat {}
45
56
@Controller('cats')
67
export class AppController {
8+
/**
9+
* create a Cat
10+
*
11+
* @returns {Promise<Cat>}
12+
* @memberof AppController
13+
*/
714
@Post()
815
async create(): Promise<Cat> {}
916
17+
/**
18+
* find a Cat
19+
*/
20+
@ApiOperation({})
21+
@Get()
22+
async findOne(): Promise<Cat> {}
23+
24+
/**
25+
* find all Cats im comment
26+
*
27+
* @returns {Promise<Cat>}
28+
* @memberof AppController
29+
*/
30+
@ApiOperation({
31+
description: 'find all Cats',
32+
})
1033
@Get()
1134
@HttpCode(HttpStatus.NO_CONTENT)
1235
async findAll(): Promise<Cat[]> {}
@@ -17,17 +40,41 @@ Object.defineProperty(exports, \"__esModule\", { value: true });
1740
exports.AppController = void 0;
1841
const openapi = require(\"@nestjs/swagger\");
1942
const common_1 = require(\"@nestjs/common\");
43+
const swagger_1 = require("@nestjs/swagger");
2044
class Cat {
2145
}
2246
let AppController = class AppController {
47+
/**
48+
* create a Cat
49+
*
50+
* @returns {Promise<Cat>}
51+
* @memberof AppController
52+
*/
2353
async create() { }
54+
/**
55+
* find a Cat
56+
*/
57+
async findOne() { }
58+
/**
59+
* find all Cats im comment
60+
*
61+
* @returns {Promise<Cat>}
62+
* @memberof AppController
63+
*/
2464
async findAll() { }
2565
};
2666
__decorate([
67+
openapi.ApiOperation({ summary: "create a Cat" }),
2768
common_1.Post(),
2869
openapi.ApiResponse({ status: 201, type: Cat })
2970
], AppController.prototype, \"create\", null);
3071
__decorate([
72+
swagger_1.ApiOperation({ summary: "find a Cat" }),
73+
Get(),
74+
openapi.ApiResponse({ status: 200, type: Cat })
75+
], AppController.prototype, \"findOne\", null);
76+
__decorate([
77+
swagger_1.ApiOperation({ summary: "find all Cats im comment", description: 'find all Cats' }),
3178
Get(),
3279
HttpCode(common_1.HttpStatus.NO_CONTENT),
3380
openapi.ApiResponse({ status: common_1.HttpStatus.NO_CONTENT, type: [Cat] })

‎test/plugin/fixtures/create-cat-alt.dto.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class CreateCatDto2 {
2525
readonly breed?: string | undefined;
2626
nodes: Node[];
2727
alias: AliasedType;
28+
/** NumberAlias */
2829
numberAlias: NumberAlias;
2930
union: 1 | 2;
3031
intersection: Function & string;
@@ -55,7 +56,7 @@ export class CreateCatDto2 {
5556
this.status = Status.ENABLED;
5657
}
5758
static _OPENAPI_METADATA_FACTORY() {
58-
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, breed: { required: false, type: () => String }, nodes: { required: true, type: () => [Object] }, alias: { required: true, type: () => Object }, numberAlias: { required: true, type: () => Number }, union: { required: true, type: () => Object }, intersection: { required: true, type: () => Object }, nested: { required: true, type: () => ({ first: { required: true, type: () => String }, second: { required: true, type: () => Number }, status: { required: true, enum: Status }, tags: { required: true, type: () => [String] }, nodes: { required: true, type: () => [Object] }, alias: { required: true, type: () => Object }, numberAlias: { required: true, type: () => Number } }) } };
59+
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, breed: { required: false, type: () => String }, nodes: { required: true, type: () => [Object] }, alias: { required: true, type: () => Object }, numberAlias: { required: true, type: () => Number, description: "NumberAlias" }, union: { required: true, type: () => Object }, intersection: { required: true, type: () => Object }, nested: { required: true, type: () => ({ first: { required: true, type: () => String }, second: { required: true, type: () => Number }, status: { required: true, enum: Status }, tags: { required: true, type: () => [String] }, nodes: { required: true, type: () => [Object] }, alias: { required: true, type: () => Object }, numberAlias: { required: true, type: () => Number } }) } };
5960
}
6061
}
6162
__decorate([

‎test/plugin/fixtures/create-cat-alt2.dto.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,39 @@ export const createCatDtoAlt2Text = `
22
import { CreateDateColumn, UpdateDateColumn, VersionColumn } from 'typeorm';
33
44
export abstract class Audit {
5+
/** test on createdAt */
56
@CreateDateColumn()
67
createdAt;
78
9+
// test on updatedAt1
10+
// test on updatedAt2
811
@UpdateDateColumn()
912
updatedAt;
1013
14+
/**
15+
* test
16+
* version
17+
* @example '0.0.1'
18+
* @memberof Audit
19+
*/
1120
@VersionColumn()
1221
version;
22+
23+
/**
24+
* testVersion
25+
*
26+
* @example '0.0.1'
27+
* @example '0.0.2'
28+
* @memberof Audit
29+
*/
30+
testVersion;
1331
}
1432
`;
1533

1634
export const createCatDtoTextAlt2Transpiled = `import { CreateDateColumn, UpdateDateColumn, VersionColumn } from 'typeorm';
1735
export class Audit {
1836
static _OPENAPI_METADATA_FACTORY() {
19-
return { createdAt: { required: true, type: () => Object }, updatedAt: { required: true, type: () => Object }, version: { required: true, type: () => Object } };
37+
return { createdAt: { required: true, type: () => Object, description: "test on createdAt" }, updatedAt: { required: true, type: () => Object }, version: { required: true, type: () => Object, description: "test\\nversion", example: "0.0.1" }, testVersion: { required: true, type: () => Object, description: "testVersion", examples: ["0.0.1", "0.0.2"] } };
2038
}
2139
}
2240
__decorate([

‎test/plugin/fixtures/create-cat.dto.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export class CreateCatDto {
2626
oneValueEnum?: OneValueEnum;
2727
oneValueEnumArr?: OneValueEnum[];
2828
29-
@ApiProperty({ type: String })
29+
/** this is breed im comment */
30+
@ApiProperty({ description: "this is breed", type: String })
3031
@IsString()
3132
readonly breed?: string;
3233
@@ -57,15 +58,15 @@ export class CreateCatDto {
5758
this.status = Status.ENABLED;
5859
}
5960
static _OPENAPI_METADATA_FACTORY() {
60-
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3, minimum: 0, maximum: 10 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, status2: { required: false, enum: Status }, statusArr: { required: false, enum: Status, isArray: true }, oneValueEnum: { required: false, enum: OneValueEnum }, oneValueEnumArr: { required: false, enum: OneValueEnum }, breed: { required: false, type: () => String }, nodes: { required: true, type: () => [Object] }, optionalBoolean: { required: false, type: () => Boolean }, date: { required: true, type: () => Date } };
61+
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3, minimum: 0, maximum: 10 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, status2: { required: false, enum: Status }, statusArr: { required: false, enum: Status, isArray: true }, oneValueEnum: { required: false, enum: OneValueEnum }, oneValueEnumArr: { required: false, enum: OneValueEnum }, breed: { required: false, type: () => String, title: "this is breed im comment" }, nodes: { required: true, type: () => [Object] }, optionalBoolean: { required: false, type: () => Boolean }, date: { required: true, type: () => Date } };
6162
}
6263
}
6364
__decorate([
6465
Min(0),
6566
Max(10)
6667
], CreateCatDto.prototype, \"age\", void 0);
6768
__decorate([
68-
ApiProperty({ type: String }),
69+
ApiProperty({ description: "this is breed", type: String }),
6970
IsString()
7071
], CreateCatDto.prototype, \"breed\", void 0);
7172
__decorate([

‎test/plugin/fixtures/es5-class.dto.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { Status } from './status';
44
import { CONSTANT_STRING, CONSTANT_OBJECT, MIN_VAL } from './constants';
55
66
export class CreateCatDtoEs5 {
7+
// field name
78
name: string = CONSTANT_STRING;
9+
/** status */
810
status: Status = Status.ENABLED;
911
obj = CONSTANT_OBJECT;
1012
@@ -22,13 +24,15 @@ var status_1 = require(\"./status\");
2224
var constants_1 = require(\"./constants\");
2325
var CreateCatDtoEs5 = /** @class */ (function () {
2426
function CreateCatDtoEs5() {
27+
// field name
2528
this.name = constants_1.CONSTANT_STRING;
29+
/** status */
2630
this.status = status_1.Status.ENABLED;
2731
this.obj = constants_1.CONSTANT_OBJECT;
2832
this.age = 3;
2933
}
3034
CreateCatDtoEs5._OPENAPI_METADATA_FACTORY = function () {
31-
return { name: { required: true, type: function () { return String; }, default: constants_1.CONSTANT_STRING }, status: { required: true, type: function () { return Object; }, default: status_1.Status.ENABLED }, obj: { required: true, type: function () { return Object; }, default: constants_1.CONSTANT_OBJECT }, age: { required: true, type: function () { return Number; }, default: 3, minimum: constants_1.MIN_VAL, maximum: 10 } };
35+
return { name: { required: true, type: function () { return String; }, default: constants_1.CONSTANT_STRING }, status: { required: true, type: function () { return Object; }, description: "status", default: status_1.Status.ENABLED }, obj: { required: true, type: function () { return Object; }, default: constants_1.CONSTANT_OBJECT }, age: { required: true, type: function () { return Number; }, default: 3, minimum: constants_1.MIN_VAL, maximum: 10 } };
3236
};
3337
__decorate([
3438
Min(constants_1.MIN_VAL),

‎test/plugin/model-class-visitor.spec.ts

+42-8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from './fixtures/nullable.dto';
2828

2929
describe('API model properties', () => {
30-
it('should add the metadata factory when no decorators exist', () => {
30+
it('should add the metadata factory when no decorators exist, and generated propertyKey is title', () => {
3131
const options: ts.CompilerOptions = {
3232
module: ts.ModuleKind.ESNext,
3333
target: ts.ScriptTarget.ESNext,
@@ -42,7 +42,16 @@ describe('API model properties', () => {
4242
compilerOptions: options,
4343
fileName: filename,
4444
transformers: {
45-
before: [before({ classValidatorShim: true }, fakeProgram)]
45+
before: [
46+
before(
47+
{
48+
classValidatorShim: true,
49+
dtoKeyOfComment: 'title',
50+
introspectComments: true
51+
},
52+
fakeProgram
53+
)
54+
]
4655
}
4756
});
4857
expect(result.outputText).toEqual(createCatDtoTextTranspiled);
@@ -63,7 +72,7 @@ describe('API model properties', () => {
6372
compilerOptions: options,
6473
fileName: filename,
6574
transformers: {
66-
before: [before({}, fakeProgram)]
75+
before: [before({ introspectComments: true }, fakeProgram)]
6776
}
6877
});
6978
expect(result.outputText).toEqual(createCatDtoTextAltTranspiled);
@@ -84,7 +93,12 @@ describe('API model properties', () => {
8493
compilerOptions: options,
8594
fileName: filename,
8695
transformers: {
87-
before: [before({ classValidatorShim: true }, fakeProgram)]
96+
before: [
97+
before(
98+
{ introspectComments: true, classValidatorShim: true },
99+
fakeProgram
100+
)
101+
]
88102
}
89103
});
90104
expect(result.outputText).toEqual(createCatDtoTextAlt2Transpiled);
@@ -105,7 +119,12 @@ describe('API model properties', () => {
105119
compilerOptions: options,
106120
fileName: filename,
107121
transformers: {
108-
before: [before({ classValidatorShim: true }, fakeProgram)]
122+
before: [
123+
before(
124+
{ introspectComments: true, classValidatorShim: true },
125+
fakeProgram
126+
)
127+
]
109128
}
110129
});
111130
expect(result.outputText).toEqual(es5CreateCatDtoTextTranspiled);
@@ -126,7 +145,12 @@ describe('API model properties', () => {
126145
compilerOptions: options,
127146
fileName: filename,
128147
transformers: {
129-
before: [before({ classValidatorShim: true }, fakeProgram)]
148+
before: [
149+
before(
150+
{ introspectComments: true, classValidatorShim: true },
151+
fakeProgram
152+
)
153+
]
130154
}
131155
});
132156
expect(result.outputText).toEqual(nullableDtoTextTranspiled);
@@ -147,15 +171,25 @@ describe('API model properties', () => {
147171
compilerOptions: options,
148172
fileName: filename,
149173
transformers: {
150-
before: [before({ classValidatorShim: true }, fakeProgram)]
174+
before: [
175+
before(
176+
{ introspectComments: true, classValidatorShim: true },
177+
fakeProgram
178+
)
179+
]
151180
}
152181
});
153182

154183
const changedResult = ts.transpileModule(changedCatDtoText, {
155184
compilerOptions: options,
156185
fileName: filename,
157186
transformers: {
158-
before: [before({ classValidatorShim: true }, fakeProgram)]
187+
before: [
188+
before(
189+
{ introspectComments: true, classValidatorShim: true },
190+
fakeProgram
191+
)
192+
]
159193
}
160194
});
161195

0 commit comments

Comments
 (0)
Please sign in to comment.