• 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
16import { ErrorTagFormat, ErrorMessage, PermissionData } from '../../../typedef/checker/result_type';
17import { Comment } from '../../../typedef/parser/Comment';
18import { CommonFunctions } from '../../../utils/checkUtils';
19import { ApiInfo, ApiType, ClassInfo } from '../../../typedef/parser/ApiInfoDefination';
20import { MethodInfo, PropertyInfo, ParamInfo } from '../../../typedef/parser/ApiInfoDefination';
21import { PunctuationMark } from '../../../utils/Constant';
22import { SystemCapability } from '../config/syscapConfigFile.json';
23import { module } from '../config/permissionConfigFile.json';
24import ts from 'typescript';
25
26export class TagValueCheck {
27  /**
28   * all jsdoc tag value check
29   * @param { ApiInfo } singleApi
30   * @param { Comment.JsDocInfo } apiJsdoc
31   * @returns { ErrorTagFormat[] }
32   */
33  static tagValueCheck(singleApi: ApiInfo, apiJsdoc: Comment.JsDocInfo): ErrorTagFormat[] {
34    const tagValueError: ErrorTagFormat[] = [];
35    const tagsName: Comment.CommentTag[] | undefined = apiJsdoc.tags;
36    let throwsIndex: number = 0;
37    let paramIndex: number = -1;
38    if (tagsName === undefined) {
39      return tagValueError;
40    }
41    tagsName.forEach((tag) => {
42      if (tag.tag === 'since') {
43        const sincevalueCheckResult = TagValueCheck.sinceTagValueCheck(tag);
44        if (!sincevalueCheckResult.state) {
45          tagValueError.push(sincevalueCheckResult);
46        }
47      }
48      if (tag.tag === 'extends') {
49        const extendsvalueCheckResult = TagValueCheck.extendsTagValueCheck(singleApi, tag);
50        if (!extendsvalueCheckResult.state) {
51          tagValueError.push(extendsvalueCheckResult);
52        }
53      }
54      if (tag.tag === 'enum') {
55        const enumvalueCheckResult = TagValueCheck.enumTagValueCheck(tag);
56        if (!enumvalueCheckResult.state) {
57          tagValueError.push(enumvalueCheckResult);
58        }
59      }
60      if (tag.tag === 'returns') {
61        const returnsvalueCheckResult = TagValueCheck.returnsTagValueCheck(singleApi, tag);
62        if (!returnsvalueCheckResult.state) {
63          tagValueError.push(returnsvalueCheckResult);
64        }
65      }
66      if (tag.tag === 'namespace' || tag.tag === 'interface' || tag.tag === 'typedef') {
67        const outerValueCheckResult = TagValueCheck.outerTagValueCheck(singleApi as ClassInfo, tag);
68        if (!outerValueCheckResult.state) {
69          tagValueError.push(outerValueCheckResult);
70        }
71      }
72      if (tag.tag === 'type') {
73        const typeValueCheckResult = TagValueCheck.typeTagValueCheck(singleApi, tag);
74        if (!typeValueCheckResult.state) {
75          tagValueError.push(typeValueCheckResult);
76        }
77      }
78      if (tag.tag === 'syscap') {
79        const syscapValueCheckResult = TagValueCheck.syscapTagValueCheck(tag);
80        if (!syscapValueCheckResult.state) {
81          tagValueError.push(syscapValueCheckResult);
82        }
83      }
84      if (tag.tag === 'default') {
85        const defaultValueCheckResult = TagValueCheck.defaultTagValueCheck(tag);
86        if (!defaultValueCheckResult.state) {
87          tagValueError.push(defaultValueCheckResult);
88        }
89      }
90      if (tag.tag === 'deprecated') {
91        const deprecatedValueCheckResult = TagValueCheck.deprecatedTagValueCheck(tag);
92        if (!deprecatedValueCheckResult.state) {
93          tagValueError.push(deprecatedValueCheckResult);
94        }
95      }
96      if (tag.tag === 'permission') {
97        const permissionValueCheckResult = TagValueCheck.permissionTagValueCheck(tag);
98        if (!permissionValueCheckResult.state) {
99          tagValueError.push(permissionValueCheckResult);
100        }
101      }
102      if (tag.tag === 'throws') {
103        throwsIndex += 1;
104        const throwsValueCheckResult = TagValueCheck.throwsTagValueCheck(tag, throwsIndex);
105        if (!throwsValueCheckResult.state) {
106          tagValueError.push(throwsValueCheckResult);
107        }
108      }
109      if (tag.tag === 'param') {
110        paramIndex += 1;
111        const paramValueCheckResult = TagValueCheck.paramTagValueCheck(singleApi, tag, paramIndex);
112        if (!paramValueCheckResult.state) {
113          tagValueError.push(paramValueCheckResult);
114        }
115      }
116      if (tag.tag === 'useinstead') {
117        const useinsteadValueCheckResult = TagValueCheck.useinsteadTagValueCheck(tag);
118        if (!useinsteadValueCheckResult.state) {
119          tagValueError.push(useinsteadValueCheckResult);
120        }
121      }
122    });
123    return tagValueError;
124  }
125
126  /**
127   * since tag value check
128   * @param { Comment.CommentTag } tag
129   * @returns { ErrorTagFormat }
130   */
131  static sinceTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
132    const sinceValueCheckResult: ErrorTagFormat = {
133      state: true,
134      errorInfo: '',
135    };
136    const sinceValue: boolean = /^\d+$/.test(tag.name);
137    if (!sinceValue) {
138      sinceValueCheckResult.state = false;
139      sinceValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_SINCE;
140    }
141    return sinceValueCheckResult;
142  }
143
144  /**
145   * extends tag value check
146   * @param { ApiInfo } singleApi
147   * @param { Comment.CommentTag } tag
148   * @returns { ErrorTagFormat }
149   */
150  static extendsTagValueCheck(singleApi: ApiInfo, tag: Comment.CommentTag): ErrorTagFormat {
151    const extendsValueCheckResult: ErrorTagFormat = {
152      state: true,
153      errorInfo: '',
154    };
155    let extendsTagValue: string = tag.name;
156    if (singleApi.getApiType() === ApiType.CLASS || singleApi.getApiType() === ApiType.INTERFACE) {
157      let extendsApiValue = (singleApi as ClassInfo).getParentClasses();
158      if (extendsTagValue !== extendsApiValue[0]) {
159        extendsValueCheckResult.state = false;
160        extendsValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_EXTENDS;
161      }
162    }
163
164    return extendsValueCheckResult;
165  }
166
167  /**
168   * enum tag value check
169   * @param { Comment.CommentTag } tag
170   * @returns { ErrorTagFormat }
171   */
172  static enumTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
173    const enumValueCheckResult: ErrorTagFormat = {
174      state: true,
175      errorInfo: '',
176    };
177    const enumValues = ['string', 'number'];
178    if (enumValues.indexOf(tag.type) === -1) {
179      enumValueCheckResult.state = false;
180      enumValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_ENUM;
181    }
182    return enumValueCheckResult;
183  }
184
185  /**
186   * retuens tag value check
187   * @param { ApiInfo } singleApi
188   * @param { Comment.CommentTag } tag
189   * @returns { ErrorTagFormat }
190   */
191  static returnsTagValueCheck(singleApi: ApiInfo, tag: Comment.CommentTag): ErrorTagFormat {
192    const returnsValueCheckResult: ErrorTagFormat = {
193      state: true,
194      errorInfo: '',
195    };
196    const returnsTagValue: string = tag.type;
197
198    let returnsApiValue: string[] = [];
199    const spacealCase: string[] = CommonFunctions.judgeSpecialCase((singleApi as MethodInfo).returnValueType);
200    if (spacealCase.length > 0) {
201      returnsApiValue = spacealCase;
202    } else {
203      returnsApiValue = (singleApi as MethodInfo).getReturnValue();
204    }
205    if (returnsApiValue.length === 0) {
206      returnsValueCheckResult.state = false;
207      returnsValueCheckResult.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_USE, ['returns']);
208    } else if (returnsTagValue !== returnsApiValue[0]) {
209      returnsValueCheckResult.state = false;
210      returnsValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_RETURNS;
211    }
212    return returnsValueCheckResult;
213  }
214
215  /**
216   * namespace tag value check
217   * @param { ClassInfo } singleApi
218   * @param { Comment.CommentTag } tag
219   * @returns { ErrorTagFormat }
220   */
221  static outerTagValueCheck(singleApi: ClassInfo, tag: Comment.CommentTag): ErrorTagFormat {
222    const outerValueCheckResult: ErrorTagFormat = {
223      state: true,
224      errorInfo: '',
225    };
226    let tagValue: string = tag.name;
227    let apiValue: string = singleApi.getApiName();
228    if (tag.tag === 'namespace' && tagValue !== apiValue) {
229      outerValueCheckResult.state = false;
230      outerValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_NAMESPACE;
231    }
232    if (tag.tag === 'interface' && tagValue !== apiValue) {
233      outerValueCheckResult.state = false;
234      outerValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_INTERFACE;
235    }
236    if (tag.tag === 'typedef' && tagValue !== apiValue) {
237      outerValueCheckResult.state = false;
238      outerValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_TYPEDEF;
239    }
240    return outerValueCheckResult;
241  }
242
243  /**
244   * type tag value check
245   * @param { ApiInfo } singleApi
246   * @param { Comment.CommentTag } tag
247   * @returns { ErrorTagFormat }
248   */
249  static typeTagValueCheck(singleApi: ApiInfo, tag: Comment.CommentTag): ErrorTagFormat {
250    const typeValueCheckResult: ErrorTagFormat = {
251      state: true,
252      errorInfo: '',
253    };
254    if (singleApi.getApiType() !== ApiType.PROPERTY) {
255      return typeValueCheckResult;
256    }
257    let typeTagValue: string = tag.type.replace(/\s/g, '');
258    let typeApiValue: string[] = [];
259    const spacealCase: string[] = CommonFunctions.judgeSpecialCase((singleApi as PropertyInfo).typeKind);
260    if (spacealCase.length > 0) {
261      typeApiValue = spacealCase;
262    } else {
263      typeApiValue = (singleApi as PropertyInfo).type;
264    }
265
266    let typeApiUnionValue: string = typeApiValue.join('|');
267    const isOptional: boolean = !(singleApi as PropertyInfo).getIsRequired();
268    if (isOptional && typeApiValue.length === 1) {
269      typeApiUnionValue = '?' + typeApiUnionValue;
270    } else if (isOptional && typeApiValue.length > 1) {
271      typeApiUnionValue = '?(' + typeApiUnionValue + ')';
272    }
273    if (typeTagValue.replace(/\s/g, '') !== typeApiUnionValue.replace(/\s/g, '')) {
274      typeValueCheckResult.state = false;
275      typeValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_TYPE;
276    }
277    return typeValueCheckResult;
278  }
279  /**
280   * syacap tag value check
281   * @param { Comment.CommentTag } tag
282   * @returns { ErrorTagFormat }
283   */
284  static syscapTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
285    const syscapValueCheckResult: ErrorTagFormat = {
286      state: true,
287      errorInfo: '',
288    };
289    const syscapRule: string[] = SystemCapability;
290    const syscapTagValue: string = tag.name;
291    if (!syscapRule.includes(syscapTagValue)) {
292      syscapValueCheckResult.state = false;
293      syscapValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_SYSCAP;
294    }
295    return syscapValueCheckResult;
296  }
297  /**
298   * default tag value check
299   * @param { Comment.CommentTag } tag
300   * @returns { ErrorTagFormat }
301   */
302  static defaultTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
303    const defaultValueCheckResult: ErrorTagFormat = {
304      state: true,
305      errorInfo: '',
306    };
307    const defaultTagValue: string = tag.name;
308    if (defaultTagValue.length === 0) {
309      defaultValueCheckResult.state = false;
310      defaultValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_DEFAULT;
311    }
312    return defaultValueCheckResult;
313  }
314
315  /**
316   * deprecated tag value check
317   * @param { Comment.CommentTag } tag
318   * @returns { ErrorTagFormat }
319   */
320  static deprecatedTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
321    const deprecatedValueCheckResult: ErrorTagFormat = {
322      state: true,
323      errorInfo: '',
324    };
325    const deprecatedFixedField: string = tag.name;
326    const deprecatedVersion: string = tag.description;
327    const isNumber: boolean = /^\d+$/.test(deprecatedVersion);
328    if (deprecatedFixedField !== 'since' || !isNumber) {
329      deprecatedValueCheckResult.state = false;
330      deprecatedValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_DEPRECATED;
331    }
332    return deprecatedValueCheckResult;
333  }
334  /**
335   * permission tag value check
336   * @param { Comment.CommentTag } tag
337   * @returns { ErrorTagFormat }
338   */
339  static permissionTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
340    const permissionValueCheckResult: ErrorTagFormat = {
341      state: true,
342      errorInfo: '',
343    };
344
345    const permissionRuleDatas: PermissionData[] = module.definePermissions as PermissionData[];
346    const permissionRule: string[] = [];
347    permissionRuleDatas.forEach((permissionRuleData: PermissionData) => {
348      permissionRule.push(permissionRuleData.name);
349    });
350    const permissionTagValue: string = tag.name + tag.description;
351    const permissionArr = permissionTagValue
352      .replace(/(\s|\(|\))/g, '')
353      .replace(/(or|and)/g, '$')
354      .split('$');
355    permissionArr.forEach((permissionItem) => {
356      if (!permissionRule.includes(permissionItem)) {
357        permissionValueCheckResult.state = false;
358        permissionValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_PERMISSION;
359      }
360    });
361    return permissionValueCheckResult;
362  }
363
364  /**
365   * throws tag value check
366   * @param { Comment.CommentTag } tag
367   * @param { number } throwsIndex
368   * @returns { ErrorTagFormat }
369   */
370  static throwsTagValueCheck(tag: Comment.CommentTag, throwsIndex: number): ErrorTagFormat {
371    const throwsValueCheckResult: ErrorTagFormat = {
372      state: true,
373      errorInfo: '',
374    };
375    const throwsTagType: string = tag.type;
376    const throwsTagName: string = tag.name;
377    const isNumber: boolean = /^\d+$/.test(throwsTagName);
378    if (throwsTagType !== 'BusinessError') {
379      throwsValueCheckResult.state = false;
380      throwsValueCheckResult.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_INFO_VALUE1_THROWS, [
381        JSON.stringify(throwsIndex),
382      ]);
383    } else if (!isNumber) {
384      throwsValueCheckResult.state = false;
385      throwsValueCheckResult.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_INFO_VALUE2_THROWS, [
386        JSON.stringify(throwsIndex),
387      ]);
388    }
389    return throwsValueCheckResult;
390  }
391
392  /**
393   * param tag value check
394   * @param { ApiInfo } singleApi
395   * @param { Comment.CommentTag } tag
396   * @param { number } paramIndex
397   * @returns { ErrorTagFormat }
398   */
399  static paramTagValueCheck(singleApi: ApiInfo, tag: Comment.CommentTag, paramIndex: number): ErrorTagFormat {
400    const paramValueCheckResult: ErrorTagFormat = {
401      state: true,
402      errorInfo: '',
403    };
404    if (singleApi.getApiType() !== ApiType.METHOD) {
405      return paramValueCheckResult;
406    }
407    const paramTagType: string = tag.type;
408    const paramTagName: string = tag.name;
409    const paramApiInfos: ParamInfo[] = (singleApi as MethodInfo).getParams();
410    const paramApiName: string = paramApiInfos[paramIndex]?.getApiName();
411    let paramApiType: string[] = [];
412    const spacealCase: string[] = paramApiInfos[paramIndex] ?
413      CommonFunctions.judgeSpecialCase(paramApiInfos[paramIndex].paramType) : [];
414    if (spacealCase.length > 0) {
415      paramApiType = spacealCase;
416    } else {
417      paramApiType = paramApiInfos[paramIndex]?.getType();
418    }
419
420    if (paramTagName !== paramApiName) {
421      paramValueCheckResult.state = false;
422      paramValueCheckResult.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_INFO_VALUE_PARAM, [
423        JSON.stringify(paramIndex + 1),
424        JSON.stringify(paramIndex + 1),
425      ]);
426    }
427    if (paramApiType === undefined || paramTagType !== paramApiType[0]) {
428      paramValueCheckResult.state = false;
429      paramValueCheckResult.errorInfo =
430        paramValueCheckResult.errorInfo +
431        CommonFunctions.createErrorInfo(ErrorMessage.ERROR_INFO_TYPE_PARAM, [
432          JSON.stringify(paramIndex + 1),
433          JSON.stringify(paramIndex + 1),
434        ]);
435    }
436
437    return paramValueCheckResult;
438  }
439
440  /**
441   *
442   * 1.引用不同文件的api接口
443   * xxx.xxx#xxx
444   *
445   * 2.引用不同文件的模块接口
446   * xxx.xxx
447   *
448   * 3.引用不同文件的api事件接口
449   * xxx.xxx#event:xxx
450   */
451  /**
452   * useinstead format check
453   * @param { string } moduleValue
454   * @returns { boolean }
455   */
456  static checkModule(moduleValue: string): boolean {
457    return (
458      /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*$/.test(moduleValue) ||
459      /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#[A-Za-z0-9_]+\b$/.test(moduleValue) ||
460      /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#event:[A-Za-z0-9_]+\b$/.test(moduleValue)
461    );
462  }
463  /**
464   * Split useinstead value to determine if the file belongs to arkui.
465   * @param { string } useinsteadTagValue
466   * @param { ErrorTagFormat } useinsteadValueCheckResult
467   */
468  static splitUseinsteadValue(useinsteadTagValue: string, useinsteadValueCheckResult: ErrorTagFormat): void {
469    if (!useinsteadTagValue || useinsteadTagValue === '') {
470      useinsteadValueCheckResult.state = false;
471      useinsteadValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_USEINSTEAD;
472    }
473    // 拆分字符串
474    const splitArray: string[] = useinsteadTagValue.split(/\//g);
475    const MODEL_COUNT: number = 1;
476    const MODEL_COUNTS: number = 2;
477    const FILENAME_MODEL_COUNT: number = 1;
478    if (splitArray.length === MODEL_COUNT) {
479      if (
480        splitArray[0].indexOf(PunctuationMark.LEFT_BRACKET) === -1 &&
481        splitArray[0].indexOf(PunctuationMark.RIGHT_BRACKET) === -1
482      ) {
483        // 同一文件
484        useinsteadValueCheckResult.state = TagValueCheck.checkModule(splitArray[0]);
485      }
486    } else if (splitArray.length === MODEL_COUNTS) {
487      // 不同文件
488      const fileNameArray: string[] = splitArray[0].split('.');
489      if (fileNameArray.length === FILENAME_MODEL_COUNT) {
490        // arkui
491        if (!/^[A-Za-z0-9_]+\b$/.test(fileNameArray[0]) || !TagValueCheck.checkModule(splitArray[1])) {
492          useinsteadValueCheckResult.state = false;
493        }
494      } else {
495        // 非arkui
496        let checkFileName: boolean = true;
497        for (let i = 0; i < fileNameArray.length; i++) {
498          if (fileNameArray[0] !== 'ohos' || !/^[A-Za-z0-9_]+\b$/.test(fileNameArray[i])) {
499            checkFileName = false;
500          }
501        }
502        if (
503          !checkFileName ||
504          (!TagValueCheck.checkModule(splitArray[1]) &&
505            splitArray[1].indexOf(PunctuationMark.LEFT_BRACKET) === -1 &&
506            splitArray[1].indexOf(PunctuationMark.RIGHT_BRACKET) === -1)
507        ) {
508          useinsteadValueCheckResult.state = false;
509        }
510      }
511    } else {
512      // 格式错误
513      useinsteadValueCheckResult.state = false;
514    }
515    if (!useinsteadValueCheckResult.state) {
516      useinsteadValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_USEINSTEAD;
517    }
518  }
519  /**
520   * useinstead tag value check
521   * @param { Comment.CommentTag } tag
522   * @returns { ErrorTagFormat }
523   */
524  static useinsteadTagValueCheck(tag: Comment.CommentTag): ErrorTagFormat {
525    let useinsteadValueCheckResult: ErrorTagFormat = {
526      state: true,
527      errorInfo: '',
528    };
529    const useinsteadTagValue: string = tag.name;
530    if (useinsteadTagValue === '') {
531      useinsteadValueCheckResult.state = false;
532      useinsteadValueCheckResult.errorInfo = ErrorMessage.ERROR_INFO_VALUE_USEINSTEAD;
533    } else {
534      TagValueCheck.splitUseinsteadValue(useinsteadTagValue, useinsteadValueCheckResult);
535    }
536    return useinsteadValueCheckResult;
537  }
538}
539