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 */ 15 16import { Scene } from 'arkanalyzer'; 17import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 18import { SceneConfig } from 'arkanalyzer'; 19import { fileRuleMapping } from './FileRuleMapping'; 20import { RuleConfig } from '../../model/RuleConfig'; 21import { ProjectConfig, SelectedFileInfo } from '../../model/ProjectConfig'; 22import { Project2Check } from '../../model/Project2Check'; 23import { File2Check } from '../../model/File2Check'; 24import { DisableText } from './Disable'; 25import { Message } from '../../model/Message'; 26import { FileUtils } from './FileUtils'; 27import { ScopeHelper } from './ScopeHelper'; 28import { RuleListUtil } from './DefectsList'; 29import { FixMode } from '../../model/Fix'; 30import { FileIssues, FileReports, IssueReport, engine } from '../../model/Defects'; 31import { FixUtils } from './FixUtils'; 32import { FixEngine } from '../../codeFix/FixEngine'; 33import { CheckerUtils } from '../checker/CheckerUtils'; 34 35const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'CheckEntry'); 36 37export class CheckEntry { 38 public ruleConfig: RuleConfig; 39 public projectConfig: ProjectConfig; 40 public projectCheck: Project2Check; 41 public fileChecks: File2Check[] = []; 42 public scene: Scene; 43 public message: Message; 44 public selectFileList: SelectedFileInfo[] = []; 45 46 constructor() { 47 } 48 49 public addFileCheck(fileCheck: File2Check): void { 50 this.fileChecks.push(fileCheck); 51 } 52 53 public addProjectCheck(projectCheck: Project2Check): void { 54 this.projectCheck = projectCheck; 55 } 56 57 public setDisableText(fileDisableText: string, nextLineDisableText: string): void { 58 DisableText.FILE_DISABLE_TEXT = fileDisableText; 59 DisableText.NEXT_LINE_DISABLE_TEXT = nextLineDisableText; 60 } 61 62 public setEngineName(engineName: string): void { 63 engine.engineName = engineName; 64 } 65 66 public setCheckFileList(selectFileList: SelectedFileInfo[]): void { 67 this.selectFileList = selectFileList; 68 } 69 70 public setMessage(message: Message): void { 71 this.message = message; 72 } 73 74 public async runAll(): Promise<void> { 75 // TODO: worker_threads改造 76 let checkedIndex = 1; 77 for (const fileCheck of this.fileChecks) { 78 try { 79 await fileCheck.run(); 80 // 进度条通知 81 this.message?.progressNotify(checkedIndex / (this.fileChecks.length + 1), fileCheck.arkFile.getFilePath()); 82 checkedIndex++; 83 } catch (error) { 84 logger.error(`Error running file check for ${fileCheck.arkFile.getFilePath()}: ${(error as Error).message}`); 85 continue; 86 } 87 } 88 89 if (this.projectCheck) { 90 try { 91 await this.projectCheck.run(); 92 this.message?.progressNotify(checkedIndex / (this.fileChecks.length + 1), 'Project Check'); 93 } catch (error) { 94 logger.error(`Error running project check: ${(error as Error).message}`); 95 } 96 } 97 } 98 99 /** 100 * 按规则维度统计并输出告警信息,按文件维度汇总并返回告警信息。 101 * 102 * @returns FileReport[] 文件报告数组,每个元素包含文件名、缺陷列表和输出信息 103 */ 104 public sortIssues(): FileIssues[] { 105 const issuesMapByChecker: Map<string, IssueReport[]> = new Map(); 106 const issuesMapByFile: Map<string, IssueReport[]> = new Map(); 107 RuleListUtil.printDefects(); 108 for (const fileCheck of this.fileChecks) { 109 if (!fileCheck.issues || fileCheck.issues.length === 0) { 110 continue; 111 } 112 issuesMapByFile.set(fileCheck.arkFile.getFilePath(), fileCheck.issues); 113 for (const issue of fileCheck.issues) { 114 logger.debug(issue.defect.mergeKey); 115 const checkerStorage = issuesMapByChecker.get(issue.defect.ruleId); 116 if (checkerStorage) { 117 checkerStorage.push(issue); 118 } else { 119 issuesMapByChecker.set(issue.defect.ruleId, [issue]); 120 } 121 } 122 } 123 124 for (const issue of this.projectCheck?.issues ?? []) { 125 logger.debug(issue.defect.mergeKey); 126 const checkerStorage = issuesMapByChecker.get(issue.defect.ruleId); 127 if (checkerStorage) { 128 checkerStorage.push(issue); 129 } else { 130 issuesMapByChecker.set(issue.defect.ruleId, [issue]); 131 } 132 133 const filePath = issue.defect.mergeKey.split('%')[0]; 134 const fileStorage = issuesMapByFile.get(filePath); 135 if (fileStorage) { 136 fileStorage.push(issue); 137 } else { 138 issuesMapByFile.set(filePath, [issue]); 139 } 140 } 141 issuesMapByChecker.forEach((issues, checker) => { 142 logger.info(issues.length + ' issues from checker - ' + checker); 143 }); 144 const fileReports: FileIssues[] = []; 145 issuesMapByFile.forEach((issues, filePath) => { 146 fileReports.push({ filePath, issues }); 147 }); 148 return fileReports; 149 } 150 151 public buildScope(): void { 152 new ScopeHelper().buildScope(this.scene); 153 } 154 155 /** 156 * 修复代码问题 157 * 158 * @param fileIssues 以文件为维度的issues信息 159 * @returns 修复后的文件报告数组,去掉已修复issues,且需更新未修复issues行列号等信息 160 */ 161 public codeFix(fileIssues: FileIssues[]): FileReports[] { 162 const fileReports: FileReports[] = []; 163 for (const fileIssue of fileIssues) { 164 const arkFile = CheckerUtils.getArkFileByFilePath(this.scene, fileIssue.filePath); 165 if (!arkFile) { 166 fileReports.push({ filePath: fileIssue.filePath, defects: fileIssue.issues.map(issue => issue.defect) }); 167 continue; 168 } 169 let keys: string[] = []; 170 let isFixAll = false; 171 // 寻找该文件的fixKey,即需要修复的issue 172 for (const fileInfo of this.selectFileList) { 173 if (fileInfo.fixKey && fileInfo.filePath === fileIssue.filePath) { 174 keys = fileInfo.fixKey; 175 break; 176 } 177 } 178 // 没有指定key,则修复所有issue 179 if (keys.length === 0) { 180 isFixAll = true; 181 } 182 183 const remainIssues: IssueReport[] = []; 184 const astFixIssues: IssueReport[] = []; 185 this.classifyIssues(fileIssue.issues, isFixAll, keys, astFixIssues, remainIssues); 186 const astFixReport = new FixEngine().getEngine(FixMode.AST).applyFix(arkFile, astFixIssues, remainIssues); 187 fileReports.push(astFixReport); 188 } 189 return fileReports; 190 } 191 192 private classifyIssues(allIssues: IssueReport[], fixAll: boolean, keys: string[], astFixIssues: IssueReport[], 193 remainIssues: IssueReport[]): void { 194 for (const issue of allIssues) { 195 if (fixAll || keys.includes(issue.defect.fixKey)) { 196 if (issue.fix && issue.defect.fixable && FixUtils.isRuleFix(issue.fix)) { 197 astFixIssues.push(issue); 198 } else { 199 remainIssues.push(issue); 200 logger.debug('Fix type is unsupported.'); 201 } 202 } else { 203 remainIssues.push(issue); 204 } 205 } 206 } 207} 208 209export async function checkEntryBuilder(checkEntry: CheckEntry): Promise<boolean> { 210 // 1、 无指定文件则检查项目下所有文件 211 let checkFileList = checkEntry.selectFileList.map(file => file.filePath); 212 if (checkFileList.length === 0) { 213 checkFileList = FileUtils.getAllFiles(checkEntry.projectConfig.projectPath, ['.ts', '.ets', '.js', '.json5']); 214 } 215 216 // 2、文件过滤和文件级屏蔽处理 217 checkFileList = await FileUtils.getFiltedFiles(checkFileList, checkEntry.ruleConfig); 218 logger.info('File count: ' + checkFileList.length); 219 if (checkFileList.length === 0) { 220 checkEntry.message?.progressNotify(1, 'No file to check.'); 221 return false; 222 } 223 224 // 3、scene按需构建、scope构建 225 if (!buildScene(checkFileList, checkEntry)) { 226 return false; 227 } 228 229 // 4、规则和文件映射构建 230 if (!(await fileRuleMapping(checkFileList, checkEntry))) { 231 return false; 232 } 233 return true; 234} 235 236/** 237 * 获取指定检查的文件列表 238 * 239 * @param checkFilePath - 指定的检查文件路径的配置文件路径,该文件内容示例{'checkPath': [{'filePath': 'xxx', 'fixKey': ['%line%sCol%eCol%ruleId']}]} 240 * filePath为需要检查的文件路径,fixKey为需要修复的缺陷key,空数组则不修复。 241 * @returns SelectFileInfo[] - 需要检查的文件列表 242 */ 243export function getSelectFileList(checkFilePath: string): SelectedFileInfo[] { 244 if (checkFilePath.length > 0) { 245 // 解析指定的文件 246 return FileUtils.getSeletctedFileInfos(checkFilePath, ['.ts', '.ets', '.json5']); 247 } 248 return []; 249} 250 251/** 252 * 构建Scene 253 * @param fileList - 文件列表 254 * @param checkEntry - 检查条目 255 * @returns {boolean} - 构建是否成功 256 */ 257function buildScene(fileList: string[], checkEntry: CheckEntry): boolean { 258 try { 259 // 构建SceneConfig信息 260 const sceneConfig = new SceneConfig(); 261 const projectName = checkEntry.projectConfig.projectName; 262 const projectPath = checkEntry.projectConfig.projectPath; 263 const languageTags = checkEntry.projectConfig.languageTags; 264 const sdkList = FileUtils.genSdks(checkEntry.projectConfig); 265 sceneConfig.buildFromProjectFiles(projectName, projectPath, fileList, sdkList, languageTags); 266 logger.info('Build sceneConfig completed.'); 267 // 构建Scene信息 268 checkEntry.scene = new Scene(); 269 checkEntry.scene.buildSceneFromFiles(sceneConfig); 270 logger.info('Build scene completed.'); 271 checkEntry.scene.inferTypes(); 272 logger.info('No.1 Infer types completed.'); 273 checkEntry.scene.inferTypes(); 274 logger.info('No.2 Infer types completed.'); 275 } catch (error) { 276 logger.error('Build scene or infer types error: ', error); 277 return false; 278 } 279 // 构建Scope信息 280 checkEntry.buildScope(); 281 logger.info('Build scope completed.'); 282 return true; 283} 284