• 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 */
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