• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}