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