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