• 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    collectAllApiDiffFromPackageMap(newPackageMap, diffReporter);
102  });
103}
104
105function collectAllApiDiffFromPackageMap(newPackageMap, diffReporter) {
106  newPackageMap.forEach((newClassMap, _) => {
107    newClassMap.children.forEach((apisMap, _) => {
108      apisMap.forEach((apis, _) => {
109        diffReporter.addNewApi(apis[0], getSyscap(apis[0]));
110        const diffInfo = formatDiffInfo(apis[0], StatusCode.NEW_API, '', apis[0].getRawText(), '', apis[0].node, getSyscap(apis[0]));
111        diffReporter.addDiffInfo(diffInfo);
112      });
113    });
114  });
115}
116
117/**
118 * 判断是否有相同的 d.ts 声明文件
119 *
120 * @param {Map} oldPackageMap d.ts内部的API集合
121 * @param {string} packageName d.ts与SDK根目录的相对路径
122 * @param {Map} newApiMap 新SDK的所有API集合
123 * @param {Object} ext 扩展参数
124 */
125function collectApiDiffFromPackageMap(oldPackageMap, packageName, newApiMap, ext) {
126  if (!newApiMap.has(packageName)) {
127    // dts文件删除
128    let dtsPath;
129    oldPackageMap.forEach((classNameMap, _) => {
130      collectApiDiffFromPackageMapClass(classNameMap, ext);
131      dtsPath = classNameMap.type.path;
132    });
133    ext.diffReporter.addDeletedPackage(packageName, dtsPath);
134  } else {
135    // 比较相同dts文件
136    const newPackageMap = newApiMap.get(packageName);
137    oldPackageMap.forEach((oldClassMap, className) => {
138      collectApiDiffFromClassMap(oldClassMap, className, newPackageMap, ext);
139    });
140  }
141}
142
143function collectApiDiffFromPackageMapClass(classNameMap, ext) {
144  classNameMap.children.forEach((apisMap, _) => {
145    apisMap.forEach((apis, _) => {
146      ext.diffReporter.addDeletedApi(apis[0], getSyscap(apis[0]));
147      const diffInfo = formatDiffInfo(apis[0], StatusCode.DELETE, apis[0].getRawText(), '',
148        apis[0].node, '', getSyscap(apis[0]));
149      ext.diffReporter.addDiffInfo(diffInfo);
150    });
151  });
152}
153
154function getSyscap(api) {
155  let curApi = api;
156  let syscap = '';
157  if (api.packageName === 'ArkUI') {
158    return 'ArkUI';
159  }
160  while (curApi && !ts.isSourceFile(curApi.node)) {
161    const jsdoc = curApi.jsdoc ? curApi.jsdoc[curApi.jsdoc.length - 1] : [];
162
163    if (!jsdoc) {
164      return syscap;
165    }
166
167    const jsdocTagItem = jsdoc.tags ? jsdoc.tags : [];
168    for (let i = 0; i < jsdocTagItem.length; i++) {
169      const tagInfo = jsdocTagItem[i];
170      if (tagInfo.tag === 'syscap') {
171        syscap = tagInfo.name;
172      }
173    }
174
175    if (syscap) {
176      return syscap;
177    }
178    curApi = curApi.parent;
179  }
180
181  if (ts.isSourceFile(curApi.node)) {
182    const fileContent = curApi.node.getFullText();
183    if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(fileContent)) {
184      fileContent.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => {
185        syscap = sysCapInfo.replace(/\@syscap/g, '').trim();
186      });
187    }
188  }
189  return syscap;
190}
191
192/**
193 * 判断是否有有相同的类型定义(class, interface, enum)
194 *
195 * @param {Object} oldClassApi 旧SDK类型内部的API集合
196 * @param {string} packageName d.ts与SDK根目录的相对路径
197 * @param {string} className 类型名称
198 * @param {Map} newPackageMap 新版本d.ts内部的API集合
199 * @param {Object} ext 扩展参数
200 */
201function collectApiDiffFromClassMap(oldClassApi, className, newPackageMap, ext) {
202  if (!newPackageMap.has(className)) {
203    // 类被删除
204    const typeApi = oldClassApi.type;
205    ext.diffReporter.addChangedApi({
206      api: typeApi,
207      statusCode: StatusCode.DELETE_CLASS,
208      oldMessage: '',
209      newMessage: '',
210      oldNode: '',
211      newNode: '',
212      syscap: getSyscap(oldClassApi.type),
213    });
214    oldClassApi.children.forEach((apisMap, _) => {
215      apisMap.forEach((apis, _) => {
216        ext.diffReporter.addDeletedApi(apis[0], getSyscap(apis[0]));
217        const diffInfo = formatDiffInfo(apis[0], StatusCode.DELETE, apis[0].getRawText(), '',
218          apis[0].node, '', getSyscap(apis[0]));
219        ext.diffReporter.addDiffInfo(diffInfo);
220      });
221    });
222  } else {
223    // 有相同的类
224    const newClassApi = newPackageMap.get(className);
225    collectClassApiDiffs(oldClassApi, newClassApi, ext);
226    oldClassApi.children.forEach((signatureMap, apiName) => {
227      collectApiDiffFromApiNameMap(signatureMap, apiName, newClassApi.children, ext);
228    });
229  }
230}
231
232/**
233 *
234 * @param {Map} oldSignatureMap
235 * @param {string} apiName
236 * @param {Map} newClassMap
237 * @param {Object} ext
238 */
239function collectApiDiffFromApiNameMap(oldSignatureMap, apiName, newClassMap, ext) {
240  if (!newClassMap.has(apiName)) {
241    // 方法被删除
242    oldSignatureMap.forEach((oldApis, _) => {
243      ext.diffReporter.addDeletedApi(oldApis[0], getSyscap(oldApis[0]));
244    });
245  } else {
246    const newSignatureMap = newClassMap.get(apiName);
247    const sameApiNameNumber = oldSignatureMap.size;
248    const sameSignatureSet = new Set();
249    oldSignatureMap.forEach((oldApis, apiSignautre) => {
250      collectApiDiffFromApiSignatureMap(oldApis, apiSignautre, newSignatureMap, ext, sameApiNameNumber, sameSignatureSet);
251    });
252
253    sameSignatureSet.forEach(sameSignature => {
254      oldSignatureMap.delete(sameSignature);
255    });
256
257    oldSignatureMap.forEach((oldApis, _) => {
258      if (newSignatureMap.size === 0) {
259        // 同名函数,方法被删除
260        ext.diffReporter.addDeletedApi(oldApis[0], getSyscap(oldApis[0]));
261      } else {
262        getFunctionDiff(oldApis, newSignatureMap, ext, sameApiNameNumber);
263      }
264    });
265
266  }
267}
268
269function collectClassApiDiffs(oldClassApi, newClassApi, ext) {
270  const oldClassDigestInfo = oldClassApi.type;
271  const newClassDigestInfo = newClassApi.type;
272  if (oldClassDigestInfo && newClassDigestInfo) {
273    if (oldClassDigestInfo.getApiSignature() !== newClassDigestInfo.getApiSignature()) {
274      ext.diffReporter.addChangedApi({
275        api: newClassDigestInfo,
276        statusCode: StatusCode.CLASS_CHANGES,
277        oldMessage: oldClassDigestInfo.getRawText(),
278        newMessage: newClassDigestInfo.getRawText(),
279        hint: '',
280      });
281    } else {
282      collectJSDocDiffs(oldClassDigestInfo, newClassDigestInfo, ext.diffReporter);
283    }
284  }
285}
286
287/**
288 * 判断是否有相同的API签名
289 *
290 * @param {Map} oldApis 旧版本特定类型的所有API集合
291 * @param {string} apiSignautre API 签名
292 * @param {Map} newClassMap 新版本特定类型内部API集合
293 * @param {Object} ext 扩展参数
294 * @param {number} sameApiNameNumber 名字相同的API个数
295 */
296function collectApiDiffFromApiSignatureMap(oldApis, apiSignautre, newClassMap, ext, sameApiNameNumber, sameSignatureSet) {
297  if (newClassMap.has(apiSignautre)) {
298    const newApis = newClassMap.get(apiSignautre);
299    collectJSDocDiffs(oldApis[0], newApis[0], ext.diffReporter);
300    newClassMap.delete(apiSignautre);
301    sameSignatureSet.add(apiSignautre);
302  }
303}
304
305/**
306 * 判断函数有变化的场景
307 *
308 * @param {Array} oldApis 旧版本API的摘要信息
309 * @param {Map} newClassMap 与旧版本API名字相同的新版本API集合
310 * @param {Object} ext
311 * @param {number} sameApiNameNumber 与旧版本API名字相同的新版本API个数
312 */
313function getFunctionDiff(oldApis, newClassMap, ext, sameApiNameNumber) {
314  if (sameApiNameNumber === 1) {
315    newClassMap.forEach((apiDigestInfo, apiSignautre) => {
316      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.FUNCTION_CHANGES, oldApis[0].getRawText(), apiDigestInfo[0].getRawText(),
317        oldApis[0].node, apiDigestInfo[0].node, getSyscap(apiDigestInfo[0]));
318      ext.diffReporter.addChangedApi(diffInfo);
319      ext.diffReporter.addDiffInfo(diffInfo);
320      collectJSDocDiffs(oldApis[0], apiDigestInfo[0], ext.diffReporter);
321      newClassMap.delete(apiSignautre);
322    });
323  } else if (sameApiNameNumber > 1) {
324    let oldApiType = judgeApiType(oldApis[0].getAstNode());
325    let newApiTypeMap = new Map();
326    getEveryNewApiType(newClassMap, newApiTypeMap);
327    if (newApiTypeMap.get(oldApiType) !== undefined && newApiTypeMap.get(oldApiType).size > 1) {
328      ext.diffReporter.addDeletedApi(oldApis[0], getSyscap(oldApis[0]));
329      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.DELETE, oldApis[0].getRawText(), '',
330        oldApis[0].node, '', getSyscap(oldApis[0]));
331      ext.diffReporter.addDiffInfo(diffInfo);
332    } else if (newApiTypeMap.get(oldApiType) !== undefined && newApiTypeMap.get(oldApiType).size === 1) {
333      const oldMessage = oldApis[0].getRawText();
334      const newApi = newClassMap.get(...newApiTypeMap.get(oldApiType))[0];
335      const newMessage = newApi.getRawText();
336      const newNode = newApi.node;
337      const syscap = getSyscap(newClassMap.get(...newApiTypeMap.get(oldApiType))[0]);
338      const diffInfo = formatDiffInfo(oldApis[0], StatusCode.FUNCTION_CHANGES, oldMessage, newMessage, oldApis[0].node, newNode, syscap);
339      ext.diffReporter.addChangedApi(diffInfo);
340      ext.diffReporter.addDiffInfo(diffInfo);
341      collectJSDocDiffs(oldApis[0], newApi, ext.diffReporter);
342      newClassMap.delete(...newApiTypeMap.get(oldApiType));
343    }
344  }
345}
346
347function formatDiffInfo(api, statusCode, oldMessage, newMessage, oldNode, newNode, syscap) {
348  return {
349    api: api,
350    statusCode: statusCode,
351    oldMessage: oldMessage,
352    newMessage: newMessage,
353    hint: '',
354    oldNode: oldNode,
355    newNode: newNode,
356    syscap: syscap,
357  };
358}
359
360/**
361 *
362 * @param {Map} newClassMap 与旧版本API名字相同的新版本API集合
363 * @param {Map} newApiTypeMap 用于存放每一个新版本API的类型和API签名
364 *                   |
365 *     key:API类型,value:同种类型的API签名所构成的Set集合
366 */
367function getEveryNewApiType(newClassMap, newApiTypeMap) {
368  const callbackApiSignautreSet = new Set();
369  const promiseApiSignautreSet = new Set();
370  const otherTypeApiSignautreSet = new Set();
371
372  newClassMap.forEach((apiDigestInfo, apiSignautre) => {
373    let apiType = judgeApiType(apiDigestInfo[0].getAstNode());
374    if (apiType === 'callback') {
375      callbackApiSignautreSet.add(apiSignautre);
376      newApiTypeMap.set(apiType, callbackApiSignautreSet);
377    } else if (apiType === 'Promise') {
378      promiseApiSignautreSet.add(apiSignautre);
379      newApiTypeMap.set(apiType, promiseApiSignautreSet);
380    } else if (apiType === 'other') {
381      otherTypeApiSignautreSet.add(apiSignautre);
382      newApiTypeMap.set(apiType, otherTypeApiSignautreSet);
383    }
384  });
385}
386
387/**
388 * 判断旧API的类型(Promise/callback/other389 *
390 * @param {ts.node} node
391 * @returns {string}
392 */
393function judgeApiType(node) {
394  const parameters = node.parameters ? node.parameters : [];
395  let apiType = 'other';
396  parameters.forEach(param => {
397    if (param.type && param.type.typeName && param.type.typeName.escapedText === 'AsyncCallback') {
398      apiType = 'callback';
399    }
400  });
401  if (node.type && node.type.typeName && node.type.typeName.escapedText === 'Promise') {
402    apiType = 'Promise';
403  }
404  return apiType;
405}
406
407/**
408 * API完全相同,比对JSDoc修改。
409 *
410 * @param {ApiDigestInfo} oldApi 旧API
411 * @param {ApiDigestInfo} newApi 新API
412 * @param {DiffReporter} diffReporter 差异报告
413 */
414function collectJSDocDiffs(oldApi, newApi, diffReporter) {
415  JSDocDiffer.collectJSDocDiffs(oldApi, newApi, diffReporter);
416}
417
418/**
419 * 比对两个SDK目录。
420 *
421 * @param {string} oldDir 旧SDK目录
422 * @param {string} newDir 新SDK目录
423 * @param {string} oldVersion 旧SDK版本号
424 * @param {string} newVersion 新SDK版本号
425 * @returns {Map}
426 */
427async function compareSdks(oldDir, newDir) {
428  const oldFiles = listApiDeclarationFiles(oldDir);
429  const newFiles = listApiDeclarationFiles(newDir);
430  const diffApis = getApiDiffs(newFiles, oldFiles, oldDir, newDir);
431  return diffApis;
432}
433
434exports.ApiDiffer = {
435  compareSdks: compareSdks,
436};