* Copyright (c) 2024 - 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ArkFile } from 'arkanalyzer';
import { CheckEntry } from './CheckEntry';
import { ConfigUtils } from './ConfigUtils';
import { CheckerUtils } from '../checker/CheckerUtils';
import { file2CheckRuleMap, project2CheckRuleMap } from './CheckerIndex';
import { Rule } from '../../model/Rule';
import { RuleConfig } from '../../model/RuleConfig';
import { fileCheckBuilder, projectCheckBuilder } from './CheckBuilder';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { FileUtils } from './FileUtils';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'fileRuleMapping');
export async function fileRuleMapping(checkFileList: string[], checkEntry: CheckEntry): Promise<boolean> {
const allRulesMap = ConfigUtils.getRuleMap(checkEntry.ruleConfig, checkEntry.projectConfig, checkEntry.message);
if (allRulesMap.size === 0) {
checkEntry.message?.progressNotify(1, 'No rule to check');
return false;
}
let arkFiles: ArkFile[] = [];
const fiRulesMap: Map<string, Rule> = new Map();
const fileRulesMap = await createFileRulesMap(checkFileList, allRulesMap, checkEntry, file2CheckRuleMap, fiRulesMap);
for (const filePath of checkFileList) {
try {
const arkFile = CheckerUtils.getArkFileByFilePath(checkEntry.scene, filePath);
if (!arkFile) {
continue;
}
arkFiles.push(arkFile);
const enabledRules = fileRulesMap.get(filePath);
if (enabledRules) {
checkEntry.addFileCheck(fileCheckBuilder(arkFile, enabledRules));
}
} catch (error) {
logger.error(`Error processing file ${filePath}: ${(error as Error).message}`);
}
}
const proRulesMap: Map<string, Rule> = new Map();
const projectRulesMap = await createFileRulesMap(checkFileList, allRulesMap, checkEntry, project2CheckRuleMap, proRulesMap);
const projectRules = Array.from(proRulesMap.values());
try {
checkEntry.addProjectCheck(projectCheckBuilder(arkFiles, projectRules));
checkEntry.projectCheck.ruleMap = projectRulesMap;
} catch (error) {
logger.error(`Error adding project check: ${(error as Error).message}`);
}
return true;
}
function filterRule(allRulesMap: Map<string, Rule>, filterMap?: Map<string, Rule[]>): Rule[] {
const rules: Rule[] = [];
for (const [key, value] of allRulesMap) {
if (filterMap && !filterMap.has(key)) {
continue;
}
rules.push(value);
}
return rules;
}
async function createFileRulesMap(allFiles: string[], allRulesMap: Map<string, Rule>, checkEntry: CheckEntry,
checksRuleMap: Map<string, any>, proRulesMap: Map<string, Rule>): Promise<Map<string, Rule[]>> {
let fileRulesMap: Map<string, Rule[]> = new Map();
const ruleMap = filterRule(allRulesMap, checksRuleMap);
const defaultRules = Array.from(ruleMap.values());
allFiles.forEach(filePath => fileRulesMap.set(filePath, defaultRules));
ruleMap.forEach(rule => {
proRulesMap.set(rule.ruleId, rule);
});
for (const override of checkEntry.ruleConfig.overrides ?? []) {
try {
const overrideFileRulesMap = await createFileRulesMapWithOverride(checkEntry, override, checksRuleMap, proRulesMap);
fileRulesMap = mergeFileRulesMap(fileRulesMap, overrideFileRulesMap);
} catch (error) {
logger.error(`Error check extra rule overrides: ${(error as Error).message}`);
}
}
return fileRulesMap;
}
async function createFileRulesMapWithOverride(checkEntry: CheckEntry, override: RuleConfig,
checksRuleMap: Map<string, any>, proRulesMap: Map<string, Rule>): Promise<Map<string, Rule[]>> {
let checkFileList = checkEntry.selectFileList.map(file => file.filePath);
if (checkFileList.length === 0) {
checkFileList = FileUtils.getAllFiles(checkEntry.projectConfig.projectPath, ['.ts', '.ets', '.json5']);
}
checkFileList = await FileUtils.getFiltedFiles(checkFileList, override);
const allRuleMap = ConfigUtils.getRuleMap(override, checkEntry.projectConfig, checkEntry.message);
const ruleMap = filterRule(allRuleMap, checksRuleMap);
ruleMap.forEach(rule => {
if (!proRulesMap.has(rule.ruleId)) {
proRulesMap.set(rule.ruleId, rule);
}
});
const fileRulesMap: Map<string, Rule[]> = new Map();
const defaultRules = Array.from(ruleMap.values());
checkFileList.forEach(filePath => fileRulesMap.set(filePath, defaultRules));
return fileRulesMap;
}
function mergeFileRulesMap(fileRulesMap: Map<string, Rule[]>,
overrideFileRulesMap: Map<string, Rule[]>): Map<string, Rule[]> {
for (const [key, vals] of overrideFileRulesMap) {
if (!fileRulesMap.has(key) || vals.length === 0) {
continue;
}
let fileRules = fileRulesMap.get(key);
if (!fileRules || fileRules.length === 0) {
fileRulesMap.set(key, vals);
continue;
}
let newRules = [...fileRules];
vals.forEach(val => {
let existIndex = fileRules?.findIndex(fileRule => fileRule.ruleId === val.ruleId);
if (existIndex === undefined || existIndex === -1) {
newRules.push(val);
} else {
newRules[existIndex] = val;
}
});
fileRulesMap.set(key, newRules);
}
const filteredRulesMap = new Map<string, Rule[]>();
for (const [key, rules] of fileRulesMap) {
const filteredRules = rules.filter(rule => rule.alert !== 0);
filteredRulesMap.set(key, filteredRules);
}
return filteredRulesMap;
}