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