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