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) !== undefined) { 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 } else { 269 data.forEach(changelogData => { 270 const newApiSignature = getSignature(changelogData.newDtsName, changelogData.newApi); 271 const diffsData = diffs.get(newApiSignature); 272 if (!diffsData) { 273 return; 274 } 275 diffsData.forEach(diffData => { 276 diffData.changelog.add(changelogData.changelog); 277 }) 278 changelogs.delete(dataSignature); 279 }); 280 } 281 }) 282 283 changelogs.forEach((changelogData, signature) => { 284 changelogData.forEach(changelogApi => { 285 if (diffs.get(signature)) { 286 diffs.get(signature).push(formatChangelogApi(changelogApi)); 287 } else { 288 diffs.set(signature, [formatChangelogApi(changelogApi)]); 289 } 290 }) 291 }) 292 293 return diffs; 294} 295 296function formatChangelogApi(changelogApi) { 297 const filePathObj = handleDtsName(changelogApi.newDtsName); 298 return { 299 packageName: '', 300 className: changelogApi.newType, 301 rawText: changelogApi.newApi, 302 dtsName: filePathObj.dtsPath, 303 hint: "", 304 changelogs: [{ 305 version: changelogApi.version, 306 url: changelogApi.changelog 307 }], 308 statusCode: StatusCode.CHANGELOG, 309 status: StatusMessages[StatusCode.CHANGELOG], 310 oldMessage: changelogApi.oldApi, 311 newMessage: changelogApi.newApi 312 } 313} 314 315function handleDtsName(dtsName) { 316 let packageName = dtsName; 317 let dtsPath = dtsName; 318 if (dtsName.indexOf('api/@internal/component/ets') > -1) { 319 packageName === 'ArkUI'; 320 dtsPath.replace('api/@internal/component/ets', 'component') 321 } else if (dtsName.indexOf('api/@internal/ets') > -1) { 322 packageName = dtsName.replace('api/@internal/ets', 'api/@internal/full'); 323 dtsPath = packageName; 324 } 325 return { packageName, dtsPath }; 326} 327 328 329async function mergeDiffWithChangeLog(diffs, oldVersion, newVersion, allVersionUrl) { 330 const dataInChangelogs = await mergeDataBetweenVersion(oldVersion, newVersion, allVersionUrl); 331 const dataMapInChangelogs = covertToMap(dataInChangelogs); 332 return mergeDiffsAndChangelogs(dataMapInChangelogs, diffs); 333} 334 335exports.applyChangeLogs = mergeDiffWithChangeLog;