• 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 ts from 'typescript';
17import { ApiInfo, ApiType, MethodInfo, PropertyInfo, TypeAliasInfo } from '../../../typedef/parser/ApiInfoDefination';
18import {
19  tagsArrayOfOrder,
20  optionalTags,
21  apiLegalityCheckTypeMap
22} from '../../../utils/checkUtils';
23import { Comment } from '../../../typedef/parser/Comment';
24import { ErrorTagFormat, ErrorMessage, ParticularErrorCode } from '../../../typedef/checker/result_type';
25import { CommonFunctions, conditionalOptionalTags } from '../../../utils/checkUtils';
26
27export class LegalityCheck {
28  /**
29   * Tag's legality check
30   * @param { ApiInfo } singleApi -Individual node information.
31   * @param { Comment.JsDocInfo } apiJsdoc -Individual node JsDoc.
32   * @returns { ErrorTagFormat[] }
33   */
34  static apiLegalityCheck(singleApi: ApiInfo, apiJsdoc: Comment.JsDocInfo): ErrorTagFormat[] {
35    const apiLegalityCheckResult: ErrorTagFormat[] = [];
36
37    //check systemapi and atomicservice
38    LegalityCheck.checkSystemapiAtomicservice(apiJsdoc, apiLegalityCheckResult);
39
40    const nodeInfo: ts.Node = singleApi.getNode() as ts.Node;
41    const apiLegalityTagsArray: string[] = apiLegalityCheckTypeMap.get(nodeInfo.kind) as string[];
42    const apiLegalityTagsSet: Set<string> = new Set(apiLegalityTagsArray);
43    const illegalTagsArray: string[] = LegalityCheck.getIllegalTagsArray(apiLegalityTagsArray);
44    let extendsApiValue = '';
45    let implementsApiValue = '';
46    if (singleApi.getApiType() === ApiType.CLASS || singleApi.getApiType() === ApiType.INTERFACE) {
47      extendsApiValue = CommonFunctions.getExtendsApiValue(singleApi);
48      implementsApiValue = CommonFunctions.getImplementsApiValue(singleApi);
49    }
50    if (extendsApiValue === '') {
51      apiLegalityTagsSet.delete('extends');
52      illegalTagsArray.push('extends');
53    }
54    if (implementsApiValue === '') {
55      apiLegalityTagsSet.delete('implements');
56      illegalTagsArray.push('implements');
57    }
58
59    if (singleApi.getApiType() === ApiType.PROPERTY) {
60      if (!(singleApi as PropertyInfo).getIsReadOnly()) {
61        apiLegalityTagsSet.delete('readonly');
62        illegalTagsArray.push('readonly');
63      }
64    }
65
66    // 判断api的jsdoc中是否存在非法标签,是否缺失必选标签
67    if (!Array.isArray(apiLegalityTagsArray)) {
68      return apiLegalityCheckResult;
69    }
70    const apiTags: Comment.CommentTag[] | undefined = apiJsdoc.tags;
71    const apiTagsName: string[] = [];
72    const throwsCodeArr: string[] = [];
73    if (apiTags === undefined) {
74      const requiredTagLost: ErrorTagFormat = {
75        state: false,
76        errorInfo: CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, ['since']) +
77          CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, ['syscap']),
78      };
79      apiLegalityCheckResult.push(requiredTagLost);
80      return apiLegalityCheckResult;
81    }
82    const tagsTag: string[] = [];
83    apiTags.forEach((apiTag: Comment.CommentTag) => { tagsTag.push(apiTag.tag); });
84    if (tagsTag.includes('deprecated')) {
85      return apiLegalityCheckResult;
86    }
87
88    let paramTagNumber: number = 0;
89    let paramApiNumber: number =
90      singleApi.getApiType() === ApiType.METHOD ? (singleApi as MethodInfo).getParams().length : 0;
91
92    paramApiNumber = singleApi.getApiType() === ApiType.TYPE_ALIAS ?
93      (singleApi as TypeAliasInfo).getParamInfos().length : paramApiNumber;
94
95    apiTags.forEach((apiTag) => {
96      apiTagsName.push(apiTag.tag);
97      if (apiTag.tag === 'throws') {
98        throwsCodeArr.push(apiTag.name);
99      }
100      paramTagNumber = apiTag.tag === 'param' ? paramTagNumber + 1 : paramTagNumber;
101      const isUseinsteadLegalSituation: boolean = apiTag.tag === 'useinstead' && apiJsdoc.deprecatedVersion !== '-1';
102      apiLegalityTagsSet.delete('param');
103      if (apiLegalityTagsSet.has(apiTag.tag)) {
104        apiLegalityTagsSet.delete(apiTag.tag);
105      }
106      if (singleApi.getApiType() === ApiType.PROPERTY || singleApi.getApiType() === ApiType.DECLARE_CONST) {
107        apiLegalityTagsSet.delete('constant');
108        illegalTagsArray.push('constant');
109      }
110      if (singleApi.getApiType() === ApiType.INTERFACE && (apiTag.tag === 'typedef' || apiTag.tag === 'interface')) {
111        apiLegalityTagsSet.delete('typedef');
112        apiLegalityTagsSet.delete('interface');
113      }
114      if (singleApi.getApiType() === ApiType.TYPE_ALIAS && singleApi.getIsExport()) {
115        apiLegalityTagsSet.delete('typedef');
116      }
117      if ((singleApi.getApiType() === ApiType.METHOD && (singleApi as MethodInfo).getReturnValue().length === 0) ||
118        singleApi.getApiType() === ApiType.TYPE_ALIAS && ((singleApi as TypeAliasInfo).getReturnType().join() === 'void' ||
119          !(singleApi as TypeAliasInfo).getTypeIsFunction())) {
120        apiLegalityTagsSet.delete('returns');
121        illegalTagsArray.push('returns');
122      }
123      if (illegalTagsArray.includes(apiTag.tag)) {
124        if (apiTag.tag !== 'useinstead' || !isUseinsteadLegalSituation) {
125          const apiRedundantResultFormat: ErrorTagFormat = {
126            state: false,
127            errorInfo: CommonFunctions.createErrorInfo(ErrorMessage.ERROR_USE, [apiTag.tag]),
128          };
129          apiLegalityCheckResult.push(apiRedundantResultFormat);
130        }
131      }
132    });
133    if (singleApi.getApiType() === ApiType.METHOD) {
134      LegalityCheck.checkThrowsCode(throwsCodeArr, apiTagsName, paramApiNumber, apiLegalityCheckResult);
135    }
136    // param合法性单独进行校验
137    LegalityCheck.paramLegalityCheck(paramTagNumber, paramApiNumber, apiLegalityCheckResult);
138    // 缺失标签set合集
139    apiLegalityTagsSet.forEach((apiLegalityTag) => {
140      if (!conditionalOptionalTags.includes(apiLegalityTag)) {
141        const apiLostResultFormat: ErrorTagFormat = {
142          state: false,
143          errorInfo: CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, [apiLegalityTag]),
144        };
145        apiLegalityCheckResult.push(apiLostResultFormat);
146      }
147    });
148    return apiLegalityCheckResult;
149  }
150
151  /**
152   * param tag  legality check
153   * @param { number } paramTagNumber
154   * @param { number } paramApiNumber
155   * @param { ErrorTagFormat[] } apiLegalityCheckResult
156   */
157  static paramLegalityCheck(
158    paramTagNumber: number,
159    paramApiNumber: number,
160    apiLegalityCheckResult: ErrorTagFormat[]
161  ): void {
162    if (paramTagNumber > paramApiNumber) {
163      const apiRedundantResultFormat: ErrorTagFormat = {
164        state: false,
165        errorInfo: CommonFunctions.createErrorInfo(ErrorMessage.ERROR_MORELABEL, [
166          JSON.stringify(paramTagNumber - paramApiNumber),
167          'param',
168        ]),
169      };
170      apiLegalityCheckResult.push(apiRedundantResultFormat);
171    } else if (paramTagNumber < paramApiNumber) {
172      const apiLostResultFormat: ErrorTagFormat = {
173        state: false,
174        errorInfo: CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, ['param']),
175      };
176      apiLegalityCheckResult.push(apiLostResultFormat);
177    }
178  }
179
180  /**
181   * check api doc legality about throws code
182   * @param { string[] } apiThrowsCode
183   * @param { string[] } apiTagsName
184   * @param { number } paramApiNumber
185   * @param { ErrorTagFormat[] } apiLegalityCheckResult
186   */
187  static checkThrowsCode(apiThrowsCode: string[], apiTagsName: string[], paramApiNumber: number, apiLegalityCheckResult: ErrorTagFormat[]): void {
188    const apiLostPermissionTag: ErrorTagFormat = {
189      state: true,
190      errorInfo: '',
191    };
192    const apiLostSystemapiTag: ErrorTagFormat = {
193      state: true,
194      errorInfo: '',
195    };
196    const apiRedundantThrows: ErrorTagFormat = {
197      state: true,
198      errorInfo: '',
199    };
200    const apiRepeatThrows: ErrorTagFormat = {
201      state: true,
202      errorInfo: '',
203    };
204    const hasPermissionTag: boolean = apiTagsName.includes(ParticularErrorCode.ERROR_PERMISSION);
205    const hasSystemapiTag: boolean = apiTagsName.includes(ParticularErrorCode.ERROR_SYSTEMAPI);
206    const hasError201: boolean = apiThrowsCode.includes(ParticularErrorCode.ERROR_CODE_201);
207    const hasError202: boolean = apiThrowsCode.includes(ParticularErrorCode.ERROR_CODE_202);
208    const hasError401: boolean = apiThrowsCode.includes(ParticularErrorCode.ERROR_CODE_401);
209    // check permission 201
210    if (hasPermissionTag !== hasError201) {
211      apiLostPermissionTag.state = false;
212      apiLostPermissionTag.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, [hasPermissionTag ? 'throws 201' : ParticularErrorCode.ERROR_PERMISSION]);
213    }
214    // check systemapi 202
215    if (hasSystemapiTag !== hasError202) {
216      apiLostSystemapiTag.state = false;
217      apiLostSystemapiTag.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_LOST_LABEL, [hasSystemapiTag ? 'throws 202' : ParticularErrorCode.ERROR_SYSTEMAPI]);
218    }
219    // check systemapi 401
220    if (hasError401 && paramApiNumber === 0) {
221      apiRedundantThrows.state = false;
222      apiRedundantThrows.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_USE, ['throws 401']);
223    }
224    // check repeat throws
225    const orderedThrowsCode: string[] = apiThrowsCode.sort();
226    for (let i = 0; i < orderedThrowsCode.length; i++) {
227      if (orderedThrowsCode[i] === orderedThrowsCode[i + 1]) {
228        apiRepeatThrows.state = false;
229        apiRepeatThrows.errorInfo = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_REPEATLABEL, ['throws']);
230      }
231
232    }
233    apiLegalityCheckResult.push(apiLostPermissionTag, apiLostSystemapiTag, apiRedundantThrows, apiRepeatThrows);
234
235  }
236
237  /**
238   * Gets all illegal tags for the api.
239   * @param { string[] } RequiredTagsArray
240   * @returns { string[] }
241   */
242  static getIllegalTagsArray(requiredTagsArray: string[]): string[] {
243    const illegalTagsArray: string[] = [];
244
245    tagsArrayOfOrder.forEach((tag) => {
246      if (!optionalTags.includes(tag) && !Array.isArray(requiredTagsArray)) {
247        illegalTagsArray.push(tag);
248      } else if (!optionalTags.includes(tag) && !requiredTagsArray.includes(tag)) {
249        illegalTagsArray.push(tag);
250      }
251    });
252    return illegalTagsArray;
253  }
254  /**
255   * systemapi and atomicservice cannot exist at the same time
256   * @param apiJsdoc
257   */
258  static checkSystemapiAtomicservice(apiJsdoc: Comment.JsDocInfo, apiLegalityCheckResult: ErrorTagFormat[]): void {
259    const apiSystemapiAtomicservice: ErrorTagFormat = {
260      state: true,
261      errorInfo: '',
262    };
263    const tagsName: string[] = [];
264    apiJsdoc.tags?.forEach((tag: Comment.CommentTag) => {
265      tagsName.push(tag.tag);
266    });
267    const hasSystemapi: boolean = tagsName.includes('systemapi');
268    const hasAtomicservice: boolean = tagsName.includes('atomicservice');
269    if (hasSystemapi && hasAtomicservice) {
270      apiSystemapiAtomicservice.state = false;
271      apiSystemapiAtomicservice.errorInfo = ErrorMessage.ERROR_ERROR_SYSTEMAPI_ATOMICSERVICE;
272    }
273    apiLegalityCheckResult.push(apiSystemapiAtomicservice);
274  }
275}
276