* 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 { Scene } from 'arkanalyzer';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { SceneConfig } from 'arkanalyzer';
import { fileRuleMapping } from './FileRuleMapping';
import { RuleConfig } from '../../model/RuleConfig';
import { ProjectConfig, SelectedFileInfo } from '../../model/ProjectConfig';
import { Project2Check } from '../../model/Project2Check';
import { File2Check } from '../../model/File2Check';
import { DisableText } from './Disable';
import { Message } from '../../model/Message';
import { FileUtils } from './FileUtils';
import { ScopeHelper } from './ScopeHelper';
import { RuleListUtil } from './DefectsList';
import { FixMode } from '../../model/Fix';
import { FileIssues, FileReports, IssueReport, engine } from '../../model/Defects';
import { FixUtils } from './FixUtils';
import { FixEngine } from '../../codeFix/FixEngine';
import { CheckerUtils } from '../checker/CheckerUtils';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'CheckEntry');
export class CheckEntry {
public ruleConfig: RuleConfig;
public projectConfig: ProjectConfig;
public projectCheck: Project2Check;
public fileChecks: File2Check[] = [];
public scene: Scene;
public message: Message;
public selectFileList: SelectedFileInfo[] = [];
constructor() {
}
public addFileCheck(fileCheck: File2Check): void {
this.fileChecks.push(fileCheck);
}
public addProjectCheck(projectCheck: Project2Check): void {
this.projectCheck = projectCheck;
}
public setDisableText(fileDisableText: string, nextLineDisableText: string): void {
DisableText.FILE_DISABLE_TEXT = fileDisableText;
DisableText.NEXT_LINE_DISABLE_TEXT = nextLineDisableText;
}
public setEngineName(engineName: string): void {
engine.engineName = engineName;
}
public setCheckFileList(selectFileList: SelectedFileInfo[]): void {
this.selectFileList = selectFileList;
}
public setMessage(message: Message): void {
this.message = message;
}
public async runAll(): Promise<void> {
let checkedIndex = 1;
for (const fileCheck of this.fileChecks) {
try {
await fileCheck.run();
this.message?.progressNotify(checkedIndex / (this.fileChecks.length + 1), fileCheck.arkFile.getFilePath());
checkedIndex++;
} catch (error) {
logger.error(`Error running file check for ${fileCheck.arkFile.getFilePath()}: ${(error as Error).message}`);
continue;
}
}
if (this.projectCheck) {
try {
await this.projectCheck.run();
this.message?.progressNotify(checkedIndex / (this.fileChecks.length + 1), 'Project Check');
} catch (error) {
logger.error(`Error running project check: ${(error as Error).message}`);
}
}
}
* 按规则维度统计并输出告警信息,按文件维度汇总并返回告警信息。
*
* @returns FileReport[] 文件报告数组,每个元素包含文件名、缺陷列表和输出信息
*/
public sortIssues(): FileIssues[] {
const issuesMapByChecker: Map<string, IssueReport[]> = new Map();
const issuesMapByFile: Map<string, IssueReport[]> = new Map();
RuleListUtil.printDefects();
for (const fileCheck of this.fileChecks) {
if (!fileCheck.issues || fileCheck.issues.length === 0) {
continue;
}
issuesMapByFile.set(fileCheck.arkFile.getFilePath(), fileCheck.issues);
for (const issue of fileCheck.issues) {
logger.debug(issue.defect.mergeKey);
const checkerStorage = issuesMapByChecker.get(issue.defect.ruleId);
if (checkerStorage) {
checkerStorage.push(issue);
} else {
issuesMapByChecker.set(issue.defect.ruleId, [issue]);
}
}
}
for (const issue of this.projectCheck?.issues ?? []) {
logger.debug(issue.defect.mergeKey);
const checkerStorage = issuesMapByChecker.get(issue.defect.ruleId);
if (checkerStorage) {
checkerStorage.push(issue);
} else {
issuesMapByChecker.set(issue.defect.ruleId, [issue]);
}
const filePath = issue.defect.mergeKey.split('%')[0];
const fileStorage = issuesMapByFile.get(filePath);
if (fileStorage) {
fileStorage.push(issue);
} else {
issuesMapByFile.set(filePath, [issue]);
}
}
issuesMapByChecker.forEach((issues, checker) => {
logger.info(issues.length + ' issues from checker - ' + checker);
});
const fileReports: FileIssues[] = [];
issuesMapByFile.forEach((issues, filePath) => {
fileReports.push({ filePath, issues });
});
return fileReports;
}
public buildScope(): void {
new ScopeHelper().buildScope(this.scene);
}
* 修复代码问题
*
* @param fileIssues 以文件为维度的issues信息
* @returns 修复后的文件报告数组,去掉已修复issues,且需更新未修复issues行列号等信息
*/
public codeFix(fileIssues: FileIssues[]): FileReports[] {
const fileReports: FileReports[] = [];
for (const fileIssue of fileIssues) {
const arkFile = CheckerUtils.getArkFileByFilePath(this.scene, fileIssue.filePath);
if (!arkFile) {
fileReports.push({ filePath: fileIssue.filePath, defects: fileIssue.issues.map(issue => issue.defect) });
continue;
}
let keys: string[] = [];
let isFixAll = false;
for (const fileInfo of this.selectFileList) {
if (fileInfo.fixKey && fileInfo.filePath === fileIssue.filePath) {
keys = fileInfo.fixKey;
break;
}
}
if (keys.length === 0) {
isFixAll = true;
}
const remainIssues: IssueReport[] = [];
const astFixIssues: IssueReport[] = [];
this.classifyIssues(fileIssue.issues, isFixAll, keys, astFixIssues, remainIssues);
const astFixReport = new FixEngine().getEngine(FixMode.AST).applyFix(arkFile, astFixIssues, remainIssues);
fileReports.push(astFixReport);
}
return fileReports;
}
private classifyIssues(allIssues: IssueReport[], fixAll: boolean, keys: string[], astFixIssues: IssueReport[],
remainIssues: IssueReport[]): void {
for (const issue of allIssues) {
if (fixAll || keys.includes(issue.defect.fixKey)) {
if (issue.fix && issue.defect.fixable && FixUtils.isRuleFix(issue.fix)) {
astFixIssues.push(issue);
} else {
remainIssues.push(issue);
logger.debug('Fix type is unsupported.');
}
} else {
remainIssues.push(issue);
}
}
}
}
export async function checkEntryBuilder(checkEntry: CheckEntry): Promise<boolean> {
let checkFileList = checkEntry.selectFileList.map(file => file.filePath);
if (checkFileList.length === 0) {
checkFileList = FileUtils.getAllFiles(checkEntry.projectConfig.projectPath, ['.ts', '.ets', '.js', '.json5']);
}
checkFileList = await FileUtils.getFiltedFiles(checkFileList, checkEntry.ruleConfig);
logger.info('File count: ' + checkFileList.length);
if (checkFileList.length === 0) {
checkEntry.message?.progressNotify(1, 'No file to check.');
return false;
}
if (!buildScene(checkFileList, checkEntry)) {
return false;
}
if (!(await fileRuleMapping(checkFileList, checkEntry))) {
return false;
}
return true;
}
* 获取指定检查的文件列表
*
* @param checkFilePath - 指定的检查文件路径的配置文件路径,该文件内容示例{'checkPath': [{'filePath': 'xxx', 'fixKey': ['%line%sCol%eCol%ruleId']}]}
* filePath为需要检查的文件路径,fixKey为需要修复的缺陷key,空数组则不修复。
* @returns SelectFileInfo[] - 需要检查的文件列表
*/
export function getSelectFileList(checkFilePath: string): SelectedFileInfo[] {
if (checkFilePath.length > 0) {
return FileUtils.getSeletctedFileInfos(checkFilePath, ['.ts', '.ets', '.json5']);
}
return [];
}
* 构建Scene
* @param fileList - 文件列表
* @param checkEntry - 检查条目
* @returns {boolean} - 构建是否成功
*/
function buildScene(fileList: string[], checkEntry: CheckEntry): boolean {
try {
const sceneConfig = new SceneConfig();
const projectName = checkEntry.projectConfig.projectName;
const projectPath = checkEntry.projectConfig.projectPath;
const languageTags = checkEntry.projectConfig.languageTags;
const sdkList = FileUtils.genSdks(checkEntry.projectConfig);
sceneConfig.buildFromProjectFiles(projectName, projectPath, fileList, sdkList, languageTags);
sceneConfig.getOptions().enableBuiltIn = true;
logger.info('Build sceneConfig completed.');
checkEntry.scene = new Scene();
checkEntry.scene.buildSceneFromFiles(sceneConfig);
logger.info('Build scene completed.');
checkEntry.scene.inferTypes();
checkEntry.scene.inferTypes();
logger.info('Infer types completed.');
} catch (error) {
logger.error('Build scene or infer types error: ', error);
return false;
}
checkEntry.buildScope();
logger.info('Build scope completed.');
return true;
}