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