1/* 2 * Copyright (c) 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 */ 15import path, { ParsedPath } from 'path'; 16import fs, { Stats } from 'fs'; 17import { Workbook, Worksheet } from 'exceljs'; 18import ts, { LineAndCharacter } from 'typescript'; 19import { ApiResultSimpleInfo, ApiResultInfo, ApiResultMessage, ApiCheckInfo, ErrorBaseInfo } from '../typedef/checker/result_type'; 20import { ApiInfo, BasicApiInfo, ClassInfo, ParentClass } from '../typedef/parser/ApiInfoDefination'; 21import { FileUtils } from './FileUtils'; 22import { ApiCheckVersion } from '../coreImpl/checker/config/api_check_version.json'; 23import { PunctuationMark } from './Constant'; 24import { Comment } from '../typedef/parser/Comment'; 25import { currentFilePath } from '../coreImpl/checker/src/api_check_plugin'; 26import { toNumber } from 'lodash'; 27 28 29export class PosOfNode { 30 /** 31 * 获取行列信息 32 * @param { ts.Node } node 33 * @param { ts.Diagnostic } diagnostic 34 */ 35 static getPosOfNode(node: ts.Node, diagnostic: ts.Diagnostic): string { 36 const posOfNode: LineAndCharacter = ts.getLineAndCharacterOfPosition( 37 node.getSourceFile(), 38 diagnostic.start as number 39 ); 40 const location: string = 41 (diagnostic.file?.fileName as string) + `(line: ${posOfNode.line + 1}, col: ${posOfNode.character + 1})`; 42 return location; 43 } 44} 45 46export class CompolerOptions { 47 /** 48 * tsconfig配置项设置 49 */ 50 static getCompolerOptions(): ts.CompilerOptions { 51 const compilerOptions: ts.CompilerOptions = ts.readConfigFile( 52 path.resolve(FileUtils.getBaseDirName(), './tsconfig.json'), 53 ts.sys.readFile 54 ).config.compilerOptions; 55 Object.assign(compilerOptions, { 56 target: 'es2020', 57 jsx: 'preserve', 58 incremental: undefined, 59 declaration: undefined, 60 declarationMap: undefined, 61 emitDeclarationOnly: undefined, 62 outFile: undefined, 63 composite: undefined, 64 tsBuildInfoFile: undefined, 65 noEmit: undefined, 66 isolatedModules: true, 67 paths: undefined, 68 rootDirs: undefined, 69 types: undefined, 70 out: undefined, 71 noLib: undefined, 72 noResolve: true, 73 noEmitOnError: undefined, 74 declarationDir: undefined, 75 suppressOutputPathCheck: true, 76 allowNonTsExtensions: true, 77 }); 78 return compilerOptions; 79 } 80} 81 82export class GenerateFile { 83 /** 84 * 将错误信息输出为txt文件 85 * @param { ApiResultSimpleInfo[] } resultData 86 * @param { string } outputPath 87 * @param { string } option 88 */ 89 static writeFile(resultData: ApiResultMessage[], outputPath: string, option: object): void { 90 const STANDARD_INDENT: number = 2; 91 fs.writeFile( 92 path.resolve(outputPath), 93 JSON.stringify(resultData, null, STANDARD_INDENT), 94 option, 95 (err) => { 96 if (err) { 97 console.error(`ERROR FOR CREATE FILE:${err}`); 98 } else { 99 console.log('API CHECK FINISH!'); 100 } 101 } 102 ); 103 } 104 105 /** 106 * 将错误信息输出为excel文件 107 * @param { ApiResultInfo[] } apiCheckArr 108 */ 109 static async writeExcelFile(apiCheckArr: ApiResultMessage[]): Promise<void> { 110 const workbook: Workbook = new Workbook(); 111 const sheet: Worksheet = workbook.addWorksheet('Js Api', { views: [{ xSplit: 1 }] }); 112 sheet.getRow(1).values = [ 113 'order', 114 'analyzerName', 115 'buggyFilePath', 116 'codeContextStaerLine', 117 'defectLevel', 118 'defectType', 119 'description', 120 'language', 121 'mainBuggyCode', 122 'apiName', 123 'apiType', 124 'hierarchicalRelations', 125 'parentModuleName' 126 ]; 127 for (let i = 1; i <= apiCheckArr.length; i++) { 128 const apiData: ApiResultMessage = apiCheckArr[i - 1]; 129 sheet.getRow(i + 1).values = [ 130 i, 131 apiData.analyzerName, 132 apiData.getFilePath(), 133 apiData.getLocation(), 134 apiData.getLevel(), 135 apiData.getType(), 136 apiData.getMessage(), 137 apiData.language, 138 apiData.getMainBuggyCode(), 139 apiData.getExtendInfo().getApiName(), 140 apiData.getExtendInfo().getApiType(), 141 apiData.getExtendInfo().getHierarchicalRelations(), 142 apiData.getExtendInfo().getParentModuleName() 143 ]; 144 } 145 workbook.xlsx.writeBuffer().then((buffer) => { 146 fs.writeFile(path.resolve(FileUtils.getBaseDirName(), './Js_Api.xlsx'), buffer, function (err) { 147 if (err) { 148 console.error(err); 149 return; 150 } 151 }); 152 }); 153 } 154} 155 156export class ObtainFullPath { 157 /** 158 * 获取仓库中api文件夹下的所有d.ts和d.ets路径 159 * @param { string } dir -api路径 160 * @param { string[] } utFiles -存放具体路径的数组 161 */ 162 static getFullFiles(dir: string, utFiles: string[]): void { 163 try { 164 const files: string[] = fs.readdirSync(dir); 165 files.forEach((element) => { 166 const filePath: string = path.join(dir, element); 167 const status: Stats = fs.statSync(filePath); 168 if (status.isDirectory()) { 169 ObtainFullPath.getFullFiles(filePath, utFiles); 170 } else if (/\.d\.ts/.test(filePath) || /\.d\.ets/.test(filePath) || /\.ts/.test(filePath)) { 171 utFiles.push(filePath); 172 } 173 }); 174 } catch (e) { 175 console.error('ETS ERROR: ' + e); 176 } 177 } 178} 179 180export class CommonFunctions { 181 static getSinceVersion(sinceValue: string): string { 182 return sinceValue.indexOf(PunctuationMark.LEFT_PARENTHESES) !== -1 ? 183 sinceValue.substring(sinceValue.indexOf(PunctuationMark.LEFT_PARENTHESES) + 1, 184 sinceValue.indexOf(PunctuationMark.RIGHT_PARENTHESES)) : sinceValue; 185 } 186 /** 187 * 判断标签是否为官方标签 188 */ 189 static isOfficialTag(tagName: string): boolean { 190 return tagsArrayOfOrder.indexOf(tagName) === -1; 191 } 192 193 /** 194 * Replaces the set placeholder with the target 195 * @param { string } errorInfo 196 * @param { string[] } params 197 * @returns { string } 198 */ 199 static createErrorInfo(errorInfo: string, params: string[]): string { 200 params.forEach((param) => { 201 errorInfo = errorInfo.replace('$$', param); 202 }); 203 return errorInfo; 204 } 205 206 /** 207 * Obtain the current version to be checked 208 * @returns { number } 209 */ 210 static getCheckApiVersion(): string { 211 let checkApiVersion: string = '-1'; 212 try { 213 checkApiVersion = JSON.stringify(ApiCheckVersion); 214 } catch (error) { 215 throw `Failed to read package.json or parse JSON content: ${error}`; 216 } 217 if (!checkApiVersion) { 218 throw 'Please configure the correct API version to be verified'; 219 } 220 return checkApiVersion; 221 } 222 223 static judgeSpecialCase(type: ts.SyntaxKind): string[] { 224 let specialCaseType: string[] = []; 225 if (type === ts.SyntaxKind.TypeLiteral) { 226 specialCaseType = ['object']; 227 } else if (type === ts.SyntaxKind.FunctionType) { 228 specialCaseType = ['function']; 229 } 230 return specialCaseType; 231 } 232 static getExtendsApiValue(singleApi: ApiInfo): string { 233 let extendsApiValue: string = ''; 234 const extendsApiValueArr: string[] = []; 235 const extendsApiArr: ParentClass[] = (singleApi as ClassInfo).getParentClasses(); 236 if (extendsApiArr.length === 0) { 237 return extendsApiValue; 238 } 239 extendsApiArr.forEach(extendsApi => { 240 if (extendsApi.getExtendClass().length !== 0) { 241 extendsApiValueArr.push(extendsApi.getExtendClass()); 242 } 243 }); 244 extendsApiValue = extendsApiValueArr.join(','); 245 return extendsApiValue; 246 } 247 248 static getImplementsApiValue(singleApi: ApiInfo): string { 249 let implementsApiValue: string = ''; 250 const implementsApiArr: ParentClass[] = (singleApi as ClassInfo).getParentClasses(); 251 if (implementsApiArr.length === 0) { 252 return implementsApiValue; 253 } 254 implementsApiArr.forEach(implementsApi => { 255 if (implementsApi.getImplementClass().length !== 0) { 256 implementsApiValue = implementsApi.getImplementClass(); 257 } 258 }); 259 return implementsApiValue; 260 } 261 262 263 static getErrorInfo(singleApi: BasicApiInfo | undefined, apiJsdoc: Comment.JsDocInfo | undefined, filePath: string, 264 errorBaseInfo: ErrorBaseInfo): ApiCheckInfo { 265 let apiInfo: ApiCheckInfo = new ApiCheckInfo(); 266 if (singleApi === undefined) { 267 return apiInfo; 268 } 269 const sinceVersion: number = apiJsdoc === undefined ? -1 : toNumber(apiJsdoc.since); 270 apiInfo 271 .setErrorID(errorBaseInfo.errorID) 272 .setErrorLevel(errorBaseInfo.errorLevel) 273 .setFilePath(filePath) 274 .setApiPostion(singleApi.getPos()) 275 .setErrorType(errorBaseInfo.errorType) 276 .setLogType(errorBaseInfo.logType) 277 .setSinceNumber(sinceVersion) 278 .setApiName(singleApi.getApiName()) 279 .setApiType(singleApi.getApiType()) 280 .setApiText(singleApi.getDefinedText()) 281 .setErrorInfo(errorBaseInfo.errorInfo) 282 .setHierarchicalRelations(singleApi.getHierarchicalRelations().join('|')) 283 .setParentModuleName(singleApi.getParentApi()?.getApiName()); 284 285 return apiInfo; 286 } 287 288 static getMdFiles(url: string): string[] { 289 const mdFiles: string[] = []; 290 const content: string = fs.readFileSync(url, 'utf-8'); 291 const filePathArr: string[] = content.split(/[(\r\n)\r\n]+/); 292 filePathArr.forEach((filePath: string) => { 293 const pathElements: Set<string> = new Set(); 294 CommonFunctions.splitPath(filePath, pathElements); 295 if (!pathElements.has('build-tools')) { 296 mdFiles.push(filePath); 297 } 298 }); 299 return mdFiles; 300 } 301 302 static splitPath(filePath: string, pathElements: Set<string>): void { 303 let spliteResult: ParsedPath = path.parse(filePath); 304 if (spliteResult.base !== '') { 305 pathElements.add(spliteResult.base); 306 CommonFunctions.splitPath(spliteResult.dir, pathElements); 307 } 308 } 309 310 static isAscending(arr: number[]): boolean { 311 for (let i = 1; i < arr.length; i++) { 312 if (arr[i] < arr[i - 1]) { 313 return false; 314 } 315 } 316 return true; 317 } 318} 319 320/** 321 * The order between labels 322 */ 323export const tagsArrayOfOrder: string[] = [ 324 'namespace', 'struct', 'typedef', 'interface', 'extends', 'implements', 'permission', 'enum', 'constant', 'type', 325 'param', 'default', 'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 326 'FAModelOnly', 'stagemodelonly', 'StageModelOnly', 'crossplatform', 'form', 'atomicservice', 'since', 'deprecated', 327 'useinstead', 'test', 'example' 328]; 329 330/** 331 * Official label 332 */ 333export const officialTagArr: string[] = [ 334 'abstract', 'access', 'alias', 'async', 'augments', 'author', 'borrows', 'class', 'classdesc', 'constructs', 335 'copyright', 'event', 'exports', 'external', 'file', 'function', 'generator', 'global', 'hideconstructor', 'ignore', 336 'inheritdoc', 'inner', 'instance', 'lends', 'license', 'listens', 'member', 'memberof', 'mixes', 337 'mixin', 'modifies', 'module', 'package', 'private', 'property', 'protected', 'public', 'requires', 'see', 'summary', 338 'this', 'todo', 'tutorial', 'variation', 'version', 'yields', 'also', 'description', 'kind', 'name', 'undocumented' 339]; 340 341/** 342 * Inherit tag 343 */ 344export const inheritTagArr: string[] = ['test', 'famodelonly', 'FAModelOnly', 'stagemodelonly', 'StageModelOnly', 345 'deprecated', 'systemapi']; 346 347export const followTagArr: string[] = ['atomicservice', 'form']; 348 349/** 350 * Optional tag 351 */ 352export const optionalTags: string[] = [ 353 'static', 'fires', 'systemapi', 'famodelonly', 'FAModelOnly', 'stagemodelonly', 354 'StageModelOnly', 'crossplatform', 'deprecated', 'test', 'form', 'example', 'atomicservice' 355]; 356/** 357 * conditional optional tag 358 */ 359export const conditionalOptionalTags: string[] = ['default', 'permission', 'throws']; 360 361/** 362 * All api types that can use the permission tag. 363 */ 364export const permissionOptionalTags: ts.SyntaxKind[] = [ 365 ts.SyntaxKind.FunctionDeclaration, 366 ts.SyntaxKind.MethodSignature, 367 ts.SyntaxKind.MethodDeclaration, 368 ts.SyntaxKind.CallSignature, 369 ts.SyntaxKind.Constructor, 370 ts.SyntaxKind.PropertyDeclaration, 371 ts.SyntaxKind.PropertySignature, 372 ts.SyntaxKind.VariableStatement, 373]; 374 375/** 376 * Each api type corresponds to a set of available tags. 377 */ 378export const apiLegalityCheckTypeMap: Map<ts.SyntaxKind, string[]> = new Map([ 379 [ts.SyntaxKind.CallSignature, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']], 380 [ts.SyntaxKind.ClassDeclaration, ['extends', 'implements', 'syscap', 'since']], 381 [ts.SyntaxKind.Constructor, ['param', 'syscap', 'permission', 'throws', 'syscap', 'since']], 382 [ts.SyntaxKind.EnumDeclaration, ['enum', 'syscap', 'since']], 383 [ts.SyntaxKind.FunctionDeclaration, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']], 384 [ts.SyntaxKind.InterfaceDeclaration, ['typedef', 'extends', 'syscap', 'since']], 385 [ts.SyntaxKind.MethodDeclaration, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']], 386 [ts.SyntaxKind.MethodSignature, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']], 387 [ts.SyntaxKind.ModuleDeclaration, ['namespace', 'syscap', 'since']], 388 [ts.SyntaxKind.PropertyDeclaration, ['type', 'default', 'permission', 'throws', 'readonly', 'syscap', 'since']], 389 [ts.SyntaxKind.PropertySignature, ['type', 'default', 'permission', 'throws', 'readonly', 'syscap', 'since']], 390 [ts.SyntaxKind.VariableStatement, ['constant', 'default', 'permission', 'throws', 'syscap', 'since']], 391 [ts.SyntaxKind.TypeAliasDeclaration, ['syscap', 'since', 'typedef', 'param', 'returns', 'throws']], 392 [ts.SyntaxKind.EnumMember, ['syscap', 'since']], 393 [ts.SyntaxKind.NamespaceExportDeclaration, ['syscap', 'since']], 394 [ts.SyntaxKind.TypeLiteral, ['syscap', 'since']], 395 [ts.SyntaxKind.LabeledStatement, ['syscap', 'since']], 396 [ts.SyntaxKind.StructDeclaration, ['struct', 'syscap', 'since']], 397]); 398 399/** 400 * An array of online error messages 401 */ 402export let compositiveResult: ApiResultSimpleInfo[] = []; 403 404/** 405 * An array of local error messages 406 */ 407export const compositiveLocalResult: ApiResultInfo[] = []; 408 409export let apiCheckResult: ApiResultMessage[] = []; 410 411export let hierarchicalRelationsSet: Set<string> = new Set(); 412 413export function cleanApiCheckResult() { 414 apiCheckResult = []; 415 compositiveResult = []; 416 hierarchicalRelationsSet = new Set(); 417} 418 419export const punctuationMarkSet: Set<string> = new Set(['\\{', '\\}', '\\(', '\\)', '\\[', '\\]', '\\@', '\\.', '\\:', 420 '\\,', '\\;', '\\(', '\\)', '\\"', '\\/', '\\_', '\\-', '\\=', '\\?', '\\<', '\\>', '\\,', '\\!', '\\#', '\:', '\,', 421 '\\:', '\\|', '\\%', '\\&', '\\¡', '\\¢', '\\+', '\\`', '\\\\', '\\\'']); 422 423export const throwsTagDescriptionArr: string[] = [ 424 'Parameter error. Possible causes:', 425 'Mandatory parameters are left unspecified', 426 'Incorrect parameter types', 427 'Parameter verification failed' 428];