1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15import ExcelJS from 'exceljs';
16import path from 'path';
17import fs from 'fs';
18import { execSync } from 'child_process';
19import { EnumUtils } from '../utils/EnumUtils';
20import { FileUtils } from '../utils/FileUtils';
21import { LogUtil } from '../utils/logUtil';
22import { FilesMap, Parser } from '../coreImpl/parser/parser';
23import { parserParam } from '../coreImpl/parser/NodeProcessor';
24import { DiffHelper } from '../coreImpl/diff/diff';
25import {
26  BasicDiffInfo,
27  diffTypeMap,
28  ApiDiffType,
29  DiffNumberInfo,
30  apiChangeMap,
31  isNotApiSet,
32} from '../typedef/diff/ApiInfoDiff';
33import { WriterHelper } from './writer';
34import { LocalEntry } from '../coreImpl/checker/local_entry';
35import { ApiResultMessage, ApiResultSimpleInfo } from '../typedef/checker/result_type';
36import { NumberConstant } from '../utils/Constant';
37import { ApiStatisticsHelper } from '../coreImpl/statistics/Statistics';
38import { ApiStatisticsInfo, StatisticsInfoValueType } from '../typedef/statistics/ApiStatistics';
39import { SyscapProcessorHelper } from '../coreImpl/diff/syscapFieldProcessor';
40import { ApiCountInfo } from '../typedef/count/ApiCount';
41import { ApiCountHelper } from '../coreImpl/count/count';
42import { CommonFunctions } from '../utils/checkUtils';
43import { FunctionUtils, KitData } from '../utils/FunctionUtils';
44import { ApiInfo, ApiType } from '../typedef/parser/ApiInfoDefination';
45import { checkEntryType } from '../typedef/checker/result_type';
46
47
48/**
49 * 工具名称的枚举值,用于判断执行哪个工具
50 *
51 * @enum { string }
52 */
53export enum toolNameType {
54  /**
55   * 统计工具
56   */
57  COLLECT = 'collect',
58  /**
59   * 检查工具
60   */
61  CHECK = 'check',
62  /**
63   * 检查工具 线上版
64   */
65  CHECKONLINE = 'checkOnline',
66  /**
67   * 兼容性变更检查工具 线上版
68   */
69  APICHANGECHECK = 'apiChangeCheck',
70  /**
71   * diff工具
72   */
73  DIFF = 'diff',
74  /**
75   * 标签漏标检查
76   */
77  LABELDETECTION = 'detection',
78  /**
79   * API个数统计
80   */
81  COUNT = 'count'
82}
83
84/**
85 * 工具名称的set集合,通过enum生成
86 */
87export const toolNameSet: Set<string> = new Set(EnumUtils.enum2arr(toolNameType));
88
89/**
90 * 输出文件格式,用于判断输出什么类型的文件
91 *
92 * @enum { string }
93 */
94export enum formatType {
95  NULL = '',
96  JSON = 'json',
97  EXCEL = 'excel',
98  CHANGELOG = 'changelog',
99}
100
101/**
102 * 输出文件格式的set集合,通过enum生成
103 */
104export const formatSet: Set<string> = new Set(EnumUtils.enum2arr(formatType));
105
106export const Plugin: PluginType = {
107  pluginOptions: {
108    name: 'parser',
109    version: '0.1.0',
110    description: 'Compare the parser the SDKS',
111    commands: [
112      {
113        isRequiredOption: true,
114        options: [`-N,--tool-name <${[...toolNameSet]}>`, 'tool name ', 'checkOnline'],
115      },
116      {
117        isRequiredOption: false,
118        options: ['-C,--collect-path <string>', 'collect api path', './api'],
119      },
120      {
121        isRequiredOption: false,
122        options: ['-F,--collect-file <string>', 'collect api file array', ''],
123      },
124      {
125        isRequiredOption: false,
126        options: ['-L,--check-labels <string>', 'detection check labels', ''],
127      },
128      {
129        isRequiredOption: false,
130        options: ['--isOH <string>', 'detection check labels', ''],
131      },
132      {
133        isRequiredOption: false,
134        options: ['--path <string>', 'check api path, split with comma', ''],
135      },
136      {
137        isRequiredOption: false,
138        options: ['--checker <string>', 'check api rule, split with comma', 'all'],
139      },
140      {
141        isRequiredOption: false,
142        options: ['--prId <string>', 'check api prId', ''],
143      },
144      {
145        isRequiredOption: false,
146        options: ['--is-increment <string>', 'check api is increment, only check change', 'true'],
147      },
148      {
149        isRequiredOption: false,
150        options: ['--excel <string>', 'check api excel', 'false'],
151      },
152      {
153        isRequiredOption: false,
154        options: ['--old <string>', 'diff old sdk path', './api'],
155      },
156      {
157        isRequiredOption: false,
158        options: ['--new <string>', 'diff new sdk path', './api'],
159      },
160      {
161        isRequiredOption: false,
162        options: ['--old-version <string>', 'old sdk version', '0'],
163      },
164      {
165        isRequiredOption: false,
166        options: ['--new-version <string>', 'new sdk version', '0'],
167      },
168      {
169        isRequiredOption: false,
170        options: ['--output <string>', 'output file path', './'],
171      },
172      {
173        isRequiredOption: false,
174        options: [`--format <${[...formatSet]}>`, 'output file format', 'json'],
175      },
176      {
177        isRequiredOption: false,
178        options: ['--changelogUrl <string>', 'changelog url', ''],
179      },
180      {
181        isRequiredOption: false,
182        options: ['--all <boolean>', 'is all sheet', ''],
183      },
184    ],
185  },
186
187  start: async function (argv: OptionObjType) {
188    const toolName: toolNameType = argv.toolName;
189    const method: ToolNameMethodType | undefined = toolNameMethod.get(toolName);
190    if (!method) {
191      LogUtil.i(
192        'CommandArgs',
193        `tool-name may use error name or don't have function,tool-name can use 'collect' or 'diff'`
194      );
195      return;
196    }
197    const options: OptionObjType = {
198      toolName: toolName,
199      collectPath: argv.collectPath,
200      collectFile: argv.collectFile,
201      checkLabels: argv.checkLabels,
202      isOH: argv.isOH,
203      path: argv.path,
204      checker: argv.checker,
205      prId: argv.prId,
206      isIncrement: argv.isIncrement,
207      old: argv.old,
208      new: argv.new,
209      oldVersion: argv.oldVersion,
210      newVersion: argv.newVersion,
211      output: argv.output,
212      format: argv.format,
213      changelogUrl: argv.changelogUrl,
214      excel: argv.excel,
215      all: argv.all,
216    };
217    const methodInfos: ToolNameValueType = method(options);
218
219    outputInfos(methodInfos.data, options, methodInfos.callback);
220  },
221  stop: function () {
222    LogUtil.i('commander', `elapsed time: ${Date.now() - startTime}`);
223  },
224};
225let startTime = Date.now();
226
227/**
228 * 工具获取完数据之后,根据format和其他数据处理输出
229 *
230 * @param {ToolReturnData} infos 工具返回的数据
231 * @param {OptionObjType} options 传入的命令参数
232 * @param {(ToolNameExcelCallback  | undefined)} callback 导出excel的回调
233 */
234function outputInfos(infos: ToolReturnData, options: OptionObjType, callback: ToolNameExcelCallback | undefined): void {
235  const format = options.format;
236  let jsonFileName = `${options.toolName}_${options.oldVersion}_${options.newVersion}.json`;
237
238  if (!format) {
239    return;
240  }
241  if (options.toolName === toolNameType.COUNT) {
242    jsonFileName = 'api_kit_js.json';
243  }
244  switch (format) {
245    case formatType.JSON:
246      WriterHelper.JSONReporter(
247        String(infos[0]),
248        options.output,
249        jsonFileName
250      );
251      break;
252    case formatType.EXCEL:
253      WriterHelper.ExcelReporter(infos, options.output, `${options.toolName}.xlsx`, callback, options);
254      break;
255    case formatType.CHANGELOG:
256      WriterHelper.JSONReporter(String(infos[0]), options.output, `${options.toolName}.json`);
257      break;
258    default:
259      break;
260  }
261}
262
263/**
264 * 收集api工具调用方法
265 *
266 * @param { OptionObjType } options
267 * @return { ToolNameValueType }
268 */
269function collectApi(options: OptionObjType): ToolNameValueType {
270  // process.env.NEED_DETECTION = 'true';
271  const fileDir: string = path.resolve(FileUtils.getBaseDirName(), options.collectPath);
272  let collectFile: string = '';
273  if (options.collectFile !== '') {
274    collectFile = path.resolve(FileUtils.getBaseDirName(), options.collectFile);
275    parserParam.setSdkPath(collectFile);
276  }
277  let allApis: FilesMap;
278  try {
279    if (FileUtils.isDirectory(fileDir)) {
280      allApis = Parser.parseDir(fileDir, collectFile);
281    } else {
282      allApis = Parser.parseFile(path.resolve(fileDir, '..'), fileDir);
283    }
284    const statisticsInfosObject: StatisticsInfoValueType = ApiStatisticsHelper.getApiStatisticsInfos(allApis);
285    const fileContent: string = Parser.getParseResults(allApis);
286    let data: ApiStatisticsInfo[] | string[] = [fileContent];
287    if (options.format === 'excel') {
288      const allApiStatisticsInfos: ApiStatisticsInfo[] | undefined = statisticsInfosObject.allApiStatisticsInfos;
289      data = statisticsInfosObject.apiStatisticsInfos;
290      if (allApiStatisticsInfos) {
291        WriterHelper.ExcelReporter(
292          allApiStatisticsInfos,
293          options.output,
294          `all_${options.toolName}.xlsx`,
295          collectApiCallback as ToolNameExcelCallback
296        );
297      }
298    }
299
300    return {
301      data: data,
302      callback: collectApiCallback as ToolNameExcelCallback,
303    };
304  } catch (exception) {
305    const error = exception as Error;
306    LogUtil.e(`error collect`, error.stack ? error.stack : error.message);
307    return {
308      data: [],
309      callback: collectApiCallback as ToolNameExcelCallback,
310    };
311  }
312}
313
314function collectApiCallback(apiData: ApiStatisticsInfo[], workbook: ExcelJS.Workbook, dest?: string,
315  options?: OptionObjType): void {
316  const sheet: ExcelJS.Worksheet = workbook.addWorksheet();
317  const apiRelationsSet: Set<string> = new Set();
318  const kitData: KitData = FunctionUtils.readKitFile();
319  sheet.name = 'JsApi';
320  sheet.views = [{ xSplit: 1 }];
321  sheet.getRow(1).values = [
322    '模块名',
323    '类名',
324    '方法名',
325    '函数',
326    '类型',
327    '起始版本',
328    '废弃版本',
329    'syscap',
330    '错误码',
331    '是否为系统API',
332    '模型限制',
333    '权限',
334    '是否支持跨平台',
335    '是否支持卡片应用',
336    '是否为高阶API',
337    '装饰器',
338    'kit',
339    '文件路径',
340    '子系统',
341    '父节点类型',
342    '父节点API是否可选'
343  ];
344  let lineNumber = 2;
345  apiData.forEach((apiInfo: ApiStatisticsInfo) => {
346    const apiRelations: string = `${apiInfo.getHierarchicalRelations()},${apiInfo.getDefinedText()}`;
347    if (apiRelationsSet.has(apiRelations)) {
348      return;
349    }
350    sheet.getRow(lineNumber).values = [
351      apiInfo.getPackageName(),
352      apiInfo.getParentModuleName(),
353      apiInfo.getApiName(),
354      apiInfo.getDefinedText(),
355      apiInfo.getApiType(),
356      apiInfo.getSince() === '-1' ? '' : apiInfo.getSince(),
357      apiInfo.getDeprecatedVersion() === '-1' ? '' : apiInfo.getDeprecatedVersion(),
358      apiInfo.getSyscap(),
359      apiInfo.getErrorCodes().join() === '-1' ? '' : apiInfo.getErrorCodes().join(),
360      apiInfo.getApiLevel(),
361      apiInfo.getModelLimitation(),
362      apiInfo.getPermission(),
363      apiInfo.getIsCrossPlatForm(),
364      apiInfo.getIsForm(),
365      apiInfo.getIsAutomicService(),
366      apiInfo.getDecorators()?.join(),
367      apiInfo.getKitInfo() === ''
368        ? kitData.kitNameMap.get(apiInfo.getFilePath().replace(/\\/g, '/').replace('api/', ''))
369        : apiInfo.getKitInfo(),
370      apiInfo.getFilePath(),
371      kitData.subsystemMap.get(apiInfo.getFilePath().replace(/\\/g, '/').replace('api/', '')),
372      apiInfo.getParentApiType(),
373      apiInfo.getIsOptional(),
374    ];
375    lineNumber++;
376    apiRelationsSet.add(apiRelations);
377  });
378
379  if (options?.all) {
380    handleCollectData(apiData, workbook);
381  }
382}
383
384/**
385 * 用于处理统计工具的数据
386 * 
387 * @param apiData 
388 * @param workbook 
389 */
390function handleCollectData(apiData: ApiStatisticsInfo[], workbook: ExcelJS.Workbook): void {
391  const sheet: ExcelJS.Worksheet = workbook.addWorksheet();
392  const apiRelationsSet: Set<string> = new Set();
393  const kitData: KitData = FunctionUtils.readKitFile();
394  sheet.name = 'JsApi定制版本';
395  sheet.views = [{ xSplit: 1 }];
396  sheet.getRow(1).values = [
397    '模块名',
398    '类名',
399    '方法名',
400    '函数',
401    '类型',
402    '起始版本',
403    '废弃版本',
404    'syscap',
405    '错误码',
406    '是否为系统API',
407    '模型限制',
408    '权限',
409    '是否支持跨平台',
410    '是否支持卡片应用',
411    '是否为高阶API',
412    '装饰器',
413    'kit',
414    '文件路径',
415    '子系统',
416    '接口全路径'
417  ];
418  let lineNumber = 2;
419  apiData.forEach((apiInfo: ApiStatisticsInfo) => {
420    const apiRelations: string = `${apiInfo.getHierarchicalRelations()},${apiInfo.getDefinedText()}`;
421    if (apiRelationsSet.has(apiRelations)) {
422      return;
423    }
424    sheet.getRow(lineNumber).values = [
425      apiInfo.getPackageName(),
426      apiInfo.getParentModuleName(),
427      apiInfo.getApiName(),
428      apiInfo.getDefinedText(),
429      apiInfo.getApiType(),
430      apiInfo.getSince() === '-1' ? '' : apiInfo.getSince(),
431      apiInfo.getDeprecatedVersion() === '-1' ? '' : apiInfo.getDeprecatedVersion(),
432      apiInfo.getSyscap(),
433      apiInfo.getErrorCodes().join() === '-1' ? '' : apiInfo.getErrorCodes().join(),
434      apiInfo.getApiLevel(),
435      apiInfo.getModelLimitation(),
436      apiInfo.getPermission(),
437      apiInfo.getIsCrossPlatForm(),
438      apiInfo.getIsForm(),
439      apiInfo.getIsAutomicService(),
440      apiInfo.getDecorators()?.join(),
441      apiInfo.getKitInfo() === ''
442        ? kitData.kitNameMap.get(apiInfo.getFilePath().replace(/\\/g, '/').replace('api/', ''))
443        : apiInfo.getKitInfo(),
444      apiInfo.getFilePath(),
445      kitData.subsystemMap.get(apiInfo.getFilePath().replace(/\\/g, '/').replace('api/', '')),
446      apiInfo.getHierarchicalRelations().replace(/\//g, '#').replace('api\\', ''),
447    ];
448    lineNumber++;
449    apiRelationsSet.add(apiRelations);
450  });
451}
452/**
453 * api检查工具调用方法
454 *
455 * @param { OptionObjType } options
456 * @return { ToolNameValueType }
457 */
458function checkApi(options: OptionObjType): ToolNameValueType {
459  try {
460    let mdApiFiles: string[] = [];
461    const filePathTxt: string = path.resolve(FileUtils.getBaseDirName(), '../mdFiles.txt');
462    if (fs.existsSync(filePathTxt)) {
463      mdApiFiles = CommonFunctions.getMdFiles(filePathTxt);
464    }
465    const checkParam: checkEntryType = {
466      filePathArr: mdApiFiles,
467      fileRuleArr: ['all'],
468      output: './result.json',
469      prId: options.prId,
470      isOutExcel: 'true',
471      isIncrement: Boolean(options.isIncrement === 'true'),
472    };
473    LocalEntry.checkEntryLocal(checkParam);
474    return {
475      data: [],
476    };
477  } catch (exception) {
478    const error = exception as Error;
479    LogUtil.e('error check', error.stack ? error.stack : error.message);
480    return {
481      data: [],
482    };
483  }
484}
485
486/**
487 * api检查工具调用方法
488 *
489 * @param { OptionObjType } options
490 * @return { ToolNameValueType }
491 */
492function checkOnline(options: OptionObjType): ToolNameValueType {
493  options.format = formatType.NULL;
494  try {
495
496    const checkParam: checkEntryType = {
497      filePathArr: options.path.split(','),
498      fileRuleArr: options.checker.split(','),
499      output: options.output,
500      prId: options.prId,
501      isOutExcel: options.excel,
502      isIncrement: Boolean(options.isIncrement === 'true'),
503    };
504    LocalEntry.checkEntryLocal(checkParam);
505    return {
506      data: [],
507    };
508  } catch (exception) {
509    const error = exception as Error;
510    LogUtil.e('error check', error.stack ? error.stack : error.message);
511  } finally {
512  }
513  return {
514    data: [],
515  };
516}
517
518/**
519 * api检查工具调用方法
520 *
521 * @param { OptionObjType } options
522 * @return { ToolNameValueType }
523 */
524function apiChangeCheck(options: OptionObjType): ToolNameValueType {
525  options.format = formatType.NULL;
526  try {
527    const checkParam: checkEntryType = {
528      filePathArr: [],
529      fileRuleArr: options.checker.split(','),
530      output: options.output,
531      prId: options.prId,
532      isOutExcel: options.excel,
533      isIncrement: Boolean(options.isIncrement === 'true'),
534    };
535    LocalEntry.apiChangeCheckEntryLocal(checkParam);
536    return {
537      data: [],
538    };
539  } catch (exception) {
540    const error = exception as Error;
541    LogUtil.e('error api change check', error.stack ? error.stack : error.message);
542  } finally {
543  }
544  return {
545    data: [],
546  };
547}
548
549/**
550 * diffApi工具调用方法
551 *
552 * @param { OptionObjType } options
553 * @return { ToolNameValueType }
554 */
555function diffApi(options: OptionObjType): ToolNameValueType {
556  const oldFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.old);
557  const newFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.new);
558  const status: fs.Stats = fs.statSync(oldFileDir);
559  let data: BasicDiffInfo[] = [];
560  try {
561    if (status.isDirectory()) {
562      const newSDKApiMap: FilesMap = Parser.parseDir(newFileDir);
563      Parser.cleanParserParamSDK();
564      const oldSDKApiMap: FilesMap = Parser.parseDir(oldFileDir);
565      data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap, options.all);
566    } else {
567      const oldSDKApiMap: FilesMap = Parser.parseFile(path.resolve(oldFileDir, '..'), oldFileDir);
568      Parser.cleanParserParamSDK();
569      const newSDKApiMap: FilesMap = Parser.parseFile(path.resolve(newFileDir, '..'), newFileDir);
570      data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap, options.all);
571    }
572    let finalData: (string | BasicDiffInfo)[] = [];
573    if (options.format === formatType.JSON) {
574      finalData = [JSON.stringify(data, null, NumberConstant.INDENT_SPACE)];
575    } else {
576      finalData = data;
577    }
578    return {
579      data: finalData,
580      callback: diffApiCallback as ToolNameExcelCallback,
581    };
582  } catch (exception) {
583    const error = exception as Error;
584    LogUtil.e('error diff', error.stack ? error.stack : error.message);
585    return {
586      data: [],
587      callback: diffApiCallback as ToolNameExcelCallback,
588    };
589  }
590}
591function detectionApi(options: OptionObjType): ToolNameValueType {
592  process.env.NEED_DETECTION = 'true';
593  process.env.IS_OH = options.isOH;
594  options.format = formatType.NULL;
595  const fileDir: string = path.resolve(FileUtils.getBaseDirName(), options.collectPath);
596  let collectFile: string = '';
597  if (options.collectFile !== '') {
598    collectFile = path.resolve(FileUtils.getBaseDirName(), options.collectFile);
599  }
600  let allApis: FilesMap;
601  let buffer: Buffer | string = Buffer.from('');
602  try {
603    if (FileUtils.isDirectory(fileDir)) {
604      allApis = Parser.parseDir(fileDir, collectFile);
605    } else {
606      allApis = Parser.parseFile(path.resolve(fileDir, '..'), fileDir);
607    }
608    const fileContent: string = Parser.getParseResults(allApis);
609    WriterHelper.JSONReporter(fileContent, path.dirname(options.output), 'detection.json');
610    let runningCommand: string = '';
611
612    if (process.env.NODE_ENV === 'development') {
613      runningCommand = `python ${path.resolve(
614        FileUtils.getBaseDirName(),
615        '../api_label_detection/src/main.py'
616      )} -N detection -L ${options.checkLabels} -P ${path.resolve(
617        path.dirname(options.output),
618        'detection.json'
619      )} -O ${path.resolve(options.output)}`;
620    } else if (process.env.NODE_ENV === 'production') {
621      runningCommand = `${path.resolve(FileUtils.getBaseDirName(), './main.exe')} -N detection -L ${options.checkLabels
622        } -P ${path.resolve(path.dirname(options.output), 'detection.json')} -O ${path.resolve(options.output)}`;
623    }
624    buffer = execSync(runningCommand, {
625      timeout: 120000,
626    });
627  } catch (exception) {
628    const error = exception as Error;
629    LogUtil.e(`error collect`, error.stack ? error.stack : error.message);
630  } finally {
631    LogUtil.i(`detection run over`, buffer.toString());
632  }
633  return {
634    data: [],
635  };
636}
637
638/**
639 * api个数统计工具的入口函数
640 * 
641 * @param { OptionObjType } options 
642 * @returns { ToolNameValueType }
643 */
644function countApi(options: OptionObjType): ToolNameValueType {
645  const fileDir: string = path.resolve(FileUtils.getBaseDirName(), '../../api');
646  let collectFile: string = '';
647  if (options.collectFile !== '') {
648    collectFile = path.resolve(FileUtils.getBaseDirName(), options.collectFile);
649  }
650  let allApis: FilesMap;
651  try {
652    if (FileUtils.isDirectory(fileDir)) {
653      allApis = Parser.parseDir(fileDir, collectFile);
654    } else {
655      allApis = Parser.parseFile(path.resolve(fileDir, '..'), fileDir);
656    }
657    const statisticApiInfos: ApiStatisticsInfo[] = ApiStatisticsHelper.getApiStatisticsInfos(allApis).apiStatisticsInfos;
658    const apiCountInfos: ApiCountInfo[] = ApiCountHelper.countApi(statisticApiInfos);
659    let finalData: (string | ApiCountInfo)[] = [];
660    if (options.format === formatType.JSON) {
661      finalData = [JSON.stringify(apiCountInfos, null, NumberConstant.INDENT_SPACE)];
662    } else {
663      finalData = apiCountInfos;
664    }
665    return {
666      data: finalData,
667      callback: countApiCallback as ToolNameExcelCallback,
668    };
669  } catch (exception) {
670    const error = exception as Error;
671    LogUtil.e(`error count`, error.stack ? error.stack : error.message);
672    return {
673      data: [],
674      callback: countApiCallback as ToolNameExcelCallback,
675    };
676  }
677}
678
679function countApiCallback(data: ApiCountInfo[], workbook: ExcelJS.Workbook): void {
680  const sheet: ExcelJS.Worksheet = workbook.addWorksheet();
681  sheet.name = 'api数量';
682  sheet.views = [{ xSplit: 1 }];
683  sheet.getRow(1).values = ['子系统', 'kit', '文件', 'api数量'];
684  data.forEach((countInfo: ApiCountInfo, index: number) => {
685    sheet.getRow(index + NumberConstant.LINE_IN_EXCEL).values = [
686      countInfo.getsubSystem(),
687      countInfo.getKitName(),
688      countInfo.getFilePath(),
689      countInfo.getApiNumber()
690    ];
691  });
692}
693
694/**
695 * diffApi工具导出excel时的回调方法
696 *
697 * @param {BasicDiffInfo[]} data diffApi工具获取到的数据
698 * @param {ExcelJS.Workbook} workbook ExcelJS构建的Workbook对象
699 */
700function diffApiCallback(
701  data: BasicDiffInfo[],
702  workbook: ExcelJS.Workbook,
703  dest?: string,
704  options?: OptionObjType
705): void {
706  const relationsSet: Set<string> = new Set();
707  const kitData: KitData = FunctionUtils.readKitFile();
708  const sheet: ExcelJS.Worksheet = workbook.addWorksheet('api差异');
709  sheet.views = [{ xSplit: 2 }];
710  sheet.getRow(1).values = ['操作标记', '差异项-旧版本', '差异项-新版本', 'd.ts文件', '归属子系统', 'kit', '是否为系统API'];
711  data.forEach((diffInfo: BasicDiffInfo, index: number) => {
712    relationsSet.add(getRelation(diffInfo));
713    const dtsName = diffInfo.getNewDtsName() ? diffInfo.getNewDtsName() : diffInfo.getOldDtsName();
714    sheet.getRow(index + NumberConstant.LINE_IN_EXCEL).values = [
715      diffTypeMap.get(diffInfo.getDiffType()),
716      joinOldMessage(diffInfo),
717      joinNewMessage(diffInfo),
718      dtsName.replace(/\\/g, '/'),
719      kitData.subsystemMap.get(dtsName.replace(/\\/g, '/').replace('api/', '')),
720      SyscapProcessorHelper.getSingleKitInfo(diffInfo) === ''
721        ? kitData.kitNameMap.get(dtsName.replace(/\\/g, '/').replace('api/', ''))
722        : SyscapProcessorHelper.getSingleKitInfo(diffInfo),
723      diffInfo.getIsSystemapi(),
724    ];
725  });
726  WriterHelper.MarkdownReporter.writeInMarkdown(data, dest);
727
728  if (options?.all) {
729    addApiNumberSheet(relationsSet, workbook, data, kitData);
730  }
731
732
733}
734
735/**
736 * 添加diff工具结果的另一个sheet页,对API变更信息的次数+兼容性信息进行统计
737 * 
738 * @param relationsSet 
739 * @param workbook 
740 * @param data 
741 * @param kitData 
742 */
743function addApiNumberSheet(relationsSet: Set<string>, workbook: ExcelJS.Workbook, data: BasicDiffInfo[],
744  kitData: KitData): void {
745  const numberSheet: ExcelJS.Worksheet = workbook.addWorksheet('api变更数量统计');
746  numberSheet.views = [{ xSplit: 2 }];
747  numberSheet.getRow(1).values = [
748    'api名称',
749    'kit名称',
750    '归属子系统',
751    '是否是api',
752    'api类型',
753    '操作标记',
754    '变更类型',
755    '兼容性',
756    '变更次数',
757    '差异项-旧版本',
758    '差异项-新版本',
759    '兼容性列表',
760    '接口全路径',
761    '是否为系统API',
762    '是否为同名API'
763  ];
764  let diffTypeNumberArr: DiffNumberInfo[] = [];
765  relationsSet.forEach((apiRelation: string) => {
766    let apiName: string = '';
767    const diffNumberInfo: DiffNumberInfo = new DiffNumberInfo();
768    data.forEach((diffInfo: BasicDiffInfo) => {
769      const dtsName: string = diffInfo.getNewDtsName() ? diffInfo.getNewDtsName() : diffInfo.getOldDtsName();
770      const kitName =
771        SyscapProcessorHelper.getSingleKitInfo(diffInfo) === ''
772          ? kitData.kitNameMap.get(dtsName.replace(/\\/g, '/').replace('api/', ''))
773          : SyscapProcessorHelper.getSingleKitInfo(diffInfo);
774      if (apiRelation === getRelation(diffInfo)) {
775        apiName = getDiffApiName(diffInfo);
776        diffNumberInfo
777          .setAllDiffType(diffInfo.getDiffMessage())
778          .setAllChangeType(apiChangeMap.get(diffInfo.getDiffType()))
779          .setOldDiffMessage(diffInfo.getOldDescription())
780          .setNewDiffMessage(diffInfo.getNewDescription())
781          .setAllCompatible(diffInfo.getIsCompatible())
782          .setIsApi(!isNotApiSet.has(diffInfo.getApiType()))
783          .setKitName(kitName)
784          .setSubsystem(kitData.subsystemMap.get(dtsName.replace(/\\/g, '/').replace('api/', '')))
785          .setApiName(diffInfo.getApiType() === ApiType.SOURCE_FILE ? 'SOURCEFILE' : getDiffApiName(diffInfo))
786          .setApiRelation(getRelation(diffInfo).replace(/\,/g, '#').replace('api\\', ''))
787          .setIsSystemapi(diffInfo.getIsSystemapi())
788          .setApiType(diffInfo.getApiType())
789          .setIsSameNameFunction(diffInfo.getIsSameNameFunction());
790      }
791    });
792    diffTypeNumberArr.push(diffNumberInfo);
793  });
794
795  diffTypeNumberArr = handleData(data, diffTypeNumberArr);
796  diffTypeNumberArr.forEach((diffNumberInfo: DiffNumberInfo, index: number) => {
797    numberSheet.getRow(index + NumberConstant.LINE_IN_EXCEL).values = [
798      diffNumberInfo.getApiName(),
799      diffNumberInfo.getKitName(),
800      diffNumberInfo.getSubsystem(),
801      diffNumberInfo.getIsApi(),
802      diffNumberInfo.getApiType(),
803      diffNumberInfo.getAllDiffType().join(' #&# '),
804      diffNumberInfo.getAllChangeType().join(' #&# '),
805      getCompatibleObject(diffNumberInfo),
806      calculateChangeNumber(diffNumberInfo),
807      diffNumberInfo.getOldDiffMessage().join(' #&# '),
808      diffNumberInfo.getNewDiffMessage().join(' #&# '),
809      diffNumberInfo.getAllCompatible().join(' #&# '),
810      diffNumberInfo.getApiRelation(),
811      diffNumberInfo.getIsSystemapi(),
812      diffNumberInfo.getIsSameNameFunction(),
813    ];
814  });
815}
816
817
818/**
819 * 用于处理diff数据的钩子函数
820 * 
821 * @param data 基础数据
822 * @param diffTypeNumberArr 处理后的数据
823 * @returns 
824 */
825function handleData(data: BasicDiffInfo[], diffTypeNumberArr: DiffNumberInfo[]): DiffNumberInfo[] {
826  return diffTypeNumberArr;
827}
828
829/**
830 * 判断API的变更集合是否兼容
831 *
832 * @param diffNumberInfo
833 * @returns
834 */
835function getCompatibleObject(diffNumberInfo: DiffNumberInfo): string {
836  const compatibleInfoSet: Set<boolean> = new Set(diffNumberInfo.getAllCompatible());
837  let compatibleSign = 0;
838  let incompatibleSign = 0;
839  if (compatibleInfoSet.size === 2) {
840    compatibleSign = 1;
841    incompatibleSign = 1;
842  } else if (compatibleInfoSet.has(true)) {
843    compatibleSign = 1;
844  } else if (compatibleInfoSet.has(false)) {
845    incompatibleSign = 1;
846  }
847  return `{
848    "兼容性":${compatibleSign},
849    "非兼容性":${incompatibleSign}
850  }`;
851}
852
853/**
854 * 计算变更次数
855 *
856 * @param diffNumberInfo
857 * @returns
858 */
859function calculateChangeNumber(diffNumberInfo: DiffNumberInfo): string {
860  const changeTypeSet: Set<string> = new Set(diffNumberInfo.getAllChangeType());
861  let newApiNumber: number = 0;
862  let apiDeleteNumber: number = 0;
863  let apiDeprecatedNumber: number = 0;
864  let apiChangeNumber: number = 0;
865  let apiConstrainedChange: number = 0;
866  let apiPrototypeChange: number = 0;
867  if (changeTypeSet.has('API修改(原型修改)')) {
868    apiPrototypeChange++;
869  }
870  if (changeTypeSet.has('API修改(约束变化)')) {
871    apiConstrainedChange++;
872  }
873  if (changeTypeSet.has('API修改(原型修改)') || changeTypeSet.has('API修改(约束变化)')) {
874    apiChangeNumber++;
875  }
876  if (changeTypeSet.has('API废弃')) {
877    apiDeprecatedNumber++;
878  }
879  if (changeTypeSet.has('API新增')) {
880    newApiNumber++;
881  }
882  if (changeTypeSet.has('API删除')) {
883    apiDeleteNumber++;
884  }
885  return `{
886    "API新增": ${newApiNumber},
887    "API删除": ${apiDeleteNumber},
888    "API废弃": ${apiDeprecatedNumber},
889    "API修改": ${apiChangeNumber},
890    "API修改(原型修改)": ${apiPrototypeChange},
891    "API修改(约束变化)": ${apiConstrainedChange}
892    }`;
893}
894
895function getDiffApiName(diffInfo: BasicDiffInfo): string {
896  if (diffInfo.getNewApiName() !== '') {
897    return diffInfo.getNewApiName();
898  }
899  return diffInfo.getOldApiName();
900}
901
902function getRelation(diffInfo: BasicDiffInfo): string {
903  const relationsArr = diffInfo.getNewHierarchicalRelations();
904  if (relationsArr.length > 0) {
905    return relationsArr.join();
906  } else {
907    return diffInfo.getOldHierarchicalRelations().join();
908  }
909}
910
911export function joinOldMessage(diffInfo: BasicDiffInfo): string {
912  if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.ADD)) {
913    return 'NA';
914  }
915  let oldDescription: string = '';
916  const relation: string[] = diffInfo.getOldHierarchicalRelations();
917  const parentModuleName: string = diffInfo.getParentModuleName(relation);
918  oldDescription =
919    diffInfo.getOldDescription() === '-1' || !diffInfo.getOldDescription() ? 'NA' : diffInfo.getOldDescription();
920  if (diffInfo.getDiffType() === ApiDiffType.KIT_CHANGE) {
921    return `${oldDescription}`;
922  }
923  return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getOldApiDefinedText()}\n差异内容:${oldDescription}`;
924}
925
926export function joinNewMessage(diffInfo: BasicDiffInfo): string {
927  if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.REDUCE)) {
928    return 'NA';
929  }
930  let newDescription: string = '';
931  const relation: string[] = diffInfo.getNewHierarchicalRelations();
932  const parentModuleName: string = diffInfo.getParentModuleName(relation);
933  newDescription =
934    diffInfo.getNewDescription() === '-1' || !diffInfo.getNewDescription() ? 'NA' : diffInfo.getNewDescription();
935  if (diffInfo.getDiffType() === ApiDiffType.KIT_CHANGE) {
936    return `${newDescription}`;
937  }
938  return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getNewApiDefinedText()}\n差异内容:${newDescription}`;
939}
940
941/**
942 * 工具名称对应执行的方法
943 */
944export const toolNameMethod: Map<string, ToolNameMethodType> = new Map([
945  [toolNameType.COLLECT, collectApi],
946  [toolNameType.CHECK, checkApi],
947  [toolNameType.CHECKONLINE, checkOnline],
948  [toolNameType.APICHANGECHECK, apiChangeCheck],
949  [toolNameType.DIFF, diffApi],
950  [toolNameType.LABELDETECTION, detectionApi],
951  [toolNameType.COUNT, countApi]
952]);
953
954/**
955 * 命令传入参数
956 */
957export type OptionObjType = {
958  toolName: toolNameType;
959  path: string;
960  checker: string;
961  prId: string;
962  isIncrement: string;
963  collectPath: string;
964  collectFile: string;
965  checkLabels: string;
966  isOH: string;
967  old: string;
968  new: string;
969  oldVersion: string;
970  newVersion: string;
971  output: string;
972  format: formatType;
973  changelogUrl: string;
974  excel: string;
975  all: boolean;
976};
977
978/**
979 * 各个工具当输出为excel时的回调方法
980 *
981 * @param { ToolReturnData } data 工具获取到的数据
982 * @param { ExcelJS.Worksheet } sheet ExcelJS构建的Worksheet对象
983 */
984export type ToolNameExcelCallback = (
985  data: ToolReturnData,
986  sheet: ExcelJS.Workbook,
987  dest?: string,
988  options?: OptionObjType
989) => void;
990
991/**
992 * 各个工具调用方法返回的格式
993 */
994export type ToolNameValueType = {
995  /**
996   * 工具返回的数据格式,默认为数组,方便excel输出,如果是字符串,则将字符串传入数组第一个元素
997   *
998   * @type { Array}
999   */
1000  data: ToolReturnData;
1001
1002  /**
1003   * 用于excel方法回调,返回数据以及ExcelJS构建的Worksheet对象
1004   *
1005   * @type {toolNameExcelCallback}
1006   */
1007  callback?: ToolNameExcelCallback;
1008};
1009export type ToolReturnData = (string | ApiStatisticsInfo | ApiResultMessage | BasicDiffInfo | ApiCountInfo)[];
1010
1011/**
1012 * 各个工具调用方法
1013 *
1014 */
1015export type ToolNameMethodType = (options: OptionObjType) => ToolNameValueType;
1016
1017export type PluginType = {
1018  pluginOptions: PluginOptionsType;
1019  start: (argv: OptionObjType) => Promise<void>;
1020  stop: () => void;
1021};
1022
1023export type PluginOptionsType = {
1024  name: string;
1025  version: string;
1026  description: string;
1027  commands: CommandType[];
1028};
1029
1030export type CommandType = {
1031  isRequiredOption: boolean;
1032  readonly options: [string, string, string];
1033};
1034