1/* 2 * Copyright (c) 2021-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 */ 15const fs = require('fs'); 16const rules = require('../../code_style_rule.json'); 17const { commentNodeWhiteList, requireTypescriptModule, systemPermissionFile, checkOption, ErrorValueInfo, 18 createErrorInfo, OptionalSymbols, parseJsDoc, getDeclareValue } = require('../../src/utils'); 19const ts = requireTypescriptModule(); 20 21function checkExtendsValue(tag, node, fileName) { 22 const extendsResult = { 23 checkResult: true, 24 errorInfo: '', 25 }; 26 const tagValue = tag.name; 27 // 获取api中的extends信息,校验标签合法性及值规范 28 if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) { 29 const apiValue = node.heritageClauses ? node.heritageClauses[0].types[0].getText() : ''; 30 if (tagValue !== apiValue) { 31 extendsResult.checkResult = false; 32 extendsResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_EXTENDS; 33 } 34 } 35 return extendsResult; 36} 37exports.checkExtendsValue = checkExtendsValue; 38 39function checkEnumValue(tag, node, fileName) { 40 const enumResult = { 41 checkResult: true, 42 errorInfo: '', 43 }; 44 const enumValues = ['string', 'number']; 45 const tagValue = tag.type; 46 const tagProblems = tag.problems.length; 47 48 // 获取api中的enum信息,校验标签合法性及值规范 49 if (tagProblems > 0 || enumValues.indexOf(tagValue) === -1) { 50 enumResult.checkResult = false; 51 enumResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_ENUM; 52 } 53 return enumResult; 54} 55exports.checkEnumValue = checkEnumValue; 56 57function checkSinceValue(tag, node, fileName) { 58 const sinceResult = { 59 checkResult: true, 60 errorInfo: '', 61 }; 62 const tagValue = tag.name; 63 const checkNumber = /^\d+$/.test(tagValue); 64 if (!checkNumber && commentNodeWhiteList.includes(node.kind)) { 65 sinceResult.checkResult = false; 66 sinceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_SINCE; 67 } 68 return sinceResult; 69} 70exports.checkSinceValue = checkSinceValue; 71 72function checkReturnsValue(tag, node, fileName) { 73 const returnsResult = { 74 checkResult: true, 75 errorInfo: '', 76 }; 77 const voidArr = ['void']; 78 const tagValue = tag.type.replace(/\n|\r|\s/g, ''); 79 let apiReturnsValue = ''; 80 if (!commentNodeWhiteList.includes(node.kind)) { 81 return returnsResult; 82 } 83 if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { 84 apiReturnsValue = ts.SyntaxKind.VoidKeyword === node.type?.type ? 'void' : node.type?.type?.getText().replace(/\n|\r|\s/g, ''); 85 } else { 86 apiReturnsValue = getDeclareValue(node.type); 87 } 88 if (voidArr.indexOf(apiReturnsValue) !== -1 || apiReturnsValue === undefined) { 89 returnsResult.checkResult = false; 90 returnsResult.errorInfo = ErrorValueInfo.ERROR_INFO_RETURNS; 91 return returnsResult; 92 } 93 if (tagValue === apiReturnsValue) { 94 return returnsResult; 95 } 96 if (apiReturnsValue === 'Function' && tagValue === 'function') { 97 return returnsResult; 98 } 99 returnsResult.checkResult = false; 100 returnsResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_RETURNS; 101 return returnsResult; 102} 103exports.checkReturnsValue = checkReturnsValue; 104 105function checkParamValue(tag, node, fileName, tagIndex) { 106 const tagNameValue = tag.name; 107 const tagTypeValue = tag.type.replace(/\n|\r|\s/g, ''); 108 let paramResult = { 109 checkResult: true, 110 errorInfo: '', 111 }; 112 if (!node.parameters) { 113 return paramResult; 114 } 115 const apiParamInfos = node.parameters; 116 if (!apiParamInfos[tagIndex]) { 117 return paramResult; 118 } 119 const apiName = apiParamInfos[tagIndex].name.escapedText; 120 let apiType = getDeclareValue(apiParamInfos[tagIndex].type); 121 let errorInfo = ''; 122 if (apiType === tagTypeValue) { 123 return paramResult; 124 } 125 if (apiType === 'Function' && tagTypeValue === 'function') { 126 return paramResult; 127 } 128 if (apiName !== tagNameValue) { 129 paramResult.checkResult = false; 130 if (errorInfo !== '') { 131 errorInfo += '\n'; 132 } 133 errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE_PARAM, [tagIndex + 1, tagIndex + 1]); 134 } 135 paramResult.checkResult = false; 136 errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_TYPE_PARAM, [tagIndex + 1, tagIndex + 1]); 137 if (!paramResult.checkResult) { 138 paramResult.errorInfo = errorInfo; 139 } 140 return paramResult; 141} 142exports.checkParamValue = checkParamValue; 143 144function checkThrowsValue(tag, node, fileName, tagIndex) { 145 const throwsResult = { 146 checkResult: true, 147 errorInfo: '', 148 }; 149 const tagNameValue = tag.name; 150 const tagTypeValue = tag.type; 151 let errorInfo = ''; 152 let hasDeprecated = false; 153 const comments = parseJsDoc(node).length > 0 ? [parseJsDoc(node).pop()] : []; 154 comments.forEach(comment => { 155 comment.tags.forEach(tag => { 156 if (tag.tag === 'deprecated') { 157 hasDeprecated = true; 158 } 159 }); 160 }); 161 if (tagTypeValue !== 'BusinessError' && !hasDeprecated) { 162 throwsResult.checkResult = false; 163 errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE1_THROWS, [tagIndex + 1]); 164 } 165 166 if (isNaN(tagNameValue) && !hasDeprecated) { 167 if (errorInfo !== '') { 168 errorInfo += '\n'; 169 } 170 throwsResult.checkResult = false; 171 errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE2_THROWS, [tagIndex + 1]); 172 } 173 if (!throwsResult.checkResult) { 174 throwsResult.errorInfo = errorInfo; 175 } 176 return throwsResult; 177} 178exports.checkThrowsValue = checkThrowsValue; 179 180/** 181 * 182 * 1.引用不同文件的api接口 183 * xxx.xxx#xxx 184 * 185 * 2.引用不同文件的模块接口 186 * xxx.xxx 187 * 188 * 3.引用不同文件的api事件接口 189 * xxx.xxx#event:xxx 190 */ 191function checkModule(moduleValue) { 192 return /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*$/.test(moduleValue) || 193 /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#[A-Za-z0-9_]+\b$/.test(moduleValue) || 194 /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#event:[A-Za-z0-9_]+\b$/.test(moduleValue); 195} 196 197function splitUseinsteadValue(useinsteadValue) { 198 if (!useinsteadValue || useinsteadValue === '') { 199 return undefined; 200 } 201 const splitResult = { 202 checkResult: true, 203 errorInfo: '', 204 }; 205 // 拆分字符串 206 const splitArray = useinsteadValue.split(/\//g); 207 const MODEL_COUNT = 1; 208 const MODEL_COUNTS = 2; 209 const FILENAME_MODEL_COUNT = 1; 210 if (splitArray.length === MODEL_COUNT) { 211 if (splitArray[0].indexOf(OptionalSymbols.LEFT_BRACKET) === -1 && 212 splitArray[0].indexOf(OptionalSymbols.RIGHT_BRACKET) === -1) { 213 // 同一文件 214 splitResult.checkResult = checkModule(splitArray[0]); 215 } 216 217 } else if (splitArray.length === MODEL_COUNTS) { 218 // 不同文件 219 const fileNameArray = splitArray[0].split('.'); 220 if (fileNameArray.length === FILENAME_MODEL_COUNT) { 221 // arkui 222 if (!/^[A-Za-z0-9_]+\b$/.test(fileNameArray[0]) || !checkModule(splitArray[1])) { 223 splitResult.checkResult = false; 224 } 225 } else { 226 // 非arkui 227 let checkFileName = true; 228 for (let i = 0; i < fileNameArray.length; i++) { 229 if (fileNameArray[0] !== 'ohos' || !/^[A-Za-z0-9_]+\b$/.test(fileNameArray[i])) { 230 checkFileName = false; 231 } 232 } 233 if (!checkFileName || (!checkModule(splitArray[1]) && splitArray[1].indexOf(OptionalSymbols.LEFT_BRACKET) === -1 && 234 splitArray[1].indexOf(OptionalSymbols.RIGHT_BRACKET) === -1)) { 235 splitResult.checkResult = false; 236 } 237 } 238 } else { 239 // 格式错误 240 splitResult.checkResult = false; 241 } 242 if (!splitResult.checkResult) { 243 splitResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_USEINSTEAD; 244 } 245 return splitResult; 246} 247 248// 精确校验功能待补全 249function checkUseinsteadValue(tag, node, fileName) { 250 const tagNameValue = tag.name; 251 let useinsteadResult = { 252 checkResult: true, 253 errorInfo: '', 254 }; 255 if (tagNameValue === '') { 256 useinsteadResult.checkResult = false; 257 useinsteadResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_USEINSTEAD; 258 } else { 259 const result = splitUseinsteadValue(tagNameValue, fileName); 260 if (result && !result.checkResult) { 261 useinsteadResult = result; 262 } 263 } 264 return useinsteadResult; 265} 266exports.checkUseinsteadValue = checkUseinsteadValue; 267 268function checkTypeValue(tag, node, fileName) { 269 const typeResult = { 270 checkResult: true, 271 errorInfo: '', 272 }; 273 const tagTypeValue = tag.type.replace(/\n|\r|\s/g, ''); 274 if (!commentNodeWhiteList.includes(node.kind)) { 275 return typeResult; 276 } 277 let apiTypeValue = getDeclareValue(node.type); 278 if (node.questionToken) { 279 apiTypeValue = ts.isUnionTypeNode(node.type) ? OptionalSymbols.LEFT_PARENTHESES + apiTypeValue + 280 OptionalSymbols.RIGHT_PARENTHESES : apiTypeValue; 281 apiTypeValue = OptionalSymbols.QUERY.concat(apiTypeValue); 282 } 283 if (apiTypeValue === tagTypeValue) { 284 return typeResult; 285 } 286 if ((apiTypeValue === 'Function' && tagTypeValue === 'function') || (apiTypeValue === '?Function' && tagTypeValue === '?function')) { 287 return typeResult; 288 } 289 typeResult.checkResult = false; 290 typeResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPE; 291 return typeResult; 292} 293exports.checkTypeValue = checkTypeValue; 294 295function checkDefaultValue(tag, node, fileName) { 296 const defaultResult = { 297 checkResult: true, 298 errorInfo: '', 299 }; 300 if (commentNodeWhiteList.includes(node.kind) && tag.name.length === 0 && tag.type.length === 0) { 301 defaultResult.checkResult = false; 302 defaultResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_DEFAULT; 303 } 304 return defaultResult; 305} 306exports.checkDefaultValue = checkDefaultValue; 307 308/** 309 * 门禁环境优先使用systemPermissionFile 310 * 本地环境从指定分支上下载 311 * 下载失败则使用默认配置 312 * 313 * @returns Set<string> 314 */ 315function getPermissionList() { 316 const permissionTags = ['ohos.permission.HEALTH_DATA', 'ohos.permission.HEART_RATE', 'ohos.permission.ACCELERATION']; 317 let permissionFileContent; 318 if (fs.existsSync(systemPermissionFile)) { 319 permissionFileContent = require(systemPermissionFile); 320 } else if (checkOption.permissionContent) { 321 permissionFileContent = JSON.parse(checkOption.permissionContent); 322 } else { 323 permissionFileContent = require('../../config/config.json'); 324 } 325 const permissionTagsObj = permissionFileContent.module.definePermissions; 326 permissionTagsObj.forEach((item) => { 327 permissionTags.push(item.name); 328 }); 329 const permissionRuleSets = new Set(permissionTags); 330 return permissionRuleSets; 331} 332 333function checkPermissionTag(tag, node, fileName) { 334 const permissionRuleSet = getPermissionList(); 335 let hasPermissionError = false; 336 let errorInfo = ''; 337 const permissionResult = { 338 checkResult: true, 339 errorInfo: '', 340 }; 341 const tagValue = tag.name + tag.description; 342 const permissionArr = tagValue.replace(/\s|\(|\)/g, '').replace(/(or|and)/g, '$').split('$'); 343 permissionArr.forEach(permissionStr => { 344 if ((permissionStr !== '' && !permissionRuleSet.has(permissionStr)) || 345 permissionStr === '') { 346 hasPermissionError = true; 347 errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_PERMISSION; 348 } 349 }); 350 if (hasPermissionError) { 351 permissionResult.checkResult = false; 352 permissionResult.errorInfo = errorInfo; 353 } 354 return permissionResult; 355} 356exports.checkPermissionTag = checkPermissionTag; 357 358function checkDeprecatedTag(tag, node, fileName) { 359 const deprecatedResult = { 360 checkResult: true, 361 errorInfo: '', 362 }; 363 const tagValue1 = tag.name; 364 const tagValue2 = tag.description; 365 const checkNumber = /^\d+$/.test(tagValue2); 366 if ((tagValue1 !== 'since' || !checkNumber) && commentNodeWhiteList.includes(node.kind)) { 367 deprecatedResult.checkResult = false; 368 deprecatedResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_DEPRECATED; 369 } 370 return deprecatedResult; 371} 372exports.checkDeprecatedTag = checkDeprecatedTag; 373 374function checkSyscapTag(tag, node, fileName) { 375 const syscapResult = { 376 checkResult: true, 377 errorInfo: '', 378 }; 379 const tagValue = tag.name; 380 const syscapRuleSet = new Set(rules.syscap.SystemCapability); 381 if (!syscapRuleSet.has(tagValue)) { 382 syscapResult.checkResult = false; 383 syscapResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_SYSCAP; 384 } 385 return syscapResult; 386} 387exports.checkSyscapTag = checkSyscapTag; 388 389function checkNamespaceTag(tag, node, fileName) { 390 const namespaceResult = { 391 checkResult: true, 392 errorInfo: '', 393 }; 394 const tagValue = tag.name; 395 if (commentNodeWhiteList.includes(node.kind)) { 396 const apiValue = node.name?.escapedText; 397 if (apiValue !== undefined && tagValue !== apiValue) { 398 namespaceResult.checkResult = false; 399 namespaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_NAMESPACE; 400 } 401 } 402 return namespaceResult; 403} 404exports.checkNamespaceTag = checkNamespaceTag; 405 406function checkInterfaceTypedefTag(tag, node, fileName) { 407 const interfaceResult = { 408 checkResult: true, 409 errorInfo: '', 410 }; 411 const tagValue = tag.name; 412 const tagType = tag.type.replace(/\n|\r|\s/g, ''); 413 414 if (commentNodeWhiteList.includes(node.kind)) { 415 const apiValue = node.name?.escapedText; 416 if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { 417 const isFunctionType = ts.SyntaxKind.FunctionType === node.type?.kind; 418 const isObjectType = ts.SyntaxKind.TypeLiteral === node.type?.kind; 419 let apiType = isFunctionType ? 'function' : 420 isObjectType ? 'object' : node.type.getText().replace(/\n|\r|\s/g, ''); 421 if (tagType !== apiType) { 422 interfaceResult.checkResult = false; 423 interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF; 424 } 425 } else { 426 if (apiValue !== undefined && tagValue !== apiValue) { 427 interfaceResult.checkResult = false; 428 if (tag.tag === 'interface') { 429 interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_INTERFACE; 430 } else if (tag.tag === 'typedef') { 431 interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF; 432 } 433 interfaceResult.errorInfo = tag.tag === 'interface' ? ErrorValueInfo.ERROR_INFO_VALUE_INTERFACE : 434 tag.tag === 'typedef' ? ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF : ''; 435 } 436 } 437 } 438 return interfaceResult; 439} 440exports.checkInterfaceTypedefTag = checkInterfaceTypedefTag; 441 442const JsDocValueChecker = { 443 extends: checkExtendsValue, 444 enum: checkEnumValue, 445 since: checkSinceValue, 446 returns: checkReturnsValue, 447 param: checkParamValue, 448 throws: checkThrowsValue, 449 useinstead: checkUseinsteadValue, 450 type: checkTypeValue, 451 default: checkDefaultValue, 452 permission: checkPermissionTag, 453 deprecated: checkDeprecatedTag, 454 syscap: checkSyscapTag, 455 namespace: checkNamespaceTag, 456 interface: checkInterfaceTypedefTag, 457 typedef: checkInterfaceTypedefTag, 458}; 459exports.JsDocValueChecker = JsDocValueChecker; 460