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 */ 15 16import _ from 'lodash'; 17import ts from 'typescript'; 18import crypto from 'crypto'; 19import { StringConstant } from '../../utils/Constant'; 20import { 21 ApiInfo, 22 ApiType, 23 BasicApiInfo, 24 ContainerApiInfo, 25 MethodInfo, 26 ParamInfo, 27 containerApiTypes, 28 TypeAliasInfo, 29 PropertyInfo, 30} from '../../typedef/parser/ApiInfoDefination'; 31import { 32 ApiDiffType, 33 ApiStatusCode, 34 BasicDiffInfo, 35 DiffTypeInfo, 36 TagInfoDiffProcessor, 37} from '../../typedef/diff/ApiInfoDiff'; 38import { ApiInfosMap, FileInfoMap, FilesMap, Parser } from '../parser/parser'; 39import { apiStatisticsType } from '../../typedef/statistics/ApiStatistics'; 40import { DiffProcessorHelper } from './DiffProcessor'; 41import { FunctionUtils } from '../../utils/FunctionUtils'; 42import { Comment } from '../../typedef/parser/Comment'; 43import { notJsDocApiTypes } from '../../typedef/parser/ApiInfoDefination'; 44import { StringUtils } from '../../utils/StringUtils'; 45import { CommentHelper } from '../parser/JsDocProcessor'; 46import { ResultsProcessHelper } from '../parser/ResultsProcess'; 47 48export class DiffHelper { 49 /** 50 * 根据解析后的新旧版本SDK结果,得到对比差异 51 * 52 * @param { FilesMap } oldSDKApiMap 旧版本SDK解析后的结果 53 * @param { FilesMap } newSDKApiMap 新版本SDK解析后的结果 54 * @returns { BasicDiffInfo[] } 差异结果集 55 */ 56 static diffSDK( 57 clonedOldSDKApiMap: FilesMap, 58 clonedNewSDKApiMap: FilesMap, 59 isAllSheet: boolean, 60 isCheck?: boolean 61 ): BasicDiffInfo[] { 62 const diffInfos: BasicDiffInfo[] = []; 63 const oldSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedOldSDKApiMap, isCheck); 64 const newSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedNewSDKApiMap, isCheck); 65 DiffHelper.diffKit(clonedOldSDKApiMap, clonedNewSDKApiMap, diffInfos); 66 const oldFilePathSet: Set<string> = new Set(Array.from(clonedOldSDKApiMap.keys())); 67 // 先以旧版本为基础进行对比 68 for (const key of oldSDKApiLocations.keys()) { 69 const apiLocation: string[] = oldSDKApiLocations.get(key) as string[]; 70 const oldApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedOldSDKApiMap, isAllSheet) as ApiInfo[]; 71 // 如果旧版本中的API在新版本中不存在,则为删除 72 if (!newSDKApiLocations.has(key)) { 73 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 74 diffInfos.push( 75 DiffProcessorHelper.wrapDiffInfo( 76 oldApiInfo, 77 undefined, 78 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText()) 79 ) 80 ); 81 }); 82 continue; 83 } 84 // 新旧版本均存在,则进行对比 85 const newApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedNewSDKApiMap, isAllSheet) as ApiInfo[]; 86 DiffHelper.diffApis(oldApiInfos, newApiInfos, diffInfos, isAllSheet, isCheck); 87 // 对比完则将新版本中的对应API进行删除 88 newSDKApiLocations.delete(key); 89 } 90 // 对比完还剩下的新版本中的API即为新增API 91 for (const key of newSDKApiLocations.keys()) { 92 const locations: string[] = newSDKApiLocations.get(key) as string[]; 93 const newApiInfos: ApiInfo[] = Parser.getApiInfo(locations, clonedNewSDKApiMap, isAllSheet) as ApiInfo[]; 94 const clonedLocations: string[] = locations; 95 const parentLocations = clonedLocations.slice(0, -1); 96 newApiInfos.forEach((newApiInfo: ApiInfo) => { 97 let isNewFile: boolean = false; 98 if (!oldFilePathSet.has(newApiInfo.getFilePath()) || !oldSDKApiLocations.get(parentLocations.join())) { 99 isNewFile = true; 100 } 101 diffInfos.push( 102 DiffProcessorHelper.wrapDiffInfo( 103 undefined, 104 newApiInfo, 105 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()), 106 isNewFile 107 ) 108 ); 109 }); 110 } 111 return diffInfos; 112 } 113 114 static diffKit(clonedOldSDKApiMap: FilesMap, clonedNewSDKApiMap: FilesMap, diffInfos: BasicDiffInfo[]): void { 115 for (const key of clonedOldSDKApiMap.keys()) { 116 const oldSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedOldSDKApiMap.get(key)); 117 oldSourceFileInfo?.setSyscap(DiffHelper.getSyscapField(oldSourceFileInfo)); 118 const oldKitInfo: string | undefined = oldSourceFileInfo?.getLastJsDocInfo()?.getKit(); 119 //文件在新版本中被删除 120 if (!clonedNewSDKApiMap.get(key) && oldKitInfo !== 'NA') { 121 diffInfos.push( 122 DiffProcessorHelper.wrapDiffInfo( 123 oldSourceFileInfo, 124 undefined, 125 new DiffTypeInfo(ApiStatusCode.KIT_CHANGE, ApiDiffType.KIT_HAVE_TO_NA, oldKitInfo, 'NA') 126 ) 127 ); 128 } else if (clonedNewSDKApiMap.get(key)) { 129 const newSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedNewSDKApiMap.get(key)); 130 const newKitInfo: string | undefined = newSourceFileInfo?.getLastJsDocInfo()?.getKit(); 131 if (oldKitInfo !== newKitInfo) { 132 diffInfos.push( 133 DiffProcessorHelper.wrapDiffInfo( 134 oldSourceFileInfo, 135 newSourceFileInfo, 136 new DiffTypeInfo( 137 ApiStatusCode.KIT_CHANGE, 138 DiffHelper.getKitDiffType(oldKitInfo, newKitInfo), 139 oldKitInfo, 140 newKitInfo 141 ) 142 ) 143 ); 144 } 145 } 146 } 147 148 for (const key of clonedNewSDKApiMap.keys()) { 149 const newSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedNewSDKApiMap.get(key)); 150 const newKitInfo: string | undefined = newSourceFileInfo?.getLastJsDocInfo()?.getKit(); 151 if (!clonedOldSDKApiMap.get(key) && newKitInfo !== 'NA') { 152 diffInfos.push( 153 DiffProcessorHelper.wrapDiffInfo( 154 undefined, 155 newSourceFileInfo, 156 new DiffTypeInfo(ApiStatusCode.KIT_CHANGE, ApiDiffType.KIT_NA_TO_HAVE, 'NA', newKitInfo) 157 ) 158 ); 159 } 160 } 161 } 162 163 static getKitDiffType(oldKitInfo: string | undefined, newKitInfo: string | undefined): ApiDiffType { 164 if (oldKitInfo === 'NA' && newKitInfo === '') { 165 return ApiDiffType.KIT_NA_TO_HAVE; 166 } else if (oldKitInfo === '' && newKitInfo === 'NA') { 167 return ApiDiffType.KIT_HAVE_TO_NA; 168 } else if (oldKitInfo === 'NA' || oldKitInfo === '') { 169 return ApiDiffType.KIT_NA_TO_HAVE; 170 } else if (newKitInfo === 'NA' || newKitInfo === '') { 171 return ApiDiffType.KIT_HAVE_TO_NA; 172 } 173 return ApiDiffType.KIT_CHANGE; 174 } 175 176 static getSourceFileInfo(fileMap: FileInfoMap | undefined): ApiInfo | undefined { 177 if (!fileMap) { 178 return undefined; 179 } 180 let sourceFileInfos: ApiInfo[] = []; 181 for (const apiKey of fileMap.keys()) { 182 if (apiKey === StringConstant.SELF) { 183 sourceFileInfos = fileMap.get(apiKey) as ApiInfo[]; 184 } 185 } 186 return sourceFileInfos[0]; 187 } 188 189 /** 190 * 通过数组长度判断是否为同名函数 191 * 192 * @param apiInfos 193 */ 194 static judgeIsSameNameFunction(apiInfos: BasicApiInfo[]): boolean { 195 let isSameNameFunction: boolean = false; 196 if (apiInfos.length > 1 && apiInfos[0].getApiType() === ApiType.METHOD) { 197 isSameNameFunction = true; 198 } 199 return isSameNameFunction; 200 } 201 202 /** 203 * 对比新旧版本API差异,类型为数组是由于同名函数的存在,因此统一为数组方便处理 204 * 205 * @param { ApiInfo[] } oldApiInfos 老版本API信息 206 * @param { ApiInfo[] } newApiInfos 新版本API信息 207 * @param { BasicDiffInfo[] } diffInfos api差异结果集 208 */ 209 static diffApis( 210 oldApiInfos: ApiInfo[], 211 newApiInfos: ApiInfo[], 212 diffInfos: BasicDiffInfo[], 213 isAllSheet: boolean, 214 isCheck?: boolean 215 ): void { 216 const diffSets: Map<string, ApiInfo>[] = DiffHelper.getDiffSet(oldApiInfos, newApiInfos); 217 const oldReduceNewMap: Map<string, ApiInfo> = diffSets[0]; 218 const newReduceOldMap: Map<string, ApiInfo> = diffSets[1]; 219 if (oldReduceNewMap.size === 0) { 220 newReduceOldMap.forEach((newApiInfo: ApiInfo) => { 221 diffInfos.push( 222 DiffProcessorHelper.wrapDiffInfo( 223 undefined, 224 newApiInfo, 225 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()) 226 ) 227 ); 228 }); 229 return; 230 } 231 if (newReduceOldMap.size === 0) { 232 oldReduceNewMap.forEach((oldApiInfo: ApiInfo) => { 233 diffInfos.push( 234 DiffProcessorHelper.wrapDiffInfo( 235 oldApiInfo, 236 undefined, 237 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText(), undefined) 238 ) 239 ); 240 }); 241 return; 242 } 243 244 DiffHelper.diffChangeApi(oldApiInfos, newApiInfos, diffInfos, isCheck); 245 } 246 247 /** 248 * 删除完全一样的API后,进行对比 249 * @param { ApiInfo[] } oldApiInfos 250 * @param { ApiInfo[] } newApiInfos 251 * @param diffInfos 252 * @param { boolean } isCheck 是否是api_check工具进行调用 253 */ 254 static diffChangeApi( 255 oldApiInfos: ApiInfo[], 256 newApiInfos: ApiInfo[], 257 diffInfos: BasicDiffInfo[], 258 isCheck?: boolean 259 ): void { 260 //长度为1说明新旧版本有变更的只有1个,可直接进行对比 261 if (oldApiInfos.length === 1 && oldApiInfos.length === newApiInfos.length) { 262 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfos[0], newApiInfos[0], diffInfos); 263 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfos[0], newApiInfos[0], diffInfos); 264 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldApiInfos[0], newApiInfos[0], diffInfos, isCheck); 265 } else { 266 const newMethodInfoMap: Map<string, ApiInfo> = DiffHelper.setmethodInfoMap(newApiInfos); 267 const oldMethodInfoMap: Map<string, ApiInfo> = DiffHelper.setmethodInfoMap(oldApiInfos); 268 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 269 const newApiInfo: ApiInfo | undefined = newMethodInfoMap.get(oldApiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 270 if (!newApiInfo) { 271 return; 272 } 273 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfo, newApiInfo, diffInfos); 274 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfo, newApiInfo, diffInfos); 275 newMethodInfoMap.delete(oldApiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 276 oldMethodInfoMap.delete(oldApiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 277 }); 278 279 for (const apiInfo of newMethodInfoMap.values()) { 280 const jsDocLength: number = apiInfo.getJsDocInfos().length; 281 if (jsDocLength === 1) { 282 diffInfos.push( 283 DiffProcessorHelper.wrapDiffInfo( 284 undefined, 285 apiInfo, 286 new DiffTypeInfo( 287 ApiStatusCode.NEW_API, 288 ApiDiffType.NEW_SAME_NAME_FUNCTION, 289 undefined, 290 apiInfo.getDefinedText() 291 ) 292 ) 293 ); 294 newMethodInfoMap.delete(apiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 295 } 296 } 297 298 if (oldMethodInfoMap.size === 1 && newMethodInfoMap.size === 1) { 299 const oldMethodInfo: ApiInfo = oldMethodInfoMap.entries().next().value[1]; 300 const newMethodInfo: ApiInfo = newMethodInfoMap.entries().next().value[1]; 301 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldMethodInfo, newMethodInfo, diffInfos); 302 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldMethodInfo, newMethodInfo, diffInfos); 303 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldMethodInfo, newMethodInfo, diffInfos, isCheck); 304 } else { 305 DiffHelper.diffSameNameFunction(oldMethodInfoMap, newMethodInfoMap, diffInfos, isCheck); 306 } 307 } 308 } 309 310 static diffSameNameFunction( 311 oldMethodInfoMap: Map<string, ApiInfo>, 312 newMethodInfoMap: Map<string, ApiInfo>, 313 diffInfos: BasicDiffInfo[], 314 isCheck?: boolean 315 ): void { 316 for (const newApiInfo of newMethodInfoMap.values()) { 317 const newJsDocInfo: Comment.JsDocInfo | undefined = newApiInfo.getPenultimateJsDocInfo(); 318 for (const oldApiInfo of oldMethodInfoMap.values()) { 319 const oldJsDocInfo: Comment.JsDocInfo | undefined = oldApiInfo.getLastJsDocInfo(); 320 if (!DiffHelper.diffJsDoc(newJsDocInfo, oldJsDocInfo)) { 321 continue; 322 } 323 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldApiInfo, newApiInfo, diffInfos, isCheck); 324 oldMethodInfoMap.delete(oldApiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 325 newMethodInfoMap.delete(newApiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, '')); 326 } 327 } 328 329 for (const apiInfo of newMethodInfoMap.values()) { 330 diffInfos.push( 331 DiffProcessorHelper.wrapDiffInfo( 332 undefined, 333 apiInfo, 334 new DiffTypeInfo( 335 ApiStatusCode.NEW_API, 336 ApiDiffType.NEW_SAME_NAME_FUNCTION, 337 undefined, 338 apiInfo.getDefinedText() 339 ) 340 ) 341 ); 342 } 343 344 for (const apiInfo of oldMethodInfoMap.values()) { 345 diffInfos.push( 346 DiffProcessorHelper.wrapDiffInfo( 347 apiInfo, 348 undefined, 349 new DiffTypeInfo( 350 ApiStatusCode.DELETE, 351 ApiDiffType.REDUCE_SAME_NAME_FUNCTION, 352 apiInfo.getDefinedText(), 353 undefined 354 ) 355 ) 356 ); 357 } 358 } 359 360 static removeApiInfo(basicApiInfo: BasicApiInfo): void { 361 DiffHelper.cleanApiInfo(basicApiInfo); 362 if (!containerApiTypes.has(basicApiInfo.getApiType())) { 363 return; 364 } 365 const containerApiInfo: ContainerApiInfo = basicApiInfo as ContainerApiInfo; 366 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 367 DiffHelper.removeApiInfo(childApiInfo); 368 }); 369 } 370 371 static cleanApiInfo(basicApiInfo: BasicApiInfo | undefined): void { 372 if (!basicApiInfo) { 373 return; 374 } 375 basicApiInfo.setParentApi(undefined); 376 basicApiInfo.removeNode(); 377 if (basicApiInfo instanceof MethodInfo || basicApiInfo instanceof PropertyInfo) { 378 DiffHelper.cleanChildrenApiInfo(basicApiInfo.getObjLocations()); 379 DiffHelper.cleanChildrenApiInfo(basicApiInfo.getTypeLocations()); 380 if (basicApiInfo instanceof MethodInfo) { 381 basicApiInfo.getParams().forEach((param: ParamInfo) => { 382 DiffHelper.cleanChildrenApiInfo(param.getObjLocations()); 383 DiffHelper.cleanChildrenApiInfo(param.getTypeLocations()); 384 DiffHelper.cleanApiInfo(param.getMethodApiInfo()); 385 }); 386 } 387 } 388 if (basicApiInfo instanceof TypeAliasInfo) { 389 DiffHelper.cleanChildrenApiInfo(basicApiInfo.getTypeLiteralApiInfos()); 390 basicApiInfo.getParamInfos().forEach((param: ParamInfo) => { 391 DiffHelper.cleanChildrenApiInfo(param.getObjLocations()); 392 DiffHelper.cleanChildrenApiInfo(param.getTypeLocations()); 393 DiffHelper.cleanApiInfo(param.getMethodApiInfo()); 394 }); 395 } 396 } 397 398 static cleanChildrenApiInfo(basicApiInfos: BasicApiInfo[] | undefined): void { 399 if (!basicApiInfos) { 400 return; 401 } 402 basicApiInfos.forEach((basicApiInfos: BasicApiInfo) => { 403 DiffHelper.processApiInfos(basicApiInfos); 404 }); 405 } 406 407 /** 408 * 将每一个节点解析后的对象的parentApi属性置为undefined,防止循环引用 409 * 410 * @param { BasicApiInfo } basicApiInfo 解析后的api对象 411 */ 412 static processApiInfos(basicApiInfo: BasicApiInfo | undefined): void { 413 if (!basicApiInfo) { 414 return; 415 } 416 DiffHelper.cleanApiInfo(basicApiInfo); 417 if (!containerApiTypes.has(basicApiInfo.getApiType())) { 418 return; 419 } 420 const containerApiInfo: ContainerApiInfo = basicApiInfo as ContainerApiInfo; 421 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 422 DiffHelper.processApiInfos(childApiInfo); 423 }); 424 } 425 426 static diffJsDoc(newJsDocInfo: Comment.JsDocInfo | undefined, oldJsDocInfo: Comment.JsDocInfo | undefined): boolean { 427 const tagInfoIsSame: Set<boolean> = new Set(); 428 const paramAndReturnsIsSame: boolean = 429 DiffTagInfoHelper.diffParamTagInfo(oldJsDocInfo, newJsDocInfo) && 430 DiffTagInfoHelper.diffReturnsTagInfo(oldJsDocInfo, newJsDocInfo); 431 newJsDocInfo?.getTags()?.forEach((tagInfo: Comment.CommentTag) => { 432 const method: TagInfoDiffProcessor | undefined = diffJsdocMethod.get(tagInfo.tag); 433 if (!method) { 434 return; 435 } 436 tagInfoIsSame.add(method(oldJsDocInfo, newJsDocInfo)); 437 }); 438 439 if (tagInfoIsSame.size === 1 && tagInfoIsSame.has(true)) { 440 return true; 441 } else if (tagInfoIsSame.size > 1 && paramAndReturnsIsSame) { 442 return true; 443 } 444 return false; 445 } 446 447 static judgeIsAllDeprecated(apiInfos: ApiInfo[]): boolean { 448 let isAllDeprecated: boolean = true; 449 apiInfos.forEach((apiInfo: ApiInfo) => { 450 const deprecatedVersion = apiInfo.getLastJsDocInfo()?.getDeprecatedVersion(); 451 if (deprecatedVersion === '-1' || !deprecatedVersion) { 452 isAllDeprecated = false; 453 } 454 }); 455 return isAllDeprecated; 456 } 457 458 static handleDeprecatedVersion(apiInfos: ApiInfo[]): void { 459 let isAllDeprecated: boolean = true; 460 apiInfos.forEach((apiInfo: ApiInfo) => { 461 const deprecatedVersion = apiInfo.getLastJsDocInfo()?.getDeprecatedVersion(); 462 if (deprecatedVersion === '-1' || !deprecatedVersion) { 463 isAllDeprecated = false; 464 } 465 }); 466 if (isAllDeprecated) { 467 return; 468 } 469 apiInfos.forEach((apiInfo: ApiInfo) => { 470 apiInfo.getLastJsDocInfo()?.setDeprecatedVersion('-1'); 471 }); 472 } 473 474 /** 475 * 拼接同名函数的API声明 476 * 477 * @param methodInfoMap 478 * @returns 479 */ 480 static joinApiText(methodInfoMap: Map<string, ApiInfo>): string { 481 const apiTextArr: string[] = []; 482 for (const apiText of methodInfoMap.keys()) { 483 apiTextArr.push(apiText); 484 } 485 return apiTextArr.join(' #&# '); 486 } 487 488 /** 489 * 生成map,key为API声明,value为API信息 490 * 491 * @param apiInfos 492 * @returns 493 */ 494 static setmethodInfoMap(apiInfos: ApiInfo[]): Map<string, ApiInfo> { 495 const methodInfoMap: Map<string, ApiInfo> = new Map(); 496 apiInfos.forEach((apiInfo: ApiInfo) => { 497 methodInfoMap.set(apiInfo.getDefinedText().replace(/\r|\n|\s+|,|;/g, ''), apiInfo); 498 }); 499 return methodInfoMap; 500 } 501 502 /** 503 * 删除新旧版本里所有信息一样的API 504 * 505 * @param oldApiInfos 506 * @param newApiInfos 507 * @returns 508 */ 509 static getDiffSet(oldApiInfos: ApiInfo[], newApiInfos: ApiInfo[]): Map<string, ApiInfo>[] { 510 const oldApiInfoMap: Map<string, ApiInfo> = new Map(); 511 const newApiInfoMap: Map<string, ApiInfo> = new Map(); 512 DiffHelper.setApiInfoMap(oldApiInfoMap, oldApiInfos); 513 DiffHelper.setApiInfoMap(newApiInfoMap, newApiInfos); 514 const oldReduceNewMap: Map<string, ApiInfo> = new Map(); 515 oldApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 516 if (!newApiInfoMap.has(key)) { 517 oldReduceNewMap.set(key, apiInfo); 518 } 519 }); 520 const newReduceOldMap: Map<string, ApiInfo> = new Map(); 521 newApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 522 if (!oldApiInfoMap.has(key)) { 523 newReduceOldMap.set(key, apiInfo); 524 } 525 }); 526 return [oldReduceNewMap, newReduceOldMap]; 527 } 528 529 static setApiInfoMap(apiInfoMap: Map<string, ApiInfo>, apiInfos: ApiInfo[]): void { 530 apiInfos.forEach((apiInfo: ApiInfo) => { 531 const key: string = `${apiInfo.getDefinedText()}#${apiInfo.getJsDocText()}#${JSON.stringify(apiInfo.getDecorators())}`; 532 apiInfoMap.set(key, apiInfo); 533 }); 534 } 535 536 static getApiLocations(apiMap: FilesMap, isCheck?: boolean): Map<string, string[]> { 537 const apiLocations: Map<string, string[]> = new Map(); 538 for (const filePath of apiMap.keys()) { 539 const index: number = 0; 540 const fileMap: FileInfoMap = apiMap.get(filePath) as FileInfoMap; 541 DiffHelper.processFileApiMap(fileMap, apiLocations, index, isCheck); 542 } 543 return apiLocations; 544 } 545 546 static processFileApiMap( 547 fileMap: FileInfoMap, 548 apiLocations: Map<string, string[]>, 549 index: number, 550 isCheck?: boolean 551 ): void { 552 for (const apiKey of fileMap.keys()) { 553 if (apiKey === StringConstant.SELF) { 554 continue; 555 } 556 const apiInfoMap: ApiInfosMap = fileMap.get(apiKey) as ApiInfosMap; 557 const apiInfos: BasicApiInfo[] = apiInfoMap.get(StringConstant.SELF) as BasicApiInfo[]; 558 apiInfos.forEach((apiInfo: BasicApiInfo) => { 559 DiffHelper.processApiInfo(apiInfo, apiLocations, index, isCheck); 560 index++; 561 }); 562 } 563 } 564 565 /** 566 * 删除最外层第一个API的jsdocText中,版权头和kit相关jsdoc 567 * 568 * @param apiInfo 569 * @returns 570 */ 571 static deleteUselessJsdoc(apiInfo: BasicApiInfo): string { 572 const jsdocTextArr: string[] = apiInfo.getJsDocText().split('*/'); 573 const clonedJsdocTextArr: string[] = jsdocTextArr; 574 if (clonedJsdocTextArr[1] && StringUtils.hasSubstring(clonedJsdocTextArr[1], CommentHelper.fileTag)) { 575 jsdocTextArr.splice(1, 1); 576 } 577 578 if (clonedJsdocTextArr[0] && StringUtils.hasSubstring(clonedJsdocTextArr[0], CommentHelper.fileTag)) { 579 jsdocTextArr.splice(0, 1); 580 } 581 582 if (clonedJsdocTextArr[0] && StringUtils.hasSubstring(clonedJsdocTextArr[0], CommentHelper.licenseKeyword)) { 583 jsdocTextArr.splice(0, 1); 584 } 585 586 return jsdocTextArr.join('*/'); 587 } 588 589 static processApiInfo( 590 basicApiInfo: BasicApiInfo, 591 apiLocations: Map<string, string[]>, 592 index: number, 593 isCheck?: boolean 594 ): void { 595 const apiNode: ts.Node | undefined = basicApiInfo.getNode(); 596 if (isCheck) { 597 const jsDocText: string | undefined = apiNode?.getFullText().replace(apiNode.getText(), ''); 598 if (jsDocText) { 599 basicApiInfo.setJsDocText(jsDocText); 600 } 601 } 602 603 if (index === 0 && basicApiInfo.getParentApiType() === ApiType.SOURCE_FILE) { 604 const jsDocText: string = DiffHelper.deleteUselessJsdoc(basicApiInfo); 605 basicApiInfo.setJsDocText(jsDocText); 606 } 607 608 basicApiInfo.setSyscap(DiffHelper.getSyscapField(basicApiInfo)); 609 if (!apiStatisticsType.has(basicApiInfo.getApiType())) { 610 return; 611 } 612 if (basicApiInfo.getApiName() === 'constructor') { 613 return; 614 } 615 const apiInfo: ApiInfo = basicApiInfo as ApiInfo; 616 const locations: string[] = apiInfo.getHierarchicalRelations(); 617 apiLocations.set(locations.toString(), locations); 618 if (!containerApiTypes.has(apiInfo.getApiType())) { 619 return; 620 } 621 const containerApiInfo: ContainerApiInfo = apiInfo as ContainerApiInfo; 622 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 623 DiffHelper.processApiInfo(childApiInfo, apiLocations, 1, isCheck); 624 }); 625 } 626 627 static getSyscapField(apiInfo: BasicApiInfo): string { 628 if (apiInfo.getApiType() === ApiType.SOURCE_FILE) { 629 const sourceFileContent: string = apiInfo.getNode()?.getFullText() as string; 630 let syscap = ''; 631 if (/\@[S|s][Y|y][S|s][C|c][A|a][P|p]\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(sourceFileContent)) { 632 sourceFileContent.replace( 633 /\@[S|s][Y|y][S|s][C|c][A|a][P|p]\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, 634 (sysCapInfo: string, args: []) => { 635 syscap = sysCapInfo.replace(/\@[S|s][Y|y][S|s][C|c][A|a][P|p]/g, '').trim(); 636 return syscap; 637 } 638 ); 639 } 640 return FunctionUtils.handleSyscap(syscap); 641 } 642 if (notJsDocApiTypes.has(apiInfo.getApiType())) { 643 return ''; 644 } 645 const clonedApiInfo: ApiInfo = apiInfo as ApiInfo; 646 const latestJsDocInfo: Comment.JsDocInfo | undefined = clonedApiInfo.getLastJsDocInfo(); 647 if (!latestJsDocInfo) { 648 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 649 } 650 let syscap: string | undefined = latestJsDocInfo?.getSyscap(); 651 if (!syscap) { 652 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 653 } 654 if (syscap) { 655 return FunctionUtils.handleSyscap(syscap); 656 } 657 return ''; 658 } 659 660 static searchSyscapFieldInParent(apiInfo: ApiInfo): string { 661 let curApiInfo: ApiInfo = apiInfo; 662 let syscap: string = ''; 663 const node: ts.Node | undefined = curApiInfo.getNode(); 664 while (node && curApiInfo && !ts.isSourceFile(node)) { 665 syscap = curApiInfo.getSyscap(); 666 if (syscap) { 667 return syscap; 668 } 669 curApiInfo = curApiInfo.getParentApi() as ApiInfo; 670 } 671 return syscap; 672 } 673} 674 675/** 676 * 比较每个标签的值 677 */ 678export class DiffTagInfoHelper { 679 static diffSyscapTagInfo( 680 oldJsDocInfo: Comment.JsDocInfo | undefined, 681 newJsDocInfo: Comment.JsDocInfo | undefined 682 ): boolean { 683 if (oldJsDocInfo?.getSyscap() === newJsDocInfo?.getSyscap()) { 684 return true; 685 } 686 return false; 687 } 688 689 static diffSinceTagInfo( 690 oldJsDocInfo: Comment.JsDocInfo | undefined, 691 newJsDocInfo: Comment.JsDocInfo | undefined 692 ): boolean { 693 if (oldJsDocInfo?.getSince() === newJsDocInfo?.getSince()) { 694 return true; 695 } 696 return false; 697 } 698 699 static diffFormTagInfo( 700 oldJsDocInfo: Comment.JsDocInfo | undefined, 701 newJsDocInfo: Comment.JsDocInfo | undefined 702 ): boolean { 703 if (oldJsDocInfo?.getIsForm() === newJsDocInfo?.getIsForm()) { 704 return true; 705 } 706 return false; 707 } 708 709 static diffCrossPlatFromTagInfo( 710 oldJsDocInfo: Comment.JsDocInfo | undefined, 711 newJsDocInfo: Comment.JsDocInfo | undefined 712 ): boolean { 713 if (oldJsDocInfo?.getIsCrossPlatForm() === newJsDocInfo?.getIsCrossPlatForm()) { 714 return true; 715 } 716 return false; 717 } 718 719 static diffSystemApiTagInfo( 720 oldJsDocInfo: Comment.JsDocInfo | undefined, 721 newJsDocInfo: Comment.JsDocInfo | undefined 722 ): boolean { 723 if (oldJsDocInfo?.getIsSystemApi() === newJsDocInfo?.getIsSystemApi()) { 724 return true; 725 } 726 return false; 727 } 728 729 static diffModelTagInfo( 730 oldJsDocInfo: Comment.JsDocInfo | undefined, 731 newJsDocInfo: Comment.JsDocInfo | undefined 732 ): boolean { 733 if (oldJsDocInfo?.getModelLimitation() === newJsDocInfo?.getModelLimitation()) { 734 return true; 735 } 736 return false; 737 } 738 739 static diffDeprecatedTagInfo( 740 oldJsDocInfo: Comment.JsDocInfo | undefined, 741 newJsDocInfo: Comment.JsDocInfo | undefined 742 ): boolean { 743 if (oldJsDocInfo?.getDeprecatedVersion() === newJsDocInfo?.getDeprecatedVersion()) { 744 return true; 745 } 746 return false; 747 } 748 749 static diffUseinsteadTagInfo( 750 oldJsDocInfo: Comment.JsDocInfo | undefined, 751 newJsDocInfo: Comment.JsDocInfo | undefined 752 ): boolean { 753 if (oldJsDocInfo?.getUseinstead() === newJsDocInfo?.getUseinstead()) { 754 return true; 755 } 756 return false; 757 } 758 759 static diffPermissionTagInfo( 760 oldJsDocInfo: Comment.JsDocInfo | undefined, 761 newJsDocInfo: Comment.JsDocInfo | undefined 762 ): boolean { 763 if (oldJsDocInfo?.getPermission() === newJsDocInfo?.getPermission()) { 764 return true; 765 } 766 return false; 767 } 768 769 static diffThrowsTagInfo( 770 oldJsDocInfo: Comment.JsDocInfo | undefined, 771 newJsDocInfo: Comment.JsDocInfo | undefined 772 ): boolean { 773 if (oldJsDocInfo?.getErrorCode().sort().join() === newJsDocInfo?.getErrorCode().sort().join()) { 774 return true; 775 } 776 return false; 777 } 778 779 static diffAtomicServiceTagInfo( 780 oldJsDocInfo: Comment.JsDocInfo | undefined, 781 newJsDocInfo: Comment.JsDocInfo | undefined 782 ): boolean { 783 if (oldJsDocInfo?.getIsAtomicService() === newJsDocInfo?.getIsAtomicService()) { 784 return true; 785 } 786 return false; 787 } 788 789 static diffParamTagInfo( 790 oldJsDocInfo: Comment.JsDocInfo | undefined, 791 newJsDocInfo: Comment.JsDocInfo | undefined 792 ): boolean { 793 const oldParamArr: string[] = []; 794 const newParamArr: string[] = []; 795 oldJsDocInfo?.getTags()?.forEach((tagInfo: Comment.CommentTag) => { 796 if (tagInfo.tag === Comment.JsDocTag.PARAM) { 797 oldParamArr.push(`${tagInfo.name}#${tagInfo.type}`); 798 } 799 }); 800 801 newJsDocInfo?.getTags()?.forEach((tagInfo: Comment.CommentTag) => { 802 if (tagInfo.tag === Comment.JsDocTag.PARAM) { 803 newParamArr.push(`${tagInfo.name}#${tagInfo.type}`); 804 } 805 }); 806 if (oldParamArr.join() === newParamArr.join()) { 807 return true; 808 } 809 return false; 810 } 811 812 static diffReturnsTagInfo( 813 oldJsDocInfo: Comment.JsDocInfo | undefined, 814 newJsDocInfo: Comment.JsDocInfo | undefined 815 ): boolean { 816 let oldReturnValue: string = ''; 817 let newReturnValue: string = ''; 818 819 oldJsDocInfo?.getTags()?.forEach((tagInfo: Comment.CommentTag) => { 820 if (tagInfo.tag === Comment.JsDocTag.RETURNS) { 821 oldReturnValue = tagInfo.type; 822 } 823 }); 824 825 newJsDocInfo?.getTags()?.forEach((tagInfo: Comment.CommentTag) => { 826 if (tagInfo.tag === Comment.JsDocTag.PARAM) { 827 oldReturnValue = tagInfo.type; 828 } 829 }); 830 if (oldReturnValue === oldReturnValue) { 831 return true; 832 } 833 return false; 834 } 835} 836 837export const diffJsdocMethod: Map<string, TagInfoDiffProcessor> = new Map([ 838 [Comment.JsDocTag.SYSCAP, DiffTagInfoHelper.diffSyscapTagInfo], 839 [Comment.JsDocTag.SINCE, DiffTagInfoHelper.diffSinceTagInfo], 840 [Comment.JsDocTag.FORM, DiffTagInfoHelper.diffFormTagInfo], 841 [Comment.JsDocTag.CROSS_PLAT_FORM, DiffTagInfoHelper.diffCrossPlatFromTagInfo], 842 [Comment.JsDocTag.SYSTEM_API, DiffTagInfoHelper.diffSystemApiTagInfo], 843 [Comment.JsDocTag.STAGE_MODEL_ONLY, DiffTagInfoHelper.diffModelTagInfo], 844 [Comment.JsDocTag.FA_MODEL_ONLY, DiffTagInfoHelper.diffModelTagInfo], 845 [Comment.JsDocTag.DEPRECATED, DiffTagInfoHelper.diffDeprecatedTagInfo], 846 [Comment.JsDocTag.USEINSTEAD, DiffTagInfoHelper.diffUseinsteadTagInfo], 847 [Comment.JsDocTag.PERMISSION, DiffTagInfoHelper.diffPermissionTagInfo], 848 [Comment.JsDocTag.THROWS, DiffTagInfoHelper.diffThrowsTagInfo], 849 [Comment.JsDocTag.ATOMIC_SERVICE, DiffTagInfoHelper.diffAtomicServiceTagInfo], 850 [Comment.JsDocTag.PARAM, DiffTagInfoHelper.diffParamTagInfo], 851 [Comment.JsDocTag.RETURNS, DiffTagInfoHelper.diffReturnsTagInfo], 852]); 853