1/* 2 * Copyright (c) 2021-2022 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 path = require('path'); 17const ExcelJS = require('exceljs'); 18const cm = require('comment-parser'); 19function requireTypescriptModule() { 20 const buildOption = require('./build.json'); 21 if (buildOption.isBundle) { 22 return require('typescript'); 23 } 24 const tsPathArray = [ 25 path.resolve(__dirname, '../node_modules/typescript'), 26 path.resolve(__dirname, '../../node_modules/typescript') 27 ]; 28 if (fs.existsSync(tsPathArray[0])) { 29 return require(tsPathArray[0]); 30 } else if (fs.existsSync(tsPathArray[1])) { 31 return require(tsPathArray[1]); 32 } 33 return null; 34} 35exports.requireTypescriptModule = requireTypescriptModule; 36const ts = requireTypescriptModule(); 37 38const commentNodeWhiteList = [ 39 ts.SyntaxKind.PropertySignature, ts.SyntaxKind.CallSignature, ts.SyntaxKind.MethodSignature, 40 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.EnumMember, ts.SyntaxKind.VariableStatement, 41 ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.Constructor, ts.SyntaxKind.ModuleDeclaration, 42 ts.SyntaxKind.NamespaceExportDeclaration, ts.SyntaxKind.ClassDeclaration, ts.SyntaxKind.InterfaceDeclaration, 43 ts.SyntaxKind.EnumDeclaration, ts.SyntaxKind.Parameter, ts.SyntaxKind.TypeLiteral, ts.SyntaxKind.FunctionDeclaration, 44 ts.SyntaxKind.LabeledStatement, ts.SyntaxKind.TypeAliasDeclaration 45]; 46exports.commentNodeWhiteList = commentNodeWhiteList; 47 48const tagsArrayOfOrder = [ 49 'namespace', 'extends', 'typedef', 'interface', 'permission', 'enum', 'constant', 'type', 'param', 'default', 50 'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 'FAModelOnly', 51 'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'test', 'form', 'example' 52]; 53exports.tagsArrayOfOrder = tagsArrayOfOrder; 54 55function getAPINote(node) { 56 const apiLength = node.getText().length; 57 const apiFullLength = node.getFullText().length; 58 return node.getFullText().substring(0, apiFullLength - apiLength); 59} 60exports.getAPINote = getAPINote; 61 62function hasAPINote(node) { 63 if (!node) { 64 return false; 65 } 66 const apiNote = getAPINote(node).replace(/[\s]/g, ''); 67 if (apiNote && apiNote.length !== 0) { 68 return true; 69 } 70 return false; 71} 72exports.hasAPINote = hasAPINote; 73 74function removeDir(url) { 75 const statObj = fs.statSync(url); 76 if (statObj.isDirectory()) { 77 let dirs = fs.readdirSync(url); 78 dirs = dirs.map(dir => path.join(url, dir)); 79 for (let i = 0; i < dirs.length; i++) { 80 removeDir(dirs[i]); 81 } 82 fs.rmdirSync(url); 83 } else { 84 fs.unlinkSync(url); 85 } 86} 87exports.removeDir = removeDir; 88 89function writeResultFile(resultData, outputPath, option) { 90 const STANDARD_INDENT = 2; 91 fs.writeFile(path.resolve(__dirname, outputPath), JSON.stringify(resultData, null, STANDARD_INDENT), option, err => { 92 if (err) { 93 console.error(`ERROR FOR CREATE FILE:${err}`); 94 } else { 95 console.log('API CHECK FINISH!'); 96 } 97 }); 98} 99exports.writeResultFile = writeResultFile; 100 101function overwriteIndexOf(item, array) { 102 const indexArr = []; 103 for (let i = 0; i < array.length; i++) { 104 if (array[i] === item) { 105 indexArr.push(i); 106 } 107 } 108 return indexArr; 109} 110exports.overwriteIndexOf = overwriteIndexOf; 111 112const ErrorType = { 113 UNKNOW_DECORATOR: { 114 id: 0, 115 description: 'unknow decorator', 116 }, 117 MISSPELL_WORDS: { 118 id: 1, 119 description: 'misspell words', 120 }, 121 NAMING_ERRORS: { 122 id: 2, 123 description: 'naming errors', 124 }, 125 UNKNOW_PERMISSION: { 126 id: 3, 127 description: 'unknow permission', 128 }, 129 UNKNOW_SYSCAP: { 130 id: 4, 131 description: 'unknow syscap', 132 }, 133 UNKNOW_DEPRECATED: { 134 id: 5, 135 description: 'unknow deprecated', 136 }, 137 WRONG_ORDER: { 138 id: 6, 139 description: 'wrong order', 140 }, 141 WRONG_VALUE: { 142 id: 7, 143 description: 'wrong value', 144 }, 145 WRONG_SCENE: { 146 id: 8, 147 description: 'wrong scene', 148 }, 149 PARAMETER_ERRORS: { 150 id: 9, 151 description: 'wrong parameter', 152 }, 153 API_PAIR_ERRORS: { 154 id: 10, 155 description: 'limited api pair errors', 156 }, 157 ILLEGAL_ANY: { 158 id: 11, 159 description: 'illegal any', 160 }, 161 API_CHANGE_ERRORS: { 162 id: 12, 163 description: 'api change errors', 164 }, 165}; 166exports.ErrorType = ErrorType; 167 168const LogType = { 169 LOG_API: 'Api', 170 LOG_JSDOC: 'JsDoc', 171 LOG_FILE: 'File', 172}; 173exports.LogType = LogType; 174 175const ErrorLevel = { 176 HIGH: 3, 177 MIDDLE: 2, 178 LOW: 1, 179}; 180exports.ErrorLevel = ErrorLevel; 181 182const FileType = { 183 API: 'Api', 184 JSDOC: 'JsDoc', 185}; 186exports.FileType = FileType; 187 188const apiCheckArr = []; 189exports.apiCheckArr = apiCheckArr; 190 191const apiCheckInfoArr = []; 192exports.apiCheckInfoArr = apiCheckInfoArr; 193 194class ApiCheckResultClass { 195 formatCheckResult = true; 196} 197exports.ApiCheckResult = new ApiCheckResultClass(); 198 199async function excelApiCheckResult(apiCheckArr) { 200 const workbook = new ExcelJS.Workbook(); 201 const sheet = workbook.addWorksheet('Js Api', { views: [{ xSplit: 1 }] }); 202 sheet.getRow(1).values = ['order', 'errorType', 'fileName', 'apiName', 'apiContent', 'type', 'errorInfo', 'version', 'model']; 203 for (let i = 1; i <= apiCheckArr.length; i++) { 204 const apiData = apiCheckArr[i - 1]; 205 sheet.getRow(i + 1).values = [i, apiData.errorType, apiData.fileName, apiData.apiName, apiData.apiFullText, 206 apiData.type, apiData.errorInfo, apiData.version, apiData.basename]; 207 } 208 const buffer = await workbook.xlsx.writeBuffer(); 209 fs.writeFile('Js_Api.xlsx', buffer, function (err) { 210 if (err) { 211 console.error(err); 212 return; 213 } 214 }); 215 return buffer; 216} 217exports.excelApiCheckResult = excelApiCheckResult; 218 219function getApiInfo(node) { 220 const notesStr = getAPINote(node); 221 const apiInfo = {}; 222 const versionArr = []; 223 if (notesStr !== '') { 224 if (/\@systemapi/g.test(notesStr)) { 225 apiInfo.isSystemApi = 'system api'; 226 } 227 if (/\@constant/g.test(notesStr)) { 228 apiInfo.isConstant = true; 229 } 230 if (/\@since\s*(\d+)/g.test(notesStr)) { 231 notesStr.replace(/\@since\s*(\d+)/g, (versionInfo) => { 232 versionArr.push(versionInfo); 233 apiInfo.version = versionInfo.replace(/\@since/g, '').trim(); 234 }); 235 apiInfo.humpVersion = versionArr[0].replace(/\@since/g, '').trim(); 236 } 237 if (/\@deprecated.*since\s*(\d+)/g.test(notesStr)) { 238 notesStr.replace(/\@deprecated.*since\s*(\d+)/g, 239 versionInfo => { 240 apiInfo.deprecated = versionInfo.replace( 241 /\@deprecated.*since\s*/g, '').trim(); 242 }); 243 } 244 if (/\@famodelonly/g.test(notesStr)) { 245 notesStr.replace(/\@famodelonly/g, modelInfo => { 246 apiInfo.model = modelInfo; 247 }); 248 } else if (/\@stagemodelonly/g.test(notesStr)) { 249 notesStr.replace(/\@stagemodelonly/g, modelInfo => { 250 apiInfo.model = modelInfo; 251 }); 252 } 253 if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) { 254 notesStr.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => { 255 apiInfo.sysCap = sysCapInfo.replace(/\@syscap/g, '').trim(); 256 }); 257 } 258 if (/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) { 259 notesStr.replace(/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, 260 permissionInfo => { 261 apiInfo.permission = 262 permissionInfo.replace(/\@permission/g, '').trim(); 263 }); 264 } 265 } 266 return apiInfo; 267} 268exports.getApiInfo = getApiInfo; 269 270function getApiVersion(node) { 271 if (getApiInfo(node).humpVersion) { 272 return getApiInfo(node).humpVersion; 273 } else if (node.parent) { 274 return getApiVersion(node.parent); 275 } else { 276 return 'NA'; 277 } 278} 279exports.getApiVersion = getApiVersion; 280 281function parseJsDoc(node) { 282 if (!hasAPINote(node)) { 283 return []; 284 } else { 285 return cm.parse(getAPINote(node)); 286 } 287} 288exports.parseJsDoc = parseJsDoc; 289 290function getDeclareValue(declareValue) { 291 let apiDeclareValue = ''; 292 if (!declareValue) { 293 return apiDeclareValue; 294 } 295 if (ts.isFunctionTypeNode(declareValue)) { 296 apiDeclareValue = 'Function'; 297 } else if (ts.isTypeLiteralNode(declareValue)) { 298 apiDeclareValue = 'object'; 299 } else { 300 apiDeclareValue = declareValue.getText().replace(/\n|\r|\s/g, ''); 301 } 302 return apiDeclareValue; 303} 304exports.getDeclareValue = getDeclareValue; 305 306const systemPermissionFile = path.resolve(__dirname, '../../../../../', 307 'base/global/system_resources/systemres/main/config.json'); 308 309exports.systemPermissionFile = systemPermissionFile; 310 311exports.checkOption = { 312 permissionContent: undefined, 313}; 314 315const inheritArr = ['test', 'famodelonly', 'FAModelOnly', 'stagemodelonly', 'StageModelOnly', 'deprecated', 316 'systemapi']; 317exports.inheritArr = inheritArr; 318 319const ErrorValueInfo = { 320 ERROR_INFO_VALUE_EXTENDS: 'the [extends] tag value is incorrect. Please check if the tag value matches the inherited class name.', 321 ERROR_INFO_VALUE_ENUM: 'the [enum] tag type is incorrect. Please check if the tag type is { string } or { number }', 322 ERROR_INFO_VALUE_SINCE: 'the [since] tag value is incorrect. Please check if the tag value is a numerical value', 323 ERROR_INFO_RETURNS: 'the [returns] tag was used incorrectly. The returns tag should not be used when the return type is void', 324 ERROR_INFO_VALUE_RETURNS: 'the [returns] tag type is incorrect. Please check if the tag type is consistent with the return type', 325 ERROR_INFO_VALUE_USEINSTEAD: 'the [useinstead] tag value is incorrect. Please check the usage method', 326 ERROR_INFO_VALUE_TYPE: 'the [type] tag type is incorrect. Please check if the type matches the attribute type', 327 ERROR_INFO_VALUE_DEFAULT: 'the [default] tag value is incorrect. Please supplement the default value', 328 ERROR_INFO_VALUE_PERMISSION: 'the [permission] tag value is incorrect. Please check if the permission field has been configured or update the configuration file', 329 ERROR_INFO_VALUE_DEPRECATED: 'the [deprecated] tag value is incorrect. Please check the usage method', 330 ERROR_INFO_VALUE_SYSCAP: 'the [syscap] tag value is incorrect. Please check if the syscap field is configured', 331 ERROR_INFO_VALUE_NAMESPACE: 'the [namespace] tag value is incorrect. Please check if it matches the namespace name', 332 ERROR_INFO_VALUE_INTERFACE: 'the [interface] label value is incorrect. Please check if it matches the interface name', 333 ERROR_INFO_VALUE_TYPEDEF: 'the [typedef] tag value is incorrect. Please check if it matches the interface name', 334 ERROR_INFO_TYPE_PARAM: 'the type of the [$$] [param] tag is incorrect. Please check if it matches the type of the [$$] parameter', 335 ERROR_INFO_VALUE_PARAM: 'the value of the [$$] [param] tag is incorrect. Please check if it matches the [$$] parameter name', 336 ERROR_INFO_VALUE1_THROWS: 'the type of the [$$] [throws] tag is incorrect. Please fill in [BusinessError]', 337 ERROR_INFO_VALUE2_THROWS: 'the type of the [$$] [throws] tag is incorrect. Please check if the tag value is a numerical value', 338 ERROR_INFO_INHERIT: 'it was detected that there is an inheritable label [$$] in the current file, but there are child nodes without this label', 339 ERROR_ORDER: 'JSDoc label order error, please make adjustments', 340 ERROR_LABELNAME: 'the [$$] tag does not exist. Please use a valid JSDoc tag', 341 ERROR_LOST_LABEL: 'JSDoc tag validity verification failed. Please confirm if the [$$] tag is missing', 342 ERROR_USE: 'JSDoc label validity verification failed. The [$$] label is not allowed. Please check the label usage method.', 343 ERROR_MORELABEL: 'JSDoc tag validity verification failed. The [$$] [$$] tag is redundant. Please check if the tag should be deleted.', 344 ERROR_REPEATLABEL: 'the validity verification of the JSDoc tag failed. The [$$] tag is not allowed to be reused, please delete the extra tags', 345 ERROR_USE_INTERFACE: 'the validity verification of the JSDoc tag failed. The [interface] tag and [typedef] tag are not allowed to be used simultaneously. Please confirm the interface class.', 346 ERROR_EVENT_NAME_STRING: 'The event name should be string.', 347 ERROR_EVENT_NAME_NULL: 'The event name cannot be Null value.', 348 ERROR_EVENT_NAME_SMALL_HUMP: 'The event name should be named by small hump. (Received [\'$$\'])', 349 ERROR_EVENT_CALLBACK_OPTIONAL: 'The callback parameter of off function should be optional.', 350 ERROR_EVENT_CALLBACK_MISSING: 'The off functions of one single event should have at least one callback parameter, and the callback parameter should be the last parameter.', 351 ERROR_EVENT_ON_AND_OFF_PAIR: 'The on and off event subscription methods do not appear in pair.', 352 ILLEGAL_USE_ANY: 'Illegal [any] keyword used in the API', 353 ERROR_CHANGES_VERSION: 'Please check if the changed API version number is 10.', 354 ERROR_CHANGES_API_HISTORY_PARAM_REQUIRED_CHANGE: 'Forbid changes: Optional parameters cannot be changed to required parameters.', 355 ERROR_CHANGES_API_HISTORY_PARAM_RANGE_CHANGE: 'Forbid changes: Parameters type range cannot be reduced.', 356 ERROR_CHANGES_API_HISTORY_PARAM_WITHOUT_TYPE_CHANGE: 'Forbid changes: Parameters Parameter must be defined by type.', 357 ERROR_CHANGES_API_HISTORY_PARAM_TYPE_CHANGE: 'Forbid changes: Parameters type cannot be modified.', 358 ERROR_CHANGES_API_HISTORY_PARAM_POSITION_CHANGE: 'Forbid changes: Parameters position not be allowed to be modified.', 359 ERROR_CHANGES_API_NEW_REQUIRED_PARAM: 'Forbid changes: Required parameters cannot be created.', 360 ERROR_CHANGES_API_DELETE_PARAM: 'Forbid changes: Parameters cannot be deleted.', 361 ERROR_CHANGES_DEPRECATED: 'Forbid changes: The api has deprecated tag.', 362 ERROR_CHANGES_JSDOC_NUMBER: 'Forbid changes: API changes must add a new section of JSDoc.', 363 ERROR_CHANGES_JSDOC_CHANGE: 'Forbid changes: Previous JSDoc cannot be changed.', 364 ERROR_CHANGES_JSDOC_TRROWS: 'Forbid changes: Throws tag cannot be created.', 365 ERROR_CHANGES_JSDOC_PERMISSION: 'Forbid changes: Permission tag cannot be created or modified.', 366}; 367exports.ErrorValueInfo = ErrorValueInfo; 368 369const DIFF_INFO = { 370 NEW_JSDOCS_LENGTH:1, 371 NEW_JSDOC_INDEX:2, 372}; 373exports.DIFF_INFO = DIFF_INFO; 374 375/** 376 * link error message 377 */ 378function createErrorInfo(errorInfo, params) { 379 params.forEach((param) => { 380 errorInfo = errorInfo.replace('$$', param); 381 }); 382 return errorInfo; 383} 384exports.createErrorInfo = createErrorInfo; 385 386/** 387 * judge if it is an API file for Arkui 388 */ 389function isArkUIApiFile(fileName) { 390 if (fileName.indexOf('component\\ets\\') >= 0 || fileName.indexOf('component/ets/') >= 0) { 391 return true; 392 } 393 return false; 394} 395exports.isArkUIApiFile = isArkUIApiFile; 396 397function isWhiteListFile(fileName, whiteList) { 398 for (let i = 0; i < whiteList.length; i++) { 399 if (path.normalize(fileName).indexOf(path.normalize(whiteList[i])) !== -1) { 400 return true; 401 } 402 } 403 return false; 404} 405exports.isWhiteListFile = isWhiteListFile; 406 407function getCheckApiVersion() { 408 const packageJsonPath = path.join(__dirname, '../package.json'); 409 let packageJson; 410 let checkApiVersion; 411 try { 412 const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); 413 packageJson = JSON.parse(packageJsonContent); 414 checkApiVersion = packageJson.checkApiVersion; 415 } catch (error) { 416 throw `Failed to read package.json or parse JSON content: ${error}`; 417 } 418 if (!checkApiVersion) { 419 throw 'Please configure the correct API version to be verified'; 420 } 421 return checkApiVersion; 422} 423exports.getCheckApiVersion = getCheckApiVersion; 424 425const OptionalSymbols = { 426 QUERY: '?', 427 LEFT_BRACKET: '[', 428 RIGHT_BRACKET: ']', 429 LEFT_BRACE: '{', 430 RIGHT_BRACE: '}', 431 LEFT_PARENTHESES: '(', 432 RIGHT_PARENTHESES: ')' 433}; 434exports.OptionalSymbols = OptionalSymbols; 435 436function removeDuplicateObj(array) { 437 const newArr = []; 438 const errorInfoSet = new Set(); 439 for (const errorInfo of array) { 440 if (!errorInfoSet.has(JSON.stringify(errorInfo))) { 441 errorInfoSet.add(JSON.stringify(errorInfo)); 442 newArr.push(errorInfo); 443 } 444 } 445 return newArr; 446}; 447exports.removeDuplicateObj = removeDuplicateObj; 448 449// check the api version 450function checkVersionNeedCheck(node) { 451 const apiVersion = getApiVersion(node); 452 const apiCheckVersion = getCheckApiVersion(); 453 if (parseInt(apiVersion) >= parseInt(apiCheckVersion)) { 454 return true; 455 } 456 return false; 457} 458exports.checkVersionNeedCheck = checkVersionNeedCheck; 459 460const FUNCTION_TYPES = [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature, 461 ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor]; 462exports.FUNCTION_TYPES = FUNCTION_TYPES; 463 464function splitPath(filePath, pathElements) { 465 let spliteResult = path.parse(filePath); 466 if (spliteResult.base !== '') { 467 pathElements.add(spliteResult.base); 468 splitPath(spliteResult.dir, pathElements); 469 } 470} 471exports.splitPath = splitPath;