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