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*/ 15 16const whiteLists = require('../config/jsdocCheckWhiteList.json'); 17const { parseJsDoc, commentNodeWhiteList, requireTypescriptModule, ErrorType, ErrorLevel, FileType, ErrorValueInfo, 18 createErrorInfo, isWhiteListFile } = require('./utils'); 19const { checkApiOrder, checkAPITagName, checkInheritTag } = require('./check_jsdoc_value/check_order'); 20const { addAPICheckErrorLogs } = require('./compile_info'); 21const ts = requireTypescriptModule(); 22 23// 标签合法性校验 24function checkJsDocLegality(node, comments, checkInfoMap) { 25 // since 26 legalityCheck(node, comments, commentNodeWhiteList, ['since'], true, checkInfoMap); 27 // syscap 28 legalityCheck(node, comments, getIllegalKinds([ts.SyntaxKind.ModuleDeclaration, ts.SyntaxKind.ClassDeclaration]), 29 ['syscap'], true, checkInfoMap); 30 // const定义语句必填 31 legalityCheck(node, comments, [ts.SyntaxKind.VariableStatement], ['constant'], true, checkInfoMap, 32 (currentNode, checkResult) => { 33 return (checkResult && (currentNode.kind !== ts.SyntaxKind.VariableStatement || !/^const\s/.test(currentNode.getText()))) || 34 (!checkResult && currentNode.kind === ts.SyntaxKind.VariableStatement && /^const\s/.test(currentNode.getText())); 35 }); 36 // 'enum' 37 legalityCheck(node, comments, [ts.SyntaxKind.EnumDeclaration], ['enum'], true, checkInfoMap); 38 // 'extends' 39 legalityCheck(node, comments, [ts.SyntaxKind.ClassDeclaration], ['extends'], true, checkInfoMap, 40 (currentNode, checkResult) => { 41 let tagCheckResult = false; 42 if (ts.isClassDeclaration(currentNode) && currentNode.heritageClauses) { 43 const clauses = currentNode.heritageClauses; 44 clauses.forEach(claus => { 45 if (/^extends\s/.test(claus.getText())) { 46 tagCheckResult = true; 47 } 48 }); 49 } 50 return (checkResult && !tagCheckResult) || (!checkResult && tagCheckResult); 51 } 52 ); 53 // 'namespace' 54 legalityCheck(node, comments, [ts.SyntaxKind.ModuleDeclaration], ['namespace'], true, checkInfoMap); 55 // 'param' 56 legalityCheck(node, comments, [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 57 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor, 58 ts.SyntaxKind.TypeAliasDeclaration], ['param'], true, checkInfoMap, 59 (currentNode, checkResult) => { 60 if (!new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 61 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.Constructor]).has(currentNode.kind)) { 62 return true; 63 } 64 return currentNode.parameters; 65 } 66 ); 67 // 'returns' 68 legalityCheck(node, comments, [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 69 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.TypeAliasDeclaration], 70 ['returns'], true, checkInfoMap, 71 (currentNode, checkResult) => { 72 if (!checkResult && !new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 73 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, 74 ts.SyntaxKind.TypeAliasDeclaration]).has(currentNode.kind)) { 75 return false; 76 } 77 return !(!checkResult && !new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 78 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, 79 ts.SyntaxKind.TypeAliasDeclaration]).has(currentNode.kind)) && (currentNode.type && 80 currentNode.type.kind !== ts.SyntaxKind.VoidKeyword); 81 } 82 ); 83 // 'useinstead' 84 legalityCheck(node, comments, commentNodeWhiteList, ['useinstead'], true, checkInfoMap, 85 (currentNode, checkResult) => { 86 return new Set(commentNodeWhiteList).has(currentNode.kind); 87 } 88 ); 89 // typedef/interface 90 legalityCheck(node, comments, [ts.SyntaxKind.InterfaceDeclaration, ts.SyntaxKind.TypeAliasDeclaration], 91 ['interface', 'typedef'], true, checkInfoMap); 92 // 'type', 'readonly' 93 legalityCheck(node, comments, [ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.PropertySignature], 94 ['type', 'readonly'], false, checkInfoMap); 95 // 'default' 96 legalityCheck(node, comments, [ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.PropertySignature, 97 ts.SyntaxKind.VariableStatement], ['default'], false, checkInfoMap); 98 return checkInfoMap; 99} 100exports.checkJsDocLegality = checkJsDocLegality; 101 102function getIllegalKinds(legalKinds) { 103 const illegalKinds = []; 104 const legalKindSet = new Set(legalKinds); 105 commentNodeWhiteList.forEach(kind => { 106 if (!legalKindSet.has(kind)) { 107 illegalKinds.push(kind); 108 } 109 }); 110 return illegalKinds; 111} 112 113function dealSpecialTag(comment, tagName) { 114 let checkResult = false; 115 const useinsteadResultObj = { 116 hasUseinstead: false, 117 hasDeprecated: false, 118 }; 119 let paramTagNum = 0; 120 comment.tags.forEach(tag => { 121 if (tagName === 'useinstead') { 122 if (tag.tag === tagName) { 123 useinsteadResultObj.hasUseinstead = true; 124 } else if (tag.tag === 'deprecated') { 125 useinsteadResultObj.hasDeprecated = true; 126 } 127 } else if (((tagName === 'interface' || tagName === 'typedef') && (tag.tag === 'interface' || 128 tag.tag === 'typedef')) || tag.tag === tagName) { 129 checkResult = true; 130 } 131 if (tag.tag === 'param') { 132 paramTagNum++; 133 } 134 }); 135 return { 136 useinsteadResultObj: useinsteadResultObj, 137 checkResult: checkResult, 138 paramTagNum: paramTagNum, 139 }; 140} 141 142function legalityCheck(node, comments, legalKinds, tagsName, isRequire, checkInfoMap, extraCheckCallback) { 143 const illegalKinds = getIllegalKinds(legalKinds); 144 let illegalKindSet = new Set(illegalKinds); 145 const legalKindSet = new Set(legalKinds); 146 const isFunctionType = ts.SyntaxKind.FunctionType === node.type?.kind; 147 const functionTag = ['param', 'returns', 'throws']; 148 tagsName.forEach(tagName => { 149 if (tagName === 'extends') { 150 illegalKindSet = new Set(commentNodeWhiteList); 151 } else if (tagName === 'syscap') { 152 illegalKindSet = new Set([]); 153 } 154 if (functionTag.includes(tagName) && !isFunctionType) { 155 legalKindSet.delete(ts.SyntaxKind.TypeAliasDeclaration); 156 } 157 if (tagName === 'returns' && node.kind === ts.SyntaxKind.TypeAliasDeclaration && isFunctionType && 158 node.type?.type?.kind === ts.SyntaxKind.VoidKeyword) { 159 legalKindSet.delete(ts.SyntaxKind.TypeAliasDeclaration); 160 } 161 comments.forEach((comment, index) => { 162 if (!checkInfoMap[index]) { 163 checkInfoMap[index] = { 164 missingTags: [], 165 illegalTags: [], 166 }; 167 } 168 const dealSpecialTagResult = dealSpecialTag(comment, tagName); 169 let parameterNum = 0; 170 if (tagName === 'since') { 171 } 172 if (tagName === 'param' && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || 173 ts.isFunctionDeclaration(node) || ts.isCallSignatureDeclaration(node) || ts.isConstructorDeclaration(node) || 174 ts.isTypeAliasDeclaration(node))) { 175 const parameterLength = ts.isTypeAliasDeclaration(node) ? node.type.parameters?.length : node.parameters.length; 176 parameterNum = parameterLength === undefined ? 0 : parameterLength; 177 checkResult = parameterNum !== dealSpecialTagResult.paramTagNum; 178 } 179 let extraCheckResult = false; 180 if (!extraCheckCallback) { 181 extraCheckResult = true; 182 } else { 183 extraCheckResult = extraCheckCallback(node, dealSpecialTagResult.checkResult); 184 } 185 // useinstead特殊处理 186 if (isRequire && tagName !== 'useinstead' && ((tagName !== 'useinstead' && tagName !== 'param' && 187 !dealSpecialTagResult.checkResult && legalKindSet.has(node.kind)) || (tagName === 'param' && 188 dealSpecialTagResult.paramTagNum < parameterNum)) && extraCheckResult) { 189 // 报错 190 checkInfoMap[index].missingTags.push(tagName); 191 } else if (((tagName !== 'useinstead' && tagName !== 'param' && dealSpecialTagResult.checkResult && 192 illegalKindSet.has(node.kind)) || (tagName === 'useinstead' && 193 !dealSpecialTagResult.useinsteadResultObj.hasDeprecated && 194 dealSpecialTagResult.useinsteadResultObj.hasUseinstead) || 195 (tagName === 'param' && dealSpecialTagResult.paramTagNum > parameterNum)) && extraCheckResult) { 196 // 报错 197 let errorInfo = createErrorInfo(ErrorValueInfo.ERROR_USE, [tagName]); 198 if (tagName === 'param') { 199 errorInfo = createErrorInfo(ErrorValueInfo.ERROR_MORELABEL, [parameterNum + 1, tagName]); 200 } 201 checkInfoMap[index].illegalTags.push({ 202 checkResult: false, 203 errorInfo, 204 index, 205 }); 206 } 207 }); 208 }); 209 return checkInfoMap; 210} 211 212// 标签重复性检查 213function checkTagsQuantity(comment, index, errorLogs) { 214 const multipleTags = ['throws', 'param']; 215 const tagCountObj = {}; 216 comment.tags.forEach(tag => { 217 if (!tagCountObj[tag.tag]) { 218 tagCountObj[tag.tag] = 0; 219 } 220 tagCountObj[tag.tag] = tagCountObj[tag.tag] + 1; 221 }); 222 for (const tagName in tagCountObj) { 223 if (tagCountObj[tagName] > 1 && multipleTags.indexOf(tagName) < 0) { 224 errorLogs.push({ 225 checkResult: false, 226 errorInfo: createErrorInfo(ErrorValueInfo.ERROR_REPEATLABEL, [tagName]), 227 index, 228 }); 229 } 230 } 231 // interface/typedef互斥校验 232 if (tagCountObj.interface > 0 & tagCountObj.typedef > 0) { 233 errorLogs.push({ 234 checkResult: false, 235 errorInfo: ErrorValueInfo.ERROR_USE_INTERFACE, 236 index, 237 }); 238 } 239} 240 241let paramIndex = 0; 242let throwsIndex = 0; 243 244function checkTagValue(tag, index, node, fileName, errorLogs) { 245 const { JsDocValueChecker } = require('./check_jsdoc_value/check_rest_value'); 246 const checker = JsDocValueChecker[tag.tag]; 247 248 if (checker) { 249 let valueCheckResult; 250 if (tag.tag === 'param' && [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 251 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor].indexOf(node.kind) >= 0) { 252 valueCheckResult = checker(tag, node, fileName, paramIndex++); 253 } else if (tag.tag === 'throws') { 254 valueCheckResult = checker(tag, node, fileName, throwsIndex++); 255 } else { 256 valueCheckResult = checker(tag, node, fileName); 257 } 258 if (!valueCheckResult.checkResult) { 259 valueCheckResult.index = index; 260 // 输出告警 261 errorLogs.push(valueCheckResult); 262 } 263 } 264} 265 266function checkJsDocOfCurrentNode(node, sourcefile, fileName, isGuard) { 267 const checkInfoArray = []; 268 const lastComment = parseJsDoc(node).length > 0 ? [parseJsDoc(node).pop()] : []; 269 const comments = isGuard ? lastComment : parseJsDoc(node); 270 const checkInfoMap = checkJsDocLegality(node, comments, {}); 271 const checkOrderResult = checkApiOrder(comments); 272 checkOrderResult.forEach((result, index) => { 273 checkInfoMap[index.toString()].orderResult = result; 274 }); 275 comments.forEach((comment, index) => { 276 const errorLogs = []; 277 // 继承校验 278 checkInheritTag(comment, node, sourcefile, fileName, index); 279 // 值检验 280 comment.tags.forEach(tag => { 281 const checkAPIDecorator = checkAPITagName(tag, node, sourcefile, fileName, index); 282 if (!checkAPIDecorator.checkResult) { 283 errorLogs.push(checkAPIDecorator); 284 } 285 checkTagValue(tag, index, node, fileName, errorLogs); 286 }); 287 paramIndex = 0; 288 throwsIndex = 0; 289 // 标签数量校验 290 checkTagsQuantity(comment, index, errorLogs); 291 checkInfoMap[index.toString()].illegalTags = checkInfoMap[index.toString()].illegalTags.concat(errorLogs); 292 }); 293 for (const key in checkInfoMap) { 294 checkInfoArray.push(checkInfoMap[key]); 295 } 296 return checkInfoArray; 297} 298exports.checkJsDocOfCurrentNode = checkJsDocOfCurrentNode; 299 300function checkJSDoc(node, sourcefile, fileName, isGuard) { 301 const verificationResult = checkJsDocOfCurrentNode(node, sourcefile, fileName, isGuard); 302 303 verificationResult.forEach(item => { 304 let errorInfo = ''; 305 if (item.missingTags.length > 0) { 306 item.missingTags.forEach(lostLabel => { 307 errorInfo = createErrorInfo(ErrorValueInfo.ERROR_LOST_LABEL, [lostLabel]); 308 addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_SCENE, errorInfo, FileType.JSDOC, 309 ErrorLevel.MIDDLE); 310 }); 311 } 312 if (item.illegalTags.length > 0) { 313 item.illegalTags.forEach(wrongValueLabel => { 314 errorInfo = wrongValueLabel.errorInfo; 315 addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_VALUE, errorInfo, FileType.JSDOC, 316 ErrorLevel.MIDDLE); 317 }); 318 } 319 if (!item.orderResult.checkResult) { 320 errorInfo = item.orderResult.errorInfo; 321 addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, errorInfo, FileType.JSDOC, 322 ErrorLevel.MIDDLE); 323 } 324 }); 325} 326exports.checkJSDoc = checkJSDoc; 327