lib/model_generator.js (483 lines of code) (raw):

/* eslint-disable max-len */ 'use strict'; const assert = require('assert'); const DSL = require('@darabonba/parser'); const { _name, _subModelName, getAttr, avoidReserveName, _upperFirst, md2Html } = require('./util'); const CommonGenerator = require('./common_generator'); class BuilderGenerator extends CommonGenerator { constructor(ast, modelName, realModelName, ctx) { super(ast, ctx); this.modelName = modelName; this.realModelName = realModelName; } codegen(level = 0) { if (this.ctx.isResponseModel) { this.emitResponseBuilderInterface(this.ctx.extendsPackage, level); this.emitln(); this.emitln(`private static final class BuilderImpl`, level); if (this.ctx.extendsPackage) { this.emitln(` extends Response.BuilderImpl<${this.realModelName}, Builder>`, level); } this.emitln(` implements Builder {`, level); this.emitFields(level + 1); this.emitln(); this.emitResponseConstructor(level + 1); } else if (this.ctx.isRequestModel && this.ctx.extendsPackage) { this.emitln(`public static final class Builder extends Request.Builder<${this.realModelName}, Builder> {`, level); this.emitFields(level + 1); this.emitln(); this.emitRequestConstructor(level + 1); } else { this.emitln(`public static final class Builder {`, level); this.emitFields(level + 1); } this.emitSetters(level + 1, this.ctx.isResponseModel && this.ctx.extendsPackage); this.emitln(); this.emitBuildMethod(level + 1, (this.ctx.isRequestModel || this.ctx.isResponseModel) && this.ctx.extendsPackage); this.emitln(); this.emitln(`} `, level); } emitResponseBuilderInterface(extendsPackage, level) { this.emitln(`public interface Builder ${extendsPackage ? 'extends Response.Builder<' + this.realModelName + ', Builder>' : ''} {`, level); for (let i = 0; i < this.ast.nodes.length; i++) { let node = this.ast.nodes[i]; const value = node.fieldValue; const fieldName = avoidReserveName(_name(node.fieldName)); this.emitln(); this.emit(`Builder ${fieldName}(`, level + 1); this.visitFieldType(value, node, this.modelName); this.emitln(`${fieldName});`); if (value.fieldType && value.fieldType.idType && value.fieldType.idType === 'enum') { this.emitln(); this.emit(`Builder ${fieldName}(`, level + 1); this.visitFieldType(value, node, this.modelName, true); this.emitln(`${fieldName});`); } } this.emitln(); if (extendsPackage) { this.emitln(`@Override`, level + 1); } this.emitln(`${this.realModelName} build();`, level + 1); this.emitln(); this.emitln(`} `, level); } emitFields(level) { assert.equal(this.ast.type, 'modelBody'); for (let i = 0; i < this.ast.nodes.length; i++) { let node = this.ast.nodes[i]; const value = node.fieldValue; this.emit('private ', level); this.visitFieldType(value, node, this.modelName); this.emitln(`${avoidReserveName(_name(node.fieldName))}; `); } } emitResponseConstructor(level) { assert.equal(this.ast.type, 'modelBody'); this.emitln(`private BuilderImpl() {`, level); this.emitln(`super();`, level + 1); this.emitln(`} `, level); this.emitln(); this.emitln(`private BuilderImpl(${this.realModelName} response) {`, level); this.emitln(`super(response);`, level + 1); for (let i = 0; i < this.ast.nodes.length; i++) { let node = this.ast.nodes[i]; this.emitln(`this.${avoidReserveName(_name(node.fieldName))} = response.${avoidReserveName(_name(node.fieldName))};`, level + 1); } this.emitln(`} `, level); } emitRequestConstructor(level) { assert.equal(this.ast.type, 'modelBody'); this.emitln(`private Builder() {`, level); this.emitln(`super();`, level + 1); this.emitln(`} `, level); this.emitln(); this.emitln(`private Builder(${this.realModelName} request) {`, level); this.emitln(`super(request);`, level + 1); for (let i = 0; i < this.ast.nodes.length; i++) { let node = this.ast.nodes[i]; this.emitln(`this.${avoidReserveName(_name(node.fieldName))} = request.${avoidReserveName(_name(node.fieldName))};`, level + 1); } this.emitln(`} `, level); } emitSetters(level, isOverride) { assert.equal(this.ast.type, 'modelBody'); for (let i = 0; i < this.ast.nodes.length; i++) { let node = this.ast.nodes[i]; const value = node.fieldValue; const fieldName = avoidReserveName(_name(node.fieldName)); const realName = getAttr(node, 'name') || _name(node.fieldName); const position = getAttr(node, 'position'); const shrink = getAttr(node, 'shrink'); this.emitln(); this.emitln('/**', level); if (this.ctx.descriptionMap[realName]) { for (let j = 0; j < this.ctx.descriptionMap[realName].length; j++) { this.emitln(this.ctx.descriptionMap[realName][j], level); } } else { this.emitln(` * ${realName}.`, level); } this.emitln(' */', level); if (isOverride) { this.emitln('@Override', level); } this.emit(`public Builder ${fieldName}(`, level); this.visitFieldType(value, node, this.modelName); this.emitln(`${fieldName}) {`); if (position) { let pos = position.split(','); for (let i = 0; i < pos.length; i++) { if (shrink) { this.emitln(`String ${fieldName}Shrink = shrink(${fieldName}, "${realName}", "${shrink}");`, level + 1); this.emitln(`this.put${pos[i]}Parameter("${realName}", ${fieldName}Shrink);`, level + 1); } else { this.emitln(`this.put${pos[i]}Parameter("${realName}", ${fieldName});`, level + 1); } } } this.emitln(`this.${fieldName} = ${fieldName};`, level + 1); this.emitln(`return this;`, level + 1); this.emitln(`}`, level); if (value.fieldType && value.fieldType.idType && value.fieldType.idType === 'enum') { this.emitln(); this.emitln('/**', level); if (this.ctx.descriptionMap[realName]) { for (let j = 0; j < this.ctx.descriptionMap[realName].length; j++) { this.emitln(this.ctx.descriptionMap[realName][j], level); } } else { this.emitln(` * ${realName}.`, level); } this.emitln(' */', level); if (isOverride) { this.emitln('@Override', level); } this.emit(`public Builder ${fieldName}(`, level); this.visitFieldType(value, node, this.modelName, true); this.emitln(`${fieldName}) {`); if (position) { this.emitln(`this.put${position}Parameter("${realName}", ${fieldName}.getValue());`, level + 1); } this.emitln(`this.${fieldName} = ${fieldName}.getValue();`, level + 1); this.emitln(`return this;`, level + 1); this.emitln(`}`, level); } } } emitBuildMethod(level, isOverride) { if (isOverride) { this.emitln('@Override', level); } this.emitln(`public ${this.realModelName} build() {`, level); this.emitln(`return new ${this.realModelName}(this);`, level + 1); this.emitln(`} `, level); } } class Generator extends CommonGenerator { constructor(ast, ctx) { super(ast, ctx); this.ctx.descriptionMap = {}; } codegen(level = 0) { this.emitHeader(level); this.eachModel(level); for (let i = 0; i < this.ctx.subModels.length; i++) { this.eachSubModel(this.ctx.subModels[i], level + 1); } this.emitln('}', level); } codegenRequest(level = 0) { this.emitln(`// This file is auto-generated, don't edit it. Thanks.`, level); this.emitln(`package ${this.ctx.package}.models;`, level); this.emitln(); this.emitln(`import darabonba.core.RequestModel;`, level); this.emitln(); this.emitln(`public class Request extends RequestModel {`, level); this.emitln(); this.emitln(`protected Request(Builder builder) {`, level + 1); this.emitln(`super(builder);`, level + 2); this.emitln(`}`, level + 1); this.emitln(); this.emitln(`}`, level); } codegenResponse(level = 0) { this.emitln(`// This file is auto-generated, don't edit it. Thanks.`, level); this.emitln(`package ${this.ctx.package}.models;`, level); this.emitln(); this.emitln(`import darabonba.core.TeaModel;`, level); this.emitln(); this.emitln(`public class Response extends TeaModel {`, level); this.emitln(); this.emitln(`protected Response(Builder builder) {`, level + 1); this.emitln(`}`, level + 1); this.emitln(); this.emitln(`public Builder toBuilder() {`, level + 1); this.emitln(`return new Builder(this);`, level + 2); this.emitln(`}`, level + 1); this.emitln(); this.emitln(`protected static class Builder {`, level + 1); this.emitln(); this.emitln(`protected Builder() {`, level + 2); this.emitln(`}`, level + 2); this.emitln(); this.emitln(`protected Builder(Response response) {`, level + 2); this.emitln(`}`, level + 2); this.emitln(); this.emitln(`}`, level + 1); this.emitln(); this.emitln(`}`, level); } emitHeader(level) { this.emitln(`// This file is auto-generated, don't edit it. Thanks.`, level); this.emitln(`package ${this.ctx.package}.models;`, level); this.emitln(); this.emitln(`import darabonba.core.RequestModel;`, level); this.emitln(`import darabonba.core.TeaModel;`, level); if (this.ctx.extendsPackage) { this.emitln(`import ${this.ctx.extendsPackage}.models.*;`, level); } this.emitln(); } eachModel(level) { assert.equal(this.ast.type, 'model'); const modelName = _name(this.ast.modelName); this.ctx.isRequestModel = modelName.endsWith('Request'); this.ctx.isResponseModel = modelName.endsWith('Response'); this.visitAnnotation(this.ast.annotation, level); const realModelName = _subModelName(modelName, this.ctx.conflictModelNameMap, this.ctx.allModleNameMap); this.emitln(`public class ${realModelName} extends ${this.ctx.isRequestModel && this.ctx.extendsPackage ? 'Request' : this.ctx.isResponseModel && this.ctx.extendsPackage ? 'Response' : 'TeaModel'} {`, level); this.emitModelBody(this.ast.modelBody, modelName, level + 1); this.emitConstructor(this.ast.modelBody, realModelName, this.ctx.isResponseModel, (this.ctx.isRequestModel || this.ctx.isResponseModel) && this.ctx.extendsPackage, level + 1); if (!this.ctx.isResponseModel) { this.emitln(); this.emitBuilderMethod(level + 1); } this.emitln(); this.emitCreatorMethod(realModelName, this.ctx.isResponseModel, level + 1); if ((this.ctx.isRequestModel || this.ctx.isResponseModel) && this.ctx.extendsPackage) { this.emitln(); this.emitToBuilderMethod(this.ctx.isResponseModel, level + 1); } this.emitGetters(this.ast.modelBody, modelName, level + 1); this.emitln(); this.emitBuilderClass(this.ast.modelBody, modelName, realModelName, level + 1); } eachSubModel(ast, level) { assert.equal(ast.type, 'model'); const modelName = _name(ast.modelName); this.ctx.isRequestModel = false; this.ctx.isResponseModel = false; this.visitAnnotation(ast.annotation, level); const realModelName = _subModelName(modelName, this.ctx.conflictModelNameMap, this.ctx.allModleNameMap); this.emit(`public static class ${realModelName} extends TeaModel {\n`, level); this.emitModelBody(ast.modelBody, modelName, level + 1); this.emitConstructor(ast.modelBody, realModelName, false, false, level + 1); this.emitln(); this.emitBuilderMethod(level + 1); this.emitln(); this.emitCreatorMethod(realModelName, false, level + 1); this.emitGetters(ast.modelBody, modelName, level + 1); this.emitln(); this.emitBuilderClass(ast.modelBody, modelName, realModelName, level + 1); this.emitln('}', level); } emitModelBody(ast, modelName, level) { assert.equal(ast.type, 'modelBody'); let node; for (let i = 0; i < ast.nodes.length; i++) { node = ast.nodes[i]; let comments = DSL.comment.getFrontComments(this.ctx.comments, node.tokenRange[0]); this.visitComments(comments, level); const value = node.fieldValue; const realName = getAttr(node, 'name') || _name(node.fieldName); const description = getAttr(node, 'description'); const position = getAttr(node, 'position'); const example = getAttr(node, 'example'); const checkBlank = getAttr(node, 'checkBlank'); const nullable = getAttr(node, 'nullable'); const sensitive = getAttr(node, 'sensitive'); const pattern = getAttr(node, 'pattern') || ''; const maxLength = getAttr(node, 'maxLength') || 0; const minLength = getAttr(node, 'minLength') || 0; const maximum = getAttr(node, 'maximum') || 0; const minimum = getAttr(node, 'minimum') || 0; const required = node.required || false; const deprecated = getAttr(node, 'deprecated'); const parentIgnore = getAttr(node, 'parentIgnore'); let hasNextSection = false; let str = ''; if (description) { const descriptions = md2Html(description).trimEnd().split('\n'); for (let j = 0; j < descriptions.length; j++) { str += ` * ${descriptions[j]}\n`; } hasNextSection = true; } if (example) { if (hasNextSection) { str += ' * \n'; } const examples = md2Html(example).trimEnd().split('\n'); str += ' * <strong>example:</strong>\n'; for (let j = 0; j < examples.length; j++) { str += ` * ${examples[j]}\n`; } hasNextSection = true; } if (typeof checkBlank !== 'undefined') { if (hasNextSection) { str += ' * \n'; } str += ' * <strong>check if is blank:</strong>\n'; str += ` * <p>${checkBlank}</p>\n`; hasNextSection = true; } if (typeof nullable !== 'undefined') { if (hasNextSection) { str += ' * \n'; } str += ' * <strong>if can be null:</strong>\n'; str += ` * <p>${nullable}</p>\n`; hasNextSection = true; } if (typeof sensitive !== 'undefined') { if (hasNextSection) { str += ' * \n'; } str += ' * <strong>if sensitive:</strong>\n'; str += ` * <p>${sensitive}</p>\n`; } if (description) { this.ctx.descriptionMap[realName] = str.trimEnd().split('\n'); } if (position) { let pos = position.split(','); for (let i = 0; i < pos.length; i++) { this.emitln(`@com.aliyun.core.annotation.${pos[i]}`, level); } } if (parentIgnore) { this.emitln(`@com.aliyun.core.annotation.ParentIgnore("${parentIgnore}")`, level); } this.emitln(`@com.aliyun.core.annotation.NameInMap("${realName}")`, level); if (deprecated === 'true') { this.emitln(`@Deprecated`, level); } if (required || maxLength > 0 || maximum > 0 || pattern !== '') { var validationAnnotation = '@com.aliyun.core.annotation.Validation('; if (required) { validationAnnotation += `required = ${required}`; } if (pattern !== '') { if (!validationAnnotation.endsWith('(')) { validationAnnotation += ', '; } validationAnnotation += `pattern = "${pattern}"`; } // 不能超过Java中Integer最大值 if (maxLength > 0 && maxLength <= 2147483647) { if (!validationAnnotation.endsWith('(')) { validationAnnotation += ', '; } validationAnnotation += `maxLength = ${maxLength}`; } // 不能超过Java中Integer最大值 if (minLength > 0 && minLength <= 2147483647) { if (!validationAnnotation.endsWith('(')) { validationAnnotation += ', '; } validationAnnotation += `minLength = ${minLength}`; } // 不能超过JS中最大安全整数 if (maximum > 0 && maximum <= Number.MAX_SAFE_INTEGER) { if (!validationAnnotation.endsWith('(')) { validationAnnotation += ', '; } validationAnnotation += `maximum = ${maximum}`; if (maximum > 2147483647) { validationAnnotation += 'D'; } } // 不能超过JS中最大安全整数 if (minimum > 0 && minimum <= Number.MAX_SAFE_INTEGER) { if (!validationAnnotation.endsWith('(')) { validationAnnotation += ', '; } validationAnnotation += `minimum = ${minimum}`; if (minimum > 2147483647) { validationAnnotation += 'D'; } } this.emit(validationAnnotation, level); this.emitln(')'); } this.emit('private ', level); this.visitFieldType(value, node, modelName); this.emitln(`${avoidReserveName(_name(node.fieldName))};`); this.emitln(); } if (node) { //find the last node's back comment let comments = DSL.comment.getBetweenComments(this.ctx.comments, node.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } if (ast.nodes.length === 0) { //empty block's comment let comments = DSL.comment.getBetweenComments(this.ctx.comments, ast.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } } emitConstructor(ast, modelName, isResponseModel, isExtendModel, level) { assert.equal(ast.type, 'modelBody'); this.emitln(`private ${modelName}(${isResponseModel ? 'BuilderImpl' : 'Builder'} builder) {`, level); if (isExtendModel) { this.emitln(`super(builder);`, level + 1); } for (let i = 0; i < ast.nodes.length; i++) { let node = ast.nodes[i]; this.emitln(`this.${avoidReserveName(_name(node.fieldName))} = builder.${avoidReserveName(_name(node.fieldName))};`, level + 1); } this.emitln(`}`, level); } emitBuilderMethod(level) { this.emitln(`public static Builder builder() {`, level); this.emitln(`return new Builder();`, level + 1); this.emitln(`}`, level); } emitCreatorMethod(modelName, isResponseModel, level) { this.emitln(`public static ${modelName} create() {`, level); if (isResponseModel) { this.emitln(`return new BuilderImpl().build();`, level + 1); } else { this.emitln(`return builder().build();`, level + 1); } this.emitln(`}`, level); } emitToBuilderMethod(isResponseModel, level) { this.emitln(`@Override`, level); this.emitln(`public Builder toBuilder() {`, level); if (isResponseModel) { this.emitln(`return new BuilderImpl(this);`, level + 1); } else { this.emitln(`return new Builder(this);`, level + 1); } this.emitln(`}`, level); } emitGetters(ast, modelName, level) { assert.equal(ast.type, 'modelBody'); for (let i = 0; i < ast.nodes.length; i++) { let node = ast.nodes[i]; const value = node.fieldValue; const fieldName = avoidReserveName(_name(node.fieldName)); this.emitln(); this.emitln(`/**`, level); this.emitln(` * @return ${fieldName}`, level); this.emitln(` */`, level); this.emit('public ', level); this.visitFieldType(value, node, modelName); this.emitln(`get${_upperFirst(fieldName)}() {`); this.emitln(`return this.${fieldName};`, level + 1); this.emitln('}', level); } } emitBuilderClass(ast, modelName, realModelName, level) { const generator = new BuilderGenerator(ast, modelName, realModelName, this.ctx); generator.codegen(level); this.emitln(generator.output); } } module.exports = Generator;