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