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 { ApiDigestInfo } = require('./api_data'); 18const { DiffReporter } = require('./reporter'); 19const { StatusCode } = require('./reporter'); 20 21class TagItem { 22 constructor() { } 23 24 addSinceVersion(version) { 25 this.setProperty('sinceVersion', version); 26 } 27 28 getSinceVersion() { 29 return this.sinceVersion; 30 } 31 32 addSyscap(sysCap) { 33 this.setProperty('sysCap', sysCap); 34 } 35 36 getSyscap() { 37 return this.sysCap; 38 } 39 40 addAppModel(model) { 41 this.setProperty('appModel', model); 42 } 43 44 getAppModel() { 45 return this.appModel; 46 } 47 48 addDeprecated(deprecated) { 49 this.setProperty('deprecated', deprecated); 50 } 51 52 getDeprecated() { 53 return this.deprecated; 54 } 55 56 addErrorCode(errorCode) { 57 this.setProperty('errorCode', errorCode); 58 } 59 60 getErrorCode() { 61 return this.errorCode; 62 } 63 64 addApiLevel(level) { 65 this.setProperty('apiLevel', level); 66 } 67 68 getApiLevel() { 69 return this.apiLevel; 70 } 71 72 addTypeTag(type) { 73 this.setProperty('type', type); 74 } 75 76 getTypeTag() { 77 return this.type; 78 } 79 80 addUseInstead(useinstead) { 81 this.setProperty('useinstead', useinstead); 82 } 83 84 getUseInstead() { 85 return this.useinstead; 86 } 87 88 addPermission(permission) { 89 this.setProperty('permission', permission); 90 } 91 92 getPermission() { 93 return this.permission; 94 } 95 96 addForm(form) { 97 this.setProperty('form', form); 98 } 99 100 getForm() { 101 return this.form; 102 } 103 104 addCrossplatform(crossplatform) { 105 this.setProperty('crossplatform', crossplatform); 106 } 107 108 getCrossplatform() { 109 return this.crossplatform; 110 } 111 112 setProperty(name, value) { 113 if (this[name]) { 114 this[name].push(value); 115 } else { 116 this[name] = [value]; 117 } 118 } 119} 120 121function isArrayEquals(first, second) { 122 if (!first && !second) { 123 return true; 124 } 125 const ret = first && second && first.length === second.length; 126 if (ret) { 127 first.sort(); 128 second.sort(); 129 for (let index = 0; index < first.length; index++) { 130 if (first[index] !== second[index]) { 131 return false; 132 } 133 } 134 } 135 return ret; 136} 137 138function arrayToString(array) { 139 if (!array || array.length === 0) { 140 return ''; 141 } 142 return array.join(); 143} 144 145/** 146 * 获取API @deprecated 标签信息, 继承父类 147 * 148 * @param {ApiDigestInfo} api 149 * @returns {Array} 150 */ 151function getApiDeprecated(api) { 152 let curApi = api; 153 while (curApi) { 154 const jsdocTagItem = getTagItemFromJSDoc(curApi); 155 if (jsdocTagItem.getDeprecated()) { 156 return jsdocTagItem.getDeprecated(); 157 } 158 curApi = curApi.getParent(); 159 } 160 return []; 161} 162 163function matchSyscapInFile(api) { 164 let syscap = getApiSyscap(api)[0]; 165 let curApi = api; 166 while (!syscap && !ts.isSourceFile(curApi.node)) { 167 curApi = curApi.getParent(); 168 } 169 if (!syscap && ts.isSourceFile(curApi.node)) { 170 const fileContent = curApi.node.getFullText(); 171 if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(fileContent)) { 172 fileContent.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => { 173 syscap = sysCapInfo.replace(/\@syscap/g, '').trim(); 174 }); 175 } 176 } 177 return syscap; 178} 179 180/** 181 * 获取 API @syscap 标签信息,继承父类 182 * 183 * @param {ApiDigestInfo} api 184 * @returns {Array} 185 */ 186function getApiSyscap(api) { 187 let curApi = api; 188 while (curApi && !ts.isSourceFile(curApi.node)) { 189 const jsdocTagItem = getTagItemFromJSDoc(curApi); 190 if (jsdocTagItem.getSyscap()) { 191 return jsdocTagItem.getSyscap(); 192 } 193 curApi = curApi.getParent(); 194 } 195 return []; 196} 197 198/** 199 * 获取API @useinstead,继承父类 200 * 201 * @param {ApiDigestInfo} api 202 * @returns {Array} 203 */ 204function getApiUseInstead(api) { 205 let curApi = api; 206 while (curApi) { 207 const jsdocTagItem = getTagItemFromJSDoc(curApi); 208 if (jsdocTagItem.getUseInstead()) { 209 return jsdocTagItem.getUseInstead(); 210 } 211 curApi = curApi.getParent(); 212 } 213 return []; 214} 215 216/** 217 * 从 JSDoc 对象获取所有Tag标签信息 218 * 219 * @param {ApiDigestInfo} api 220 * @returns {Object} 221 */ 222function getTagItemFromJSDoc(api) { 223 let jsdocTagItem = api.getJSDocTagItem(); 224 if (!jsdocTagItem) { 225 jsdocTagItem = createTagItemFromJSDoc(api.jsdoc); 226 api.setJSDocTagItem(jsdocTagItem); 227 } 228 return jsdocTagItem; 229} 230 231function wrapApiChanges(api, statusCode, oldMessage, newMessage, hint, oldNode, newNode, syscap) { 232 return { 233 api: api, 234 statusCode: statusCode, 235 oldMessage: oldMessage, 236 newMessage: newMessage, 237 hint: hint, 238 oldNode: oldNode, 239 newNode: newNode, 240 syscap: syscap, 241 }; 242} 243 244/** 245 * 比较JSDoc的差异 246 * 247 * @param {ApiDigestInfo} oldApi 248 * @param {ApiDigestInfo} newApi 249 * @param {DiffReporter} diffReporter 250 */ 251function compareJSDocs(oldApi, newApi, diffReporter) { 252 const oldTagItem = getTagItemFromJSDoc(oldApi); 253 const newTagItem = getTagItemFromJSDoc(newApi); 254 const useinstead = getApiUseInstead(newApi); 255 const hint = useinstead.length > 0 ? `useinstead: ${useinstead[0]}` : ''; 256 diffErrorCode(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 257 diffPermission(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 258 diffForm(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 259 diffCrossplatform(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 260 diffDeprecated(diffReporter, oldApi, newApi, hint); 261 diffApiLevel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 262 diffAppModel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 263 diffType(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint); 264} 265 266/** 267 * 比较@type 268 * 269 * @param {DiffReporter} diffReporter 270 * @param {TagItem} oldTagItem 271 * @param {TagItem} newTagItem 272 * @param {ApiDigestInfo} oldApi 273 * @param {ApiDigestInfo} newApi 274 * @param {string} hint 275 */ 276function diffType(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 277 const oldType = oldTagItem.getTypeTag(); 278 const newType = newTagItem.getTypeTag(); 279 if (!isArrayEquals(oldType, newType)) { 280 diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.TYPE_CHNAGES, 281 arrayToString(oldType), arrayToString(newType), 282 hint, '', '', matchSyscapInFile(newApi) 283 )); 284 285 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.TYPE_CHNAGES, 286 arrayToString(oldType), 287 arrayToString(newType), 288 hint, 289 oldApi.node, 290 newApi.node 291 )); 292 } 293} 294 295/** 296 * 比较@FAModelOnly @StageModelOnly 297 * 298 * @param {DiffReporter} diffReporter 299 * @param {TagItem} oldTagItem 300 * @param {TagItem} newTagItem 301 * @param {ApiDigestInfo} oldApi 302 * @param {ApiDigestInfo} newApi 303 * @param {string} hint 304 */ 305function diffAppModel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 306 const oldAppModel = oldTagItem.getAppModel(); 307 const newAppModel = newTagItem.getAppModel(); 308 if (!isArrayEquals(oldAppModel, newTagItem.getAppModel())) { 309 diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.MODEL_CHNAGES, 310 arrayToString(oldAppModel), arrayToString(newAppModel), 311 hint, '', '', matchSyscapInFile(newApi) 312 )); 313 314 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.MODEL_CHNAGES, 315 arrayToString(oldAppModel), 316 arrayToString(newAppModel), 317 hint, 318 oldApi.node, 319 newApi.node 320 )); 321 } 322} 323 324/** 325 * 比较@systemapi 326 * 327 * @param {DiffReporter} diffReporter 328 * @param {TagItem} oldTagItem 329 * @param {TagItem} newTagItem 330 * @param {ApiDigestInfo} oldApi 331 * @param {ApiDigestInfo} newApi 332 * @param {string} hint 333 */ 334function diffApiLevel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 335 const oldApiLevel = oldTagItem.getApiLevel(); 336 const newApiLevel = newTagItem.getApiLevel(); 337 if (!isArrayEquals(oldApiLevel, newApiLevel)) { 338 diffReporter.addChangedApi(wrapApiChanges( 339 newApi, StatusCode.SYSTEM_API_CHNAGES, 340 arrayToString(oldApiLevel), 341 arrayToString(newApiLevel), 342 hint, '', '', matchSyscapInFile(newApi) 343 )); 344 345 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.SYSTEM_API_CHNAGES, 346 arrayToString(oldApiLevel), 347 arrayToString(newApiLevel), 348 hint, 349 oldApi.node, 350 newApi.node 351 )); 352 } 353} 354 355/** 356 * 比较@crossplatform 357 * 358 * @param {DiffReporter} diffReporter 359 * @param {TagItem} oldTagItem 360 * @param {TagItem} newTagItem 361 * @param {ApiDigestInfo} oldApi 362 * @param {ApiDigestInfo} newApi 363 * @param {string} hint 364 */ 365function diffCrossplatform(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 366 if (!isArrayEquals(oldTagItem.getCrossplatform(), newTagItem.getCrossplatform())) { 367 diffReporter.addChangedApi(wrapApiChanges( 368 newApi, StatusCode.CROSSPLATFORM_CHANGED, arrayToString(oldTagItem.getCrossplatform()), 369 arrayToString(newTagItem.getCrossplatform()), 370 hint, '', '', matchSyscapInFile(newApi) 371 )); 372 373 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.CROSSPLATFORM_CHANGED, 374 arrayToString(oldTagItem.getCrossplatform()), 375 arrayToString(newTagItem.getCrossplatform()), 376 hint, 377 oldApi.node, 378 newApi.node 379 )); 380 } 381} 382 383/** 384 * 比较@form 385 * 386 * @param {DiffReporter} diffReporter 387 * @param {TagItem} oldTagItem 388 * @param {TagItem} newTagItem 389 * @param {ApiDigestInfo} oldApi 390 * @param {ApiDigestInfo} newApi 391 * @param {string} hint 392 */ 393function diffForm(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 394 if (!isArrayEquals(oldTagItem.getForm(), newTagItem.getForm())) { 395 diffReporter.addChangedApi(wrapApiChanges( 396 newApi, StatusCode.FORM_CHANGED, arrayToString(oldTagItem.getForm()), 397 arrayToString(newTagItem.getForm()), 398 hint, '', '', matchSyscapInFile(newApi) 399 )); 400 401 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.FORM_CHANGED, 402 arrayToString(oldTagItem.getForm()), 403 arrayToString(newTagItem.getForm()), 404 hint, 405 oldApi.node, 406 newApi.node 407 )); 408 } 409} 410 411/** 412 * 比较@syscap 413 * 414 * @param {DiffReporter} diffReporter 415 * @param {ApiDigestInfo} oldApi 416 * @param {ApiDigestInfo} newApi 417 * @param {string} hint 418 */ 419function diffSyscap(diffReporter, oldApi, newApi, hint) { 420 const oldSyscap = getApiSyscap(oldApi); 421 const newSyscap = getApiSyscap(newApi); 422 if (!isArrayEquals(oldSyscap, newSyscap)) { 423 diffReporter.addChangedApi(wrapApiChanges( 424 newApi, StatusCode.SYSCAP_CHANGES, arrayToString(oldSyscap), 425 arrayToString(newSyscap), 426 hint, '', '', matchSyscapInFile(newApi) 427 )); 428 diffReporter.addDiffInfo(wrapApiChanges( 429 newApi, StatusCode.SYSCAP_CHANGES, arrayToString(oldSyscap), 430 arrayToString(newSyscap), 431 hint, 432 oldApi.node, 433 newApi.node 434 )); 435 } 436} 437 438/** 439 * 比较@since 440 * 441 * @param {DiffReporter} diffReporter 442 * @param {TagItem} oldTagItem 443 * @param {TagItem} newTagItem 444 * @param {ApiDigestInfo} oldApi 445 * @param {ApiDigestInfo} newApi 446 * @param {string} hint 447 */ 448function diffSinceVersion(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 449 const oldVersion = oldTagItem.getSinceVersion(); 450 const newVersion = newTagItem.getSinceVersion(); 451 if (!isArrayEquals(oldVersion, newVersion)) { 452 diffReporter.addChangedApi(wrapApiChanges( 453 newApi, StatusCode.VERSION_CHNAGES, arrayToString(oldVersion), 454 arrayToString(newVersion), 455 hint, '', '', matchSyscapInFile(newApi) 456 )); 457 diffReporter.addDiffInfo(wrapApiChanges( 458 newApi, StatusCode.VERSION_CHNAGES, arrayToString(oldVersion), 459 arrayToString(newVersion), 460 hint, 461 oldApi.node, 462 newApi.node 463 )); 464 } 465} 466 467/** 468 * 比较@deprecated 469 * 470 * @param {DiffReporter} diffReporter 471 * @param {ApiDigestInfo} oldApi 472 * @param {ApiDigestInfo} newApi 473 * @param {string} hint 474 */ 475function diffDeprecated(diffReporter, oldApi, newApi, hint) { 476 const oldDeprecated = getApiDeprecated(oldApi); 477 const newDeprecated = getApiDeprecated(newApi); 478 if (!isArrayEquals(oldDeprecated, newDeprecated)) { 479 diffReporter.addChangedApi(wrapApiChanges( 480 newApi, StatusCode.DEPRECATED_CHNAGES, 481 arrayToString(oldDeprecated), arrayToString(newDeprecated), 482 hint, '', '', matchSyscapInFile(newApi) 483 )); 484 diffReporter.addDiffInfo(wrapApiChanges( 485 newApi, StatusCode.DEPRECATED_CHNAGES, arrayToString(oldDeprecated), 486 arrayToString(newDeprecated), 487 hint, 488 oldApi.node, 489 newApi.node 490 )); 491 } 492} 493 494/** 495 * 比较权限的差异@permission 496 * 497 * @param {DiffReporter} diffReporter 498 * @param {TagItem} oldTagItem 499 * @param {TagItem} newTagItem 500 * @param {ApiDigestInfo} oldApi 501 * @param {ApiDigestInfo} newApi 502 * @param {string} hint 503 */ 504function diffPermission(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 505 if (!isArrayEquals(oldTagItem.getPermission(), newTagItem.getPermission())) { 506 diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.PERMISSION_CHANGES, 507 arrayToString(oldTagItem.getPermission()), 508 arrayToString(newTagItem.getPermission()), 509 hint, 510 oldApi.node, 511 newApi.node, 512 matchSyscapInFile(newApi) 513 )); 514 515 diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.PERMISSION_CHANGES, 516 arrayToString(oldTagItem.getPermission()), 517 arrayToString(newTagItem.getPermission()), 518 hint, 519 oldApi.node, 520 newApi.node 521 )); 522 } 523} 524 525/** 526 * 比较错误码的差异@throws 527 * 528 * @param {DiffReporter} diffReporter 529 * @param {TagItem} oldTagItem 530 * @param {TagItem} newTagItem 531 * @param {ApiDigestInfo} oldApi 532 * @param {ApiDigestInfo} newApi 533 * @param {string} hint 534 */ 535function diffErrorCode(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) { 536 if (!isArrayEquals(oldTagItem.getErrorCode(), newTagItem.getErrorCode())) { 537 let statusCode = ''; 538 if (oldTagItem.getErrorCode() === undefined) { 539 statusCode = StatusCode.NEW_ERRORCODE; 540 } else { 541 statusCode = StatusCode.ERRORCODE_CHANGES; 542 } 543 diffReporter.addChangedApi(wrapApiChanges( 544 newApi, statusCode, arrayToString(oldTagItem.getErrorCode()), 545 arrayToString(newTagItem.getErrorCode()), 546 hint, 547 oldApi.node, 548 newApi.node, 549 matchSyscapInFile(newApi) 550 )); 551 diffReporter.addDiffInfo(wrapApiChanges( 552 newApi, statusCode, arrayToString(oldTagItem.getErrorCode()), 553 arrayToString(newTagItem.getErrorCode()), 554 hint, 555 oldApi.node, 556 newApi.node 557 )); 558 } 559} 560 561/** 562 * 解析 @since 7 563 * 564 * @param {Object} tagObject 565 * @param {TagItem} tagItem 566 */ 567function appendSinceTag(tagObject, tagItem) { 568 tagItem.addSinceVersion(tagObject.name ? tagObject.name : ''); 569} 570 571/** 572 * 解析 @syscap 573 * 574 * @param {Object} tagObject 575 * @param {TagItem} tagItem 576 */ 577function appendSyscapTag(tagObject, tagItem) { 578 tagItem.addSyscap(tagObject.name ? tagObject.name : ''); 579} 580 581/** 582 * 解析 @permission 583 * 584 * @param {Object} tagObject 585 * @param {TagItem} tagItem 586 */ 587function appendPermissionTag(tagObject, tagItem) { 588 const permissionRegExp = RegExp(/ohos\.permission\.\w+/g); 589 let sourceText = ''; 590 tagObject.source.forEach((src) => { 591 sourceText += src.source; 592 }); 593 const permissionArray = sourceText.match(permissionRegExp); 594 if (permissionArray) { 595 permissionArray.forEach((permission) => { 596 tagItem.addPermission(permission); 597 }); 598 } 599} 600 601/** 602 * 解析 @deprecated 标签 603 * 604 * @param {Object} tagObject 605 * @param {TagItem} tagItem 606 */ 607function appendDeprecatedTag(tagObject, tagItem) { 608 tagItem.addDeprecated(tagObject.description); 609} 610 611/** 612 * 解析 @FAModelOnly @StageModelOnly 613 * 614 * @param {Object} tagObject 615 * @param {TagItem} tagItem 616 */ 617function appendModelTag(tagObject, tagItem) { 618 tagItem.addAppModel(tagObject.tag); 619} 620 621/** 622 * 解析 @systemapi 623 * 624 * @param {Object} tagObject 625 * @param {TagItem} tagItem 626 */ 627function appendApiLevelTag(tagObject, tagItem) { 628 tagItem.addApiLevel(tagObject.tag); 629} 630 631/** 632 * 解析 @type {string} 633 * 634 * @param {Object} tagObject 635 * @param {TagItem} tagItem 636 */ 637function appendTypeTag(tagObject, tagItem) { 638 tagItem.addTypeTag(tagObject.type); 639} 640 641/** 642 * 解析 @useinstedad 标签 643 * 644 * @param {Object} tagObject 645 * @param {TagItem} tagItem 646 */ 647function appendUseInsteadTag(tagObject, tagItem) { 648 tagItem.addUseInstead(tagObject.name); 649} 650 651/** 652 * 解析 @throws { BusinessError } 201 - 标签 653 * 654 * @param {Object} tagObject 655 * @param {TagItem} tagItem 656 */ 657function appendErrorCodeTag(tagObject, tagItem) { 658 tagItem.addErrorCode(tagObject.name); 659} 660 661/** 662 * 解析 @form - 标签 663 * 664 * @param {Object} tagObject 665 * @param {TagItem} tagItem 666 */ 667function appendForm(tagObject, tagItem) { 668 tagItem.addForm(tagObject.tag); 669} 670 671function appendCrossplatform(tagObject, tagItem) { 672 tagItem.addCrossplatform(tagObject.tag); 673} 674 675const tagHandlerMap = new Map([ 676 ['syscap', appendSyscapTag], 677 ['permission', appendPermissionTag], 678 ['deprecated', appendDeprecatedTag], 679 ['famodelonly', appendModelTag], 680 ['stagemodelonly', appendModelTag], 681 ['systemapi', appendApiLevelTag], 682 ['type', appendTypeTag], 683 ['useinstead', appendUseInsteadTag], 684 ['throws', appendErrorCodeTag], 685 ['form', appendForm], 686 ['crossplatform', appendCrossplatform] 687]); 688 689 690 691/** 692 * 从 comment-parser 库解析的JSDoc对象中提取标签信息 693 * 694 * @param {Object} jsdocs 695 */ 696function createTagItemFromJSDoc(jsdocs) { 697 const tagItem = new TagItem(); 698 if (!jsdocs || jsdocs.length === 0) { 699 return tagItem; 700 } 701 const singleJSDoc = jsdocs[jsdocs.length - 1]; 702 const firstJsDoc = jsdocs[0]; 703 if (singleJSDoc.tags) { 704 singleJSDoc.tags.forEach((tagObject) => { 705 const handler = tagHandlerMap.get(tagObject.tag.toLowerCase()); 706 if (handler) { 707 handler(tagObject, tagItem); 708 } 709 }); 710 } 711 712 if (firstJsDoc.tags) { 713 firstJsDoc.tags.forEach((tagObject) => { 714 if (tagObject.tag.toLowerCase() === 'since') { 715 appendSinceTag(tagObject, tagItem); 716 } 717 }); 718 } 719 return tagItem; 720} 721 722exports.JSDocDiffer = { 723 collectJSDocDiffs: compareJSDocs, 724};