1/* 2 * Copyright (c) 2024 - 2025 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 { ALERT_LEVEL, ExtRuleSet, Rule } from '../../model/Rule'; 16import { FileUtils } from './FileUtils'; 17import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 18import { Utils } from './Utils'; 19import { execSync } from 'child_process'; 20import { Json5parser } from './Json5parser'; 21import { OptionValues } from 'commander'; 22import { CheckerStorage } from './CheckerStorage'; 23import path from 'path'; 24import { CheckEntry } from './CheckEntry'; 25import { RuleConfig } from '../../model/RuleConfig'; 26import { ProjectConfig } from '../../model/ProjectConfig'; 27import { file2CheckRuleMap, project2CheckRuleMap } from './CheckerIndex'; 28import fs from 'fs'; 29import { Message, MessageType } from '../../model/Message'; 30 31const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ConfigUtils'); 32 33export class ConfigUtils { 34 /** 35 * 获取配置文件 36 * @param configPath 配置文件路径 37 * @param rootDir 根目录,可选参数 38 * @returns 返回解析后的配置对象,如果解析失败则返回null 39 */ 40 public static getConfig(configPath: string, rootDir?: string): any | null { 41 if (!fs.existsSync(configPath) && rootDir) { 42 // 规则配置文件不存在,使用默认配置文件 43 configPath = path.join(rootDir, 'config', 'ruleConfg.json'); 44 } 45 46 try { 47 return Json5parser.parseJsonText(FileUtils.readFile(configPath)); 48 } catch (e) { 49 logger.error(e); 50 return null; 51 } 52 } 53 54 /** 55 * 解析配置文件并设置检查入口 56 * @param argvObj 命令行参数对象 57 * @param checkEntry 检查入口对象 58 * @returns 是否成功解析配置文件 59 */ 60 public static parseConfig(argvObj: OptionValues, checkEntry: CheckEntry): boolean { 61 const ruleConfig = ConfigUtils.getConfig(argvObj.configPath, argvObj.arkCheckPath); 62 const projectConfig = ConfigUtils.getConfig(argvObj.projectConfigPath); 63 if (!ruleConfig || !projectConfig) { 64 return false; 65 } 66 67 // 解析规则配置文件 68 checkEntry.ruleConfig = new RuleConfig(ruleConfig); 69 // 解析项目配置文件 70 checkEntry.projectConfig = new ProjectConfig(projectConfig); 71 // 日志配置 72 const logPath = checkEntry.projectConfig.logPath; 73 Utils.setLogPath(logPath.length === 0 ? './HomeCheck.log' : logPath); 74 logger.info('Checking started.'); 75 // api版本配置 76 CheckerStorage.getInstance().setApiVersion(checkEntry.projectConfig.apiVersion); 77 // product配置 78 CheckerStorage.getInstance().setProduct(checkEntry.projectConfig.product); 79 return true; 80 } 81 82 /** 83 * 从配置文件中获取规则 84 * @param ruleConfig 规则配置 85 * @param projectConfig 项目配置 86 * @param message 消息通知实例 87 * @returns Map, ruleId -- Rule 88 */ 89 public static getRuleMap(ruleConfig: RuleConfig, projectConfig: ProjectConfig, message: Message): Map<string, Rule> { 90 let ruleMap: Map<string, Rule> = new Map(); 91 const allRules = ConfigUtils.getRuleSetMap(projectConfig.arkCheckPath); 92 for (const ruleSetStr of ruleConfig.ruleSet ?? []) { 93 const ruleSet = allRules.get(ruleSetStr); 94 if (!ruleSet) { 95 logger.error('Invalid ruleSet name: ' + ruleSetStr); 96 continue; 97 } 98 for (const [ruleId, level] of Object.entries(ruleSet)) { 99 const alert = Utils.getEnumValues(level, ALERT_LEVEL); 100 const rule = new Rule(ruleId, alert); 101 ruleMap.set(rule.ruleId, rule); 102 } 103 } 104 105 for (const ruleInfo of Object.entries(ruleConfig.rules ?? {})) { 106 if (!this.isOnlineRule(ruleInfo[0], allRules)) { 107 logger.error('Invalid rule name: ' + ruleInfo[0]); 108 continue; 109 } 110 const rule = this.genRuleByOneRuleCfg(ruleInfo); 111 if (!rule) { 112 continue; 113 } 114 ruleMap.set(rule.ruleId, rule); 115 } 116 117 // override 独有配置 118 Object.entries(ruleConfig.extRules ?? {}).forEach(ruleInfo => { 119 const rule = this.genRuleByOneRuleCfg(ruleInfo); 120 if (!rule) { 121 return; 122 } 123 ruleMap.set(rule.ruleId, rule); 124 }); 125 126 // 解析自定义规则集配置 127 this.parseExtRuleConfig(ruleConfig, projectConfig, message, allRules, ruleMap); 128 return ruleMap; 129 } 130 131 /** 132 * 解析自定义规则配置 133 * @param ruleConfig 规则配置 134 * @param projectConfig 项目配置 135 * @param message 消息对象 136 * @param allRules 所有规则集合 137 * @param ruleMap 规则映射 138 */ 139 private static parseExtRuleConfig(ruleConfig: RuleConfig, projectConfig: ProjectConfig, message: Message, 140 allRules: Map<string, object>, ruleMap: Map<string, Rule>): void { 141 logger.info('The npmPath:' + projectConfig.npmPath); 142 logger.info('The npmInstallDir:' + projectConfig.npmInstallDir); 143 const extRuleSetSet = new Set<string>(); 144 (ruleConfig.extRuleSet as ExtRuleSet[]).forEach((ruleSet) => { 145 if (!this.checkExtRuleSetConfig(ruleSet, allRules, extRuleSetSet, message)) { 146 return; 147 } 148 try { 149 const cmd = `${projectConfig.npmPath} install --no-save --prefix "${projectConfig.npmInstallDir}" "${ruleSet.packagePath}"`; 150 logger.info('Start to execute cmd: ' + cmd); 151 const execLog = execSync(cmd); 152 logger.info('Exec log: ' + execLog.toString()); 153 } catch (e) { 154 logger.error((e as Error).message); 155 return; 156 } 157 logger.info('npm install completed.'); 158 159 let extPkg: any = null; 160 try { 161 extPkg = require(path.resolve(projectConfig.npmInstallDir, 'node_modules', ruleSet.ruleSetName)); 162 } catch (e) { 163 logger.error((e as Error).message); 164 message?.messageNotify(MessageType.CHECK_WARN, `Failed to get ${ruleSet.ruleSetName}, please check the ruleSetName.`); 165 return; 166 } 167 extRuleSetSet.add(ruleSet.ruleSetName); 168 169 this.processExternalRules(ruleSet, allRules, extPkg, message, ruleMap); 170 }); 171 } 172 173 private static processExternalRules(ruleSet: ExtRuleSet, allRules: Map<string, object>, extPkg: any, message: Message, ruleMap: Map<string, Rule>): void { 174 Object.entries(ruleSet.extRules ?? {}).forEach(ruleInfo => { 175 if (this.isOnlineRule(ruleInfo[0], allRules)) { 176 message?.messageNotify(MessageType.CHECK_WARN, `The extRuleName can't be the same as the internal rules name, name = ${ruleInfo[0]}.`); 177 return; 178 } 179 const rule = this.genRuleByOneRuleCfg(ruleInfo); 180 if (!rule) { 181 return; 182 } 183 let module = extPkg?.file2CheckRuleMap?.get(rule.ruleId); 184 if (module) { 185 file2CheckRuleMap.set(rule.ruleId, module); 186 } else { 187 module = extPkg?.project2CheckRuleMap?.get(rule.ruleId); 188 if (module) { 189 project2CheckRuleMap.set(rule.ruleId, module); 190 } else { 191 message?.messageNotify(MessageType.CHECK_WARN, `Failed to get '${rule.ruleId}' in '${ruleSet.ruleSetName}', please check the extRules.`); 192 return; 193 } 194 } 195 ruleMap.set(rule.ruleId, rule); 196 }); 197 } 198 199 /** 200 * 通过单个规则配置生成Rule对象,eg: '@ruleSet/ruleName': 'error' | ['error', []...] 201 * @param ruleCfg - 规则配置,格式为 [string, any] 202 * @returns Rule | null - 生成的规则对象或 null 203 */ 204 private static genRuleByOneRuleCfg(ruleCfg: [string, any]): Rule | null { 205 let alert = ALERT_LEVEL.SUGGESTION; 206 let option: any[] = []; 207 if (ruleCfg[1] instanceof Array) { 208 alert = Utils.getEnumValues(ruleCfg[1][0], ALERT_LEVEL); 209 for (let i = 1; i < ruleCfg[1].length; i++) { 210 option.push(ruleCfg[1][i]); 211 } 212 } else { 213 alert = Utils.getEnumValues(ruleCfg[1], ALERT_LEVEL); 214 } 215 const rule = new Rule(ruleCfg[0], alert); 216 rule.option = option; 217 return rule; 218 } 219 220 /** 221 * 读取RuleSet.json中配置的规则集 222 */ 223 static getRuleSetMap(rootDir: string): Map<string, object> { 224 const ruleSetMap: Map<string, object> = new Map(); 225 try { 226 const fileStr = FileUtils.readFile(path.join(rootDir, 'ruleSet.json')); 227 const config: Record<string, object> = JSON.parse(fileStr); 228 for (const [key, value] of Object.entries(config)) { 229 ruleSetMap.set(key, value); 230 } 231 } catch (error) { 232 logger.error((error as Error).message); 233 } 234 return ruleSetMap; 235 } 236 237 /** 238 * 检查指定的规则是否存在 239 * @param ruleId - 要检查的规则ID 240 * @param allRules - 包含所有规则的Map对象 241 * @returns 如果规则存在则返回true,否则返回false 242 */ 243 static isOnlineRule(ruleId: string, allRules: Map<string, object>): boolean { 244 for (const [ruleSet, rules] of allRules) { 245 if (Object.keys(rules).includes(ruleId)) { 246 return true; 247 } 248 } 249 return false; 250 } 251 252 /** 253 * 检查自定义规则集配置的有效性 254 * @param ruleSet - 自定义规则集 255 * @param allRules - 所有规则集合 256 * @param extRuleSetSet - 自定义规则集集合 257 * @param message - 消息对象 258 * @returns {boolean} - 是否通过检查 259 */ 260 static checkExtRuleSetConfig(ruleSet: ExtRuleSet, allRules: Map<string, object>, extRuleSetSet: Set<string>, message: Message): boolean { 261 if (allRules.get(`${ruleSet.ruleSetName}`)) { 262 message?.messageNotify(MessageType.CHECK_WARN, `The extRuleSetName can't be the same as the name of internal rule set name, name = ${ruleSet.ruleSetName}.`); 263 return false; 264 } 265 if (!ruleSet.packagePath || ruleSet.packagePath.length === 0 || FileUtils.isExistsSync(ruleSet.packagePath) === false) { 266 message?.messageNotify(MessageType.CHECK_WARN, `'${ruleSet.packagePath}' is invalid or not exist, please check the packagePath.`); 267 return false; 268 } 269 if (extRuleSetSet.has(ruleSet.ruleSetName)) { 270 message?.messageNotify(MessageType.CHECK_WARN, `'${ruleSet.ruleSetName}' is conflict, please check the ruleSetName.`); 271 return false; 272 } 273 return true; 274 } 275}