• 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');
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};