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