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