• 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 _ 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