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 { 20 ApiInfo, 21 ApiType, 22 BasicApiInfo, 23 ContainerApiInfo, 24 containerApiTypes, 25} from '../../typedef/parser/ApiInfoDefination'; 26import { ApiDiffType, ApiStatusCode, BasicDiffInfo, DiffTypeInfo } from '../../typedef/diff/ApiInfoDiff'; 27import { ApiInfosMap, FileInfoMap, FilesMap, Parser } from '../parser/parser'; 28import { apiStatisticsType } from '../../typedef/statistics/ApiStatistics'; 29import { DiffProcessorHelper } from './DiffProcessor'; 30import { FunctionUtils } from '../../utils/FunctionUtils'; 31import { Comment } from '../../typedef/parser/Comment'; 32import { notJsDocApiTypes } from '../../typedef/parser/ApiInfoDefination'; 33import { StringUtils } from '../../utils/StringUtils'; 34import { CommentHelper } from '../parser/JsDocProcessor'; 35 36export class DiffHelper { 37 /** 38 * 根据解析后的新旧版本SDK结果,得到对比差异 39 * 40 * @param { FilesMap } oldSDKApiMap 旧版本SDK解析后的结果 41 * @param { FilesMap } newSDKApiMap 新版本SDK解析后的结果 42 * @returns { BasicDiffInfo[] } 差异结果集 43 */ 44 static diffSDK( 45 oldSDKApiMap: FilesMap, 46 newSDKApiMap: FilesMap, 47 isAllSheet: boolean, 48 isCheck?: boolean 49 ): BasicDiffInfo[] { 50 const clonedOldSDKApiMap: FilesMap = _.cloneDeep(oldSDKApiMap); 51 const clonedNewSDKApiMap: FilesMap = _.cloneDeep(newSDKApiMap); 52 const diffInfos: BasicDiffInfo[] = []; 53 const oldSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedOldSDKApiMap, isCheck); 54 const newSDKApiLocations: Map<string, string[]> = DiffHelper.getApiLocations(clonedNewSDKApiMap, isCheck); 55 DiffHelper.diffKit(clonedOldSDKApiMap, clonedNewSDKApiMap, diffInfos); 56 const oldFilePathSet: Set<string> = new Set(Array.from(clonedOldSDKApiMap.keys())); 57 // 先以旧版本为基础进行对比 58 for (const key of oldSDKApiLocations.keys()) { 59 const apiLocation: string[] = oldSDKApiLocations.get(key) as string[]; 60 const oldApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedOldSDKApiMap, isAllSheet) as ApiInfo[]; 61 // 如果旧版本中的API在新版本中不存在,则为删除 62 if (!newSDKApiLocations.has(key)) { 63 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 64 diffInfos.push( 65 DiffProcessorHelper.wrapDiffInfo( 66 oldApiInfo, 67 undefined, 68 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText()) 69 ) 70 ); 71 }); 72 continue; 73 } 74 // 新旧版本均存在,则进行对比 75 const newApiInfos: ApiInfo[] = Parser.getApiInfo(apiLocation, clonedNewSDKApiMap, isAllSheet) as ApiInfo[]; 76 DiffHelper.diffApis(oldApiInfos, newApiInfos, diffInfos, isAllSheet, isCheck); 77 // 对比完则将新版本中的对应API进行删除 78 newSDKApiLocations.delete(key); 79 } 80 // 对比完还剩下的新版本中的API即为新增API 81 for (const key of newSDKApiLocations.keys()) { 82 const locations: string[] = newSDKApiLocations.get(key) as string[]; 83 const newApiInfos: ApiInfo[] = Parser.getApiInfo(locations, clonedNewSDKApiMap, isAllSheet) as ApiInfo[]; 84 newApiInfos.forEach((newApiInfo: ApiInfo) => { 85 let isNewFile: boolean = true; 86 if (oldFilePathSet.has(newApiInfo.getFilePath())) { 87 isNewFile = false; 88 } 89 diffInfos.push( 90 DiffProcessorHelper.wrapDiffInfo( 91 undefined, 92 newApiInfo, 93 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()), 94 isNewFile 95 ) 96 ); 97 }); 98 } 99 return diffInfos; 100 } 101 102 static diffKit(clonedOldSDKApiMap: FilesMap, clonedNewSDKApiMap: FilesMap, diffInfos: BasicDiffInfo[]): void { 103 for (const key of clonedOldSDKApiMap.keys()) { 104 const oldSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedOldSDKApiMap.get(key)); 105 oldSourceFileInfo?.setSyscap(DiffHelper.getSyscapField(oldSourceFileInfo)); 106 const oldKitInfo: string | undefined = oldSourceFileInfo?.getLastJsDocInfo()?.getKit(); 107 //文件在新版本中被删除 108 if (!clonedNewSDKApiMap.get(key) && oldKitInfo !== 'NA') { 109 diffInfos.push( 110 DiffProcessorHelper.wrapDiffInfo( 111 oldSourceFileInfo, 112 undefined, 113 new DiffTypeInfo(ApiStatusCode.KIT_CHANGE, ApiDiffType.KIT_HAVE_TO_NA, oldKitInfo, 'NA') 114 ) 115 ); 116 } else if (clonedNewSDKApiMap.get(key)) { 117 const newSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedNewSDKApiMap.get(key)); 118 const newKitInfo: string | undefined = newSourceFileInfo?.getLastJsDocInfo()?.getKit(); 119 if (oldKitInfo !== newKitInfo) { 120 diffInfos.push( 121 DiffProcessorHelper.wrapDiffInfo( 122 oldSourceFileInfo, 123 newSourceFileInfo, 124 new DiffTypeInfo(ApiStatusCode.KIT_CHANGE, DiffHelper.getKitDiffType(oldKitInfo, newKitInfo), oldKitInfo, newKitInfo) 125 ) 126 ); 127 } 128 } 129 } 130 131 for (const key of clonedNewSDKApiMap.keys()) { 132 const newSourceFileInfo: ApiInfo | undefined = DiffHelper.getSourceFileInfo(clonedNewSDKApiMap.get(key)); 133 const newKitInfo: string | undefined = newSourceFileInfo?.getLastJsDocInfo()?.getKit(); 134 if (!clonedOldSDKApiMap.get(key) && newKitInfo !== 'NA') { 135 diffInfos.push( 136 DiffProcessorHelper.wrapDiffInfo( 137 undefined, 138 newSourceFileInfo, 139 new DiffTypeInfo(ApiStatusCode.KIT_CHANGE, ApiDiffType.KIT_NA_TO_HAVE, 'NA', newKitInfo) 140 ) 141 ); 142 } 143 } 144 } 145 146 static getKitDiffType(oldKitInfo: string | undefined, newKitInfo: string | undefined): ApiDiffType { 147 if (oldKitInfo === 'NA' && newKitInfo === '') { 148 return ApiDiffType.KIT_NA_TO_HAVE; 149 } else if (oldKitInfo === '' && newKitInfo === 'NA') { 150 return ApiDiffType.KIT_HAVE_TO_NA; 151 } else if (oldKitInfo === 'NA' || oldKitInfo === '') { 152 return ApiDiffType.KIT_NA_TO_HAVE; 153 } else if (newKitInfo === 'NA' || newKitInfo === '') { 154 return ApiDiffType.KIT_HAVE_TO_NA; 155 } 156 return ApiDiffType.KIT_CHANGE; 157 } 158 159 static getSourceFileInfo(fileMap: FileInfoMap | undefined): ApiInfo | undefined { 160 if (!fileMap) { 161 return undefined; 162 } 163 let sourceFileInfos: ApiInfo[] = []; 164 for (const apiKey of fileMap.keys()) { 165 if (apiKey === StringConstant.SELF) { 166 sourceFileInfos = fileMap.get(apiKey) as ApiInfo[]; 167 } 168 } 169 return sourceFileInfos[0]; 170 } 171 172 /** 173 * 通过数组长度判断是否为同名函数 174 * 175 * @param apiInfos 176 */ 177 static judgeIsSameNameFunction(apiInfos: BasicApiInfo[]): boolean { 178 let isSameNameFunction: boolean = false; 179 if (apiInfos.length > 1 && apiInfos[0].getApiType() === ApiType.METHOD) { 180 isSameNameFunction = true; 181 } 182 return isSameNameFunction; 183 } 184 185 /** 186 * 对比新旧版本API差异,类型为数组是由于同名函数的存在,因此统一为数组方便处理 187 * 188 * @param { ApiInfo[] } oldApiInfos 老版本API信息 189 * @param { ApiInfo[] } newApiInfos 新版本API信息 190 * @param { BasicDiffInfo[] } diffInfos api差异结果集 191 */ 192 static diffApis( 193 oldApiInfos: ApiInfo[], 194 newApiInfos: ApiInfo[], 195 diffInfos: BasicDiffInfo[], 196 isAllSheet: boolean, 197 isCheck?: boolean 198 ): void { 199 const diffSets: Map<string, ApiInfo>[] = DiffHelper.getDiffSet(oldApiInfos, newApiInfos); 200 const oldReduceNewMap: Map<string, ApiInfo> = diffSets[0]; 201 const newReduceOldMap: Map<string, ApiInfo> = diffSets[1]; 202 if (oldReduceNewMap.size === 0) { 203 newReduceOldMap.forEach((newApiInfo: ApiInfo) => { 204 diffInfos.push( 205 DiffProcessorHelper.wrapDiffInfo( 206 undefined, 207 newApiInfo, 208 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, newApiInfo.getDefinedText()) 209 ) 210 ); 211 }); 212 return; 213 } 214 if (newReduceOldMap.size === 0) { 215 oldReduceNewMap.forEach((oldApiInfo: ApiInfo) => { 216 diffInfos.push( 217 DiffProcessorHelper.wrapDiffInfo( 218 oldApiInfo, 219 undefined, 220 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, oldApiInfo.getDefinedText(), undefined) 221 ) 222 ); 223 }); 224 return; 225 } 226 227 DiffHelper.diffSameNumberFunction(oldApiInfos, newApiInfos, diffInfos, isCheck); 228 } 229 230 /** 231 * 删除完全一样的API后,进行对比 232 * @param { ApiInfo[] } oldApiInfos 233 * @param { ApiInfo[] } newApiInfos 234 * @param diffInfos 235 * @param { boolean } isCheck 是否是api_check工具进行调用 236 */ 237 static diffSameNumberFunction( 238 oldApiInfos: ApiInfo[], 239 newApiInfos: ApiInfo[], 240 diffInfos: BasicDiffInfo[], 241 isCheck?: boolean 242 ): void { 243 //长度为1说明新旧版本有变更的只有1个,可直接进行对比 244 if (oldApiInfos.length === 1 && oldApiInfos.length === newApiInfos.length) { 245 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfos[0], newApiInfos[0], diffInfos); 246 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfos[0], newApiInfos[0], diffInfos); 247 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldApiInfos[0], newApiInfos[0], diffInfos, isCheck); 248 } else { 249 const newMethodInfoMap: Map<string, ApiInfo> = DiffHelper.setmethodInfoMap(newApiInfos); 250 const oldMethodInfoMap: Map<string, ApiInfo> = DiffHelper.setmethodInfoMap(oldApiInfos); 251 oldApiInfos.forEach((oldApiInfo: ApiInfo) => { 252 const newApiInfo: ApiInfo | undefined = newMethodInfoMap.get(oldApiInfo.getDefinedText()); 253 if (newApiInfo) { 254 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldApiInfo, newApiInfo, diffInfos); 255 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldApiInfo, newApiInfo, diffInfos); 256 newMethodInfoMap.delete(oldApiInfo.getDefinedText()); 257 oldMethodInfoMap.delete(oldApiInfo.getDefinedText()); 258 } 259 }); 260 261 if (oldMethodInfoMap.size === 1 && newMethodInfoMap.size === 1) { 262 const oldMethodInfo: ApiInfo = oldMethodInfoMap.entries().next().value[1]; 263 const newMethodInfo: ApiInfo = newMethodInfoMap.entries().next().value[1]; 264 DiffProcessorHelper.JsDocDiffHelper.diffJsDocInfo(oldMethodInfo, newMethodInfo, diffInfos); 265 DiffProcessorHelper.ApiDecoratorsDiffHelper.diffDecorator(oldMethodInfo, newMethodInfo, diffInfos); 266 DiffProcessorHelper.ApiNodeDiffHelper.diffNodeInfo(oldMethodInfo, newMethodInfo, diffInfos, isCheck); 267 } else { 268 newMethodInfoMap.forEach((apiInfo: ApiInfo, _) => { 269 diffInfos.push( 270 DiffProcessorHelper.wrapDiffInfo( 271 undefined, 272 apiInfo, 273 new DiffTypeInfo(ApiStatusCode.NEW_API, ApiDiffType.ADD, undefined, apiInfo.getDefinedText()), 274 ) 275 ); 276 }); 277 oldMethodInfoMap.forEach((apiInfo: ApiInfo, _) => { 278 diffInfos.push( 279 DiffProcessorHelper.wrapDiffInfo( 280 apiInfo, 281 undefined, 282 new DiffTypeInfo(ApiStatusCode.DELETE, ApiDiffType.REDUCE, apiInfo.getDefinedText(), undefined), 283 ) 284 ); 285 }); 286 } 287 } 288 } 289 290 static judgeIsAllDeprecated(apiInfos: ApiInfo[]): boolean { 291 let isAllDeprecated: boolean = true; 292 apiInfos.forEach((apiInfo: ApiInfo) => { 293 const deprecatedVersion = apiInfo.getLastJsDocInfo()?.getDeprecatedVersion(); 294 if (deprecatedVersion === '-1' || !deprecatedVersion) { 295 isAllDeprecated = false; 296 } 297 }); 298 return isAllDeprecated; 299 } 300 301 static handleDeprecatedVersion(apiInfos: ApiInfo[]): void { 302 let isAllDeprecated: boolean = true; 303 apiInfos.forEach((apiInfo: ApiInfo) => { 304 const deprecatedVersion = apiInfo.getLastJsDocInfo()?.getDeprecatedVersion(); 305 if (deprecatedVersion === '-1' || !deprecatedVersion) { 306 isAllDeprecated = false; 307 } 308 }); 309 if (isAllDeprecated) { 310 return; 311 } 312 apiInfos.forEach((apiInfo: ApiInfo) => { 313 apiInfo.getLastJsDocInfo()?.setDeprecatedVersion('-1'); 314 }); 315 } 316 317 /** 318 * 拼接同名函数的API声明 319 * 320 * @param methodInfoMap 321 * @returns 322 */ 323 static joinApiText(methodInfoMap: Map<string, ApiInfo>): string { 324 const apiTextArr: string[] = []; 325 for (const apiText of methodInfoMap.keys()) { 326 apiTextArr.push(apiText); 327 } 328 return apiTextArr.join(' #&# '); 329 } 330 331 /** 332 * 生成map,key为API声明,value为API信息 333 * 334 * @param apiInfos 335 * @returns 336 */ 337 static setmethodInfoMap(apiInfos: ApiInfo[]): Map<string, ApiInfo> { 338 const methodInfoMap: Map<string, ApiInfo> = new Map(); 339 apiInfos.forEach((apiInfo: ApiInfo) => { 340 methodInfoMap.set(apiInfo.getDefinedText(), apiInfo); 341 }); 342 return methodInfoMap; 343 } 344 345 /** 346 * 删除新旧版本里所有信息一样的API 347 * 348 * @param oldApiInfos 349 * @param newApiInfos 350 * @returns 351 */ 352 static getDiffSet(oldApiInfos: ApiInfo[], newApiInfos: ApiInfo[]): Map<string, ApiInfo>[] { 353 const oldApiInfoMap: Map<string, ApiInfo> = new Map(); 354 const newApiInfoMap: Map<string, ApiInfo> = new Map(); 355 DiffHelper.setApiInfoMap(oldApiInfoMap, oldApiInfos); 356 DiffHelper.setApiInfoMap(newApiInfoMap, newApiInfos); 357 const oldReduceNewMap: Map<string, ApiInfo> = new Map(); 358 oldApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 359 if (!newApiInfoMap.has(key)) { 360 oldReduceNewMap.set(key, apiInfo); 361 } 362 }); 363 const newReduceOldMap: Map<string, ApiInfo> = new Map(); 364 newApiInfoMap.forEach((apiInfo: ApiInfo, key: string) => { 365 if (!oldApiInfoMap.has(key)) { 366 newReduceOldMap.set(key, apiInfo); 367 } 368 }); 369 return [oldReduceNewMap, newReduceOldMap]; 370 } 371 372 static setApiInfoMap(apiInfoMap: Map<string, ApiInfo>, apiInfos: ApiInfo[]): void { 373 apiInfos.forEach((apiInfo: ApiInfo) => { 374 const key = JSON.stringify(apiInfo); 375 apiInfoMap.set(key, apiInfo); 376 }); 377 } 378 379 static getApiLocations(apiMap: FilesMap, isCheck?: boolean): Map<string, string[]> { 380 const apiLocations: Map<string, string[]> = new Map(); 381 for (const filePath of apiMap.keys()) { 382 const index: number = 0; 383 const fileMap: FileInfoMap = apiMap.get(filePath) as FileInfoMap; 384 DiffHelper.processFileApiMap(fileMap, apiLocations, index, isCheck); 385 } 386 return apiLocations; 387 } 388 389 static processFileApiMap( 390 fileMap: FileInfoMap, 391 apiLocations: Map<string, string[]>, 392 index: number, 393 isCheck?: boolean 394 ): void { 395 for (const apiKey of fileMap.keys()) { 396 if (apiKey === StringConstant.SELF) { 397 continue; 398 } 399 const apiInfoMap: ApiInfosMap = fileMap.get(apiKey) as ApiInfosMap; 400 const apiInfos: BasicApiInfo[] = apiInfoMap.get(StringConstant.SELF) as BasicApiInfo[]; 401 apiInfos.forEach((apiInfo: BasicApiInfo) => { 402 DiffHelper.processApiInfo(apiInfo, apiLocations, index, isCheck); 403 index++; 404 }); 405 } 406 } 407 408 /** 409 * 删除最外层第一个API的jsdocText中,版权头和kit相关jsdoc 410 * 411 * @param apiInfo 412 * @returns 413 */ 414 static deleteUselessJsdoc(apiInfo: BasicApiInfo): string { 415 const jsdocTextArr: string[] = apiInfo.getJsDocText().split('*/'); 416 const clonedJsdocTextArr: string[] = jsdocTextArr; 417 if (clonedJsdocTextArr[1] && StringUtils.hasSubstring(clonedJsdocTextArr[1], CommentHelper.fileTag)) { 418 jsdocTextArr.splice(1, 1); 419 } 420 421 if (clonedJsdocTextArr[0] && StringUtils.hasSubstring(clonedJsdocTextArr[0], CommentHelper.fileTag)) { 422 jsdocTextArr.splice(0, 1); 423 } 424 425 if (clonedJsdocTextArr[0] && StringUtils.hasSubstring(clonedJsdocTextArr[0], CommentHelper.licenseKeyword)) { 426 jsdocTextArr.splice(0, 1); 427 } 428 429 return jsdocTextArr.join('*/'); 430 } 431 432 static processApiInfo( 433 basicApiInfo: BasicApiInfo, 434 apiLocations: Map<string, string[]>, 435 index: number, 436 isCheck?: boolean 437 ): void { 438 const apiNode: ts.Node | undefined = basicApiInfo.getNode(); 439 if (isCheck) { 440 const jsDocText: string | undefined = apiNode?.getFullText().replace(apiNode.getText(), ''); 441 if (jsDocText) { 442 basicApiInfo.setJsDocText(jsDocText); 443 } 444 } 445 446 if (index === 0 && basicApiInfo.getParentApiType() === ApiType.SOURCE_FILE) { 447 const jsDocText: string = DiffHelper.deleteUselessJsdoc(basicApiInfo); 448 basicApiInfo.setJsDocText(jsDocText); 449 } 450 451 basicApiInfo.setSyscap(DiffHelper.getSyscapField(basicApiInfo)); 452 basicApiInfo.setParentApi(undefined); 453 454 basicApiInfo.removeNode(); 455 if (!apiStatisticsType.has(basicApiInfo.getApiType())) { 456 return; 457 } 458 if (basicApiInfo.getApiName() === 'constructor') { 459 return; 460 } 461 const apiInfo: ApiInfo = basicApiInfo as ApiInfo; 462 const locations: string[] = apiInfo.getHierarchicalRelations(); 463 apiLocations.set(locations.toString(), locations); 464 if (!containerApiTypes.has(apiInfo.getApiType())) { 465 return; 466 } 467 const containerApiInfo: ContainerApiInfo = apiInfo as ContainerApiInfo; 468 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 469 DiffHelper.processApiInfo(childApiInfo, apiLocations, 1, isCheck); 470 }); 471 } 472 473 static getSyscapField(apiInfo: BasicApiInfo): string { 474 if (apiInfo.getApiType() === ApiType.SOURCE_FILE) { 475 const sourceFileContent: string = apiInfo.getNode()?.getFullText() as string; 476 let syscap = ''; 477 if (/\@[S|s][Y|y][S|s][C|c][A|a][P|p]\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(sourceFileContent)) { 478 sourceFileContent.replace( 479 /\@[S|s][Y|y][S|s][C|c][A|a][P|p]\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, 480 (sysCapInfo: string, args: []) => { 481 syscap = sysCapInfo.replace(/\@[S|s][Y|y][S|s][C|c][A|a][P|p]/g, '').trim(); 482 return syscap; 483 } 484 ); 485 } 486 return FunctionUtils.handleSyscap(syscap); 487 } 488 if (notJsDocApiTypes.has(apiInfo.getApiType())) { 489 return ''; 490 } 491 const clonedApiInfo: ApiInfo = apiInfo as ApiInfo; 492 const latestJsDocInfo: Comment.JsDocInfo | undefined = clonedApiInfo.getLastJsDocInfo(); 493 if (!latestJsDocInfo) { 494 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 495 } 496 let syscap: string | undefined = latestJsDocInfo?.getSyscap(); 497 if (!syscap) { 498 return DiffHelper.searchSyscapFieldInParent(clonedApiInfo); 499 } 500 if (syscap) { 501 return FunctionUtils.handleSyscap(syscap); 502 } 503 return ''; 504 } 505 506 static searchSyscapFieldInParent(apiInfo: ApiInfo): string { 507 let curApiInfo: ApiInfo = apiInfo; 508 let syscap: string = ''; 509 const node: ts.Node | undefined = curApiInfo.getNode(); 510 while (node && curApiInfo && !ts.isSourceFile(node)) { 511 syscap = curApiInfo.getSyscap(); 512 if (syscap) { 513 return syscap; 514 } 515 curApiInfo = curApiInfo.getParentApi() as ApiInfo; 516 } 517 return syscap; 518 } 519} 520