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 { StringConstant } from '../../utils/Constant'; 19import { ApiInfo, BasicApiInfo, ContainerApiInfo, containerApiTypes } from '../../typedef/parser/ApiInfoDefination'; 20import { ApiDiffType, ApiStatusCode, BasicDiffInfo, DiffTypeInfo } from '../../typedef/diff/ApiInfoDiff'; 21import { ApiInfosMap, FileInfoMap, FilesMap, Parser } from '../parser/parser'; 22import { apiStatisticsType } from '../../typedef/statistics/ApiStatistics'; 23import { DiffProcessorHelper } from './DiffProcessor'; 24import { FunctionUtils } from '../../utils/FunctionUtils'; 25import { Comment } from '../../typedef/parser/Comment'; 26import { notJsDocApiTypes } from '../../typedef/parser/ApiInfoDefination'; 27 28export class DiffHelper { 29 /** 30 * 根据解析后的新旧版本SDK结果,得到对比差异 31 * 32 * @param { FilesMap } oldSDKApiMap 旧版本SDK解析后的结果 33 * @param { FilesMap } newSDKApiMap 新版本SDK解析后的结果 34 * @returns { BasicDiffInfo[] } 差异结果集 35 */ 36 static diffSDK(oldSDKApiMap: FilesMap, newSDKApiMap: FilesMap, isCheck?: boolean): BasicDiffInfo[] { 37 const clonedOldSDKApiMap: FilesMap = _.cloneDeep(oldSDKApiMap); 38 const clonedNewSDKApiMap: FilesMap = _.cloneDeep(newSDKApiMap); 39 const diffInfos: BasicDiffInfo[] = []; 40 const oldSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedOldSDKApiMap, isCheck); 41 const newSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedNewSDKApiMap, isCheck); 42 // 先以旧版本为基础进行对比 43 for (const key of oldSDKApiLocations.keys()) { 44 const apiLocation: string[] = oldSDKApiLocations.get(key) as string[]; 45 const oldApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedOldSDKApiMap) as ApiInfo[]; 46 // 如果旧版本中的API在新版本中不存在,则为删除 47 if (!newSDKApiLocations.has(key)) { 48 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 49 diffInfos.push( 50 DiffProcessorHelper.wrapDiffInfo( 51 oldApiInfo, 52 undefined, 53 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText()) 54 ) 55 ); 56 }); 57 continue; 58 } 59 // 新旧版本均存在,则进行对比 60 const newApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedNewSDKApiMap) as ApiInfo[]; 61 DiffHelper.diffApis(oldApiInfos, newApiInfos, diffInfos, isCheck); 62 // 对比完则将新版本中的对应API进行删除 63 newSDKApiLocations.delete(key); 64 } 65 // 对比完还剩下的新版本中的API即为新增API 66 for (const key of newSDKApiLocations.keys()) { 67 const locations: string[] = newSDKApiLocations.get(key) as string[]; 68 const newApiInfos: ApiInfo[] = Parser.getApiInfo(locations, clonedNewSDKApiMap) as ApiInfo[]; 69 newApiInfos.forEach((newApiInfo: ApiInfo) => { 70 diffInfos.push( 71 DiffProcessorHelper.wrapDiffInfo( 72 undefined, 73 newApiInfo, 74 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()) 75 ) 76 ); 77 }); 78 } 79 return diffInfos; 80 } 81 82 /** 83 * 对比新旧版本API差异,类型为数组是由于同名函数的存在,因此统一为数组方便处理 84 * 85 * @param { ApiInfo[] } oldApiInfos 老版本API信息 86 * @param { ApiInfo[] } newApiInfos 新版本API信息 87 * @param { BasicDiffInfo[] } diffInfos api差异结果集 88 */ 89 static diffApis(oldApiInfos: ApiInfo[], newApiInfos: ApiInfo[], diffInfos: BasicDiffInfo[], isCheck?: boolean): void { 90 const diffSets: Map<string, ApiInfo>[] = DiffHelper.getDiffSet(oldApiInfos, newApiInfos); 91 const oldReduceNewMap: Map<string, ApiInfo> = diffSets[0]; 92 const newReduceOldMap: Map<string, ApiInfo> = diffSets[1]; 93 if (oldReduceNewMap.size === 0) { 94 newReduceOldMap.forEach((newApiInfo: ApiInfo) => { 95 diffInfos.push( 96 DiffProcessorHelper.wrapDiffInfo( 97 undefined, 98 newApiInfo, 99 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()) 100 ) 101 ); 102 }); 103 return; 104 } 105 if (newReduceOldMap.size === 0) { 106 oldReduceNewMap.forEach((oldApiInfo: ApiInfo) => { 107 diffInfos.push( 108 DiffProcessorHelper.wrapDiffInfo( 109 oldApiInfo, 110 undefined, 111 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText(), undefined) 112 ) 113 ); 114 }); 115 return; 116 } 117 DiffHelper.diffSameNumberFunction(oldApiInfos, newApiInfos, diffInfos, isCheck); 118 } 119 120 static diffSameNumberFunction( 121 oldApiInfos: ApiInfo[], 122 newApiInfos: ApiInfo[], 123 diffInfos: BasicDiffInfo[], 124 isCheck?: boolean 125 ): void { 126 if (oldApiInfos.length === newApiInfos.length) { 127 const apiNumber: number = oldApiInfos.length; 128 for (let i = 0; i < apiNumber; i++) { 129 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfos[i], newApiInfos[i], diffInfos); 130 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfos[i], newApiInfos[i], diffInfos); 131 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldApiInfos[i], newApiInfos[i], diffInfos, isCheck); 132 } 133 } else { 134 const methodInfoMap: Map<string, ApiInfo> = DiffHelper.setmethodInfoMap(newApiInfos); 135 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 136 const newApiInfo: ApiInfo | undefined = methodInfoMap.get(oldApiInfo.getDefinedText()); 137 if (newApiInfo) { 138 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfo, newApiInfo, diffInfos); 139 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfo, newApiInfo, diffInfos); 140 methodInfoMap.delete(oldApiInfo.getDefinedText()); 141 } else { 142 diffInfos.push( 143 DiffProcessorHelper.wrapDiffInfo( 144 oldApiInfo, 145 undefined, 146 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText(), undefined) 147 ) 148 ); 149 } 150 }); 151 152 methodInfoMap.forEach((apiInfo: ApiInfo, _) => { 153 diffInfos.push( 154 DiffProcessorHelper.wrapDiffInfo( 155 undefined, 156 apiInfo, 157 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, apiInfo.getDefinedText()) 158 ) 159 ); 160 }); 161 } 162 } 163 164 static setmethodInfoMap(apiInfos: ApiInfo[]): Map<string, ApiInfo> { 165 const methodInfoMap: Map<string, ApiInfo> = new Map(); 166 apiInfos.forEach((apiInfo: ApiInfo) => { 167 methodInfoMap.set(apiInfo.getDefinedText(), apiInfo); 168 }); 169 return methodInfoMap; 170 } 171 172 static getDiffSet(oldApiInfos: ApiInfo[], newApiInfos: ApiInfo[]): Map<string, ApiInfo>[] { 173 const oldApiInfoMap: Map<string, ApiInfo> = new Map(); 174 const newApiInfoMap: Map<string, ApiInfo> = new Map(); 175 DiffHelper.setApiInfoMap(oldApiInfoMap, oldApiInfos); 176 DiffHelper.setApiInfoMap(newApiInfoMap, newApiInfos); 177 const oldReduceNewMap: Map<string, ApiInfo> = new Map(); 178 oldApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 179 if (!newApiInfoMap.has(key)) { 180 oldReduceNewMap.set(key, apiInfo); 181 } 182 }); 183 const newReduceOldMap: Map<string, ApiInfo> = new Map(); 184 newApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 185 if (!oldApiInfoMap.has(key)) { 186 newReduceOldMap.set(key, apiInfo); 187 } 188 }); 189 return [oldReduceNewMap, newReduceOldMap]; 190 } 191 192 static setApiInfoMap(apiInfoMap: Map<string, ApiInfo>, apiInfos: ApiInfo[]): void { 193 apiInfos.forEach((apiInfo: ApiInfo) => { 194 const key = JSON.stringify(apiInfo); 195 apiInfoMap.set(key, apiInfo); 196 }); 197 } 198 199 static getApiLocations(apiMap: FilesMap, isCheck?: boolean): Map<string, string[]> { 200 const apiLocations: Map<string, string[]> = new Map(); 201 for (const filePath of apiMap.keys()) { 202 const fileMap: FileInfoMap = apiMap.get(filePath) as FileInfoMap; 203 DiffHelper.processFileApiMap(fileMap, apiLocations, isCheck); 204 } 205 return apiLocations; 206 } 207 208 static processFileApiMap(fileMap: FileInfoMap, apiLocations: Map<string, string[]>, isCheck?: boolean): void { 209 for (const apiKey of fileMap.keys()) { 210 if (apiKey === StringConstant.SELF) { 211 continue; 212 } 213 const apiInfoMap: ApiInfosMap = fileMap.get(apiKey) as ApiInfosMap; 214 const apiInfos: BasicApiInfo[] = apiInfoMap.get(StringConstant.SELF) as BasicApiInfo[]; 215 apiInfos.forEach((apiInfo: BasicApiInfo) => { 216 DiffHelper.processApiInfo(apiInfo, apiLocations, isCheck); 217 }); 218 } 219 } 220 221 static processApiInfo(basicApiInfo: BasicApiInfo, apiLocations: Map<string, string[]>, isCheck?: boolean): void { 222 const apiNode: ts.Node | undefined = basicApiInfo.getNode(); 223 if (isCheck) { 224 const jsDocText: string | undefined = apiNode?.getFullText().replace(apiNode.getText(), ''); 225 if (jsDocText) { 226 basicApiInfo.setJsDocText(jsDocText); 227 } 228 } 229 230 basicApiInfo.setSyscap(DiffHelper.getSyscapField(basicApiInfo)); 231 basicApiInfo.setParentApi(undefined); 232 233 basicApiInfo.removeNode(); 234 if (!apiStatisticsType.has(basicApiInfo.getApiType())) { 235 return; 236 } 237 if (basicApiInfo.getApiName() === 'constructor') { 238 return; 239 } 240 const apiInfo: ApiInfo = basicApiInfo as ApiInfo; 241 const locations: string[] = apiInfo.getHierarchicalRelations(); 242 apiLocations.set(locations.toString(), locations); 243 if (!containerApiTypes.has(apiInfo.getApiType())) { 244 return; 245 } 246 const containerApiInfo: ContainerApiInfo = apiInfo as ContainerApiInfo; 247 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 248 DiffHelper.processApiInfo(childApiInfo, apiLocations, isCheck); 249 }); 250 } 251 252 static getSyscapField(apiInfo: BasicApiInfo): string { 253 if (notJsDocApiTypes.has(apiInfo.getApiType())) { 254 return ''; 255 } 256 const clonedApiInfo: ApiInfo = apiInfo as ApiInfo; 257 const latestJsDocInfo: Comment.JsDocInfo | undefined = clonedApiInfo.getLastJsDocInfo(); 258 if (!latestJsDocInfo) { 259 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 260 } 261 let syscap: string | undefined = latestJsDocInfo?.getSyscap(); 262 if (!syscap) { 263 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 264 } 265 if (syscap) { 266 return FunctionUtils.handleSyscap(syscap); 267 } 268 return ''; 269 } 270 271 static searchSyscapFieldInParent(apiInfo: ApiInfo): string { 272 let curApiInfo: ApiInfo = apiInfo; 273 let syscap: string = ''; 274 const node: ts.Node | undefined = curApiInfo.getNode(); 275 while (node && curApiInfo && !ts.isSourceFile(node)) { 276 syscap = curApiInfo.getSyscap(); 277 if (syscap) { 278 return syscap; 279 } 280 curApiInfo = curApiInfo.getParentApi() as ApiInfo; 281 } 282 return syscap; 283 } 284} 285