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 { 17 ApiInfo, 18 ApiType, 19 BasicApiInfo, 20 ContainerApiInfo, 21 MethodInfo, 22 PropertyInfo, 23 containerApiTypes, 24 notJsDocApiTypes, 25} from '../../typedef/parser/ApiInfoDefination'; 26import { 27 ApiStatisticsInfo, 28 apiNotStatisticsType, 29 apiStatisticsType, 30 mergeDefinedTextType, 31 notMergeDefinedText, 32 StatisticsInfoValueType, 33} from '../../typedef/statistics/ApiStatistics'; 34import { Comment } from '../../typedef/parser/Comment'; 35import { StringConstant, NumberConstant } from '../../utils/Constant'; 36import { ApiInfosMap, FileInfoMap, FilesMap, BasicApiInfoMap } from '../parser/parser'; 37import { LogUtil } from '../../utils/logUtil'; 38import ts from 'typescript'; 39 40export class ApiStatisticsHelper { 41 /** 42 * 获取需要收集的api信息 43 * 44 * @param { FilesMap } apiMap 根据接口定义文件处理得到的map结果 45 * @returns { ApiStatisticsInfo[] } 返回需要统计的api列表 46 */ 47 static getApiStatisticsInfos(apiMap: FilesMap): StatisticsInfoValueType { 48 const apiStatisticsInfos: ApiStatisticsInfo[] = []; 49 const allApiStatisticsInfos: ApiStatisticsInfo[] = []; 50 // map的第一层key均为文件路径名 51 for (const filePath of apiMap.keys()) { 52 const fileMap: FileInfoMap | undefined = apiMap.get(filePath); 53 if (!fileMap) { 54 continue; 55 } 56 ApiStatisticsHelper.processFileApiMap(fileMap, apiStatisticsInfos, allApiStatisticsInfos); 57 } 58 return { apiStatisticsInfos: apiStatisticsInfos, allApiStatisticsInfos: allApiStatisticsInfos }; 59 } 60 61 /** 62 * 得到SourceFile节点下的第一层节点解析后的对象,方便后续以此为基础,得到整个文件所有的节点信息 63 * 64 * @param { FileInfoMap } fileMap 获取到的一个文件解析到的map对象 65 * @param { ApiStatisticsInfo[] } apiStatisticsInfos SourceFile节点下的第一层节点解析后的对象 66 */ 67 static processFileApiMap( 68 fileMap: FileInfoMap, 69 apiStatisticsInfos: ApiStatisticsInfo[], 70 allApiStatisticsInfos: ApiStatisticsInfo[] 71 ): void { 72 for (const apiKey of fileMap.keys()) { 73 if (apiKey === StringConstant.SELF) { 74 continue; 75 } 76 const apiInfoMap: ApiInfosMap | BasicApiInfo[] | undefined = fileMap.get(apiKey); 77 if (!apiInfoMap || Array.isArray(apiInfoMap)) { 78 continue; 79 } 80 const apiInfos: BasicApiInfo[] | BasicApiInfoMap | undefined = apiInfoMap.get(StringConstant.SELF); 81 const sameNameRawTextMap: Map<string, string> = new Map(); 82 if (!apiInfos || !Array.isArray(apiInfos)) { 83 continue; 84 } 85 ApiStatisticsHelper.connectDefinedText(apiInfos, sameNameRawTextMap); 86 const apiRelations: Set<string> = new Set(); 87 apiInfos.forEach((apiInfo: BasicApiInfo) => { 88 ApiStatisticsHelper.processApiInfo( 89 apiInfo, 90 apiStatisticsInfos, 91 sameNameRawTextMap, 92 allApiStatisticsInfos, 93 apiRelations 94 ); 95 }); 96 } 97 } 98 99 /** 100 * 遍历一个文件内的所有API,将同名函数的API声明合并 101 * 102 * @param { BasicApiInfo[] } apiInfos 存放API信息的数组 103 * @param { Map<string, string> } sameNameRawTextMap 存放处理后的同名函数声明 104 */ 105 static connectDefinedText(apiInfos: BasicApiInfo[], sameNameRawTextMap: Map<string, string>): void { 106 apiInfos.forEach((apiInfo: BasicApiInfo) => { 107 if (apiInfo.getApiType() === ApiType.METHOD) { 108 if (notMergeDefinedText.has(apiInfo.getApiName())) { 109 return; 110 } 111 const currentApiText: string | undefined = sameNameRawTextMap.get(ApiStatisticsHelper.joinRelations(apiInfo)); 112 if (currentApiText) { 113 const definedText: string = `${currentApiText}\n${apiInfo.getDefinedText()}`; 114 sameNameRawTextMap.set(ApiStatisticsHelper.joinRelations(apiInfo), definedText); 115 return; 116 } else { 117 sameNameRawTextMap.set(ApiStatisticsHelper.joinRelations(apiInfo), apiInfo.getDefinedText()); 118 } 119 } 120 121 if (containerApiTypes.has(apiInfo.getApiType())) { 122 const containerApiInfo: ContainerApiInfo = apiInfo as ContainerApiInfo; 123 ApiStatisticsHelper.connectDefinedText(containerApiInfo.getChildApis(), sameNameRawTextMap); 124 } 125 }); 126 } 127 128 /** 129 * 处理API层级关系 130 * 131 * @param { BasicApiInfo } childApiInfo API信息 132 * @returns 133 */ 134 static joinRelations(childApiInfo: BasicApiInfo): string { 135 const relations = childApiInfo.getHierarchicalRelations().join(); 136 return relations; 137 } 138 139 /** 140 * 根据传入的api信息,转化为需要统计的api信息对象并统计 141 * 142 * @param { BasicApiInfo } basicApiInfo 解析后的api对象 143 * @param apiStatisticsInfos api统计列表对象 144 */ 145 static processApiInfo( 146 basicApiInfo: BasicApiInfo, 147 apiStatisticsInfos: ApiStatisticsInfo[], 148 sameNameRawTextMap: Map<string, string>, 149 allApiStatisticsInfos: ApiStatisticsInfo[], 150 apiRelations: Set<string> 151 ): void { 152 const apiInfo: ApiInfo = basicApiInfo as ApiInfo; 153 allApiStatisticsInfos.push(ApiStatisticsHelper.initApiStatisticsInfo(apiInfo, sameNameRawTextMap, true)); 154 155 if (!apiStatisticsType.has(basicApiInfo.getApiType())) { 156 return; 157 } 158 if (basicApiInfo.getApiName() === StringConstant.CONSTRUCTOR_API_NAME) { 159 return; 160 } 161 const apiStatisticsInfo: ApiStatisticsInfo = ApiStatisticsHelper.initApiStatisticsInfo(apiInfo, sameNameRawTextMap); 162 if ( 163 !apiNotStatisticsType.has(apiStatisticsInfo.getApiType()) && 164 !apiRelations.has(ApiStatisticsHelper.joinRelations(basicApiInfo)) 165 ) { 166 apiStatisticsInfos.push(apiStatisticsInfo); 167 } 168 apiRelations.add(ApiStatisticsHelper.joinRelations(basicApiInfo)); 169 if (!containerApiTypes.has(apiInfo.getApiType())) { 170 return; 171 } 172 const containerApiInfo: ContainerApiInfo = apiInfo as ContainerApiInfo; 173 containerApiInfo.getChildApis().forEach((childApiInfo: BasicApiInfo) => { 174 ApiStatisticsHelper.processApiInfo( 175 childApiInfo, 176 apiStatisticsInfos, 177 sameNameRawTextMap, 178 allApiStatisticsInfos, 179 apiRelations 180 ); 181 }); 182 } 183 184 static initApiStatisticsInfo( 185 apiInfo: ApiInfo, 186 sameNameRawTextMap: Map<string, string>, 187 isAll?: boolean 188 ): ApiStatisticsInfo { 189 const apiStatisticsInfo: ApiStatisticsInfo = new ApiStatisticsInfo(); 190 const relations = apiInfo.getHierarchicalRelations(); 191 if (relations.length > NumberConstant.RELATION_LENGTH) { 192 apiStatisticsInfo.setParentModuleName(relations[relations.length - NumberConstant.RELATION_LENGTH]); 193 } 194 const apiRelations: string = ApiStatisticsHelper.joinRelations(apiInfo); 195 const sameNameDefinedText: string | undefined = sameNameRawTextMap.get(apiRelations); 196 197 if (isAll) { 198 apiStatisticsInfo.setDefinedText(apiInfo.getDefinedText()); 199 } else { 200 apiStatisticsInfo.setDefinedText(sameNameDefinedText ? sameNameDefinedText : apiInfo.getDefinedText()); 201 } 202 let isOptional: boolean = false; 203 if (apiInfo.getApiType() === ApiType.METHOD) { 204 const methodInfo: MethodInfo = apiInfo as MethodInfo; 205 isOptional = !methodInfo.getIsRequired(); 206 } else if (apiInfo.getApiType() === ApiType.PROPERTY) { 207 const propertyInfo: PropertyInfo = apiInfo as PropertyInfo; 208 isOptional = !propertyInfo.getIsRequired(); 209 } 210 apiStatisticsInfo 211 .setFilePath(apiInfo.getFilePath()) 212 .setApiType(apiInfo.getApiType()) 213 .setApiName(apiInfo.getApiName()) 214 .setPos(apiInfo.getPos()) 215 .setHierarchicalRelations(relations.join('/')) 216 .setDecorators(apiInfo.getDecorators()) 217 .setParentApiType(apiInfo.getParentApiType()) 218 .setIsOptional(isOptional); 219 if (notJsDocApiTypes.has(apiInfo.getApiType())) { 220 return apiStatisticsInfo; 221 } 222 const firstJsDocInfo: Comment.JsDocInfo | undefined = apiInfo.getJsDocInfos()[0]; 223 if (firstJsDocInfo) { 224 apiStatisticsInfo.setSince(firstJsDocInfo.getSince()); 225 } 226 const jsDocInfo: Comment.JsDocInfo | undefined = apiInfo.getLastJsDocInfo(); 227 if (jsDocInfo) { 228 apiStatisticsInfo 229 .setSyscap(jsDocInfo.getSyscap() ? jsDocInfo.getSyscap() : ApiStatisticsHelper.extendSyscap(apiInfo)) 230 .setPermission(jsDocInfo.getPermission()) 231 .setIsForm(jsDocInfo.getIsForm()) 232 .setIsCrossPlatForm(jsDocInfo.getIsCrossPlatForm()) 233 .setDeprecatedVersion( 234 jsDocInfo.getDeprecatedVersion() === NumberConstant.DEFAULT_DEPRECATED_VERSION ? 235 '' : 236 jsDocInfo.getDeprecatedVersion() 237 ) 238 .setUseInstead(jsDocInfo.getUseinstead()) 239 .setApiLevel(jsDocInfo.getIsSystemApi()) 240 .setModelLimitation(jsDocInfo.getModelLimitation()) 241 .setIsAutomicService(jsDocInfo.getIsAtomicService()) 242 .setErrorCodes(jsDocInfo.getErrorCode()) 243 .setKitInfo(jsDocInfo.getKit()); 244 } else { 245 apiStatisticsInfo.setSyscap(ApiStatisticsHelper.extendSyscap(apiInfo)); 246 } 247 return apiStatisticsInfo; 248 } 249 250 /** 251 * 未标注@syscap的API,从父级API寻找syscap 252 * 253 * @param {ApiInfo} apiInfo API信息 254 * @returns syscap 255 */ 256 static extendSyscap(apiInfo: ApiInfo): string { 257 let curApiInfo: ApiInfo = apiInfo; 258 let syscap: string = ''; 259 const node: ts.Node | undefined = curApiInfo.getNode(); 260 try { 261 while (node && curApiInfo && !ts.isSourceFile(node)) { 262 const jsdoc: Comment.JsDocInfo | undefined = curApiInfo.getLastJsDocInfo(); 263 if (jsdoc) { 264 syscap = jsdoc.getSyscap(); 265 } 266 if (syscap) { 267 return syscap; 268 } 269 curApiInfo = curApiInfo.getParentApi() as ApiInfo; 270 } 271 } catch (error) { 272 LogUtil.e('SYSCAP ERROR', error); 273 } 274 return syscap; 275 } 276 277 /** 278 * 获取api的总数 279 * 280 * @param { ApiStatisticsInfo[] } apiStatisticsInfos api统计列表对象 281 * @returns { number } api的数量 282 */ 283 static getApiNumber(apiStatisticsInfos: ApiStatisticsInfo[]): number { 284 const apis: Set<string> = new Set(); 285 apiStatisticsInfos.forEach((apiStatisticsInfo: ApiStatisticsInfo) => { 286 apis.add(apiStatisticsInfo.getHierarchicalRelations()); 287 }); 288 return apis.size; 289 } 290} 291