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