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 https = require('https'); 17const { StatusCode, StatusMessages } = require('./reporter'); 18 19async function mergeDataBetweenVersion(oldVersion, newVersion, allVersionUrl) { 20 const oldVersionNumber = oldVersion.replace(/\./g, ''); 21 const newVersionNumber = newVersion.replace(/\./g, ''); 22 const url = allVersionUrl.replace('allVersion.json', ''); 23 let versionArr = []; 24 await downloadFiles(allVersionUrl).then((versionNumbers) => { 25 versionArr = JSON.parse(versionNumbers); 26 }).catch(err => { 27 console.log('ERROR CODE:', err.code); 28 }); 29 30 if (versionArr.length === 0) { 31 return []; 32 } 33 34 for (let i = 0; i < versionArr.length; i++) { 35 const version = versionArr[i].replace(/\./g, ''); 36 if (version < oldVersionNumber || version > newVersionNumber) { 37 versionArr.splice(i, 1); 38 i--; 39 } 40 } 41 let orderVersionArr = versionArr.sort((a, b) => { 42 return a.replace(/\.|.json/g, '') - b.replace(/\.|.json/g, ''); 43 }); 44 return formatExcelData(orderVersionArr, url); 45} 46 47function downloadFiles(allVersionUrl) { 48 return new Promise((resolve, reject) => { 49 getVersionsData(allVersionUrl, (versions) => { 50 if (versions) { 51 resolve(versions); 52 } else { 53 reject({ code: -1 }); 54 } 55 }); 56 }); 57} 58 59function getLink(url, fileName) { 60 return `${url}${fileName}.json`; 61} 62 63function getVersionsData(versionUrl, callback) { 64 let json = ''; 65 const SUCCESS_CODE = 200; 66 const request = https.get(versionUrl, { timeout: 2000 }, function (res) { 67 if (res.statusCode !== SUCCESS_CODE) { 68 return; 69 } 70 res.on('data', function (d) { 71 json += d; 72 }); 73 }).on('error', function (e) { 74 console.log('ERROR:', e.message); 75 }).on('close', () => { 76 callback(json); 77 }).on('timeout', () => { 78 request.destroy(); 79 }); 80} 81 82/** 83 * 将最老版本changelog中函数有变化的数据作为初始数据,去跟其他版本的changelog数据进行合并。 84 * @param {Array} orderVersionArr 85 * @param {String} url 86 * @returns {Array} 87 */ 88async function formatExcelData(orderVersionArr, url) { 89 let allMergeData = []; 90 const versionUrl = getLink(url, orderVersionArr[0]); 91 let oldestVersionData = []; 92 const MAX_LENGTH = 2; 93 await downloadFiles(versionUrl).then(versionData => { 94 oldestVersionData = JSON.parse(versionData); 95 }).catch(err => { 96 console.log('ERROR CODE:', err.code); 97 }); 98 99 if (orderVersionArr.length < MAX_LENGTH) { 100 return allMergeData; 101 } 102 103 oldestVersionData.forEach(oldestData => { 104 if (oldestData.newApi !== oldestData.oldApi) { 105 oldestData.version = orderVersionArr[0]; 106 allMergeData.push(oldestData); 107 } 108 }); 109 await mergeAllData(orderVersionArr, url, allMergeData); 110 return allMergeData; 111} 112 113/** 114 * 将初始数据挨个跟下一个版本的changelog数据进行比较合并。 115 * 116 * @param {Array} orderVersionArr 117 * @param {String} url 118 * @param {Array} allMergeData 119 */ 120async function mergeAllData(orderVersionArr, url, allMergeData) { 121 let index = 1; 122 while (index < orderVersionArr.length) { 123 const versionUrl = getLink(url, orderVersionArr[index]); 124 let newVersionData = []; 125 await downloadFiles(versionUrl).then(versionData => { 126 newVersionData = JSON.parse(versionData); 127 }).catch(err => { 128 console.log('ERROR CODE:', err.code); 129 }); 130 mergeTwoVersionData(allMergeData, newVersionData, orderVersionArr[index]); 131 index++; 132 } 133 134} 135 136/** 137 * 跟下一个版本的数据进行合并 138 * 139 * @param {Array} allMergeData 140 * @param {Array} newVersionData 141 */ 142function mergeTwoVersionData(allMergeData, newVersionData, currentVersion) { 143 let indexListSet = new Set(); 144 for (let i = 0; i < allMergeData.length; i++) { 145 const data = allMergeData[i]; 146 for (let j = 0; j < newVersionData.length; j++) { 147 if (!indexListSet.has(j) && data.newDtsName === newVersionData[j].oldDtsName && 148 compareApiText(data.newApi, newVersionData[j].oldApi,) && data.newApi !== data.oldApi) { 149 allMergeData[i].newApi = newVersionData[j].newApi; 150 allMergeData[i].version = currentVersion; 151 indexListSet.add(j); 152 } 153 } 154 } 155 156 newVersionData.forEach((data, index) => { 157 if (!indexListSet.has(index)) { 158 data.version = currentVersion; 159 allMergeData.push(data); 160 } 161 }); 162} 163 164/** 165 * 比较旧版本的newApi和新版本的oldApi,判断是否一样 166 * 167 * @param {string} oldApiText 168 * @param {string} newApiText 169 * @returns {Boolean} 170 */ 171function compareApiText(oldApiText, newApiText) { 172 if (formatApi(oldApiText) === formatApi(newApiText) && formatApi(oldApiText) !== '') { 173 return true; 174 } 175 return false; 176} 177 178/** 179 * 格式化API定义内容,排除空格,换行,符号对比较API的影响。 180 * 181 * @param {string} apiText 182 * @returns {string} 183 */ 184function formatApi(apiText) { 185 if (!apiText) { 186 return ''; 187 } 188 return apiText.replace(/\r|\n|\s+|\,|\;/g, ''); 189} 190 191/** 192 * 将数组形式的changelog数据转换成Map 193 * 194 * @param {Array} dataInChangelogs 195 * @returns {Map} 196 */ 197function covertToMap(dataInChangelogs) { 198 let dataMap = new Map(); 199 dataInChangelogs.forEach(data => { 200 const dataSignature = getSignature(data.newDtsName, data.oldApi); 201 if (!dataMap.get(dataSignature)) { 202 dataMap.set(dataSignature, [data]); 203 } else { 204 const dataArr = dataMap.get(dataSignature); 205 dataArr.push(data); 206 dataMap.set(dataSignature, dataArr); 207 } 208 }); 209 return dataMap; 210} 211 212function getSignature(dtsName, apiText) { 213 const handledDtsName = handleDtsName(dtsName).dtsPath; 214 return `${handledDtsName}#${formatApi(apiText)}`; 215} 216 217function addChangelogLink(changelogsData, diffsData, diffs) { 218 diffsData.forEach(diffData => { 219 changelogsData.forEach(changelogData => { 220 diffData.changelogs.add({ 221 version: changelogData.version, 222 url: changelogData.changelog 223 }); 224 225 if (changelogData.oldType !== changelogData.newType) { 226 diffData.status = StatusMessages[StatusCode.CLASS_CHANGES]; 227 diffData.StatusCode = StatusCode.CLASS_CHANGES; 228 diffData.oldMessage = changelogData.oldApi; 229 diffData.newMessage = changelogData.newApi; 230 } 231 232 if (changelogData.oldDtsName !== changelogData.newDtsName) { 233 diffData.status = StatusMessages[StatusCode.DTS_CHANGED]; 234 diffData.StatusCode = StatusCode.DTS_CHANGED; 235 diffData.oldMessage = changelogData.oldDtsName; 236 diffData.newMessage = changelogData.newDtsName; 237 } 238 239 if (changelogData.newApi === changelogData.oldApi || changelogData.newApi === 'N/A') { 240 241 return; 242 } 243 244 if (diffData.statusCode === StatusCode.DELETE) { 245 const newApiSignature = getSignature(changelogData.newDtsName, changelogData.newApi); 246 diffs.delete(newApiSignature); 247 diffData.status = StatusMessages[StatusCode.FUNCTION_CHANGES]; 248 diffData.StatusCode = StatusCode.FUNCTION_CHANGES; 249 diffData.oldMessage = changelogData.oldApi; 250 diffData.newMessage = changelogData.newApi; 251 } else if (diffData.statusCode === StatusCode.DELETE_CLASS) { 252 diffData.status = StatusMessages[StatusCode.CLASS_CHANGES]; 253 diffData.StatusCode = StatusCode.CLASS_CHANGES; 254 diffData.oldMessage = changelogData.oldApi; 255 diffData.newMessage = changelogData.newApi; 256 } 257 }); 258 }); 259 return diffsData; 260} 261 262function mergeDiffsAndChangelogs(changelogs, diffs) { 263 changelogs.forEach((data, dataSignature) => { 264 const diffsData = diffs.get(dataSignature); 265 if (diffsData) { 266 diffs.set(dataSignature, addChangelogLink(data, diffsData, diffs)); 267 changelogs.delete(dataSignature); 268 return; 269 } 270 data.forEach(changelogData => { 271 const newApiSignature = getSignature(changelogData.newDtsName, changelogData.newApi); 272 const diffsData = diffs.get(newApiSignature); 273 if (!diffsData) { 274 return; 275 } 276 diffsData.forEach(diffData => { 277 diffData.changelog.add(changelogData.changelog); 278 }); 279 changelogs.delete(dataSignature); 280 }); 281 282 }); 283 284 changelogs.forEach((changelogData, signature) => { 285 changelogData.forEach(changelogApi => { 286 if (diffs.get(signature)) { 287 diffs.get(signature).push(formatChangelogApi(changelogApi)); 288 } else { 289 diffs.set(signature, [formatChangelogApi(changelogApi)]); 290 } 291 }); 292 }); 293 294 return diffs; 295} 296 297function formatChangelogApi(changelogApi) { 298 const filePathObj = handleDtsName(changelogApi.newDtsName); 299 return { 300 packageName: '', 301 className: changelogApi.newType, 302 rawText: changelogApi.newApi, 303 dtsName: filePathObj.dtsPath, 304 hint: '', 305 changelogs: [{ 306 version: changelogApi.version, 307 url: changelogApi.changelog 308 }], 309 statusCode: StatusCode.CHANGELOG, 310 status: StatusMessages[StatusCode.CHANGELOG], 311 oldMessage: changelogApi.oldApi, 312 newMessage: changelogApi.newApi 313 }; 314} 315 316function handleDtsName(dtsName) { 317 let packageName = dtsName; 318 let dtsPath = dtsName; 319 if (dtsName.indexOf('api/@internal/component/ets') > -1) { 320 packageName === 'ArkUI'; 321 dtsPath.replace('api/@internal/component/ets', 'component'); 322 } else if (dtsName.indexOf('api/@internal/ets') > -1) { 323 packageName = dtsName.replace('api/@internal/ets', 'api/@internal/full'); 324 dtsPath = packageName; 325 } 326 return { packageName, dtsPath }; 327} 328 329 330async function mergeDiffWithChangeLog(diffs, oldVersion, newVersion, allVersionUrl) { 331 const dataInChangelogs = await mergeDataBetweenVersion(oldVersion, newVersion, allVersionUrl); 332 const dataMapInChangelogs = covertToMap(dataInChangelogs); 333 return mergeDiffsAndChangelogs(dataMapInChangelogs, diffs); 334} 335 336exports.applyChangeLogs = mergeDiffWithChangeLog;