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/other) 389 * 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};