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 { ArkClass, ArkFile, ArkMethod, AstTreeUtils, ExportInfo, ImportInfo, Stmt, ts } from 'arkanalyzer'; 17import { AIFix, FunctionFix, Range, RuleFix } from '../../model/Fix'; 18 19export type FixPosition = { 20 startLine: number, 21 startCol: number, 22 endLine: number, 23 endCol: number 24}; 25 26export class FixUtils { 27 28 public static getRangeStart(arkFile: ArkFile, codeNode: Stmt | ArkMethod | ArkClass | ExportInfo | ImportInfo): number { 29 let lineNum = 0; 30 let startColumn = 0; 31 if (codeNode instanceof Stmt) { 32 let originalPosition = codeNode.getOriginPositionInfo(); 33 lineNum = originalPosition.getLineNo(); 34 startColumn = originalPosition.getColNo(); 35 } else if (codeNode instanceof ArkMethod) { 36 lineNum = codeNode.getLine() ?? 0; 37 startColumn = codeNode.getColumn() ?? 0; 38 } else if (codeNode instanceof ArkClass) { 39 lineNum = codeNode.getLine() ?? 0; 40 startColumn = codeNode.getColumn() ?? 0; 41 } else if (codeNode instanceof ExportInfo) { 42 let originalPosition = codeNode.getOriginTsPosition(); 43 lineNum = originalPosition.getLineNo(); 44 startColumn = originalPosition.getColNo(); 45 } else if (codeNode instanceof ImportInfo) { 46 let originalPosition = codeNode.getOriginTsPosition(); 47 lineNum = originalPosition.getLineNo(); 48 startColumn = originalPosition.getColNo(); 49 } 50 // 原文代码 51 let code = arkFile.getCode(); 52 // 找到当前分割符所在行 53 let lineBreak = this.getTextEof(code); 54 let cnt = 0; 55 if (lineBreak.length > 0) { 56 for (let index = 1; index !== lineNum; index++) { 57 cnt = code.indexOf(lineBreak, cnt + 1); 58 } 59 } 60 let start = (cnt === 0 && startColumn === 1) ? 0 : (cnt + startColumn + 1);//对第一行第一列特殊处理,后续代码都是以0,所以需要+1 61 return start; 62 } 63 64 // 根据输入的代码片段的起始、结束行列号信息,计算此代码片段在该文件中的起始偏移量、结束偏移量数据 65 public static getRangeWithAst(sourceFile: ts.SourceFile, fixPosition: FixPosition): Range { 66 const startNumber = ts.getPositionOfLineAndCharacter(sourceFile, fixPosition.startLine - 1, fixPosition.startCol - 1); 67 const endNumber = ts.getPositionOfLineAndCharacter(sourceFile, fixPosition.endLine - 1, fixPosition.endCol - 1); 68 return [startNumber, endNumber]; 69 } 70 71 // 根据输入的起始行号信息,计算该行的起始偏移量、结束偏移量数据 72 public static getLineRange(sourceFile: ts.SourceFile, lineNumber: number): Range | null { 73 const lineStarts = sourceFile.getLineStarts(); 74 75 // 验证行号范围 76 if (lineNumber < 1 || lineNumber > lineStarts.length) { 77 return null; 78 } 79 80 const startPos = lineStarts[lineNumber - 1]; 81 let endPos: number; 82 83 // 处理文件最后一行 84 if (lineNumber === lineStarts.length) { 85 endPos = sourceFile.text.length; 86 } else { 87 endPos = lineStarts[lineNumber] - 1; 88 } 89 return [startPos, endPos]; 90 } 91 92 // 根据给定的起始、结束偏移量数据,获取此段代码片段的源码字符串,位置信息不合法则返回null 93 public static getSourceWithRange(sourceFile: ts.SourceFile, range: Range): string | null { 94 const start = range[0]; 95 const end = range[1]; 96 if (start < 0 || end > sourceFile.text.length || start > end) { 97 return null; 98 } 99 return sourceFile.text.substring(start, end); 100 } 101 102 // 根据给定的行号,获取该行的源码字符串,行号不合法则返回null 103 public static getLineText(sourceFile: ts.SourceFile, lineNumber: number): string | null { 104 const range = this.getLineRange(sourceFile, lineNumber); 105 if (range === null) { 106 return null; 107 } 108 109 return sourceFile.text.substring(range[0], range[1]); 110 } 111 112 // 根据给定的行号,获取该行的换行符,获取失败则使用默认的'\n'换行符 113 public static getEolSymbol(sourceFile: ts.SourceFile, lineNumber: number): string { 114 let res = '\n'; 115 const lineStr = this.getLineText(sourceFile, lineNumber); 116 if (lineStr === null) { 117 return res; 118 } 119 120 const lfIndex = lineStr.indexOf('\n'); 121 if (lfIndex > 0 && lineStr[lfIndex - 1] === '\r') { 122 res = '\r\n'; 123 } 124 return res; 125 } 126 127 // 根据给定的行号,获取该行的缩进数量,采用空格缩进时为空格数量,采用tab缩进时为tab数量,行号不合法则返回null 128 public static getIndentOfLine(sourceFile: ts.SourceFile, lineNumber: number): number | null { 129 const lineStr = this.getLineText(sourceFile, lineNumber); 130 if (lineStr === null) { 131 return null; 132 } 133 134 const space = ' '; 135 let res = 0; 136 for (const char of lineStr) { 137 if (char === space) { 138 res++; 139 } else { 140 break; 141 } 142 } 143 return res; 144 } 145 146 // 根据给定的行号,获取该行附近的缩进宽度,采用空格缩进时为空格数量,采用tab缩进时为tab数量,无法找到则返回2 147 public static getIndentWidth(sourceFile: ts.SourceFile, lineNumber: number): number { 148 const lineIndent = FixUtils.getIndentOfLine(sourceFile, lineNumber); 149 let indentWidth = 0; 150 151 // 从当前行向上寻找最近的缩进量 152 let currLineIndent = lineIndent; 153 let previousLine = lineNumber - 1; 154 while (indentWidth <= 0 && previousLine > 0 && currLineIndent !== null) { 155 const previousLineIndent = FixUtils.getIndentOfLine(sourceFile, previousLine); 156 if (previousLineIndent !== null) { 157 indentWidth = Math.abs(currLineIndent - previousLineIndent); 158 } 159 currLineIndent = previousLineIndent; 160 previousLine--; 161 } 162 if (indentWidth > 0) { 163 return indentWidth; 164 } 165 166 // 从当前行向下寻找最近的缩进量 167 currLineIndent = lineIndent; 168 let nextLine = lineNumber + 1; 169 while (indentWidth <= 0 && nextLine < sourceFile.getLineStarts().length && currLineIndent !== null) { 170 const nextLineIndent = FixUtils.getIndentOfLine(sourceFile, nextLine); 171 if (nextLineIndent !== null) { 172 indentWidth = Math.abs(nextLineIndent - currLineIndent); 173 } 174 currLineIndent = nextLineIndent; 175 nextLine++; 176 } 177 if (indentWidth > 0) { 178 return indentWidth; 179 } 180 return 2; 181 } 182 183 public static getTextEof(text: string): string { 184 if (text.includes('\r\n')) { 185 return '\r\n'; 186 } else if (text.includes('\n')) { 187 return '\n'; 188 } else if (text.includes('\r')) { 189 return '\r'; 190 } else { 191 return ''; 192 } 193 } 194 195 public static isRuleFix(object: any): object is RuleFix { 196 return typeof object === 'object' && 'range' in object && 'text' in object; 197 } 198 199 public static isFunctionFix(object: any): object is FunctionFix { 200 return typeof object === 'object' && 'fix' in object; 201 } 202 203 public static isAIFix(object: any): object is AIFix { 204 return typeof object === 'object' && 'text' in object; 205 } 206 207 public static hasOwnPropertyOwn(object: any, key: string): boolean { 208 return typeof object === 'object' && key in object; 209 } 210} 211