diff --git a/packages/type-compiler/src/compiler.ts b/packages/type-compiler/src/compiler.ts index d88c41266..b2e0333f1 100644 --- a/packages/type-compiler/src/compiler.ts +++ b/packages/type-compiler/src/compiler.ts @@ -142,6 +142,7 @@ const { isSourceFile, isStringLiteral, isTypeAliasDeclaration, + isTypeLiteralNode, isTypeParameterDeclaration, isTypeQueryNode, isTypeReferenceNode, @@ -1246,6 +1247,8 @@ export class ReflectionTransformer implements CustomTransformer { if (node) { const members: ClassElement[] = []; + const description = extractJSDocAttribute(narrowed, 'description'); + if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description)); if (narrowed.typeParameters) { for (const typeParameter of narrowed.typeParameters) { @@ -1288,6 +1291,7 @@ export class ReflectionTransformer implements CustomTransformer { } program.pushOp(ReflectionOp.class); + if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description)); if (narrowed.heritageClauses && narrowed.heritageClauses[0] && narrowed.heritageClauses[0].types[0]) { const first = narrowed.heritageClauses[0].types[0]; @@ -1375,6 +1379,7 @@ export class ReflectionTransformer implements CustomTransformer { case SyntaxKind.InterfaceDeclaration: { //TypeScript does not narrow types down const narrowed = node as TypeLiteralNode | InterfaceDeclaration; + let descriptionNode: Node = narrowed; program.pushFrame(); //first all extend expressions @@ -1392,6 +1397,11 @@ export class ReflectionTransformer implements CustomTransformer { this.extractPackStructOfType(member, program); } program.pushOp(ReflectionOp.objectLiteral); + if (isTypeLiteralNode(narrowed)) { + descriptionNode = narrowed.parent; + } + const description = extractJSDocAttribute(descriptionNode, 'description'); + if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description)); program.popFrameImplicit(); break; } @@ -1622,6 +1632,8 @@ export class ReflectionTransformer implements CustomTransformer { if (hasModifier(narrowed, SyntaxKind.AbstractKeyword)) program.pushOp(ReflectionOp.abstract); if (hasModifier(narrowed, SyntaxKind.StaticKeyword)) program.pushOp(ReflectionOp.static); } + const description = extractJSDocAttribute(narrowed, 'description'); + if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description)); program.popFrameImplicit(); break; } @@ -1691,6 +1703,8 @@ export class ReflectionTransformer implements CustomTransformer { } } program.pushOp(ReflectionOp.enum); + const description = extractJSDocAttribute(narrowed, 'description'); + if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description)); program.popFrameImplicit(); break; } diff --git a/packages/type/src/reflection/reflection.ts b/packages/type/src/reflection/reflection.ts index 24927bf6f..17137fb16 100644 --- a/packages/type/src/reflection/reflection.ts +++ b/packages/type/src/reflection/reflection.ts @@ -332,6 +332,7 @@ export class ReflectionParameter { export class ReflectionFunction { parameters: ReflectionParameter[] = []; + description: string = ''; constructor( public readonly type: TypeMethod | TypeMethodSignature | TypeFunction, @@ -339,6 +340,7 @@ export class ReflectionFunction { for (const p of this.type.parameters) { this.parameters.push(new ReflectionParameter(p, this)); } + if (this.type.description) this.description = this.type.description; } static from(fn: Function): ReflectionFunction { @@ -395,6 +397,10 @@ export class ReflectionFunction { return this.type.name || 'anonymous'; } + getDescription(): string { + return this.description; + } + get name(): string { return memberNameToString(this.getName()); } @@ -858,6 +864,7 @@ export class ReflectionClass { this.name = parent.name; this.collectionName = parent.collectionName; this.databaseSchemaName = parent.databaseSchemaName; + this.description = parent.description; for (const member of parent.getProperties()) { this.registerProperty(member.clone(this)); @@ -875,6 +882,7 @@ export class ReflectionClass { if (entityOptions) { applyEntityOptions(this, entityOptions); } + this.description = this.type.description || this.description; //apply decorators if (type.kind === ReflectionKind.class && isWithDeferredDecorators(type.classType)) { @@ -911,6 +919,7 @@ export class ReflectionClass { reflection.indexes = this.indexes.slice(); reflection.subClasses = this.subClasses.slice(); reflection.data = { ...this.data }; + reflection.description = this.description; return reflection; } @@ -954,6 +963,10 @@ export class ReflectionClass { return this.name || this.getClassName(); } + getDescription(): string { + return this.description; + } + getCollectionName(): string { return this.collectionName || this.getName(); } diff --git a/packages/type/src/reflection/type.ts b/packages/type/src/reflection/type.ts index 730389bba..c30aaef12 100644 --- a/packages/type/src/reflection/type.ts +++ b/packages/type/src/reflection/type.ts @@ -269,6 +269,7 @@ export interface TypeMethod extends TypeBaseMember { kind: ReflectionKind.method, parent: TypeClass; name: number | string | symbol; + description?: string; parameters: TypeParameter[]; return: Type; } @@ -291,6 +292,7 @@ export interface TypeFunction extends TypeAnnotations { kind: ReflectionKind.function, parent?: Type; name?: number | string | symbol, + description?: string; function?: Function; //reference to the real function if available parameters: TypeParameter[]; return: Type; @@ -313,6 +315,7 @@ export interface TypeClass extends TypeAnnotations { kind: ReflectionKind.class, parent?: Type; classType: ClassType; + description?: string; /** * When the class extends another class and uses on it generic type arguments, then those arguments @@ -339,6 +342,7 @@ export interface TypeEnum extends TypeAnnotations { enum: { [name: string]: string | number | undefined | null }; values: (string | number | undefined | null)[]; indexType: Type; + description?: string; } export interface TypeEnumMember extends TypeAnnotations { @@ -387,6 +391,7 @@ export interface TypeMethodSignature extends TypeAnnotations { parent: TypeObjectLiteral; name: number | string | symbol; optional?: true; + description?: string; parameters: TypeParameter[]; return: Type; } @@ -397,6 +402,7 @@ export interface TypeMethodSignature extends TypeAnnotations { export interface TypeObjectLiteral extends TypeAnnotations { kind: ReflectionKind.objectLiteral, parent?: Type; + description?: string; types: (TypeIndexSignature | TypePropertySignature | TypeMethodSignature | TypeCallSignature)[]; } diff --git a/packages/type/tests/jsdoc.spec.ts b/packages/type/tests/jsdoc.spec.ts new file mode 100644 index 000000000..4ca0f8d02 --- /dev/null +++ b/packages/type/tests/jsdoc.spec.ts @@ -0,0 +1,76 @@ +import { expect, test } from '@jest/globals'; +import { TypeEnum } from '../src/reflection/type.js'; +import { ReflectionClass, ReflectionMethod, ReflectionFunction, typeOf } from '../src/reflection/reflection.js'; + +test('description available on Interface and Type alias', () => { + /** @description user interface */ + interface IUser { + username: string; + } + + /** @description user type declaration */ + type IUser2 = { + username: string; + } + + const reflectTypeInterface = ReflectionClass.from(typeOf()); + expect(reflectTypeInterface.description).toEqual('user interface'); + expect(reflectTypeInterface.type.description).toEqual('user interface'); + + const reflectTypeObjectLiteral = ReflectionClass.from(typeOf()); + expect(reflectTypeObjectLiteral.description).toEqual('user type declaration'); + expect(reflectTypeObjectLiteral.type.description).toEqual('user type declaration'); +}); + +test('description available on ReflectionClass', () => { + class MyDate {} + + /** @description user class */ + class User { + myDate?: MyDate; + created: Date = new Date; + } + const reflection = ReflectionClass.from(typeOf()); + expect(reflection.description).toEqual('user class'); +}); + +test('description available on ReflectionFunction and ReflectionMethod', () => { + class MyDate {} + /** @description user class */ + class User { + myDate?: MyDate; + created: Date = new Date; + } + /** @description getUser function */ + function getUser(): User { + return new User() + } + + class FunctionContainer { + /** @description getUser member */ + getUser(): User { + return new User() + } + } + + const reflectionFunction = ReflectionFunction.from(getUser); + expect(reflectionFunction.getDescription()).toEqual('getUser function'); + + const fc = new FunctionContainer(); + const reflection = ReflectionClass.from(typeOf()); + const method:ReflectionMethod = reflection.getMethod('getUser') + expect(method.description).toEqual('getUser member'); +}); + +test('description available on TypeEnum', () => { + /** @description results enum */ + enum RESULTS { + SUCCESS = 'success', + FAILURE = 'failure' + } + const type = typeOf() as TypeEnum; + expect(type.description).toEqual('results enum'); + +}); + +