1/* 2 * Copyright (c) 2024 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 16import ts from 'typescript'; 17import path from 'path'; 18import fs from 'fs'; 19 20import { 21 projectConfig, 22 extendSdkConfigs, 23 globalProgram, 24 ohosSystemModulePaths, 25 systemModules, 26 allModulesPaths, 27 ohosSystemModuleSubDirPaths 28} from '../../../main'; 29import { 30 LogType, 31 LogInfo, 32 IFileLog 33} from '../../utils'; 34import { type ResolveModuleInfo } from '../../ets_checker'; 35import { 36 PERMISSION_TAG_CHECK_NAME, 37 PERMISSION_TAG_CHECK_ERROR, 38 SYSTEM_API_TAG_CHECK_NAME, 39 SYSTEM_API_TAG_CHECK_WARNING, 40 TEST_TAG_CHECK_NAME, 41 TEST_TAG_CHECK_ERROR, 42 SYSCAP_TAG_CHECK_NAME, 43 SYSCAP_TAG_CONDITION_CHECK_WARNING, 44 SYSCAP_TAG_CHECK_WARNING, 45 CANIUSE_FUNCTION_NAME, 46 FORM_TAG_CHECK_NAME, 47 FORM_TAG_CHECK_ERROR, 48 FIND_MODULE_WARNING, 49 CROSSPLATFORM_TAG_CHECK_NAME, 50 CROSSPLATFORM_TAG_CHECK_ERROER, 51 DEPRECATED_TAG_CHECK_NAME, 52 DEPRECATED_TAG_CHECK_WARNING, 53 FA_TAG_CHECK_NAME, 54 FA_TAG_HUMP_CHECK_NAME, 55 FA_TAG_CHECK_ERROR, 56 STAGE_TAG_CHECK_NAME, 57 STAGE_TAG_HUMP_CHECK_NAME, 58 STAGE_TAG_CHECK_ERROR, 59 STAGE_COMPILE_MODE, 60 ATOMICSERVICE_BUNDLE_TYPE, 61 ATOMICSERVICE_TAG_CHECK_NAME, 62 ATOMICSERVICE_TAG_CHECK_ERROER, 63 ATOMICSERVICE_TAG_CHECK_VERSION, 64 RUNTIME_OS_OH, 65 CONSTANT_STEP_0, 66 CONSTANT_STEP_1, 67 CONSTANT_STEP_2, 68 CONSTANT_STEP_3, 69 GLOBAL_DECLARE_WHITE_LIST 70} from './api_check_define'; 71import { JsDocCheckService } from './api_check_permission'; 72 73/** 74 * bundle info 75 * 76 * @interface BundleInfo 77 */ 78interface BundleInfo { 79 bundlePath: string; 80 bundleVersion: string; 81} 82 83export interface CheckValidCallbackInterface { 84 (jsDocTag: ts.JSDocTag, config: ts.JsDocNodeCheckConfigItem): boolean; 85} 86 87export interface CheckJsDocSpecialValidCallbackInterface { 88 (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; 89} 90 91/** 92 * get the bundleInfo of ohm 93 * 94 * @param {string} modulePath 95 * @return {BundleInfo} 96 */ 97function parseOhmBundle(modulePath: string): BundleInfo { 98 const apiCode: string = fs.readFileSync(modulePath, { encoding: 'utf-8' }); 99 const bundleTags: string[] = apiCode.match(/@bundle.+/g); 100 const bundleInfo: BundleInfo = { 101 bundlePath: '', 102 bundleVersion: '' 103 }; 104 if (bundleTags && bundleTags.length > CONSTANT_STEP_0) { 105 const bundleTag: string = bundleTags[CONSTANT_STEP_0]; 106 const bundleInfos: string[] = bundleTag.split(' '); 107 if (bundleInfos.length === CONSTANT_STEP_3) { 108 bundleInfo.bundlePath = bundleInfos[CONSTANT_STEP_1]; 109 bundleInfo.bundleVersion = bundleInfos[CONSTANT_STEP_2]; 110 } 111 } 112 return bundleInfo; 113} 114 115/** 116 * jude a version string , string has two format 117 * xx:is a number and need greater than 10 118 * x.x.x: a string join '.', the first part and second part is number and need greater than 4.1 119 * 120 * @param {string} bundleVersion - version string 121 * @returns {boolean} 122 */ 123function checkBundleVersion(bundleVersion: string): boolean { 124 const compatibleSdkVersion: string = projectConfig.compatibleSdkVersion; 125 let bundleVersionNumber: number = 0; 126 const bundleVersionArr = bundleVersion.match(/(?<=\().*(?=\))/g); 127 if (bundleVersionArr && bundleVersionArr.length === 1) { 128 bundleVersionNumber = Number(bundleVersionArr[CONSTANT_STEP_0]); 129 } else { 130 bundleVersionNumber = Number(bundleVersion); 131 } 132 if (bundleVersion && bundleVersion !== '' && !isNaN(bundleVersionNumber) && 133 !isNaN(Number(compatibleSdkVersion)) && Number(compatibleSdkVersion) >= bundleVersionNumber) { 134 return true; 135 } 136 return false; 137} 138 139/** 140 * get the real path about a list in module path 141 * 142 * @param {string[]} apiDirs - file list 143 * @param {string} moduleName - module dir 144 * @param {string[]} exts - ext 145 * @returns {ResolveModuleInfo} 146 */ 147export function getRealModulePath(apiDirs: string[], moduleName: string, exts: string[]): ResolveModuleInfo { 148 const resolveResult: ResolveModuleInfo = { 149 modulePath: '', 150 isEts: true 151 }; 152 for (let i = 0; i < apiDirs.length; i++) { 153 const dir = apiDirs[i]; 154 for (let i = 0; i < exts.length; i++) { 155 const ext = exts[i]; 156 const moduleDir = path.resolve(dir, moduleName + ext); 157 if (!fs.existsSync(moduleDir)) { 158 continue; 159 } 160 resolveResult.modulePath = moduleDir; 161 if (ext === '.d.ts') { 162 resolveResult.isEts = false; 163 } 164 break; 165 } 166 } 167 return resolveResult; 168} 169 170/** 171 * get a request path about ohos 172 * 173 * @param {string} moduleRequest - import request path 174 * @param {string} _ - import request path 175 * @param {number} moduleType 176 * @param {string} systemKey 177 * @returns {string} 178 */ 179export function moduleRequestCallback(moduleRequest: string, _: string, 180 moduleType: string, systemKey: string): string { 181 for (const config of extendSdkConfigs.values()) { 182 if (config.prefix === '@arkui-x') { 183 continue; 184 } 185 if (moduleRequest.startsWith(config.prefix + '.')) { 186 let compileRequest: string = `${config.prefix}:${systemKey}`; 187 const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(config.apiPath, moduleRequest, 188 ['.d.ts', '.d.ets']); 189 const modulePath: string = resolveModuleInfo.modulePath; 190 if (!fs.existsSync(modulePath)) { 191 return compileRequest; 192 } 193 const bundleInfo: BundleInfo = parseOhmBundle(modulePath); 194 if (checkBundleVersion(bundleInfo.bundleVersion)) { 195 compileRequest = `@bundle:${bundleInfo.bundlePath}`; 196 } 197 return compileRequest; 198 } 199 } 200 return ''; 201} 202 203/** 204 * check arkui dependences in ts files 205 * api check from sdk 206 * 207 * @param {ts.TypeReferenceNode} node - typeReferenceNode 208 * @param {IFileLog} transformLog - log info 209 */ 210export function checkTypeReference(node: ts.TypeReferenceNode, transformLog: IFileLog): void { 211 const fileName: string = transformLog.sourceFile.fileName; 212 const currentTypeName: string = node.getText(); 213 if (/(?<!\.d)\.ts$/g.test(fileName)) { 214 const checker: ts.TypeChecker = globalProgram.checker; 215 if (!checker) { 216 return; 217 } 218 const type: ts.Type = checker.getTypeAtLocation(node); 219 let sourceFile: ts.SourceFile | undefined; 220 if (type && type.aliasSymbol && type.aliasSymbol.declarations && type.aliasSymbol.declarations.length > 0) { 221 sourceFile = ts.getSourceFileOfNode(type.aliasSymbol.declarations[0]); 222 } else if (type && type.symbol && type.symbol.declarations && type.symbol.declarations.length > 0) { 223 sourceFile = ts.getSourceFileOfNode(type.symbol.declarations[0]); 224 } 225 if (!sourceFile) { 226 return; 227 } 228 const sourceBaseName: string = path.basename(sourceFile.fileName); 229 if (isArkuiDependence(sourceFile.fileName) && 230 sourceBaseName !== 'common_ts_ets_api.d.ts' && 231 sourceBaseName !== 'global.d.ts' 232 ) { 233 // TODO: change to error 234 transformLog.errors.push({ 235 type: LogType.WARN, 236 message: `Cannot find name '${currentTypeName}'.`, 237 pos: node.getStart() 238 }); 239 } else if (GLOBAL_DECLARE_WHITE_LIST.has(currentTypeName) && 240 ohosSystemModulePaths.includes(sourceFile.fileName.replace(/\//g, '\\'))) { 241 transformLog.errors.push({ 242 type: LogType.WARN, 243 message: `Cannot find name '${currentTypeName}'.`, 244 pos: node.getStart() 245 }); 246 } 247 } 248} 249 250/** 251 * get jsDocNodeCheckConfigItem object 252 * 253 * @param {string[]} tagName - tag name 254 * @param {string} message - error message 255 * @param {ts.DiagnosticCategory} type - error type 256 * @param {boolean} tagNameShouldExisted - tag is required 257 * @param {CheckValidCallbackInterface} [checkValidCallback] 258 * @param {CheckJsDocSpecialValidCallbackInterface} [checkJsDocSpecialValidCallback] 259 * @returns {ts.JsDocNodeCheckConfigItem} 260 */ 261function getJsDocNodeCheckConfigItem(tagName: string[], message: string, needConditionCheck: boolean, 262 type: ts.DiagnosticCategory, specifyCheckConditionFuncName: string, 263 tagNameShouldExisted: boolean, checkValidCallback?: CheckValidCallbackInterface, 264 checkJsDocSpecialValidCallback?: CheckJsDocSpecialValidCallbackInterface): ts.JsDocNodeCheckConfigItem { 265 return { 266 tagName: tagName, 267 message: message, 268 needConditionCheck: needConditionCheck, 269 type: type, 270 specifyCheckConditionFuncName: specifyCheckConditionFuncName, 271 tagNameShouldExisted: tagNameShouldExisted, 272 checkValidCallback: checkValidCallback, 273 checkJsDocSpecialValidCallback: checkJsDocSpecialValidCallback 274 }; 275} 276 277/** 278 * judge a file is card file 279 * 280 * @param {string} file - file path 281 * @returns {boolean} 282 */ 283export function isCardFile(file: string): boolean { 284 for (const key in projectConfig.cardEntryObj) { 285 if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) { 286 return true; 287 } 288 } 289 return false; 290} 291 292const jsDocNodeCheckConfigCache: Map<string, Map<string, ts.JsDocNodeCheckConfig>> = new Map<string, Map<string, ts.JsDocNodeCheckConfig>>(); 293let permissionsArray: string[] = []; 294/** 295 * get tagName where need to be determined based on the file path 296 * 297 * @param {string} fileName - file name 298 * @param {string} sourceFileName - resource reference path 299 * @returns {ts.JsDocNodeCheckConfig} 300 */ 301export function getJsDocNodeCheckConfig(fileName: string, sourceFileName: string): ts.JsDocNodeCheckConfig { 302 let byFileName: Map<string, ts.JsDocNodeCheckConfig> | undefined = jsDocNodeCheckConfigCache.get(fileName); 303 if (byFileName === undefined) { 304 byFileName = new Map<string, ts.JsDocNodeCheckConfig>(); 305 jsDocNodeCheckConfigCache.set(fileName, byFileName); 306 } 307 let result: ts.JsDocNodeCheckConfig | undefined = byFileName.get(sourceFileName); 308 if (result !== undefined) { 309 return result; 310 } 311 let needCheckResult: boolean = false; 312 const checkConfigArray: ts.JsDocNodeCheckConfigItem[] = []; 313 const apiName: string = path.basename(fileName); 314 const sourceBaseName: string = path.basename(sourceFileName); 315 if (/(?<!\.d)\.ts$/g.test(fileName) && isArkuiDependence(sourceFileName) && 316 sourceBaseName !== 'common_ts_ets_api.d.ts' && sourceBaseName !== 'global.d.ts') { 317 checkConfigArray.push(getJsDocNodeCheckConfigItem([], FIND_MODULE_WARNING, false, ts.DiagnosticCategory.Warning, 318 '', true)); 319 } 320 if (!systemModules.includes(apiName) && (allModulesPaths.includes(path.normalize(sourceFileName)) || 321 isArkuiDependence(sourceFileName))) { 322 permissionsArray = projectConfig.requestPermissions; 323 checkConfigArray.push(getJsDocNodeCheckConfigItem([DEPRECATED_TAG_CHECK_NAME], DEPRECATED_TAG_CHECK_WARNING, false, 324 ts.DiagnosticCategory.Warning, '', false)); 325 checkConfigArray.push(getJsDocNodeCheckConfigItem([SYSTEM_API_TAG_CHECK_NAME], SYSTEM_API_TAG_CHECK_WARNING, false, 326 ts.DiagnosticCategory.Warning, '', false)); 327 // TODO: the third param is to be opened 328 checkConfigArray.push(getJsDocNodeCheckConfigItem([SYSCAP_TAG_CHECK_NAME], 329 SYSCAP_TAG_CHECK_WARNING, false, ts.DiagnosticCategory.Warning, 330 CANIUSE_FUNCTION_NAME, false, undefined, checkSyscapAbility)); 331 if (projectConfig.projectRootPath) { 332 const ohosTestDir = ts.sys.resolvePath(path.join(projectConfig.projectRootPath, 'entry', 'src', 'ohosTest')); 333 // TODO:fix error type in the feature 334 if (!ts.sys.resolvePath(fileName).startsWith(ohosTestDir)) { 335 permissionsArray = projectConfig.requestPermissions; 336 checkConfigArray.push(getJsDocNodeCheckConfigItem([TEST_TAG_CHECK_NAME], TEST_TAG_CHECK_ERROR, false, 337 ts.DiagnosticCategory.Warning, '', false)); 338 } 339 } 340 checkConfigArray.push(getJsDocNodeCheckConfigItem([PERMISSION_TAG_CHECK_NAME], PERMISSION_TAG_CHECK_ERROR, false, 341 ts.DiagnosticCategory.Warning, '', false, undefined, checkPermissionValue)); 342 if (isCardFile(fileName)) { 343 needCheckResult = true; 344 checkConfigArray.push(getJsDocNodeCheckConfigItem([FORM_TAG_CHECK_NAME], FORM_TAG_CHECK_ERROR, false, 345 ts.DiagnosticCategory.Error, '', true)); 346 } 347 if (projectConfig.isCrossplatform) { 348 needCheckResult = true; 349 checkConfigArray.push(getJsDocNodeCheckConfigItem([CROSSPLATFORM_TAG_CHECK_NAME], CROSSPLATFORM_TAG_CHECK_ERROER, 350 false, ts.DiagnosticCategory.Warning, '', true)); 351 } 352 if (process.env.compileMode === STAGE_COMPILE_MODE) { 353 needCheckResult = true; 354 checkConfigArray.push(getJsDocNodeCheckConfigItem([FA_TAG_CHECK_NAME, FA_TAG_HUMP_CHECK_NAME], 355 FA_TAG_CHECK_ERROR, false, ts.DiagnosticCategory.Error, '', false)); 356 } else if (process.env.compileMode !== '') { 357 needCheckResult = true; 358 checkConfigArray.push(getJsDocNodeCheckConfigItem([STAGE_TAG_CHECK_NAME, STAGE_TAG_HUMP_CHECK_NAME], 359 STAGE_TAG_CHECK_ERROR, false, 360 ts.DiagnosticCategory.Error, '', false)); 361 } 362 if (projectConfig.bundleType === ATOMICSERVICE_BUNDLE_TYPE && 363 projectConfig.compileSdkVersion >= ATOMICSERVICE_TAG_CHECK_VERSION) { 364 needCheckResult = true; 365 checkConfigArray.push(getJsDocNodeCheckConfigItem([ATOMICSERVICE_TAG_CHECK_NAME], ATOMICSERVICE_TAG_CHECK_ERROER, 366 false, ts.DiagnosticCategory.Error, '', true)); 367 } 368 } 369 result = { 370 nodeNeedCheck: needCheckResult, 371 checkConfig: checkConfigArray 372 }; 373 byFileName.set(sourceFileName, result); 374 return result; 375} 376 377const arkuiDependenceMap: Map<string, boolean> = new Map<string, boolean>(); 378/** 379 * return a file path is Arkui path 380 * 381 * @param {string} file - file path 382 * @returns {boolean} 383 */ 384function isArkuiDependence(file: string): boolean { 385 let exists: boolean | undefined = arkuiDependenceMap.get(file); 386 if (exists !== undefined) { 387 return exists; 388 } 389 const fileDir: string = path.dirname(file); 390 const declarationsPath: string = path.resolve(__dirname, '../../../declarations').replace(/\\/g, '/'); 391 const componentPath: string = path.resolve(__dirname, '../../../../../component').replace(/\\/g, '/'); 392 exists = fileDir === declarationsPath || fileDir === componentPath; 393 arkuiDependenceMap.set(file, exists); 394 return exists; 395} 396 397/** 398 * check a secondary directory of Arkui is used in the moduleSpecifier of import 399 * 400 * @param {ts.Expression} moduleSpecifier - the moduleSpecifier of import 401 * @param {LogInfo[]} log - log list 402 */ 403export function validateModuleSpecifier(moduleSpecifier: ts.Expression, log: LogInfo[]): void { 404 const moduleSpecifierStr: string = moduleSpecifier.getText().replace(/'|"/g, ''); 405 const hasSubDirPath: boolean = ohosSystemModuleSubDirPaths.some((filePath: string) => { 406 return filePath === moduleSpecifierStr; 407 }); 408 if (hasSubDirPath) { 409 // TODO: change to error 410 const error: LogInfo = { 411 type: LogType.WARN, 412 message: `Cannot find module '${moduleSpecifierStr}' or its corresponding type declarations.`, 413 pos: moduleSpecifier.getStart() 414 }; 415 log.push(error); 416 } 417} 418 419interface SystemConfig { 420 deviceTypesMessage: string, 421 deviceTypes: string[], 422 runtimeOS: string, 423 externalApiPaths: string[], 424 syscapIntersectionSet: Set<string>, 425 syscapUnionSet: Set<string> 426} 427 428interface SyscapConfig { 429 SysCaps: string[] 430} 431 432/** 433 * configure syscapInfo to this.share.projectConfig 434 * 435 * @param config this.share.projectConfig 436 */ 437export function configureSyscapInfo(config: SystemConfig): void { 438 config.deviceTypesMessage = config.deviceTypes.join(','); 439 const deviceDir: string = path.resolve(__dirname, '../../../../../api/device-define/'); 440 const deviceInfoMap: Map<string, string[]> = new Map(); 441 const syscaps: Array<string[]> = []; 442 let allSyscaps: string[] = []; 443 config.deviceTypes.forEach((deviceType: string) => { 444 collectOhSyscapInfos(deviceType, deviceDir, deviceInfoMap); 445 }); 446 if (config.runtimeOS !== RUNTIME_OS_OH) { 447 collectExternalSyscapInfos(config.externalApiPaths, config.deviceTypes, deviceInfoMap); 448 } 449 deviceInfoMap.forEach((value: string[]) => { 450 syscaps.push(value); 451 allSyscaps = allSyscaps.concat(value); 452 }); 453 const intersectNoRepeatTwice = (arrs: Array<string[]>) => { 454 return arrs.reduce(function (prev: string[], cur: string[]) { 455 return Array.from(new Set(cur.filter((item: string) => { 456 return prev.includes(item); 457 }))); 458 }); 459 }; 460 let syscapIntersection: string[] = []; 461 if (config.deviceTypes.length === 1 || syscaps.length === 1) { 462 syscapIntersection = syscaps[0]; 463 } else if (syscaps.length > 1) { 464 syscapIntersection = intersectNoRepeatTwice(syscaps); 465 } 466 config.syscapIntersectionSet = new Set(syscapIntersection); 467 config.syscapUnionSet = new Set(allSyscaps); 468} 469 470function collectOhSyscapInfos(deviceType: string, deviceDir: string, deviceInfoMap: Map<string, string[]>) { 471 let syscapFilePath: string = ''; 472 if (deviceType === 'phone') { 473 syscapFilePath = path.resolve(deviceDir, 'default.json'); 474 } else { 475 syscapFilePath = path.resolve(deviceDir, deviceType + '.json'); 476 } 477 if (fs.existsSync(syscapFilePath)) { 478 const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); 479 if (deviceInfoMap.get(deviceType)) { 480 deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps)); 481 } else { 482 deviceInfoMap.set(deviceType, content.SysCaps); 483 } 484 } 485} 486 487function collectExternalSyscapInfos( 488 externalApiPaths: string[], 489 deviceTypes: string[], 490 deviceInfoMap: Map<string, string[]> 491) { 492 const externalDeviceDirs: string[] = []; 493 externalApiPaths.forEach((externalApiPath: string) => { 494 const externalDeviceDir: string = path.resolve(externalApiPath, './api/device-define'); 495 if (fs.existsSync(externalDeviceDir)) { 496 externalDeviceDirs.push(externalDeviceDir); 497 } 498 }); 499 externalDeviceDirs.forEach((externalDeviceDir: string) => { 500 deviceTypes.forEach((deviceType: string) => { 501 let syscapFilePath: string = ''; 502 const files: string[] = fs.readdirSync(externalDeviceDir); 503 files.forEach((fileName: string) => { 504 if (fileName.startsWith(deviceType)) { 505 syscapFilePath = path.resolve(externalDeviceDir, fileName); 506 if (fs.existsSync(syscapFilePath)) { 507 const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); 508 if (deviceInfoMap.get(deviceType)) { 509 deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps)); 510 } else { 511 deviceInfoMap.set(deviceType, content.SysCaps); 512 } 513 } 514 } 515 }); 516 }); 517 }); 518} 519 520/** 521 * Determine the necessity of syscap check. 522 * @param jsDocTags 523 * @param config 524 * @returns 525 */ 526export function checkSyscapAbility(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { 527 let currentSyscapValue: string = ''; 528 for (let i = 0; i < jsDocTags.length; i++) { 529 const jsDocTag: ts.JSDocTag = jsDocTags[i]; 530 if (jsDocTag && jsDocTag.tagName.escapedText.toString() === SYSCAP_TAG_CHECK_NAME) { 531 currentSyscapValue = jsDocTag.comment as string; 532 break; 533 } 534 } 535 return projectConfig.syscapIntersectionSet && !projectConfig.syscapIntersectionSet.has(currentSyscapValue); 536} 537 538interface ConfigPermission { 539 requestPermissions: Array<{ name: string }>; 540 definePermissions: Array<{ name: string }>; 541} 542 543interface PermissionsConfig { 544 permission: ConfigPermission, 545 requestPermissions: string[], 546 definePermissions: string[], 547} 548/** 549 * configure permissionInfo to this.share.projectConfig 550 * 551 * @param config this.share.projectConfig 552 */ 553export function configurePermission(config: PermissionsConfig): void { 554 const permission: ConfigPermission = config.permission; 555 config.requestPermissions = []; 556 config.definePermissions = []; 557 if (permission.requestPermissions) { 558 config.requestPermissions = getNameFromArray(permission.requestPermissions); 559 } 560 if (permission.definePermissions) { 561 config.definePermissions = getNameFromArray(permission.definePermissions); 562 } 563} 564 565function getNameFromArray(array: Array<{ name: string }>): string[] { 566 return array.map((item: { name: string }) => { 567 return String(item.name); 568 }); 569} 570 571/** 572 * Determine the necessity of permission check 573 * 574 * @param {ts.JSDocTag[]} jsDocTags 575 * @param {ts.JsDocNodeCheckConfigItem} config 576 * @returns {boolean} 577 */ 578export function checkPermissionValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { 579 const jsDocTag: ts.JSDocTag = jsDocTags.find((item: ts.JSDocTag) => { 580 return item.tagName.getText() === PERMISSION_TAG_CHECK_NAME; 581 }); 582 if (!jsDocTag) { 583 return false; 584 } 585 const comment: string = typeof jsDocTag.comment === 'string' ? 586 jsDocTag.comment : 587 ts.getTextOfJSDocComment(jsDocTag.comment); 588 config.message = config.message.replace('$DT', comment); 589 return comment !== '' && !JsDocCheckService.validPermission(comment, permissionsArray); 590} 591 592/** 593 * custom condition check 594 * @param { ts.FileCheckModuleInfo } jsDocFileCheckedInfo 595 * @param { ts.JsDocTagInfo[] } jsDocs 596 * @returns 597 */ 598export function getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocs: ts.JsDocTagInfo[]): 599 ts.ConditionCheckResult { 600 const result: ts.ConditionCheckResult = { 601 valid: true 602 }; 603 let currentSyscapValue: string = ''; 604 for (let i = 0; i < jsDocs.length; i++) { 605 const jsDocTag: ts.JsDocTagInfo = jsDocs[i]; 606 if (jsDocTag.name === SYSCAP_TAG_CHECK_NAME) { 607 currentSyscapValue = jsDocTag.text as string; 608 break; 609 } 610 } 611 if (!projectConfig.syscapIntersectionSet || !projectConfig.syscapUnionSet) { 612 return result; 613 } 614 if (!projectConfig.syscapIntersectionSet.has(currentSyscapValue) && projectConfig.syscapUnionSet.has(currentSyscapValue)) { 615 result.valid = false; 616 result.type = ts.DiagnosticCategory.Warning; 617 result.message = SYSCAP_TAG_CONDITION_CHECK_WARNING; 618 } else if (!projectConfig.syscapUnionSet.has(currentSyscapValue)) { 619 result.valid = false; 620 // TODO: fix to error in the feature 621 result.type = ts.DiagnosticCategory.Warning; 622 result.message = SYSCAP_TAG_CHECK_WARNING.replace('$DT', projectConfig.deviceTypesMessage); 623 } 624 return result; 625} 626