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