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 { EnumUtils } from '../utils/EnumUtils'; 19import { FileUtils } from '../utils/FileUtils'; 20import { LogUtil } from '../utils/logUtil'; 21import { FilesMap, Parser } from '../coreImpl/parser/parser'; 22import { DiffHelper } from '../coreImpl/diff/diff'; 23import { BasicDiffInfo, diffTypeMap, ApiDiffType } from '../typedef/diff/ApiInfoDiff'; 24import { WriterHelper } from './writer'; 25import { LocalEntry } from '../coreImpl/checker/local_entry'; 26import { ApiResultSimpleInfo } from '../typedef/checker/result_type'; 27import { NumberConstant } from '../utils/Constant'; 28import { ApiStatisticsHelper } from '../coreImpl/statistics/Statistics'; 29import { ApiStatisticsInfo } from '../typedef/statistics/ApiStatistics'; 30import { SyscapProcessorHelper } from '../coreImpl/diff/syscapFieldProcessor'; 31import { FunctionUtils } from '../utils/FunctionUtils'; 32 33/** 34 * 工具名称的枚举值,用于判断执行哪个工具 35 * 36 * @enum { string } 37 */ 38export enum toolNameType { 39 /** 40 * 统计工具 41 */ 42 COOLECT = 'collect', 43 /** 44 * 检查工具 45 */ 46 CHECK = 'check', 47 /** 48 * diff工具 49 */ 50 DIFF = 'diff', 51} 52 53/** 54 * 工具名称的set集合,通过enum生成 55 */ 56export const toolNameSet: Set<string> = new Set(EnumUtils.enum2arr(toolNameType)); 57 58/** 59 * 输出文件格式,用于判断输出什么类型的文件 60 * 61 * @enum { string } 62 */ 63export enum formatType { 64 JSON = 'json', 65 EXCEL = 'excel', 66 CHANGELOG = 'changelog', 67} 68 69/** 70 * 输出文件格式的set集合,通过enum生成 71 */ 72export const formatSet: Set<string> = new Set(EnumUtils.enum2arr(formatType)); 73 74export const Plugin: PluginType = { 75 pluginOptions: { 76 name: 'parser', 77 version: '0.1.0', 78 description: 'Compare the parser the SDKS', 79 commands: [ 80 { 81 isRequiredOption: true, 82 options: [`-N,--tool-name <${[...toolNameSet]}>`, 'tool name ', 'collect'], 83 }, 84 { 85 isRequiredOption: false, 86 options: ['-C,--collect-path <string>', 'collect api path', './api'], 87 }, 88 { 89 isRequiredOption: false, 90 options: ['--old <string>', 'diff old sdk path', './api'], 91 }, 92 { 93 isRequiredOption: false, 94 options: ['--new <string>', 'diff new sdk path', './api'], 95 }, 96 { 97 isRequiredOption: false, 98 options: ['--old-version <string>', 'old sdk version', '0'], 99 }, 100 { 101 isRequiredOption: false, 102 options: ['--new-version <string>', 'new sdk version', '0'], 103 }, 104 { 105 isRequiredOption: false, 106 options: ['--output <string>', 'output file path', './'], 107 }, 108 { 109 isRequiredOption: false, 110 options: [`--format <${[...formatSet]}>`, 'output file format', 'json'], 111 }, 112 { 113 isRequiredOption: false, 114 options: ['--changelogUrl <string>', 'changelog url', ''], 115 }, 116 ], 117 }, 118 119 start: async function (argv: OptionObjType) { 120 const toolName: toolNameType = argv.toolName; 121 const method: ToolNameMethodType | undefined = toolNameMethod.get(toolName); 122 if (!method) { 123 LogUtil.i( 124 'CommandArgs', 125 `tool-name may use error name or don't have function,tool-name can use 'collect' or 'diff'` 126 ); 127 return; 128 } 129 const options: OptionObjType = { 130 toolName: toolName, 131 collectPath: argv.collectPath, 132 old: argv.old, 133 new: argv.new, 134 oldVersion: argv.oldVersion, 135 newVersion: argv.newVersion, 136 output: argv.output, 137 format: argv.format, 138 changelogUrl: argv.changelogUrl, 139 }; 140 const methodInfos: ToolNameValueType = method(options); 141 142 outputInfos(methodInfos.data, options, methodInfos.callback); 143 }, 144 stop: function () { 145 LogUtil.i('commander', `elapsed time: ${Date.now() - startTime}`); 146 }, 147}; 148let startTime = Date.now(); 149 150/** 151 * 工具获取完数据之后,根据format和其他数据处理输出 152 * 153 * @param {ToolReturnData} infos 工具返回的数据 154 * @param {OptionObjType} options 传入的命令参数 155 * @param {(ToolNameExcelCallback | undefined)} callback 导出excel的回调 156 */ 157function outputInfos(infos: ToolReturnData, options: OptionObjType, callback: ToolNameExcelCallback | undefined): void { 158 const format = options.format; 159 if (!format) { 160 return; 161 } 162 switch (format) { 163 case formatType.JSON: 164 WriterHelper.JSONReporter( 165 String(infos[0]), 166 options.output, 167 `${options.toolName}_${options.oldVersion}_${options.newVersion}.json` 168 ); 169 break; 170 case formatType.EXCEL: 171 WriterHelper.ExcelReporter(infos, options.output, `${options.toolName}.xlsx`, callback); 172 break; 173 case formatType.CHANGELOG: 174 WriterHelper.JSONReporter(String(infos[0]), options.output, `${options.toolName}.json`); 175 break; 176 default: 177 break; 178 } 179} 180 181/** 182 * 收集api工具调用方法 183 * 184 * @param { OptionObjType } options 185 * @return { ToolNameValueType } 186 */ 187function collectApi(options: OptionObjType): ToolNameValueType { 188 const fileDir: string = path.resolve(FileUtils.getBaseDirName(), options.collectPath); 189 let allApis: FilesMap; 190 try { 191 if (FileUtils.isDirectory(fileDir)) { 192 allApis = Parser.parseDir(fileDir); 193 } else { 194 allApis = Parser.parseFile(path.resolve(fileDir, '..'), fileDir); 195 } 196 const fileContent: string = Parser.getParseResults(allApis); 197 if (options.format === 'excel') { 198 const allApiStatisticsInfos: ApiStatisticsInfo[] | undefined = 199 ApiStatisticsHelper.getApiStatisticsInfos(allApis).allApiStatisticsInfos; 200 if (allApiStatisticsInfos) { 201 WriterHelper.ExcelReporter( 202 allApiStatisticsInfos, 203 options.output, 204 `all_${options.toolName}.xlsx`, 205 collectApiCallback as ToolNameExcelCallback 206 ); 207 } 208 } 209 210 return { 211 data: 212 options.format === 'excel' ? 213 ApiStatisticsHelper.getApiStatisticsInfos(allApis).apiStatisticsInfos : 214 [fileContent], 215 callback: collectApiCallback as ToolNameExcelCallback, 216 }; 217 } catch (exception) { 218 const error = exception as Error; 219 LogUtil.e(`error collect`, error.stack ? error.stack : error.message); 220 return { 221 data: [], 222 callback: collectApiCallback as ToolNameExcelCallback, 223 }; 224 } 225} 226 227function collectApiCallback(apiData: ApiStatisticsInfo[], sheet: ExcelJS.Worksheet): void { 228 const apiRelationsSet: Set<string> = new Set(); 229 const subsystemMap: Map<string, string> = FunctionUtils.readSubsystemFile().subsystemMap; 230 sheet.name = 'JsApi'; 231 sheet.views = [{ xSplit: 1 }]; 232 sheet.getRow(1).values = [ 233 '模块名', 234 '类名', 235 '方法名', 236 '函数', 237 '类型', 238 '起始版本', 239 '废弃版本', 240 'syscap', 241 '错误码', 242 '是否为系统API', 243 '模型限制', 244 '权限', 245 '是否支持跨平台', 246 '是否支持卡片应用', 247 '是否为高阶API', 248 '装饰器', 249 '文件路径', 250 '子系统', 251 ]; 252 let lineNumber = 2; 253 apiData.forEach((apiInfo: ApiStatisticsInfo) => { 254 const apiRelations: string = `${apiInfo.getHierarchicalRelations()},${apiInfo.getDefinedText()}`; 255 if (apiRelationsSet.has(apiRelations)) { 256 return; 257 } 258 259 sheet.getRow(lineNumber).values = [ 260 apiInfo.getPackageName(), 261 apiInfo.getParentModuleName(), 262 apiInfo.getApiName(), 263 apiInfo.getDefinedText(), 264 apiInfo.getApiType(), 265 apiInfo.getSince() === '-1' ? '' : apiInfo.getSince(), 266 apiInfo.getDeprecatedVersion() === '-1' ? '' : apiInfo.getDeprecatedVersion(), 267 apiInfo.getSyscap(), 268 apiInfo.getErrorCodes().join() === '-1' ? '' : apiInfo.getErrorCodes().join(), 269 apiInfo.getApiLevel(), 270 apiInfo.getModelLimitation(), 271 apiInfo.getPermission(), 272 apiInfo.getIsCrossPlatForm(), 273 apiInfo.getIsForm(), 274 apiInfo.getIsAutomicService(), 275 apiInfo.getDecorators()?.join(), 276 apiInfo.getFilePath(), 277 subsystemMap.get(FunctionUtils.handleSyscap(apiInfo.getSyscap())), 278 ]; 279 lineNumber++; 280 apiRelationsSet.add(apiRelations); 281 }); 282} 283/** 284 * 收集api工具调用方法 285 * 286 * @param { OptionObjType } options 287 * @return { ToolNameValueType } 288 */ 289function checkApi(options: OptionObjType): ToolNameValueType { 290 let allApis: FilesMap; 291 try { 292 let fileContent: ApiResultSimpleInfo[] = []; 293 if (process.env.NODE_ENV === 'development') { 294 fileContent = LocalEntry.checkEntryLocal(); 295 } else if (process.env.NODE_ENV === 'production') { 296 } 297 let finalData: (string | ApiResultSimpleInfo)[] = []; 298 if (options.format === formatType.JSON) { 299 finalData = [JSON.stringify(fileContent, null, NumberConstant.INDENT_SPACE)]; 300 } else { 301 finalData = fileContent; 302 } 303 return { 304 data: finalData, 305 }; 306 } catch (exception) { 307 const error = exception as Error; 308 LogUtil.e('error collect', error.stack ? error.stack : error.message); 309 return { 310 data: [], 311 }; 312 } 313} 314 315/** 316 * diffApi工具调用方法 317 * 318 * @param { OptionObjType } options 319 * @return { ToolNameValueType } 320 */ 321function diffApi(options: OptionObjType): ToolNameValueType { 322 const oldFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.old); 323 const newFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.new); 324 const status: fs.Stats = fs.statSync(oldFileDir); 325 let data: BasicDiffInfo[] = []; 326 try { 327 if (status.isDirectory()) { 328 const oldSDKApiMap: FilesMap = Parser.parseDir(oldFileDir); 329 const newSDKApiMap: FilesMap = Parser.parseDir(newFileDir); 330 data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap); 331 } else { 332 const oldSDKApiMap: FilesMap = Parser.parseFile(path.resolve(oldFileDir, '..'), oldFileDir); 333 const newSDKApiMap: FilesMap = Parser.parseFile(path.resolve(newFileDir, '..'), newFileDir); 334 data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap); 335 } 336 let finalData: (string | BasicDiffInfo)[] = []; 337 if (options.format === formatType.JSON) { 338 finalData = [JSON.stringify(data, null, NumberConstant.INDENT_SPACE)]; 339 } else { 340 finalData = data; 341 } 342 return { 343 data: finalData, 344 callback: diffApiCallback as ToolNameExcelCallback, 345 }; 346 } catch (exception) { 347 const error = exception as Error; 348 LogUtil.e('error diff', error.stack ? error.stack : error.message); 349 return { 350 data: [], 351 callback: diffApiCallback as ToolNameExcelCallback, 352 }; 353 } 354} 355 356/** 357 * diffApi工具导出excel时的回调方法 358 * 359 * @param {BasicDiffInfo[]} data diffApi工具获取到的数据 360 * @param {ExcelJS.Worksheet} sheet ExcelJS构建的Worksheet对象 361 */ 362function diffApiCallback(data: BasicDiffInfo[], sheet: ExcelJS.Worksheet, dest?: string): void { 363 sheet.name = 'api差异'; 364 sheet.views = [{ xSplit: 1 }]; 365 sheet.getRow(1).values = ['操作标记', '差异项-旧版本', '差异项-新版本', 'd.ts文件', '归属子系统']; 366 data.forEach((diffInfo: BasicDiffInfo, index: number) => { 367 const dtsName = diffInfo.getNewDtsName() ? diffInfo.getNewDtsName() : diffInfo.getOldDtsName(); 368 sheet.getRow(index + NumberConstant.LINE_IN_EXCEL).values = [ 369 diffTypeMap.get(diffInfo.getDiffType()), 370 joinOldMessage(diffInfo), 371 joinNewMessage(diffInfo), 372 dtsName.replace(/\\/g, '/'), 373 SyscapProcessorHelper.matchSubsystem(diffInfo), 374 ]; 375 }); 376 377 WriterHelper.MarkdownReporter.writeInMarkdown(data, dest); 378} 379 380export function joinOldMessage(diffInfo: BasicDiffInfo): string { 381 if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.ADD)) { 382 return 'NA'; 383 } 384 let oldDescription: string = ''; 385 const relation: string[] = diffInfo.getOldHierarchicalRelations(); 386 const parentModuleName: string = diffInfo.getParentModuleName(relation); 387 oldDescription = 388 diffInfo.getOldDescription() === '-1' || !diffInfo.getOldDescription() ? 'NA' : diffInfo.getOldDescription(); 389 return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getOldApiDefinedText()}\n差异内容:${oldDescription}`; 390} 391 392export function joinNewMessage(diffInfo: BasicDiffInfo): string { 393 if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.REDUCE)) { 394 return 'NA'; 395 } 396 let newDescription: string = ''; 397 const relation: string[] = diffInfo.getNewHierarchicalRelations(); 398 const parentModuleName: string = diffInfo.getParentModuleName(relation); 399 newDescription = 400 diffInfo.getNewDescription() === '-1' || !diffInfo.getNewDescription() ? 'NA' : diffInfo.getNewDescription(); 401 return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getNewApiDefinedText()}\n差异内容:${newDescription}`; 402} 403 404/** 405 * 工具名称对应执行的方法 406 */ 407export const toolNameMethod: Map<string, ToolNameMethodType> = new Map([ 408 [toolNameType.COOLECT, collectApi], 409 [toolNameType.CHECK, checkApi], 410 [toolNameType.DIFF, diffApi], 411]); 412 413/** 414 * 命令传入参数 415 */ 416export type OptionObjType = { 417 toolName: toolNameType; 418 collectPath: string; 419 old: string; 420 new: string; 421 oldVersion: string; 422 newVersion: string; 423 output: string; 424 format: formatType; 425 changelogUrl: string; 426}; 427 428/** 429 * 各个工具当输出为excel时的回调方法 430 * 431 * @param { ToolReturnData } data 工具获取到的数据 432 * @param { ExcelJS.Worksheet } sheet ExcelJS构建的Worksheet对象 433 */ 434export type ToolNameExcelCallback = (data: ToolReturnData, sheet: ExcelJS.Worksheet, dest?: string) => void; 435 436/** 437 * 各个工具调用方法返回的格式 438 */ 439export type ToolNameValueType = { 440 /** 441 * 工具返回的数据格式,默认为数组,方便excel输出,如果是字符串,则将字符串传入数组第一个元素 442 * 443 * @type { Array} 444 */ 445 data: ToolReturnData; 446 447 /** 448 * 用于excel方法回调,返回数据以及ExcelJS构建的Worksheet对象 449 * 450 * @type {toolNameExcelCallback} 451 */ 452 callback?: ToolNameExcelCallback; 453}; 454export type ToolReturnData = (string | ApiStatisticsInfo | ApiResultSimpleInfo | BasicDiffInfo)[]; 455 456/** 457 * 各个工具调用方法 458 * 459 */ 460export type ToolNameMethodType = (options: OptionObjType) => ToolNameValueType; 461 462export type PluginType = { 463 pluginOptions: PluginOptionsType; 464 start: (argv: OptionObjType) => Promise<void>; 465 stop: () => void; 466}; 467 468export type PluginOptionsType = { 469 name: string; 470 version: string; 471 description: string; 472 commands: CommandType[]; 473}; 474 475export type CommandType = { 476 isRequiredOption: boolean; 477 readonly options: [string, string, string]; 478}; 479