* Copyright (c) 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 { FileReports, IssueReport } from '../../model/Defects';
import { Engine } from '../../model/Engine';
import { RuleFix } from '../../model/Fix';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { FixUtils } from '../../utils/common/FixUtils';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'EsLintFixEngine');
const BOM = '\uFEFF';
let eof = '\r\n';
export class EsLintFixEngine implements Engine {
applyFix(arkFile: ArkFile, fixIssues: IssueReport[], remainIssues: IssueReport[]): FileReports {
let sourceText = arkFile.getCode();
const bom = sourceText.startsWith(BOM) ? BOM : '';
let text = bom ? sourceText.slice(1) : sourceText;
let lastPos = Number.NEGATIVE_INFINITY;
let output = bom;
eof = FixUtils.getTextEof(text) || eof;
const ret = this.checkAndSortIssues(fixIssues, remainIssues);
fixIssues = ret.fixIssues, remainIssues = ret.remainIssues;
if (fixIssues.length === 0) {
return { defects: remainIssues.map((issue => issue.defect)), output: '', filePath: arkFile.getFilePath() };
}
const remainIssuesCopy = JSON.parse(JSON.stringify(remainIssues));
for (const issue of fixIssues) {
let fix = issue.fix as RuleFix;
const start = fix.range[0];
const end = fix.range[1];
output += text.slice(Math.max(0, lastPos), Math.max(0, start));
output += fix.text;
lastPos = end;
fix.fixed = true;
this.updateRemainIssues(text, issue, remainIssues, remainIssuesCopy);
}
output += text.slice(Math.max(0, lastPos));
return { defects: remainIssues.map((issue => issue.defect)), output: bom + output, filePath: arkFile.getFilePath() };
}
private checkAndSortIssues(fixIssues: IssueReport[], remainIssues: IssueReport[]): {
fixIssues: IssueReport[], remainIssues: IssueReport[]
} {
const fixIssuesValid: IssueReport[] = [];
fixIssues.forEach((issue) => {
const fix = issue.fix as RuleFix;
if (fix.range[0] <= fix.range[1] && fix.range[1] !== 0 && fix.range[0] >= 0) {
fixIssuesValid.push(issue);
} else {
remainIssues.push(issue);
}
});
return { fixIssues: fixIssuesValid.sort(this.compareIssueByRange), remainIssues: remainIssues.sort(this.compareIssueByLocation) };
}
private compareIssueByRange(issue1: IssueReport, issue2: IssueReport): number {
let fix1 = issue1.fix;
let fix2 = issue2.fix;
if (FixUtils.isRuleFix(fix1) && FixUtils.isRuleFix(fix2)) {
return fix1.range[0] - fix2.range[0] || fix1.range[1] - fix2.range[1];
} else {
return 0;
}
}
private compareIssueByLocation(a: IssueReport, b: IssueReport): number {
return a.defect.reportLine - b.defect.reportLine || a.defect.reportColumn - b.defect.reportColumn;
}
private updateRemainIssues(sourceText: string, issue: IssueReport, remainIssues: IssueReport[], remainIssuesOld: IssueReport[]): void {
if (remainIssues.length === 0) {
return;
}
let fix = issue.fix as RuleFix;
const start = fix.range[0];
const end = fix.range[1];
const fixEndCol = Number.parseInt(issue.defect.fixKey.split('%')[2]);
const originLineNum = sourceText.slice(start, end).split(eof).length;
const fixTextLineNUM = fix.text.split(eof).length;
const subLine = fixTextLineNUM - originLineNum;
for (let i = 0; i < remainIssuesOld.length; i++) {
const defectOld = remainIssuesOld[i].defect;
const defectOldEndCol = Number.parseInt(defectOld.fixKey.split('%')[2]);
if (defectOld.reportLine < issue.defect.reportLine ||
(defectOld.reportLine === issue.defect.reportLine && defectOldEndCol < issue.defect.reportColumn)) {
continue;
}
if (defectOld.reportLine === issue.defect.reportLine &&
((issue.defect.reportColumn < defectOld.reportColumn && defectOld.reportColumn < fixEndCol) ||
(issue.defect.reportColumn < defectOldEndCol && defectOldEndCol < fixEndCol))) {
logger.warn(`The current defect area overlaps with the repair area, delete the defect, fixKey = ${defectOld.fixKey}`);
remainIssues.splice(i, 1);
remainIssuesOld.splice(i, 1);
i--;
continue;
}
const defectNew = remainIssues[i].defect;
defectNew.reportLine += subLine;
defectNew.fixKey = defectNew.fixKey.replace(/^[^%]*/, `${defectNew.reportLine}`);
defectNew.mergeKey = defectNew.mergeKey.replace(/%(.*?)%/, `%${defectNew.fixKey}%`);
if (defectOld.reportLine === issue.defect.reportLine) {
const splitText = fix.text.split(eof);
let endCol = 0;
if (splitText.length > 1) {
const subCol = defectNew.reportColumn - fixEndCol;
const colLen = Number.parseInt(defectNew.fixKey.split('%')[2]) - defectNew.reportColumn;
defectNew.reportColumn = splitText[splitText.length - 1].length + subCol;
endCol = defectNew.reportColumn + colLen;
} else {
const subCol = fix.text.length - (end - start);
defectNew.reportColumn += subCol;
endCol = Number.parseInt(defectNew.fixKey.split('%')[2]) + subCol;
}
defectNew.fixKey = `${defectNew.reportLine}%${defectNew.reportColumn}%${endCol}%${defectNew.ruleId}`;
defectNew.mergeKey = defectNew.mergeKey.replace(/%(.*?)%/, `%${defectNew.fixKey}%`);
}
}
}
}