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