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