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