visitObjectField()

in lib/generator.js [2033:2985]


  visitObjectField(ast, level, env) {
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level);
    if (ast.type === 'objectField') {
      var key = _name(ast.fieldName) || _string(ast.fieldName);
      this.emit(`{"${key}", `, level);
      this.visitObjectFieldValue(ast.expr, level, env);
    } else if (ast.type === 'expandField') {
      // // TODO: more cases
      // this.emit(`...`, level);
      // this.visitExpr(ast.expr, level);
    } else {
      throw new Error('unimpelemented');
    }
    this.emit('},\n');
  }

  visitObjectFieldValue(ast, level, env) {
    this.visitExpr(ast, level, env);
  }

  visitPropertyAccess(ast, level, env) {
    assert.equal(ast.type, 'property_access');

    var id = _name(ast.id);

    var expr = '';
    if (id === '__response') {
      expr += RESPONSE;
    } else if (id === '__request') {
      expr += REQUEST;
    } else {
      expr += _avoidReserveName(id);
    }

    var current = ast.id.inferred;
    for (var i = 0; i < ast.propertyPath.length; i++) {
      var name = _name(ast.propertyPath[i]);
      if (current.type === 'model') {
        expr += `.${_upperFirst(name)}`;
      } else if (current.type === 'map') {
        expr += `.Get("${name}")`;
      } else {
        expr += `["${name}"]`;
      }
      current = ast.propertyPathTypes[i];
    }

    this.emit(expr);
  }

  visitReturn(ast, level, env = {}) {
    assert.equal(ast.type, 'return');
    this.emit('return ', level);
    if (!ast.expr) {
      this.emit(';\n');
      return;
    }
    if (ast.needCast) {
      this.emit('Darabonba.Model.ToObject<');
      this.visitType(ast.expectedType);
      this.emit('>(');
    }

    if (ast.expr && ast.expr.type === 'object' && env && env.returnType && _name(env.returnType) === 'object') {
      env.castToObject = true;
    }
    this.visitExpr(ast.expr, level, env);

    if (ast.needCast) {
      this.emit(')');
    }

    this.emit(';\n');
  }

  visitRetry(ast, level) {
    assert.equal(ast.type, 'retry');
    this.emit(`throw new DaraRetryableException(${REQUEST}, ${RESPONSE});\n`, level);
  }

  visitWhile(ast, level, env) {
    assert.equal(ast.type, 'while');
    this.emit('\n');
    this.emit('while (', level);
    this.visitExpr(ast.condition, level + 1, env);
    this.emit(') {\n');
    this.visitStmts(ast.stmts, level + 1, env);
    this.emit('}\n', level);
  }

  visitFor(ast, level, env) {
    assert.equal(ast.type, 'for');
    this.emit('\n');
    this.emit(`foreach (var ${_avoidReserveName(_name(ast.id))} in `, level);
    this.visitExpr(ast.list, level + 1, env);
    this.emit(') {\n');
    this.visitStmts(ast.stmts, level + 1, env);
    this.emit('}\n', level);
  }

  visitExtendOn(extendOn, type = 'model') {
    if (!extendOn) {
      if (type === 'model') {
        return this.emit('Darabonba.Model');
      }
      this.emit('DaraException');
      this.used.push('Darabonba.Exceptions');
      return;
    }
    let namespace = this.namespace;
    let modelName = _name(extendOn);
    let extendType = 'Models';
    if (this.predefined[modelName] && this.predefined[modelName].isException) {
      extendType = 'Exceptions';
    }
    if (extendOn.type === 'moduleModel') {
      const [moduleId, ...rest] = extendOn.path;
      namespace = this.moduleClass.get(moduleId.lexeme).namespace;
      modelName = rest.map((item) => {
        return item.lexeme;
      }).join('.');
      const usedEx = this.usedExternException.get(moduleId.lexeme);
      if (usedEx && usedEx.has(modelName)) {
        type = 'Exceptions';
      }
    } else if (extendOn.type === 'subModel') {
      const [moduleId, ...rest] = extendOn.path;
      modelName = [moduleId.lexeme, ...rest.map((item) => {
        return item.lexeme;
      })].join('.');
    } else if (extendOn.idType === 'builtin_model') {
      if (extendOn.lexeme === '$ResponseError') {
        this.emit(this.getRealModelName('Darabonba.Exceptions', 'DaraResponseException'));
      }
      if (extendOn.lexeme === '$Error') {
        this.emit(this.getRealModelName('Darabonba.Exceptions', 'DaraException'));
      }
    } else {
      if (extendOn.moduleName) {
        this.emit(`${extendOn.moduleName}.`);
      }
      this.emit(this.getRealModelName(`${namespace}.${extendType}`, modelName, extendType));
    }
  }

  visitEcxceptionBody(ast, level, exceptionName, env) {
    assert.equal(ast.type, 'exceptionBody');
    let node;
    for (let i = 0; i < ast.nodes.length; i++) {
      node = ast.nodes[i];
      const fieldName = _name(node.fieldName);
      // 父类里有的字段如果重复写,则表示new(隐藏父类)而不是override(覆盖父类)
      if (!exceptionFields.includes(fieldName)) {
        let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]);
        this.visitComments(comments, level);
        this.emit('public ', level);
        this.visitFieldType(node.fieldValue, level, exceptionName, fieldName);
        this.emit(` ${_escape(_upperFirst(_name(node.fieldName)))}`);
        this.emit(' { get; set; }\n');
      }

      if (node.fieldValue.type === 'modelBody') {
        this.emit(`public class ${exceptionName}${_upperFirst(node.fieldName.lexeme)} : Darabonba.Model\n`, level);
        this.emit('{\n', level);
        this.visitModelBody(node.fieldValue, level + 1, env, exceptionName + _upperFirst(node.fieldName.lexeme));
        this.emit('}\n', level);
      }
    }
    if (node) {
      //find the last node's back comment
      let comments = DSL.comment.getBetweenComments(this.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.comments, ast.tokenRange[0], ast.tokenRange[1]);
      this.visitComments(comments, level);
    }
  }

  visitExceptions(ast, filepath, level) {
    const exceptions = ast.moduleBody.nodes.filter((item) => {
      return item.type === 'exception';
    });
    const exDir = path.join(path.dirname(filepath), 'Exceptions');
    for (let i = 0; i < exceptions.length; i++) {
      this.used.push('System');
      this.used.push('System.IO');
      this.used.push('System.Collections');
      this.used.push('System.Collections.Generic');
      const exceptionName = _avoidReserveName(exceptions[i].exceptionName.lexeme);
      const realExceptionName = `${exceptionName}Exception`;
      this.fileName = realExceptionName;
      this.eachException(exceptions[i], realExceptionName, level + 1);
      this.exceptionAfter();
      const modelFilePath = path.join(exDir, `${this.fileName ? this.fileName : exceptionName}.cs`);
      this.save(modelFilePath);
    }
  }

  visitException(ast, exceptionName, extendOn, level, env) {
    this.emit(`public class ${exceptionName} : `, level);
    this.visitExtendOn(extendOn, 'exception');
    this.emit('\n');
    this.emit('{\n', level);
    this.visitEcxceptionBody(ast.exceptionBody, level + 1, exceptionName, env);
    this.emit(`\n`);
    for (let i = 0; i < ast.exceptionBody.nodes.length; i++) {
      const node = ast.exceptionBody.nodes[i];
      if (node.fieldValue.type === 'modelBody') {
        this.emit(`public class ${exceptionName}${_upperFirst(node.fieldName.lexeme)} : Darabonba.Model\n`, level + 1);
        this.emit('{\n', level + 1);
        this.visitModelBody(node.fieldValue, level + 2, env, exceptionName + _upperFirst(node.fieldName.lexeme));
        this.emit('}\n', level + 1);
      }
    }
    this.emit('}\n\n', level);
  }

  /*******************************************************/
  eachException(exception, realExceptionName, level, env) {
    assert.equal(exception.type, 'exception');
    const exceptionName = realExceptionName ? realExceptionName : _avoidReserveName(_name(exception.exceptionName));
    this.visitAnnotation(exception.annotation, level);
    let comments = DSL.comment.getFrontComments(this.comments, exception.tokenRange[0]);
    this.visitComments(comments, level);
    this.visitException(exception, exceptionName, exception.extendOn, level, env);
  }

  eachModel(ast, modelName, level, predefined) {
    assert.equal(ast.type, 'model');
    const env = {
      predefined
    };
    // const modelName = _upperFirst(_name(ast.modelName));
    this.emit(`public class ${modelName} : `, level);
    this.visitExtendOn(ast.extendOn);
    this.emit(' {\n');
    this.visitModelBody(ast.modelBody, level + 1, env, modelName);
    this.emit('}\n\n', level);
  }

  eachAPI(ast, level, env = {}) {
    this.visitAnnotation(ast.annotation, level);
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level);
    this.emit('public ', level);
    let apiName = _upperFirst(_name(ast.apiName));
    if (env.isAsyncMode) {
      this.emit('async ');
      apiName += 'Async';
    }
    env.returnType = ast.returnType;
    this.visitReturnType(ast, 0, env);
    this.emit(apiName);
    this.visitParams(ast.params, level, env);
    this.emit('\n');
    this.emit('{\n', level);

    // Validator
    // for (var i = 0; i < ast.params.params.length; i++) {
    //   const param = ast.params.params[i];
    //   if (_name(param.paramType) && !DSL.util.isBasicType(_name(param.paramType))) {
    //     this.emit(`${_avoidReserveName(param.paramName.lexeme)}.Validate();\n`, level + 1);
    //   }
    // }

    let baseLevel = ast.runtimeBody ? level + 2 : level;
    // api level
    if (ast.runtimeBody) {
      this.visitRuntimeBefore(ast.runtimeBody, level + 1, env);
    }

    // temp level
    this.visitAPIBody(ast.apiBody, baseLevel + 1, env);

    if (env.isAsyncMode) {
      this.emit(`Darabonba.Response ${RESPONSE} = await Core.DoActionAsync(${REQUEST}`, baseLevel + 1);
    } else {
      this.emit(`Darabonba.Response ${RESPONSE} = Core.DoAction(${REQUEST}`, baseLevel + 1);
    }

    if (ast.runtimeBody) {
      this.emit(', runtime_');
    }
    this.emit(');\n');

    if (ast.runtimeBody) {
      this.emit(`_lastRequest = ${REQUEST};\n`, baseLevel + 1);
      this.emit(`_lastResponse = ${RESPONSE};\n`, baseLevel + 1);
    }

    if (ast.returns) {
      this.visitReturnBody(ast.returns, baseLevel + 1, env);
    } else {
      this.visitDefaultReturnBody(baseLevel + 1, env);
    }

    if (ast.runtimeBody) {
      this.visitRuntimeAfter(ast.runtimeBody, level + 1);
    }

    this.emit('}\n', level);
  }

  importBefore(level) {
    // Nothing
  }

  eachImport(imports, usedModels, innerModule, filepath, level) {
    this.imports = {};
    if (imports.length === 0) {
      return;
    }

    if (!this.config.pkgDir) {
      throw new Error(`Must specific pkgDir when have imports`);
    }

    let lock;
    const lockPath = path.join(this.config.pkgDir, '.libraries.json');
    if (fs.existsSync(lockPath)) {
      lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
    }

    for (let i = 0; i < imports.length; i++) {
      const item = imports[i];
      const aliasId = item.lexeme;
      const main = item.mainModule;
      const inner = item.module;

      let moduleDir;
      if (this.config.libraries) {
        moduleDir = main ? this.config.libraries[main] : this.config.libraries[aliasId];
      }
      const innerPath = item.innerPath;
      const importNameSpace = _nameSpace(path.join(path.dirname(filepath), _upperPath(innerPath)));
      if (!moduleDir && innerPath) {
        let csPath = innerPath.replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, '');
        const className = this.getInnerClient(aliasId, csPath);  // Common,Util,User 
        // 这里设置 path.join(path.dirname(csPath),`${className}.cs`))而不是cspath.cs,因为文件名需要和类名保持一致
        innerModule.set(aliasId, path.join(path.dirname(csPath), `${className}.cs`));
        const aliasName = this.getAliasName(`${this.namespace}.${importNameSpace}`, className, aliasId);
        this.moduleClass.set(aliasId, {
          namespace: `${this.namespace}.${importNameSpace}`.replace(/\.$/, ''),
          className: className,
          aliasName: aliasName
        });
        this.imports[aliasId] = {
          namespace: `${this.namespace}.${importNameSpace}`.replace(/\.$/, ''),
          release: '',
          className: aliasId
        };
        continue;
      }
      let targetPath = '';
      if (moduleDir.startsWith('./') || moduleDir.startsWith('../')) {
        targetPath = path.join(this.config.pkgDir, moduleDir);
      } else if (moduleDir.startsWith('/')) {
        targetPath = moduleDir;
      } else {
        targetPath = path.join(this.config.pkgDir, lock[moduleDir]);
      }
      const pkgPath = fs.existsSync(path.join(targetPath, 'Teafile')) ? path.join(targetPath, 'Teafile') : path.join(targetPath, 'Darafile');
      const pkg = JSON.parse(fs.readFileSync(pkgPath));
      let csharpConfig = pkg.csharp;
      pkg.releases = pkg.releases || {};
      if (!csharpConfig) {
        throw new Error(`The '${aliasId}' has no csharp supported.`);
      }
      csharpConfig.release = pkg.releases.csharp;
      this.imports[aliasId] = csharpConfig;
      let className = csharpConfig.className || 'Client';
      let namespace = csharpConfig.namespace;
      if (inner && pkg.exports[inner]) {
        let csPath = path.dirname(pkg.exports[inner]);
        const arr = csPath.split(path.sep).slice(1);
        const [filename] = pkg.exports[inner].split(path.sep).slice(-1);
        arr.map(key => {
          namespace += '.' + _upperFirst(key);
        });
        const processedFilename = filename === '.' ? '' : _upperFirst(filename.toLowerCase().replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, ''));
        const exportsName = csharpConfig.exports ? csharpConfig.exports[inner] : '';
        className = exportsName ? exportsName : `${processedFilename}Client`;
      }
      const aliasName = this.getAliasName(namespace, className, aliasId);
      this.moduleClass.set(aliasId, {
        namespace: namespace,
        className: className,
        aliasName: aliasName
      });

      this.moduleTypedef[aliasId] = csharpConfig.typedef;
    }
  }

  importAfter() {
    // Nothing
  }

  moduleBefore() {
    // Nothing
  }

  modelAfter() {
    this.emit('}\n');
  }

  exceptionAfter() {
    this.emit('}\n');
  }

  interfaceEachAPI(ast, level) {
    this.visitAnnotation(ast.annotation, level);
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level);
    let apiName = _upperFirst(_name(ast.apiName));
    let env = {};
    env.returnType = ast.returnType;
    this.emit('', level);
    this.visitReturnType(ast, 0, env);
    this.emit(apiName);
    this.visitParams(ast.params, level, env);
    this.emit(';\n');
  }

  InterfaceEachFunction(ast, level) {
    let wrapName = _upperFirst(_name(ast.functionName));
    this.visitAnnotation(ast.annotation, level);
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level);

    this.emit('', level);
    let env = {};
    env.returnType = ast.returnType;
    this.visitReturnType(ast, 0, env);
    this.emit(`${wrapName}`);
    if (this.isExec && wrapName === 'Main') {
      this.emit('(string[] args)');
    } else {
      this.visitParams(ast.params, level, env);
    }
    this.emit(';\n');

  }

  interfaceAfter(level) {
    this.emit('}\n', level);
    this.emit('}\n');
  }

  apiBefore(level, extendParam, filepath, main) {
    this.used.push('System');
    this.used.push('System.IO');
    this.used.push('System.Collections');
    this.used.push('System.Collections.Generic');
    this.used.push('System.Threading.Tasks');
    this.used.push('Darabonba');
    this.used.push('Darabonba.Utils');
    let className = this.className;
    const fileInfo = path.parse(filepath);
    if (!main) {
      const beginNotes = DSL.note.getNotes(this.notes, 0, this.ast.moduleBody.nodes[0].tokenRange[0]);
      const clientNote = beginNotes.find(note => note.note.lexeme === '@clientName');
      if (clientNote) {
        className = clientNote.arg.value.string;
      } else {
        className = `${_upperFirst(fileInfo.name.toLowerCase())}Client`;
      }
    }
    this.emit(`public class ${className} `, level + 1);

    if (extendParam.extend && extendParam.extend.length > 0) {
      const extendsModuleName = this.getRealClientName(this.ast.extends.lexeme);
      const extendsInterfaces = extendParam.extend.filter(item => item.startsWith('I'));
      this.emit(`: ${extendsModuleName}${extendsInterfaces.join(', ') === '' ? '' : ', '}${extendsInterfaces.join(', ')}
    {
`);
    } else {
      this.emit(`
    {
`);
    }

  }

  // init(level) {
  //   // Nothing
  // }

  apiAfter() {
    // Nothing
  }

  wrapBefore() {

  }

  eachFunction(ast, level, env = {}) {
    let wrapName = _upperFirst(_name(ast.functionName));
    if (wrapName === 'Main' && env.isAsyncMode && !(this.isExec && this.asyncOnly)) {
      return;
    }
    this.visitAnnotation(ast.annotation, level);
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level);
    this.emit('public ', level);
    if (ast.isStatic) {
      this.emit('static ');
    }
    if (env.isAsyncMode) {
      this.emit('async ');
      if (wrapName !== 'Main') {
        wrapName += 'Async';
      }
    }

    env.returnType = ast.returnType;
    this.visitReturnType(ast, 0, env);
    this.emit(`${wrapName}`);
    if (this.isExec && wrapName === 'Main') {
      this.emit('(string[] args)');
    } else {
      this.visitParams(ast.params, level, env);
    }

    this.emit('\n');
    this.emit('{\n', level);
    if (ast.functionBody) {
      this.visitWrapBody(ast.functionBody, level + 1, env);
    } else {
      this.emit('throw new NotImplementedException();\n', level + 1);
    }
    this.emit('}\n', level);
  }

  wrapAfter() {
    // Nothing
  }

  moduleAfter() {
    this.emit(`
    }
}\n`, 0);
  }

  isIterator(returnType) {
    if (returnType.type === 'iterator' || returnType.type === 'asyncIterator') {
      return true;
    }
    return false;
  }

  visitWrapBody(ast, level, env) {
    assert.equal(ast.type, 'functionBody');
    this.visitStmts(ast.stmts, level, env);
  }

  visitInit(ast, types, main, filepath, level, env) {
    assert.equal(ast.type, 'init');
    types.forEach((item) => {
      let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]);
      this.visitComments(comments, level + 2);
      this.emit('protected ', level + 2);
      if (item.value.type || item.value.idType) {
        this.visitType(item.value);
        this.emit(' ');
      } else if (this.imports[_name(item.value)]) {
        const realClsName = this.getRealClientName(_name(item.value));
        this.emit(`${realClsName || 'Client'} `);
      } else {
        this.emit(`${this._type(_name(item.value))} `);
      }
      this.emit(`${_vid(item.vid)};\n`);
    });

    this.emit('\n');
    this.visitAnnotation(ast.annotation, level + 2);
    let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
    this.visitComments(comments, level + 2);
    let className = this.className;
    if (!main) {
      const fileInfo = path.parse(filepath);
      const beginNotes = DSL.note.getNotes(this.notes, 0, this.ast.moduleBody.nodes[0].tokenRange[0]);
      const clientNote = beginNotes.find(note => note.note.lexeme === '@clientName');
      if (clientNote) {
        className = clientNote.arg.value.string;
      } else {
        className = `${_upperFirst(fileInfo.name.toLowerCase())}Client`;
      }
    }
    this.emit(`public ${className || 'Client'}`, level + 2);
    this.visitParams(ast.params);
    if (ast.initBody && ast.initBody.stmts[0] && ast.initBody.stmts[0].type === 'super') {
      this.emit(`: base(`);
      for (let i = 0; i < ast.initBody.stmts[0].args.length; i++) {
        if (i > 0) {
          this.emit(', ');
        }
        this.visitExpr(ast.initBody.stmts[0].args[i], level, env);
      }
      this.emit(`)`);
    }
    this.emit('\n');
    this.emit('{\n', level + 2);
    if (ast.initBody) {
      this.visitStmts(ast.initBody, level + 3, {
        isAsyncMode: false
      });
    }
    this.emit('}\n\n', level + 2);
  }

  visitAnnotation(annotation, level) {
    if (!annotation || !annotation.value) {
      return;
    }
    let comments = DSL.comment.getFrontComments(this.comments, annotation.index);
    this.visitComments(comments, level);
    var ast = Annotation.parse(annotation.value);
    var description = ast.items.find((item) => {
      return item.type === 'description';
    });
    var summary = ast.items.find((item) => {
      return item.type === 'summary';
    });
    var _return = ast.items.find((item) => {
      return item.type === 'return';
    });
    var deprecated = ast.items.find((item) => {
      return item.type === 'deprecated';
    });
    var params = ast.items.filter((item) => {
      return item.type === 'param';
    }).map((item) => {
      return {
        name: item.name.id,
        text: item.text.text
      };
    });
    var throws = ast.items.filter((item) => {
      return item.type === 'throws';
    }).map((item) => {
      return item.text.text;
    });

    const deprecatedText = deprecated ? deprecated.text.text : '';
    const summaryText = summary ? summary.text.text : '';
    const descriptionText = description ? description.text.text.trimEnd() : '';
    const returnText = _return ? _return.text.text.trimEnd() : '';
    let hasNextSection = false;

    if (deprecated) {
      this.emit(`/// <term><b>Deprecated</b></term>\n`, level);
      this.emit(`/// \n`, level);
      deprecatedText.trimEnd().split('\n').forEach((line) => {
        this.emit(`/// ${line}\n`, level);
      });
      hasNextSection = true;
    }
    if (summaryText !== '') {
      if (hasNextSection) {
        this.emit(`/// \n`, level);
      }
      this.emit(`/// <term><b>Summary:</b></term>\n`, level);
      this.emit(`/// <summary>\n`, level);
      const summaryTexts = md2Xml(summaryText);
      summaryTexts.split('\n').forEach((line) => {
        this.emit(`/// ${line}\n`, level);
      });
      this.emit(`/// </summary>\n`, level);
      hasNextSection = true;
    }
    if (descriptionText !== '') {
      if (hasNextSection) {
        this.emit(`/// \n`, level);
      }
      this.emit(`/// <term><b>Description:</b></term>\n`, level);
      this.emit(`/// <description>\n`, level);
      const descriptionTexts = md2Xml(descriptionText);
      descriptionTexts.split('\n').forEach((line) => {
        this.emit(`/// ${line}\n`, level);
      });
      this.emit(`/// </description>\n`, level);
      hasNextSection = true;
    }
    if (params.length > 0) {
      if (hasNextSection) {
        this.emit(`/// \n`, level);
      }
      params.forEach((item) => {
        this.emit(`/// <param name="${item.name}">\n`, level);
        item.text.trimEnd().split('\n').forEach((line) => {
          this.emit(`/// ${line}\n`, level);
        });
        this.emit(`/// </param>\n`, level);
      });
      hasNextSection = true;
    }
    if (returnText) {
      if (hasNextSection) {
        this.emit(`/// \n`, level);
      }
      this.emit(`/// <returns>\n`, level);
      returnText.split('\n').forEach((line) => {
        this.emit(`/// ${line}\n`, level);
      });
      this.emit(`/// </returns>\n`, level);
      hasNextSection = true;
    }
    if (throws.length > 0) {
      if (hasNextSection) {
        this.emit(`/// \n`, level);
      }
      throws.forEach((item, index) => {
        this.emit(`/// <term><b>Exception:</b></term>\n`, level);
        item.trimEnd().split('\n').forEach((line) => {
          this.emit(`/// ${line}\n`, level);
        });
        if (index < throws.length - 1) {
          this.emit(`/// \n`, level);
        }
      });
    }
    if (deprecated) {
      this.emit(`[Obsolete("`, level);
      const lines = deprecatedText.trimEnd().split('\n');
      lines.forEach((line, index) => {
        if (index === lines.length - 1) {
          this.emit(`${line}`);
        } else {
          this.emit(`${line}\\n`);
        }
      });
      this.emit(`")]\n`);
    }
  }

  visitComments(comments, level) {
    comments.forEach(comment => {
      this.emit(`${comment.value}`, level);
      this.emit(`\n`);
    });
  }

  visitConsoleCSProj(uniqueUsed) {
    let csprojPath = path.join(this.csprojOutputDir, (this.config.packageInfo.name || 'client') + '.csproj');
    let json = {};
    json = parse(fs.readFileSync(path.join(__dirname, 'files', 'consoleCsproj.tmpl')));
    //填写包的基本信息
    let propertyGroup = json.Project.PropertyGroup;
    propertyGroup.forEach((property) => {
      if (Object.hasOwn(property, 'RootNamespace')) {
        property['RootNamespace'] = this.config.namespace;
      }
    });

    let dependenciesClass = [];
    if (uniqueUsed.includes('Newtonsoft.Json')) {
      dependenciesClass.push('Newtonsoft.Json:13.0.1');
    }
    Object.keys(this.imports).forEach((key) => {
      dependenciesClass.push(this.imports[key].release);
    });
    // 兜底依赖 Darabonba 的 1.0.0 版本
    const teaVersion = '1.0.0';
    dependenciesClass.push(`Darabonba:${teaVersion}`);

    let currentItemGroup = {};
    dependenciesClass.forEach((item, index) => {
      let dependency = item.split(':');
      currentItemGroup[dependency[0]] = dependency[1];
    });
    const newItemGroup = { ...currentItemGroup, ...this.packageManager };

    //寻找用来写入的itemGroup, 避开有特殊参数的itemGroup
    let itemGroup = {};

    //添加或更新依赖包
    Object.entries(newItemGroup || {}).forEach(([key, value]) => {
      let writeReference = {};
      itemGroup.PackageReference = itemGroup.PackageReference || [];
      writeReference.$ = { 'Include': key, 'Version': value };
      itemGroup.PackageReference.push(writeReference);
    });

    if (this.typedef) {
      Object.keys(this.typedef).map(key => {
        if (!this.typedef[key].package) {
          return;
        }
        dependenciesClass.push(this.typedef[key].package);
      });
    }

    json.Project.ItemGroup = itemGroup;
    const builder = new xml2js.Builder();
    const newCsproj = builder.buildObject(json);
    fs.writeFileSync(csprojPath, Entities.decode(newCsproj));
  }

  visitCSProj(uniqueUsed) {
    let csprojPath = path.join(this.csprojOutputDir, (this.config.packageInfo.name || 'client') + '.csproj');
    let json = {};
    // 保留本地已有的包
    if (!fs.existsSync(csprojPath)) {
      json = parse(fs.readFileSync(path.join(__dirname, 'files', 'csproj.tmpl')));
    } else {
      json = parse(fs.readFileSync(csprojPath));
    }

    //填写包的基本信息
    let propertyGroup = json.Project.PropertyGroup;
    let assemblyInfo = this.release.split(':');
    propertyGroup.forEach((property) => {
      if (Object.hasOwn(property, 'RootNamespace')) {
        property['RootNamespace'] = this.config.namespace;
        if (this.config.packageInfo && this.config.packageInfo.company) {
          property['Authors'] = this.config.packageInfo.company;
        } else if (this.config.maintainers && this.config.maintainers.name) {
          property['Authors'] = this.config.maintainers.name;
        }
        if (this.config.packageInfo && this.config.packageInfo.description) {
          property['Description'] = this.config.packageInfo.description;
        }
        if (this.config.packageInfo && this.config.packageInfo.property) {
          Object.assign(property, this.config.packageInfo.property);
        }
      }
      if (Object.hasOwn(property, 'AssemblyName')) {
        property['AssemblyName'] = assemblyInfo[0];
      }
      // 支持 packageInfo 中配置当前包发布的版本(之前是<Version/>)
      if (Object.hasOwn(property, 'Version') && this.packageVersion) {
        property['Version'] = this.packageVersion;
      }
    });

    let dependenciesClass = [];
    if (uniqueUsed.includes('Newtonsoft.Json')) {
      dependenciesClass.push('Newtonsoft.Json:13.0.1');
    }
    Object.keys(this.imports).forEach((key) => {
      if (!this.imports[key].release) {
        return;
      }
      dependenciesClass.push(this.imports[key].release);
    });

    if (this.typedef) {
      Object.keys(this.typedef).map(key => {
        if (!this.typedef[key].package) {
          return;
        }
        dependenciesClass.push(this.typedef[key].package);
      });
    }

    // 兜底依赖 Darabonba 的 1.0.0 版本
    const teaVersion = '1.0.0';
    dependenciesClass.push(`Darabonba:${teaVersion}`);

    //寻找用来写入的itemGroup, 避开有特殊参数的itemGroup
    let itemGroup = json.Project.ItemGroup;
    let writeItem = {};
    itemGroup.forEach((item) => {
      if (!item.$) {
        writeItem = item;
      }
    });

    let newDependenciesClass = [];
    if (this.packageManager) {
      const needAddPackages = Object.entries(this.packageManager || {}).map(([key, value]) => `${key}:${value}`);
      const needAddPackageName = new Set(needAddPackages.map(item => item.split(':')[0]));
      newDependenciesClass = [...needAddPackages];
      dependenciesClass.forEach(item => {
        const [pkgName] = item.split(':');
        if (!needAddPackageName.has(pkgName)) {
          newDependenciesClass.push(item);
        }
      });
    } else {
      newDependenciesClass = dependenciesClass;
    }

    //添加或更新依赖包
    newDependenciesClass.forEach((item, index) => {
      if (item) {
        let dependency = item.split(':');
        //遍历所有的itemGroup,判断是否已存在该依赖
        let writeReference = null;
        itemGroup.forEach((group) => {
          if (group.PackageReference) {
            group.PackageReference.forEach((reference) => {
              if (reference.$.Include === dependency[0]) {
                writeReference = reference;
              }
            });
          }
        });
        if (writeReference) {
          writeReference.$.Version = dependency[1];
        } else {
          writeReference = {};
          writeItem.PackageReference = writeItem.PackageReference || [];
          writeReference.$ = { 'Include': dependency[0], 'Version': dependency[1] };
          writeItem.PackageReference.push(writeReference);
        }
      }
    });

    const builder = new xml2js.Builder();
    const newCsproj = builder.buildObject(json);
    fs.writeFileSync(csprojPath, Entities.decode(newCsproj));
  }

  visitAssemblyInfo() {
    let propertiesDir = path.join(this.csprojOutputDir, 'Properties');
    if (!fs.existsSync(propertiesDir)) {
      fs.mkdirSync(propertiesDir, {
        recursive: true
      });
    }
    let assemblyPath = path.join(propertiesDir, 'AssemblyInfo.cs');
    let content = {};
    if (!fs.existsSync(assemblyPath)) {
      content = fs.readFileSync(path.join(__dirname, 'files', 'assemblyInfo.tmpl')).toString();
      let params = {
        title: this.config.packageInfo.title || '',
        description: this.config.packageInfo.description || '',
        company: this.config.packageInfo.company || '',
        product: this.config.packageInfo.product || '',
        guid: UUID.v1(),
        version: this.release.split(':')[1],
        copyRight: this.config.packageInfo.copyRight || '',
      };

      if (content !== '') {
        content = render(content, params);
      }
      fs.writeFileSync(assemblyPath, content);
    } else {
      content = fs.readFileSync(assemblyPath).toString();
      content = content.replace(/AssemblyVersion\("[\S\s].*?"\)/, `AssemblyVersion("${this.release.split(':')[1]}.0")`);
      content = content.replace(/AssemblyFileVersion\("[\S\s].*?"\)/, `AssemblyFileVersion("${this.release.split(':')[1]}.0")`);
      fs.writeFileSync(assemblyPath, content);
    }

  }
}