• 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 { 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