• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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 { ArkFile } from 'arkanalyzer';
17import { FileReports, IssueReport } from '../../model/Defects';
18import { Engine } from '../../model/Engine';
19import { RuleFix } from '../../model/Fix';
20import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
21import { FixUtils } from '../../utils/common/FixUtils';
22
23const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'EsLintFixEngine');
24const BOM = '\uFEFF';
25let eof = '\r\n';
26
27
28export class EsLintFixEngine implements Engine {
29    applyFix(arkFile: ArkFile, fixIssues: IssueReport[], remainIssues: IssueReport[]): FileReports {
30        let sourceText = arkFile.getCode();
31        const bom = sourceText.startsWith(BOM) ? BOM : '';
32        let text = bom ? sourceText.slice(1) : sourceText;
33        let lastPos = Number.NEGATIVE_INFINITY;
34        let output = bom;
35        eof = FixUtils.getTextEof(text) || eof;
36        // issue非法数据检查及排序
37        const ret = this.checkAndSortIssues(fixIssues, remainIssues);
38        fixIssues = ret.fixIssues, remainIssues = ret.remainIssues;
39
40        if (fixIssues.length === 0) {
41            return { defects: remainIssues.map((issue => issue.defect)), output: '', filePath: arkFile.getFilePath() };
42        }
43        // 深拷贝remainIssues,防止在遍历过程中修改remainIssues导致后续迭代出错
44        const remainIssuesCopy = JSON.parse(JSON.stringify(remainIssues));
45        for (const issue of fixIssues) {
46            let fix = issue.fix as RuleFix;
47            const start = fix.range[0];
48            const end = fix.range[1];
49
50            output += text.slice(Math.max(0, lastPos), Math.max(0, start));
51            output += fix.text;
52            lastPos = end;
53            fix.fixed = true;
54            this.updateRemainIssues(text, issue, remainIssues, remainIssuesCopy);
55        }
56        output += text.slice(Math.max(0, lastPos));
57        return { defects: remainIssues.map((issue => issue.defect)), output: bom + output, filePath: arkFile.getFilePath() };
58    }
59
60    private checkAndSortIssues(fixIssues: IssueReport[], remainIssues: IssueReport[]): {
61        fixIssues: IssueReport[], remainIssues: IssueReport[]
62    } {
63        const fixIssuesValid: IssueReport[] = [];
64        fixIssues.forEach((issue) => {
65            const fix = issue.fix as RuleFix;
66            if (fix.range[0] <= fix.range[1] && fix.range[1] !== 0 && fix.range[0] >= 0) {
67                fixIssuesValid.push(issue);
68            } else {
69                remainIssues.push(issue);
70            }
71        });
72        return { fixIssues: fixIssuesValid.sort(this.compareIssueByRange), remainIssues: remainIssues.sort(this.compareIssueByLocation) };
73    }
74
75    private compareIssueByRange(issue1: IssueReport, issue2: IssueReport): number {
76        let fix1 = issue1.fix;
77        let fix2 = issue2.fix;
78        if (FixUtils.isRuleFix(fix1) && FixUtils.isRuleFix(fix2)) {
79            return fix1.range[0] - fix2.range[0] || fix1.range[1] - fix2.range[1];
80        } else {
81            return 0;
82        }
83    }
84
85    private compareIssueByLocation(a: IssueReport, b: IssueReport): number {
86        return a.defect.reportLine - b.defect.reportLine || a.defect.reportColumn - b.defect.reportColumn;
87    }
88
89    private updateRemainIssues(sourceText: string, issue: IssueReport, remainIssues: IssueReport[], remainIssuesOld: IssueReport[]): void {
90        if (remainIssues.length === 0) {
91            return;
92        }
93        let fix = issue.fix as RuleFix;
94        const start = fix.range[0];
95        const end = fix.range[1];
96        const fixEndCol = Number.parseInt(issue.defect.fixKey.split('%')[2]);
97
98        const originLineNum = sourceText.slice(start, end).split(eof).length;
99        const fixTextLineNUM = fix.text.split(eof).length;
100        const subLine = fixTextLineNUM - originLineNum;
101        for (let i = 0; i < remainIssuesOld.length; i++) {
102            const defectOld = remainIssuesOld[i].defect;
103            const defectOldEndCol = Number.parseInt(defectOld.fixKey.split('%')[2]);
104
105            // 1、当前告警区域完全在修复区域之前,不做处理。注意判断需使用旧的defect信息
106            if (defectOld.reportLine < issue.defect.reportLine ||
107                (defectOld.reportLine === issue.defect.reportLine && defectOldEndCol < issue.defect.reportColumn)) {
108                continue;
109            }
110            // 2、当前告警区域跟修复区域有重叠,不进行修复,直接删除该issue。TODO:该操作会导致重叠告警漏报,后续优化
111            if (defectOld.reportLine === issue.defect.reportLine &&
112                ((issue.defect.reportColumn < defectOld.reportColumn && defectOld.reportColumn < fixEndCol) ||
113                    (issue.defect.reportColumn < defectOldEndCol && defectOldEndCol < fixEndCol))) {
114                logger.warn(`The current defect area overlaps with the repair area, delete the defect, fixKey = ${defectOld.fixKey}`);
115                remainIssues.splice(i, 1);
116                remainIssuesOld.splice(i, 1);
117                i--;
118                continue;
119            }
120            // 注意行列号的累加需使用新的defect信息进行叠加
121            const defectNew = remainIssues[i].defect;
122            // 更新行号
123            defectNew.reportLine += subLine;
124            defectNew.fixKey = defectNew.fixKey.replace(/^[^%]*/, `${defectNew.reportLine}`);
125            defectNew.mergeKey = defectNew.mergeKey.replace(/%(.*?)%/, `%${defectNew.fixKey}%`);
126            // 更新列号, 当前告警跟修复issue在同一行,且在修复issue之后,需要进行列偏移
127            if (defectOld.reportLine === issue.defect.reportLine) {
128                const splitText = fix.text.split(eof);
129                let endCol = 0;
130                if (splitText.length > 1) {
131                    // 单行改多行,则偏移后的列号 = fixCode最后一行的长度 + 修复之前两个告警的间隔差值subCol
132                    const subCol = defectNew.reportColumn - fixEndCol;
133                    const colLen = Number.parseInt(defectNew.fixKey.split('%')[2]) - defectNew.reportColumn;
134                    defectNew.reportColumn = splitText[splitText.length - 1].length + subCol;
135                    endCol = defectNew.reportColumn + colLen;
136                } else {
137                    // 单行改单行,则偏移后的列号 = 当前列号 + 修复后的列差(fixCode的长度 - 被替换的文本长度)
138                    const subCol = fix.text.length - (end - start);
139                    defectNew.reportColumn += subCol;
140                    endCol = Number.parseInt(defectNew.fixKey.split('%')[2]) + subCol;
141                }
142                defectNew.fixKey = `${defectNew.reportLine}%${defectNew.reportColumn}%${endCol}%${defectNew.ruleId}`;
143                defectNew.mergeKey = defectNew.mergeKey.replace(/%(.*?)%/, `%${defectNew.fixKey}%`);
144            }
145        }
146    }
147}
148