• 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
16const ts = require('typescript');
17const { listApiDeclarationFiles } = require('./util');
18const { StatusCode, DiffReporter } = require('./reporter');
19const { ApiCollector } = require('./api_collector');
20const { ApiDigestInfo } = require('./api_data');
21const { JSDocDiffer } = require('./jsdoc_diff');
22
23/**
24 * 循环搜集API信息
25 *
26 * @param {string[]} files
27 * @param {string} rootDir
28 * @returns {Map} Map
29 */
30function collectApiDigestInfos(files, rootDir) {
31  const apiMap = new Map();
32  files.forEach(filePath => {
33    ApiCollector.collectApi(filePath, rootDir, apiMap);
34  });
35  return apiMap;
36}
37
38/**
39 * 解析API,对比新旧版本的SDK集合筛选出变化。
40 *
41 * @param {string[]} newFiles 新版本API文件列表
42 * @param {string[]} oldFiles 旧版本API文件列表
43 * @param {string} oldDir 旧版本SDK根目录
44 * @param {string} newDir 新版本SDK根目录
45 * @returns {Map}
46 */
47function getApiDiffs(newFiles, oldFiles, oldDir, newDir) {
48  const newApiInfos = collectApiDigestInfos(newFiles, newDir);
49  const oldApiInfos = collectApiDigestInfos(oldFiles, oldDir);
50  const diffReporter = new DiffReporter();
51  collectAllApiDiffs(newApiInfos, oldApiInfos, diffReporter, oldDir, newDir);
52  return diffReporter.getResults();
53}
54
55function exportDiffInfo(newFiles, oldFiles, newDir, oldDir) {
56  const newApiInfos = collectApiDigestInfos(newFiles, newDir);
57  const oldApiInfos = collectApiDigestInfos(oldFiles, oldDir);
58  const diffReporter = new DiffReporter();
59  collectAllApiDiffs(newApiInfos, oldApiInfos, diffReporter, oldDir, newDir);
60  return diffReporter.getDiffInfos();
61}
62
63exports.exportDiffInfo = exportDiffInfo;
64
65/**
66 * 比对API摘要信息获取差异。
67 * @example API集合的数据结构如下:
68 * { packageName : Map }
69 *                  |
70 *      { className : { type: ApiDigestInfo, children: Map } }
71 *                                                      |
72 *                                             { apiName : Map }
73 *                                                          |
74 *                                                { signature : ApiDigestInfo[] }
75 *
76 *  针对上述数据结构, 比对的策略如下:
77 *  优先遍历旧SDK API, 因为相对新版本SDK其API数量较少。
78 *  1. 比对 packageName, 新SDK中没有则可能为删除或调整目录结构
79 *  2. 比对 className, 新SDK中没有则可能为删除或结构调整
80 *  3. 比对 apiSignature, 新SDK中没有则可能为删除或者调整
81 *  4. 上述路径完全匹配时,则API定义没有变动,再比JSDoc修改
82 *  5. 第 4 步结束后从新SDK集合中删除, 最后新SDK集合中剩余的即新增的API
83 *  以上策略需要进一步细化, 尽可能推断出API的具体变化细节。
84 *
85 * @param {Map} newApiMap 新SDK API集合
86 * @param {Map} oldApiMap 旧SDK API集合
87 * @param {DiffReporter} diffReporter 差异报告
88 * @param {string} oldDir 旧SDK根目录
89 * @param {string} newDir 新SDK根目录
90 */
91function collectAllApiDiffs(newApiMap, oldApiMap, diffReporter, oldDir, newDir) {
92  // 遍历旧sdk
93  oldApiMap.forEach((oldPackageMap, packageName) => {
94    collectApiDiffFromPackageMap(oldPackageMap, packageName, newApiMap, {
95      diffReporter: diffReporter,
96      oldDir: oldDir,
97      newDir: newDir,
98    });
99  });
100  newApiMap.forEach((newPackageMap, _) => {
101    newPackageMap.forEach((newClassMap, _) => {
102      newClassMap.children.forEach((apisMap, _) => {
103        apisMap.forEach((apis, _) => {
104          diffReporter.addNewApi(apis[0], getSycap(apis[0]));
105          const diffInfo = formatDiffInfo(apis[0], StatusCode.NEW_API, '', apis[0].getRawText(), '', apis[0].node, getSycap(apis[0]));
106          diffReporter.addDiffInfo(diffInfo);
107        });
108      });
109    });
110  });
111}
112
113/**
114 * 判断是否有相同的 d.ts 声明文件
115 *
116 * @param {Map} oldPackageMap d.ts内部的API集合
117 * @param {string} packageName d.ts与SDK根目录的相对路径
118 * @param {Map} newApiMap 新SDK的所有API集合
119 * @param {Object} ext 扩展参数
120 */
121function collectApiDiffFromPackageMap(oldPackageMap, packageName, newApiMap, ext) {
122  if (!newApiMap.has(packageName)) {
123    // dts文件删除
124    let dtsPath;
125    oldPackageMap.forEach((classNameMap, _) => {
126      classNameMap.children.forEach((apisMap, _) => {
127        apisMap.forEach((apis, _) => {
128          ext.diffReporter.addDeletedApi(apis[0], getSycap(apis[0]));
129          const diffInfo = formatDiffInfo(apis[0], StatusCode.DELETE, apis[0].getRawText(), '',
130            apis[0].node, '', getSycap(apis[0]));
131          ext.diffReporter.addDiffInfo(diffInfo);
132        });
133      });
134      dtsPath = classNameMap.type.path;
135    });
136    ext.diffReporter.addDeletedPackage(packageName, dtsPath);
137  } else {
138    // 比较相同dts文件
139    const newPackageMap = newApiMap.get(packageName);
140    oldPackageMap.forEach((oldClassMap, className) => {
141      collectApiDiffFromClassMap(oldClassMap, className, newPackageMap, ext);
142    });
143  }
144}
145
146function getSycap(api) {
147  let curApi = api;
148  let syscap = '';
149  if (api.packageName === 'ArkUI') {
150    return 'ArkUI';
151  }
152  while (curApi && !ts.isSourceFile(curApi.node)) {
153    const jsdoc = curApi.jsdoc ? curApi.jsdoc[curApi.jsdoc.length - 1] : [];
154
155    if (!jsdoc) {
156      return syscap;
157    }
158
159    const jsdocTagItem = jsdoc.tags ? jsdoc.tags : [];
160    for (let i = 0; i < jsdocTagItem.length; i++) {
161      const tagInfo = jsdocTagItem[i];
162      if (tagInfo.tag === 'syscap') {
163        syscap = tagInfo.name;
164      }
165    }
166
167    if (syscap) {
168      return syscap;
169    }
170    curApi = curApi.parent;
171  }
172
173  if (ts.isSourceFile(curApi.node)) {
174    const fileContent = curApi.node.getFullText();
175    if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(fileContent)) {
176      fileContent.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => {
177        syscap = sysCapInfo.replace(/\@syscap/g, '').trim();
178      })
179    }
180    return syscap;
181  }
182}
183
184/**
185 * 判断是否有有相同的类型定义(class, interface, enum)
186 *
187 * @param {Object} oldClassApi 旧SDK类型内部的API集合
188 * @param {string} packageName d.ts与SDK根目录的相对路径
189 * @param {string} className 类型名称
190 * @param {Map} newPackageMap 新版本d.ts内部的API集合
191 * @param {Object} ext 扩展参数
192 */
193function collectApiDiffFromClassMap(oldClassApi, className, newPackageMap, ext) {
194  if (!newPackageMap.has(className)) {
195    // 类被删除
196    const typeApi = oldClassApi.type;
197    ext.diffReporter.addChangedApi({
198      api: typeApi,
199      statusCode: StatusCode.DELETE_CLASS,
200      oldMessage: '',
201      newMessage: '',
202      oldNode: '',
203      newNode: '',
204      syscap: getSycap(oldClassApi.type),
205    });
206    oldClassApi.children.forEach((apisMap, _) => {
207      apisMap.forEach((apis, _) => {
208        ext.diffReporter.addDeletedApi(apis[0], getSycap(apis[0]));
209        const diffInfo = formatDiffInfo(apis[0], StatusCode.DELETE, apis[0].getRawText(), '',
210          apis[0].node, '', getSycap(apis[0]));
211        ext.diffReporter.addDiffInfo(diffInfo);
212      });
213    });
214  } else {
215    // 有相同的类
216    const newClassApi = newPackageMap.get(className);
217    collectClassApiDiffs(oldClassApi, newClassApi, ext);
218    oldClassApi.children.forEach((signatureMap, apiName) => {
219      collectApiDiffFromApiNameMap(signatureMap, apiName, newClassApi.children, ext);
220    });
221  }
222}
223
224/**
225 *
226 * @param {Map} oldSignatureMap
227 * @param {string} apiName
228 * @param {Map} newClassMap
229 * @param {Object} ext
230 */
231function collectApiDiffFromApiNameMap(oldSignatureMap, apiName, newClassMap, ext) {
232  if (!newClassMap.has(apiName)) {
233    // 方法被删除
234    oldSignatureMap.forEach((oldApis, _) => {
235      ext.diffReporter.addDeletedApi(oldApis[0], getSycap(oldApis[0]));
236    });
237  } else {
238    const newSignatureMap = newClassMap.get(apiName);
239    const sameApiNameNumber = oldSignatureMap.size;
240    oldSignatureMap.forEach((oldApis, apiSignautre) => {
241      collectApiDiffFromApiSignatureMap(oldApis, apiSignautre, newSignatureMap, ext, sameApiNameNumber);
242    });
243  }
244}
245
246function collectClassApiDiffs(oldClassApi, newClassApi, ext) {
247  const oldClassDigestInfo = oldClassApi.type;
248  const newClassDigestInfo = newClassApi.type;
249  if (oldClassDigestInfo && newClassDigestInfo) {
250    if (oldClassDigestInfo.getApiSignature() !== newClassDigestInfo.getApiSignature()) {
251      ext.diffReporter.addChangedApi({
252        api: newClassDigestInfo,
253        statusCode: StatusCode.CLASS_CHANGES,
254        oldMessage: oldClassDigestInfo.getRawText(),
255        newMessage: newClassDigestInfo.getRawText(),
256        hint: '',
257      });
258    } else {
259      collectJSDocDiffs(oldClassDigestInfo, newClassDigestInfo, ext.diffReporter);
260    }
261  }
262}
263
264/**
265 * 判断是否有相同的API签名
266 *
267 * @param {Map} oldApis 旧版本特定类型的所有API集合
268 * @param {string} apiSignautre API 签名
269 * @param {Map} newClassMap 新版本特定类型内部API集合
270 * @param {Object} ext 扩展参数
271 * @param {number} sameApiNameNumber 名字相同的API个数
272 */
273function collectApiDiffFromApiSignatureMap(oldApis, apiSignautre, newClassMap, ext, sameApiNameNumber) {
274  if (newClassMap.has(apiSignautre)) {
275    const newApis = newClassMap.get(apiSignautre);
276    collectJSDocDiffs(oldApis[0], newApis[0], ext.diffReporter);
277    newClassMap.delete(apiSignautre);
278  } else {
279    getFunctionDiff(oldApis, newClassMap, ext, sameApiNameNumber);
280  }
281}
282
283/**
284 * 判断函数有变化的场景
285 *
286 * @param {Array} oldApis 旧版本API的摘要信息
287 * @param {Map} newClassMap 与旧版本API名字相同的新版本API集合
288 * @param {Object} ext
289 * @param {number} sameApiNameNumber 与旧版本API名字相同的新版本API个数
290 */
291function getFunctionDiff(oldApis, newClassMap, ext, sameApiNameNumber) {
292  if (sameApiNameNumber === 1) {
293    newClassMap.forEach((apiDigestInfo, apiSignautre) => {
294      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.FUNCTION_CHANGES, oldApis[0].getRawText(), apiDigestInfo[0].getRawText(),
295        oldApis[0].node, apiDigestInfo[0].node, getSycap(apiDigestInfo[0]));
296      ext.diffReporter.addChangedApi(diffInfo);
297      ext.diffReporter.addDiffInfo(diffInfo);
298      collectJSDocDiffs(oldApis[0], apiDigestInfo[0], ext.diffReporter);
299      newClassMap.delete(apiSignautre);
300    });
301  } else if (sameApiNameNumber > 1) {
302    let oldApiType = judgeApiType(oldApis[0].getAstNode());
303    let newApiTypeMap = new Map();
304    getEveryNewApiType(newClassMap, newApiTypeMap);
305    if (newApiTypeMap.get(oldApiType) !== undefined && newApiTypeMap.get(oldApiType).size > 1) {
306      ext.diffReporter.addDeletedApi(oldApis[0], getSycap(oldApis[0]));
307      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.DELETE, oldApis[0].getRawText(), '',
308        oldApis[0].node, '', getSycap(oldApis[0]));
309      ext.diffReporter.addDiffInfo(diffInfo);
310    } else if (newApiTypeMap.get(oldApiType) !== undefined && newApiTypeMap.get(oldApiType).size === 1) {
311      const oldMessage = oldApis[0].getRawText();
312      const newApi = newClassMap.get(...newApiTypeMap.get(oldApiType))[0];
313      const newMessage = newApi.getRawText();
314      const newNode = newApi.node;
315      const syscap = getSycap(newClassMap.get(...newApiTypeMap.get(oldApiType))[0]);
316      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.FUNCTION_CHANGES, oldMessage, newMessage, oldApis[0].node, newNode, syscap);
317      ext.diffReporter.addChangedApi(diffInfo);
318      ext.diffReporter.addDiffInfo(diffInfo);
319      collectJSDocDiffs(oldApis[0], newApi, ext.diffReporter);
320      newClassMap.delete(...newApiTypeMap.get(oldApiType));
321    }
322  }
323}
324
325function formatDiffInfo(api, statusCode, oldMessage, newMessage, oldNode, newNode, syscap) {
326  return {
327    api: api,
328    statusCode: statusCode,
329    oldMessage: oldMessage,
330    newMessage: newMessage,
331    hint: '',
332    oldNode: oldNode,
333    newNode: newNode,
334    syscap: syscap,
335  };
336}
337
338/**
339 *
340 * @param {Map} newClassMap 与旧版本API名字相同的新版本API集合
341 * @param {Map} newApiTypeMap 用于存放每一个新版本API的类型和API签名
342 *                   |
343 *     key:API类型,value:同种类型的API签名所构成的Set集合
344 */
345function getEveryNewApiType(newClassMap, newApiTypeMap) {
346  const callbackApiSignautreSet = new Set();
347  const promiseApiSignautreSet = new Set();
348  const otherTypeApiSignautreSet = new Set();
349
350  newClassMap.forEach((apiDigestInfo, apiSignautre) => {
351    let apiType = judgeApiType(apiDigestInfo[0].getAstNode());
352    if (apiType === 'callback') {
353      callbackApiSignautreSet.add(apiSignautre);
354      newApiTypeMap.set(apiType, callbackApiSignautreSet);
355    } else if (apiType === 'Promise') {
356      promiseApiSignautreSet.add(apiSignautre);
357      newApiTypeMap.set(apiType, promiseApiSignautreSet);
358    } else if (apiType === 'other') {
359      otherTypeApiSignautreSet.add(apiSignautre);
360      newApiTypeMap.set(apiType, otherTypeApiSignautreSet);
361    }
362  });
363}
364
365/**
366 * 判断旧API的类型(Promise/callback/other367 *
368 * @param {ts.node} node
369 * @returns {string}
370 */
371function judgeApiType(node) {
372  const parameters = node.parameters ? node.parameters : [];
373  let apiType = 'other';
374  parameters.forEach(param => {
375    if (param.type && param.type.typeName && param.type.typeName.escapedText === 'AsyncCallback') {
376      apiType = 'callback';
377    }
378  });
379  if (node.type && node.type.typeName && node.type.typeName.escapedText === 'Promise') {
380    apiType = 'Promise';
381  }
382  return apiType;
383}
384
385/**
386 * API完全相同,比对JSDoc修改。
387 *
388 * @param {ApiDigestInfo} oldApi 旧API
389 * @param {ApiDigestInfo} newApi 新API
390 * @param {DiffReporter} diffReporter 差异报告
391 */
392function collectJSDocDiffs(oldApi, newApi, diffReporter) {
393  JSDocDiffer.collectJSDocDiffs(oldApi, newApi, diffReporter);
394}
395
396/**
397 * 比对两个SDK目录。
398 *
399 * @param {string} oldDir 旧SDK目录
400 * @param {string} newDir 新SDK目录
401 * @param {string} oldVersion 旧SDK版本号
402 * @param {string} newVersion 新SDK版本号
403 * @returns {Map}
404 */
405async function compareSdks(oldDir, newDir) {
406  const oldFiles = listApiDeclarationFiles(oldDir);
407  const newFiles = listApiDeclarationFiles(newDir);
408  const diffApis = getApiDiffs(newFiles, oldFiles, oldDir, newDir);
409  return diffApis;
410}
411
412exports.ApiDiffer = {
413  compareSdks: compareSdks,
414};