Skip to content

Commit

Permalink
feat: jsdoc for Aptos endpoint methods. (#1088)
Browse files Browse the repository at this point in the history
* jsdoc fo endpoint methods.
  • Loading branch information
b4rtaz authored Apr 11, 2023
1 parent 37cbd81 commit 27d6249
Show file tree
Hide file tree
Showing 20 changed files with 1,128 additions and 376 deletions.
6 changes: 6 additions & 0 deletions .changeset/honest-windows-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@moralisweb3/common-aptos-utils': patch
'@moralisweb3/aptos-api': patch
---

Added JSDoc comments to API methods.
6 changes: 5 additions & 1 deletion packages/apiGenerator/src/generator/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export class Generator {
this.generateUnionType(unionType);
}

const abstractClientFileGenerator = new AbstractClientFileGenerator(this.contract.operations, this.typeResolver);
const abstractClientFileGenerator = new AbstractClientFileGenerator(
this.contract.operations,
this.typeResolver,
this.typeInfoResolver,
);
this.writer.writeAbstractClient(abstractClientFileGenerator.generate());

this.operationsIndexGenerator.add('operations');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,73 @@
import { OperationInfo } from '../../reader/OpenApiContract';
import { NameFormatter } from './codeGenerators/NameFormatter';
import { TypeName } from '../../reader/utils/TypeName';
import { TypeResolver } from './resolvers/TypeResolver';
import { TypeCodesGenerator } from './codeGenerators/TypeCodesGenerator';
import { ResolvedType, TypeResolver } from './resolvers/TypeResolver';
import { TypeCodes, TypeCodesGenerator } from './codeGenerators/TypeCodesGenerator';
import { TypeScriptOutput } from '../output/TypeScriptOutput';
import { Output } from '../output/Output';
import { OperationParameterModelBuilder } from './modelBuilders/OperationParameterModelBuilder';
import { TypeInfoResolver } from './resolvers/TypeInfoResolver';
import { isReferenceTypeDescriptor } from '../../reader/TypeDescriptor';
import { ComplexTypePropertyModelBuilder } from './modelBuilders/ComplexTypePropertyModelBuilder';
import { ComplexTypePropertyModel } from './modelBuilders/ComplexTypePropertyModelBuilder';
import { JSDocTypeResolver } from './codeGenerators/JSDocTypeResolver';

export class AbstractClientFileGenerator {
public constructor(private readonly operations: OperationInfo[], private readonly typeResolver: TypeResolver) {}
private readonly operationParameterModelBuilder = new OperationParameterModelBuilder(this.typeResolver);
private readonly complexTypePropertyModelBuilder = new ComplexTypePropertyModelBuilder(this.typeResolver);

public generate(): Output {
const output = new TypeScriptOutput();
public constructor(
private readonly operations: OperationInfo[],
private readonly typeResolver: TypeResolver,
private readonly typeInfoResolver: TypeInfoResolver,
) {}

public generate(): Output {
const groupNames = [...new Set(this.operations.map((o) => o.groupName))];
groupNames.sort();

const operations = this.operations.map((operation) => {
const operations = this.operations.map((operation: OperationInfo) => {
const className = NameFormatter.getClassName(operation.operationId) + 'Operation';

const resolvedResponseType = operation.response ? this.typeResolver.resolve(operation.response.descriptor) : null;
const responseTypeCodes = resolvedResponseType
? TypeCodesGenerator.generate(resolvedResponseType, true)
: TypeCodesGenerator.generateNull();
const responseJsdocType = resolvedResponseType ? JSDocTypeResolver.resolve(resolvedResponseType) : null;

let resolvedBodyType: ResolvedType | null = null;
let bodyTypeCodes: TypeCodes | null = null;
let bodyProperties: ComplexTypePropertyModel[] | null = null;
if (operation.body) {
resolvedBodyType = this.typeResolver.resolve(operation.body.descriptor);
bodyTypeCodes = TypeCodesGenerator.generate(resolvedBodyType, operation.body.isRequired);

if (isReferenceTypeDescriptor(operation.body.descriptor)) {
const bodyComplexTypeInfo = this.typeInfoResolver.tryGetComplexType(operation.body.descriptor);
if (bodyComplexTypeInfo) {
bodyProperties = this.complexTypePropertyModelBuilder.build(bodyComplexTypeInfo.properties);
}
}
}

const resolvedBodyType = operation.body ? this.typeResolver.resolve(operation.body.descriptor) : null;
const bodyTypeCodes =
resolvedBodyType && operation.body
? TypeCodesGenerator.generate(resolvedBodyType, operation.body.isRequired)
: null;

const parameterName = NameFormatter.getParameterName(
const endpointNormalizedName = NameFormatter.getParameterName(
NameFormatter.getClassName(TypeName.from(operation.operationId)),
);

const parameters = this.operationParameterModelBuilder
.build(operation.parameters)
.sort((a, b) => Number(b.isRequired) - Number(a.isRequired));

return {
groupName: operation.groupName,
description: operation.description,
parameters,
className,
responseTypeCodes,
responseJsdocType,
bodyTypeCodes,
parameterName,
bodyProperties,
endpointNormalizedName,
};
});

Expand All @@ -53,6 +82,8 @@ export class AbstractClientFileGenerator {

// view:

const output = new TypeScriptOutput();

for (const operation of operations) {
output.addImport(
[operation.className, `${operation.className}Request`, `${operation.className}RequestJSON`],
Expand Down Expand Up @@ -113,17 +144,45 @@ export class AbstractClientFileGenerator {
output.write(1, `public readonly ${group.safeName} = {`);
for (const operation of group.operations) {
const factoryMethodName = operation.bodyTypeCodes ? 'createEndpointWithBody' : 'createEndpoint';
output.write(2, `${operation.parameterName}: this.${factoryMethodName}<`);
output.write(3, `${operation.className}Request`);
output.write(3, `, ${operation.className}RequestJSON`);
output.write(3, `, ${operation.responseTypeCodes.valueTypeCode}`);
output.write(3, `, ${operation.responseTypeCodes.jsonTypeCode}`);
if (operation.bodyTypeCodes) {
output.write(
3,
`, ${operation.bodyTypeCodes.inputOrValueTypeCode}${operation.bodyTypeCodes.undefinedSuffix}`,

const comment = output
.createComment(2)
.description(operation.description)
.param(null, 'request', true, 'Request with parameters.');
for (const parameter of operation.parameters) {
comment.param(
parameter.jsdocType,
`request${parameter.name.normalizedAccessCode}`,
parameter.isRequired,
parameter.description,
);
output.write(3, `, ${operation.bodyTypeCodes.jsonTypeCode}${operation.bodyTypeCodes.undefinedSuffix}`);
}
if (operation.bodyTypeCodes) {
comment.param(null, 'body', true, 'Request body.');
}
if (operation.bodyProperties) {
for (const bodyProperty of operation.bodyProperties) {
comment.param(
bodyProperty.jsdocType,
`body${bodyProperty.name.normalizedAccessCode}`,
bodyProperty.isRequired,
bodyProperty.description,
);
}
}
if (operation.responseJsdocType) {
comment.returns(operation.responseJsdocType, 'Response for the request.');
}
comment.apply();

output.write(2, `${operation.endpointNormalizedName}: this.${factoryMethodName}<`);
output.write(3, `${operation.className}Request,`);
output.write(3, `${operation.className}RequestJSON,`);
output.write(3, `${operation.responseTypeCodes.valueTypeCode},`);
output.write(3, `${operation.responseTypeCodes.jsonTypeCode}` + (operation.bodyTypeCodes ? ',' : ''));
if (operation.bodyTypeCodes) {
output.write(3, `${operation.bodyTypeCodes.inputOrValueTypeCode}${operation.bodyTypeCodes.undefinedSuffix},`);
output.write(3, `${operation.bodyTypeCodes.jsonTypeCode}${operation.bodyTypeCodes.undefinedSuffix}`);
}
output.write(2, `>(${operation.className}),`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { ComplexTypeInfo } from 'src/reader/OpenApiContract';
import { Output } from '../output/Output';
import { ValueMappingCodeGenerator } from './codeGenerators/ValueMappingCodeGenerator';
import { TypeCodesGenerator } from './codeGenerators/TypeCodesGenerator';
import { TypeScriptOutput } from '../output/TypeScriptOutput';
import { PropertyNameCodeGenerator } from './codeGenerators/PropertyNameCodeGenerator';
import { TypeDeterminantResolver } from './resolvers/TypeDeterminantResolver';
import { TypeInfoResolver } from './resolvers/TypeInfoResolver';
import { TypeResolver } from './resolvers/TypeResolver';
import { ComplexTypePropertyModelBuilder } from './modelBuilders/ComplexTypePropertyModelBuilder';

export interface TypeClassGeneratorResult {
className: string;
output: Output;
}

export class ComplexTypeFileGenerator {
private readonly complexTypePropertyModelBuilder = new ComplexTypePropertyModelBuilder(this.typeResolver);

public constructor(
private readonly info: ComplexTypeInfo,
private readonly typeResolver: TypeResolver,
Expand All @@ -34,38 +35,15 @@ export class ComplexTypeFileGenerator {

const output = new TypeScriptOutput();

const properties = this.info.properties.map((property) => {
const resolvedPropertyType = this.typeResolver.resolveForComplexTypeProperty(property.descriptor, property.name);
const propertyNameCodes = PropertyNameCodeGenerator.generate(property.name);
return {
property,
propertyNameCodes,
typeCodes: TypeCodesGenerator.generate(resolvedPropertyType, property.isRequired),
json2TypeCode: ValueMappingCodeGenerator.generateJSON2TypeCode(
resolvedPropertyType,
`json${propertyNameCodes.accessCode}`,
property.isRequired,
),
type2jsonCode: ValueMappingCodeGenerator.generateType2JSONCode(
resolvedPropertyType,
`this${propertyNameCodes.camelCasedAccessCode}`,
property.isRequired,
),
input2typeCode: ValueMappingCodeGenerator.generateInput2TypeCode(
resolvedPropertyType,
`input${propertyNameCodes.camelCasedAccessCode}`,
property.isRequired,
),
};
});
const properties = this.complexTypePropertyModelBuilder.build(this.info.properties);

const customTypeDeterminant = this.typeDeterminantResolver.tryResolve(this.info.descriptor.typeName);
const requiredInputFieldNames = properties
.filter((property) => property.property.isRequired)
.map((property) => property.propertyNameCodes.camelCasedNameCode);
.filter((property) => property.isRequired)
.map((property) => property.name.normalizedNameCode);
const requiredJSONFieldNames = properties
.filter((property) => property.property.isRequired)
.map((property) => property.propertyNameCodes.nameCode);
.filter((property) => property.isRequired)
.map((property) => property.name.rawNameCode);

const isUsedByUnions = this.typeInfoResolver.isUsedByUnions(this.info.descriptor);

Expand Down Expand Up @@ -95,23 +73,20 @@ export class ComplexTypeFileGenerator {
output.write(0, `// type: ${this.info.descriptor.typeName.toString()}`);
output.write(0, `// properties:`);
for (const p of properties) {
output.write(0, `// - ${p.property.name} ($ref: ${p.property.descriptor.ref})`);
output.write(0, `// - ${p.name.rawName} ($ref: ${p.ref})`);
}
output.newLine();

output.write(0, `export interface ${typeCodes.referenceType.jsonClassName} {`);
for (const p of properties) {
output.write(1, `readonly ${p.propertyNameCodes.nameCode}${p.typeCodes.colon} ${p.typeCodes.jsonTypeCode};`);
output.write(1, `readonly ${p.name.rawNameCode}${p.typeCodes.colon} ${p.typeCodes.jsonTypeCode};`);
}
output.write(0, '}');
output.newLine();

output.write(0, `export interface ${typeCodes.referenceType.inputClassName} {`);
for (const p of properties) {
output.write(
1,
`readonly ${p.propertyNameCodes.camelCasedNameCode}${p.typeCodes.colon} ${p.typeCodes.inputOrValueTypeCode};`,
);
output.write(1, `readonly ${p.name.normalizedNameCode}${p.typeCodes.colon} ${p.typeCodes.inputOrValueTypeCode};`);
}
output.write(0, '}');
output.newLine();
Expand All @@ -135,7 +110,7 @@ export class ComplexTypeFileGenerator {
);
output.write(2, `const input: ${typeCodes.referenceType.inputClassName} = {`);
for (const p of properties) {
output.write(3, `${p.propertyNameCodes.camelCasedNameCode}: ${p.json2TypeCode},`);
output.write(3, `${p.name.normalizedNameCode}: ${p.json2typeCode},`);
}
output.write(2, `};`);
output.write(2, `return ${typeCodes.referenceType.factoryClassName}.create(input);`);
Expand Down Expand Up @@ -166,27 +141,22 @@ export class ComplexTypeFileGenerator {
}

for (const p of properties) {
output.writeComment(1, null, {
description: p.property.description,
});
output.write(
1,
`public readonly ${p.propertyNameCodes.camelCasedNameCode}${p.typeCodes.colon} ${p.typeCodes.valueTypeCode};`,
);
output.createComment(1).description(p.description).apply();
output.write(1, `public readonly ${p.name.normalizedNameCode}${p.typeCodes.colon} ${p.typeCodes.valueTypeCode};`);
}
output.newLine();

output.write(1, `private constructor(input: ${typeCodes.referenceType.inputClassName}) {`);
for (const p of properties) {
output.write(2, `this${p.propertyNameCodes.camelCasedAccessCode} = ${p.input2typeCode};`);
output.write(2, `this${p.name.normalizedAccessCode} = ${p.input2typeCode};`);
}
output.write(1, '}');
output.newLine();

output.write(1, `public toJSON(): ${typeCodes.referenceType.jsonClassName} {`);
output.write(2, 'return {');
for (const p of properties) {
output.write(3, `${p.propertyNameCodes.nameCode}: ${p.type2jsonCode},`);
output.write(3, `${p.name.rawNameCode}: ${p.type2jsonCode},`);
}
output.write(2, '}');
output.write(1, '}');
Expand Down
Loading

1 comment on commit 27d6249

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage

Title Lines Statements Branches Functions
api-utils Coverage: 30%
30.34% (61/201) 30.35% (17/56) 30.76% (12/39)
auth Coverage: 89%
92.38% (97/105) 83.33% (20/24) 86.2% (25/29)
evm-api Coverage: 100%
100% (89/89) 66.66% (6/9) 100% (57/57)
common-aptos-utils Coverage: 4%
4.8% (149/3103) 5.47% (25/457) 5.66% (45/794)
common-evm-utils Coverage: 70%
71.15% (1423/2000) 32.04% (314/980) 48.28% (393/814)
sol-api Coverage: 96%
96.66% (29/30) 66.66% (6/9) 91.66% (11/12)
common-sol-utils Coverage: 74%
74.55% (167/224) 66.66% (18/27) 65.38% (51/78)
common-streams-utils Coverage: 91%
91.42% (1184/1295) 77.57% (415/535) 82.08% (426/519)
streams Coverage: 88%
88.19% (575/652) 68.81% (64/93) 87.94% (124/141)

Please sign in to comment.