• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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