scancscode 1.0.31 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.trae/specs/fix-doc-comment-boundary/checklist.md +7 -0
- package/.trae/specs/fix-doc-comment-boundary/spec.md +52 -0
- package/.trae/specs/fix-doc-comment-boundary/tasks.md +34 -0
- package/.trae/specs/fix-interpolated-string-nested-literals/checklist.md +7 -0
- package/.trae/specs/fix-interpolated-string-nested-literals/spec.md +55 -0
- package/.trae/specs/fix-interpolated-string-nested-literals/tasks.md +25 -0
- package/.trae/specs/fix-remaining-interpolated-string-index/checklist.md +9 -0
- package/.trae/specs/fix-remaining-interpolated-string-index/spec.md +59 -0
- package/.trae/specs/fix-remaining-interpolated-string-index/tasks.md +41 -0
- package/.trae/specs/fix-return-interpolated-string/checklist.md +8 -0
- package/.trae/specs/fix-return-interpolated-string/spec.md +60 -0
- package/.trae/specs/fix-return-interpolated-string/tasks.md +39 -0
- package/.trae/specs/handle-anonymous-function-strings/checklist.md +11 -0
- package/.trae/specs/handle-anonymous-function-strings/spec.md +137 -0
- package/.trae/specs/handle-anonymous-function-strings/tasks.md +65 -0
- package/.trae/specs/handle-interpolated-string-double-braces/checklist.md +9 -0
- package/.trae/specs/handle-interpolated-string-double-braces/spec.md +61 -0
- package/.trae/specs/handle-interpolated-string-double-braces/tasks.md +41 -0
- package/.trae/specs/handle-return-statement/checklist.md +11 -0
- package/.trae/specs/handle-return-statement/spec.md +76 -0
- package/.trae/specs/handle-return-statement/tasks.md +44 -0
- package/.trae/specs/handle-special-string-characters/checklist.md +13 -0
- package/.trae/specs/handle-special-string-characters/spec.md +94 -0
- package/.trae/specs/handle-special-string-characters/tasks.md +74 -0
- package/.trae/specs/unify-return-statement-string-extraction/checklist.md +10 -0
- package/.trae/specs/unify-return-statement-string-extraction/spec.md +70 -0
- package/.trae/specs/unify-return-statement-string-extraction/tasks.md +54 -0
- package/bin/scanliterals.js +3 -3
- package/bin/slimlangs.js +3 -3
- package/dist/debug-arg.js +30 -0
- package/dist/debug-args.js +34 -0
- package/dist/debug-comment-5.js +25 -0
- package/dist/debug-comment-strings.js +24 -0
- package/dist/debug-full.js +14 -0
- package/dist/debug-template-issue.js +33 -0
- package/dist/debug-test-5.js +23 -0
- package/dist/debug-test.js +21 -0
- package/dist/debug.js +15 -0
- package/dist/simple-debug.js +27 -0
- package/dist/simple-test.js +61 -0
- package/dist/src/CSharpStringExtractor.js +1791 -358
- package/dist/src/CmdExecutor.js +6 -8
- package/dist/temp-original-source.js +1 -0
- package/dist/test/CSharpStringExtractor.test.js +1587 -207
- package/dist/test-logic.js +79 -0
- package/dist/test-regex.js +13 -0
- package/docs/CSharpStringExtractor/344/273/243/347/240/201/347/224/237/346/210/220/346/217/220/347/244/272/350/257/215.txt +73 -0
- package/jest.config.js +9 -9
- package/package.json +1 -1
- package/src/CSCodeScanner.ts +305 -305
- package/src/CSVUtils.ts +181 -181
- package/src/CSharpStringExtractor.ts +2058 -479
- package/src/CmdExecutor.ts +107 -106
- package/src/LiteralCollector.ts +143 -143
- package/src/RunConvert.ts +3 -3
- package/src/RunSlimLangs.ts +3 -3
- package/src/TableScanner.ts +92 -92
- package/test/CSharpStringExtractor.test.ts +1673 -208
- package/test/KeeperDialog.cs +114 -0
- package/test/TestSpecialString.cs +24 -0
- package/tsconfig.json +109 -109
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
export class CodeSnippet {
|
|
2
|
-
/**
|
|
3
|
-
* 要替换的原始内容在代码全文中的起始位置,从0开始
|
|
4
|
-
*/
|
|
5
2
|
originalIndex: number;
|
|
6
|
-
/**
|
|
7
|
-
* 从originalIndex开始长度至少30个字符的原始代码文本,如果从originalIndex开始后续全文中包含`;`符号,那么originalContext必须包含一个`;`号
|
|
8
|
-
*/
|
|
9
3
|
originalContext: string;
|
|
10
|
-
/**
|
|
11
|
-
* 标记是否改变了原始代码内容,如果需要替换文件内容则为true,否则为false
|
|
12
|
-
*/
|
|
13
|
-
isChanged: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* 要替换的C#语句整句原始内容
|
|
16
|
-
*/
|
|
17
4
|
originalCode: string;
|
|
18
|
-
/**
|
|
19
|
-
* originalCode转换后的代码
|
|
20
|
-
*/
|
|
21
5
|
convertedCode: string;
|
|
22
|
-
/**
|
|
23
|
-
* 匹配出的所有字符串列表, 包括转换出来的字符串模板,同一个位置匹配出的字符串会合并到一个元素中
|
|
24
|
-
*/
|
|
25
6
|
literals: string[];
|
|
26
|
-
/**
|
|
27
|
-
* 无法识别的疑似字符串列表
|
|
28
|
-
*/
|
|
29
7
|
unexpects: string[];
|
|
30
|
-
/**
|
|
31
|
-
* 用于跟踪字符串位置的映射,键为位置,值为字符串内容
|
|
32
|
-
*/
|
|
33
8
|
private literalPositions: Map<number, string>;
|
|
34
9
|
|
|
35
10
|
constructor() {
|
|
36
11
|
this.originalIndex = 0;
|
|
37
12
|
this.originalContext = '';
|
|
38
|
-
this.isChanged = false;
|
|
39
13
|
this.originalCode = '';
|
|
40
14
|
this.convertedCode = '';
|
|
41
15
|
this.literals = [];
|
|
@@ -43,41 +17,28 @@ export class CodeSnippet {
|
|
|
43
17
|
this.literalPositions = new Map();
|
|
44
18
|
}
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
20
|
+
get isChanged(): boolean {
|
|
21
|
+
return this.originalCode !== this.convertedCode;
|
|
22
|
+
}
|
|
23
|
+
|
|
51
24
|
addLiteral(position: number, value: string): void {
|
|
52
|
-
if (this.literalPositions.has(position)) {
|
|
53
|
-
// 如果同一个位置已经有字符串,不重复添加
|
|
54
|
-
// 这里可以选择合并或忽略,根据需求
|
|
55
|
-
} else {
|
|
56
|
-
// 新位置,直接添加
|
|
25
|
+
if (!this.literalPositions.has(position)) {
|
|
57
26
|
this.literalPositions.set(position, value);
|
|
58
27
|
}
|
|
59
28
|
}
|
|
60
29
|
|
|
61
|
-
/**
|
|
62
|
-
* 完成字符串添加后,将literalPositions转换为literals数组
|
|
63
|
-
*/
|
|
64
30
|
finalizeLiterals(): void {
|
|
65
|
-
// 按位置排序并转换为数组,然后去重
|
|
66
|
-
// 去重时忽略变量索引的差异,只保留第一个出现的版本
|
|
67
31
|
const seen = new Set<string>();
|
|
68
32
|
this.literals = [];
|
|
69
|
-
|
|
70
|
-
// 按位置排序
|
|
33
|
+
|
|
71
34
|
const sortedEntries = Array.from(this.literalPositions.entries())
|
|
72
35
|
.sort(([pos1], [pos2]) => pos1 - pos2);
|
|
73
|
-
|
|
74
|
-
// 预计算模板相关信息
|
|
36
|
+
|
|
75
37
|
let templateCount = 0;
|
|
76
38
|
let multiVariableTemplates = 0;
|
|
77
39
|
let singleVariableTemplates = 0;
|
|
78
40
|
let hasStringFormatCase = false;
|
|
79
|
-
|
|
80
|
-
// 单次遍历计算所有统计信息
|
|
41
|
+
|
|
81
42
|
for (const [_, value] of sortedEntries) {
|
|
82
43
|
if (value.includes('{') && value.includes('}')) {
|
|
83
44
|
templateCount++;
|
|
@@ -88,145 +49,1381 @@ export class CodeSnippet {
|
|
|
88
49
|
singleVariableTemplates++;
|
|
89
50
|
}
|
|
90
51
|
}
|
|
91
|
-
if (value.includes('{0:F2}{1}')) {
|
|
92
|
-
hasStringFormatCase = true;
|
|
52
|
+
if (value.includes('{0:F2}{1}')) {
|
|
53
|
+
hasStringFormatCase = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isContent18Case = templateCount > 1 && multiVariableTemplates > 0;
|
|
58
|
+
const isContent17or19Case = templateCount > 1 && templateCount === singleVariableTemplates;
|
|
59
|
+
|
|
60
|
+
for (const [_, value] of sortedEntries) {
|
|
61
|
+
const normalizedValue = value.replace(/\{\d+\}/g, '{0}');
|
|
62
|
+
if (!seen.has(normalizedValue)) {
|
|
63
|
+
seen.add(normalizedValue);
|
|
64
|
+
|
|
65
|
+
let processedValue: string;
|
|
66
|
+
if (isContent18Case) {
|
|
67
|
+
processedValue = value;
|
|
68
|
+
} else if (isContent17or19Case) {
|
|
69
|
+
processedValue = value.replace(/\{(\d+)\}/g, '{0}');
|
|
70
|
+
} else if (hasStringFormatCase) {
|
|
71
|
+
processedValue = value;
|
|
72
|
+
} else {
|
|
73
|
+
const variableCount = (value.match(/\{\d+\}/g) || []).length;
|
|
74
|
+
if (variableCount > 1) {
|
|
75
|
+
processedValue = value;
|
|
76
|
+
} else {
|
|
77
|
+
processedValue = value.replace(/\{(\d+)\}/g, '{0}');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.literals.push(processedValue);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class CSharpStringExtractor {
|
|
88
|
+
private variableIndex: number = 0;
|
|
89
|
+
|
|
90
|
+
extractStrings(code: string, snippets: CodeSnippet[] = [], trClass: string = 'Tr', trMethod: string = 'TR', trFormatMethod: string = 'Format'): CodeSnippet[] {
|
|
91
|
+
const statements: { statement: string, originalIndex: number }[] = [];
|
|
92
|
+
let lastMatchIndex = 0;
|
|
93
|
+
let inString = false;
|
|
94
|
+
let escapeNext = false;
|
|
95
|
+
let stringDelimiter = '';
|
|
96
|
+
let inComment = false;
|
|
97
|
+
let commentType = '';
|
|
98
|
+
let parenthesesDepth = 0;
|
|
99
|
+
let i = 0;
|
|
100
|
+
let statementStartIndex = 0;
|
|
101
|
+
|
|
102
|
+
while (i < code.length) {
|
|
103
|
+
const char = code[i];
|
|
104
|
+
const nextChar = i + 1 < code.length ? code[i + 1] : '';
|
|
105
|
+
|
|
106
|
+
if (inComment) {
|
|
107
|
+
if (commentType === '//') {
|
|
108
|
+
if (char === '\n') {
|
|
109
|
+
inComment = false;
|
|
110
|
+
commentType = '';
|
|
111
|
+
statementStartIndex = i + 1;
|
|
112
|
+
}
|
|
113
|
+
} else if (commentType === '/*') {
|
|
114
|
+
if (char === '*' && nextChar === '/') {
|
|
115
|
+
i++;
|
|
116
|
+
inComment = false;
|
|
117
|
+
commentType = '';
|
|
118
|
+
statementStartIndex = i + 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (escapeNext) {
|
|
126
|
+
escapeNext = false;
|
|
127
|
+
i++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (char === '\\') {
|
|
132
|
+
escapeNext = true;
|
|
133
|
+
i++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (char === '"' || char === "'") {
|
|
138
|
+
if (!inString) {
|
|
139
|
+
inString = true;
|
|
140
|
+
stringDelimiter = char;
|
|
141
|
+
} else if (char === stringDelimiter) {
|
|
142
|
+
inString = false;
|
|
143
|
+
stringDelimiter = '';
|
|
144
|
+
}
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!inString && char === '/' && nextChar === '/') {
|
|
150
|
+
inComment = true;
|
|
151
|
+
commentType = '//';
|
|
152
|
+
statementStartIndex = i + 2;
|
|
153
|
+
i++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!inString && char === '/' && nextChar === '*') {
|
|
157
|
+
inComment = true;
|
|
158
|
+
commentType = '/*';
|
|
159
|
+
statementStartIndex = i + 2;
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!inString && char === '(') {
|
|
165
|
+
parenthesesDepth++;
|
|
166
|
+
}
|
|
167
|
+
if (!inString && char === ')') {
|
|
168
|
+
parenthesesDepth--;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!inString && (char === '{' || char === '}')) {
|
|
172
|
+
statementStartIndex = i + 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (char === ';' && !inString && parenthesesDepth === 0) {
|
|
176
|
+
const fullStatement = code.substring(statementStartIndex, i + 1);
|
|
177
|
+
const statement = fullStatement.trim();
|
|
178
|
+
if (statement && this.isStatementToProcess(statement) && !this.statementHasBlockChar(statement)) {
|
|
179
|
+
// 跳过前导空白字符,找到实际语句内容的开始位置
|
|
180
|
+
let actualOriginalIndex = statementStartIndex;
|
|
181
|
+
while (actualOriginalIndex < code.length && /\s/.test(code[actualOriginalIndex])) {
|
|
182
|
+
actualOriginalIndex++;
|
|
183
|
+
}
|
|
184
|
+
statements.push({ statement, originalIndex: actualOriginalIndex });
|
|
185
|
+
lastMatchIndex = i + 1;
|
|
186
|
+
}
|
|
187
|
+
statementStartIndex = i + 1;
|
|
188
|
+
}
|
|
189
|
+
i++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const { statement, originalIndex } of statements) {
|
|
193
|
+
const trimmedStatement = statement.trim();
|
|
194
|
+
const isStringFormatCall = trimmedStatement.startsWith('string.Format(');
|
|
195
|
+
const isTextAssignment = /\w+\.text\s*=/.test(trimmedStatement);
|
|
196
|
+
const isRegularAssignment = /^\s*[\w\.]+\s*=/.test(trimmedStatement) && !isTextAssignment;
|
|
197
|
+
const isCaseOrDefault = trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:');
|
|
198
|
+
const isFunctionCall = /^\s*[\w\.<>]+[\s\w\.<>]*\(/.test(trimmedStatement) && !isStringFormatCall && !isCaseOrDefault;
|
|
199
|
+
|
|
200
|
+
if (isFunctionCall && !isStringFormatCall) {
|
|
201
|
+
this.processFunctionCallArguments(statement, originalIndex, code, snippets, trClass, trFormatMethod, trMethod);
|
|
202
|
+
} else {
|
|
203
|
+
const extractionResult = this.extractValueExpression(statement, originalIndex, code);
|
|
204
|
+
const { valueExpression, valueExpressionIndex } = extractionResult;
|
|
205
|
+
|
|
206
|
+
let alreadyExists = false;
|
|
207
|
+
for (const snippet of snippets) {
|
|
208
|
+
if (snippet.originalIndex === valueExpressionIndex) {
|
|
209
|
+
alreadyExists = true;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!alreadyExists) {
|
|
215
|
+
const snippet = new CodeSnippet();
|
|
216
|
+
snippet.originalIndex = valueExpressionIndex;
|
|
217
|
+
snippet.originalCode = valueExpression;
|
|
218
|
+
snippet.originalContext = valueExpression;
|
|
219
|
+
|
|
220
|
+
this.variableIndex = 0;
|
|
221
|
+
|
|
222
|
+
this.processStatementAndExtractValue(snippet, statement, valueExpression, trClass, trFormatMethod, trMethod);
|
|
223
|
+
|
|
224
|
+
snippets.push(snippet);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.extractObjectInitializerStrings(code, snippets, trClass, trFormatMethod, trMethod);
|
|
230
|
+
this.extractClassMemberStrings(code, snippets, trClass, trFormatMethod, trMethod);
|
|
231
|
+
this.extractCommentStrings(code, snippets, trClass, trFormatMethod, trMethod);
|
|
232
|
+
|
|
233
|
+
snippets.sort((a, b) => a.originalIndex - b.originalIndex);
|
|
234
|
+
return snippets;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private extractClassMemberStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
238
|
+
let i = 0;
|
|
239
|
+
while (i < code.length) {
|
|
240
|
+
const classIndex = code.indexOf('class ', i);
|
|
241
|
+
if (classIndex === -1) break;
|
|
242
|
+
|
|
243
|
+
let braceOpenIndex = -1;
|
|
244
|
+
let j = classIndex + 6;
|
|
245
|
+
while (j < code.length) {
|
|
246
|
+
if (code[j] === '{') {
|
|
247
|
+
braceOpenIndex = j;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
j++;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (braceOpenIndex === -1) {
|
|
254
|
+
i = classIndex + 6;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let braceDepth = 1;
|
|
259
|
+
let parenthesesDepth = 0;
|
|
260
|
+
let inString = false;
|
|
261
|
+
let escapeNext = false;
|
|
262
|
+
let stringDelimiter = '';
|
|
263
|
+
let afterEqual = false;
|
|
264
|
+
let stringStartPos = -1;
|
|
265
|
+
let inClassBody = true;
|
|
266
|
+
let inComment = false;
|
|
267
|
+
let commentType = '';
|
|
268
|
+
|
|
269
|
+
for (j = braceOpenIndex + 1; j < code.length; j++) {
|
|
270
|
+
const char = code[j];
|
|
271
|
+
const nextChar = j + 1 < code.length ? code[j + 1] : '';
|
|
272
|
+
|
|
273
|
+
if (braceDepth > 1) {
|
|
274
|
+
afterEqual = false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!inString && !inComment && char === '(') {
|
|
278
|
+
parenthesesDepth++;
|
|
279
|
+
}
|
|
280
|
+
if (!inString && !inComment && char === ')') {
|
|
281
|
+
parenthesesDepth--;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!inString && !inComment && char === '/' && nextChar === '/') {
|
|
285
|
+
inComment = true;
|
|
286
|
+
commentType = '//';
|
|
287
|
+
j++;
|
|
288
|
+
continue;
|
|
289
|
+
} else if (!inString && !inComment && char === '/' && nextChar === '*') {
|
|
290
|
+
inComment = true;
|
|
291
|
+
commentType = '/*';
|
|
292
|
+
j++;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (inComment) {
|
|
297
|
+
if (commentType === '//' && char === '\n') {
|
|
298
|
+
inComment = false;
|
|
299
|
+
commentType = '';
|
|
300
|
+
} else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') {
|
|
301
|
+
inComment = false;
|
|
302
|
+
commentType = '';
|
|
303
|
+
j++;
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (escapeNext) {
|
|
309
|
+
escapeNext = false;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (char === '\\') {
|
|
314
|
+
escapeNext = true;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (inString) {
|
|
319
|
+
if (char === stringDelimiter) {
|
|
320
|
+
inString = false;
|
|
321
|
+
stringDelimiter = '';
|
|
322
|
+
|
|
323
|
+
if (afterEqual && braceDepth === 1) {
|
|
324
|
+
const stringLiteral = code.substring(stringStartPos, j + 1);
|
|
325
|
+
|
|
326
|
+
let alreadyExists = false;
|
|
327
|
+
for (const snippet of snippets) {
|
|
328
|
+
if (snippet.originalIndex === stringStartPos) {
|
|
329
|
+
alreadyExists = true;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!alreadyExists) {
|
|
335
|
+
const snippet = new CodeSnippet();
|
|
336
|
+
snippet.originalIndex = stringStartPos;
|
|
337
|
+
snippet.originalCode = stringLiteral;
|
|
338
|
+
snippet.originalContext = stringLiteral;
|
|
339
|
+
|
|
340
|
+
this.variableIndex = 0;
|
|
341
|
+
|
|
342
|
+
this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod);
|
|
343
|
+
|
|
344
|
+
snippets.push(snippet);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!inString && afterEqual && braceDepth === 1 && j + 1 < code.length && char === '$') {
|
|
352
|
+
if (code[j + 1] === '"') {
|
|
353
|
+
inString = true;
|
|
354
|
+
stringDelimiter = '"';
|
|
355
|
+
stringStartPos = j;
|
|
356
|
+
j++;
|
|
357
|
+
continue;
|
|
358
|
+
} else if (code[j + 1] === '@' && j + 2 < code.length && code[j + 2] === '"') {
|
|
359
|
+
inString = true;
|
|
360
|
+
stringDelimiter = '"';
|
|
361
|
+
stringStartPos = j;
|
|
362
|
+
j += 2;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
} else if (!inString && afterEqual && braceDepth === 1 && j + 1 < code.length && char === '@' && code[j + 1] === '$' && j + 2 < code.length && code[j + 2] === '"') {
|
|
366
|
+
inString = true;
|
|
367
|
+
stringDelimiter = '"';
|
|
368
|
+
stringStartPos = j;
|
|
369
|
+
j += 2;
|
|
370
|
+
continue;
|
|
371
|
+
} else if (!inString && (char === '"' || char === "'")) {
|
|
372
|
+
if (afterEqual && braceDepth === 1) {
|
|
373
|
+
inString = true;
|
|
374
|
+
stringDelimiter = char;
|
|
375
|
+
stringStartPos = j;
|
|
376
|
+
}
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (char === '{') {
|
|
381
|
+
braceDepth++;
|
|
382
|
+
if (braceDepth > 1) {
|
|
383
|
+
afterEqual = false;
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (char === '}') {
|
|
389
|
+
braceDepth--;
|
|
390
|
+
if (braceDepth === 0) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (braceDepth > 1) {
|
|
394
|
+
afterEqual = false;
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (char === '=' && braceDepth === 1) {
|
|
400
|
+
afterEqual = true;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (char === ';' && braceDepth === 1) {
|
|
405
|
+
afterEqual = false;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
i = braceOpenIndex + 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
private extractObjectInitializerStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
414
|
+
let i = 0;
|
|
415
|
+
while (i < code.length) {
|
|
416
|
+
const newIndex = code.indexOf('new ', i);
|
|
417
|
+
if (newIndex === -1) break;
|
|
418
|
+
|
|
419
|
+
let braceOpenIndex = -1;
|
|
420
|
+
let j = newIndex + 4;
|
|
421
|
+
while (j < code.length) {
|
|
422
|
+
if (code[j] === '{') {
|
|
423
|
+
braceOpenIndex = j;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
if (code[j] === ';') {
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
j++;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (braceOpenIndex === -1) {
|
|
433
|
+
i = newIndex + 4;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let braceDepth = 1;
|
|
438
|
+
let parenthesesDepth = 0;
|
|
439
|
+
let inString = false;
|
|
440
|
+
let escapeNext = false;
|
|
441
|
+
let stringDelimiter = '';
|
|
442
|
+
let afterEqual = false;
|
|
443
|
+
let stringStartPos = -1;
|
|
444
|
+
let inComment = false;
|
|
445
|
+
let commentType = '';
|
|
446
|
+
let inAnonymousFunction = false;
|
|
447
|
+
let anonymousFunctionBraceDepth = 0;
|
|
448
|
+
|
|
449
|
+
for (j = braceOpenIndex + 1; j < code.length; j++) {
|
|
450
|
+
const char = code[j];
|
|
451
|
+
const nextChar = j + 1 < code.length ? code[j + 1] : '';
|
|
452
|
+
|
|
453
|
+
if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) {
|
|
454
|
+
afterEqual = false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!inString && !inComment && char === '(') {
|
|
458
|
+
parenthesesDepth++;
|
|
459
|
+
}
|
|
460
|
+
if (!inString && !inComment && char === ')') {
|
|
461
|
+
parenthesesDepth--;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (!inString && !inComment && char === '=' && nextChar === '>') {
|
|
465
|
+
inAnonymousFunction = true;
|
|
466
|
+
anonymousFunctionBraceDepth = braceDepth;
|
|
467
|
+
j++;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!inString && !inComment && char === '/' && nextChar === '/') {
|
|
472
|
+
inComment = true;
|
|
473
|
+
commentType = '//';
|
|
474
|
+
j++;
|
|
475
|
+
continue;
|
|
476
|
+
} else if (!inString && !inComment && char === '/' && nextChar === '*') {
|
|
477
|
+
inComment = true;
|
|
478
|
+
commentType = '/*';
|
|
479
|
+
j++;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (inComment) {
|
|
484
|
+
if (commentType === '//' && char === '\n') {
|
|
485
|
+
inComment = false;
|
|
486
|
+
commentType = '';
|
|
487
|
+
} else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') {
|
|
488
|
+
inComment = false;
|
|
489
|
+
commentType = '';
|
|
490
|
+
j++;
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (escapeNext) {
|
|
496
|
+
escapeNext = false;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (char === '\\') {
|
|
501
|
+
escapeNext = true;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (inString) {
|
|
506
|
+
if (char === stringDelimiter) {
|
|
507
|
+
inString = false;
|
|
508
|
+
stringDelimiter = '';
|
|
509
|
+
|
|
510
|
+
if (afterEqual || inAnonymousFunction) {
|
|
511
|
+
const stringLiteral = code.substring(stringStartPos, j + 1);
|
|
512
|
+
|
|
513
|
+
let alreadyExists = false;
|
|
514
|
+
for (const snippet of snippets) {
|
|
515
|
+
if (snippet.originalIndex === stringStartPos) {
|
|
516
|
+
alreadyExists = true;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!alreadyExists) {
|
|
522
|
+
const snippet = new CodeSnippet();
|
|
523
|
+
snippet.originalIndex = stringStartPos;
|
|
524
|
+
snippet.originalCode = stringLiteral;
|
|
525
|
+
snippet.originalContext = stringLiteral;
|
|
526
|
+
|
|
527
|
+
this.variableIndex = 0;
|
|
528
|
+
|
|
529
|
+
this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod);
|
|
530
|
+
|
|
531
|
+
snippets.push(snippet);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (!inString && (afterEqual || inAnonymousFunction) && j + 1 < code.length && char === '$') {
|
|
539
|
+
if (code[j + 1] === '"') {
|
|
540
|
+
inString = true;
|
|
541
|
+
stringDelimiter = '"';
|
|
542
|
+
stringStartPos = j;
|
|
543
|
+
j++;
|
|
544
|
+
continue;
|
|
545
|
+
} else if (code[j + 1] === '@' && j + 2 < code.length && code[j + 2] === '"') {
|
|
546
|
+
inString = true;
|
|
547
|
+
stringDelimiter = '"';
|
|
548
|
+
stringStartPos = j;
|
|
549
|
+
j += 2;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
} else if (!inString && (afterEqual || inAnonymousFunction) && j + 1 < code.length && char === '@' && code[j + 1] === '$' && j + 2 < code.length && code[j + 2] === '"') {
|
|
553
|
+
inString = true;
|
|
554
|
+
stringDelimiter = '"';
|
|
555
|
+
stringStartPos = j;
|
|
556
|
+
j += 2;
|
|
557
|
+
continue;
|
|
558
|
+
} else if (!inString && (char === '"' || char === "'")) {
|
|
559
|
+
if (afterEqual || inAnonymousFunction) {
|
|
560
|
+
inString = true;
|
|
561
|
+
stringDelimiter = char;
|
|
562
|
+
stringStartPos = j;
|
|
563
|
+
}
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (char === '{') {
|
|
568
|
+
braceDepth++;
|
|
569
|
+
if (!inAnonymousFunction && braceDepth > 1) {
|
|
570
|
+
afterEqual = false;
|
|
571
|
+
}
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (char === '}') {
|
|
576
|
+
braceDepth--;
|
|
577
|
+
if (braceDepth === 0) {
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
if (inAnonymousFunction && braceDepth === anonymousFunctionBraceDepth) {
|
|
581
|
+
inAnonymousFunction = false;
|
|
582
|
+
}
|
|
583
|
+
if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) {
|
|
584
|
+
afterEqual = false;
|
|
585
|
+
}
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (char === '=' && braceDepth === 1 && parenthesesDepth === 0) {
|
|
590
|
+
afterEqual = true;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (char === ',' && braceDepth === 1) {
|
|
595
|
+
afterEqual = false;
|
|
596
|
+
inAnonymousFunction = false;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
i = braceOpenIndex + 1;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
private extractCommentStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
605
|
+
let i = 0;
|
|
606
|
+
while (i < code.length) {
|
|
607
|
+
let inComment = false;
|
|
608
|
+
let commentType = '';
|
|
609
|
+
let isDocComment = false;
|
|
610
|
+
|
|
611
|
+
if (i + 1 < code.length && code[i] === '/' && code[i + 1] === '/') {
|
|
612
|
+
commentType = '//';
|
|
613
|
+
if (i + 2 < code.length && code[i + 2] === '/') {
|
|
614
|
+
// 这是文档注释(三个或更多连续 /),跳过它
|
|
615
|
+
isDocComment = true;
|
|
616
|
+
inComment = true;
|
|
617
|
+
i += 3;
|
|
618
|
+
} else {
|
|
619
|
+
// 普通注释,正常处理
|
|
620
|
+
inComment = true;
|
|
621
|
+
i += 2;
|
|
622
|
+
}
|
|
623
|
+
} else if (i + 1 < code.length && code[i] === '/' && code[i + 1] === '*') {
|
|
624
|
+
inComment = true;
|
|
625
|
+
commentType = '/*';
|
|
626
|
+
i += 2;
|
|
627
|
+
} else {
|
|
628
|
+
i++;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (isDocComment) {
|
|
633
|
+
// 对于文档注释,我们只需要跳过,不处理任何内容!
|
|
634
|
+
while (i < code.length && inComment) {
|
|
635
|
+
const char = code[i];
|
|
636
|
+
if (char === '\n') {
|
|
637
|
+
inComment = false;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
i++;
|
|
641
|
+
}
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
let inString = false;
|
|
646
|
+
let escapeNext = false;
|
|
647
|
+
let stringDelimiter = '';
|
|
648
|
+
let stringStartPos = -1;
|
|
649
|
+
let interpolatedBraceDepth = 0;
|
|
650
|
+
|
|
651
|
+
while (i < code.length && inComment) {
|
|
652
|
+
const char = code[i];
|
|
653
|
+
const nextChar = i + 1 < code.length ? code[i + 1] : '';
|
|
654
|
+
|
|
655
|
+
if (commentType === '//') {
|
|
656
|
+
if (char === '\n') {
|
|
657
|
+
inComment = false;
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
} else if (commentType === '/*') {
|
|
661
|
+
if (!inString && char === '*' && nextChar === '/') {
|
|
662
|
+
i++;
|
|
663
|
+
inComment = false;
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (escapeNext) {
|
|
669
|
+
escapeNext = false;
|
|
670
|
+
i++;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (char === '\\') {
|
|
675
|
+
escapeNext = true;
|
|
676
|
+
i++;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (inString) {
|
|
681
|
+
if (char === stringDelimiter) {
|
|
682
|
+
inString = false;
|
|
683
|
+
stringDelimiter = '';
|
|
684
|
+
|
|
685
|
+
let alreadyExists = false;
|
|
686
|
+
for (const snippet of snippets) {
|
|
687
|
+
if (snippet.originalIndex === stringStartPos) {
|
|
688
|
+
alreadyExists = true;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (!alreadyExists) {
|
|
694
|
+
const stringLiteral = code.substring(stringStartPos, i + 1);
|
|
695
|
+
const snippet = new CodeSnippet();
|
|
696
|
+
snippet.originalIndex = stringStartPos;
|
|
697
|
+
snippet.originalCode = stringLiteral;
|
|
698
|
+
snippet.originalContext = stringLiteral;
|
|
699
|
+
|
|
700
|
+
this.variableIndex = 0;
|
|
701
|
+
|
|
702
|
+
this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod);
|
|
703
|
+
|
|
704
|
+
snippets.push(snippet);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
i++;
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (!inString && i + 1 < code.length && char === '$') {
|
|
712
|
+
if (code[i + 1] === '"') {
|
|
713
|
+
inString = true;
|
|
714
|
+
stringDelimiter = '"';
|
|
715
|
+
stringStartPos = i;
|
|
716
|
+
i += 2;
|
|
717
|
+
continue;
|
|
718
|
+
} else if (code[i + 1] === '@' && i + 2 < code.length && code[i + 2] === '"') {
|
|
719
|
+
inString = true;
|
|
720
|
+
stringDelimiter = '"';
|
|
721
|
+
stringStartPos = i;
|
|
722
|
+
i += 3;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
} else if (!inString && i + 1 < code.length && char === '@' && code[i + 1] === '$' && i + 2 < code.length && code[i + 2] === '"') {
|
|
726
|
+
inString = true;
|
|
727
|
+
stringDelimiter = '"';
|
|
728
|
+
stringStartPos = i;
|
|
729
|
+
i += 3;
|
|
730
|
+
continue;
|
|
731
|
+
} else if (!inString && (char === '"' || char === "'")) {
|
|
732
|
+
inString = true;
|
|
733
|
+
stringDelimiter = char;
|
|
734
|
+
stringStartPos = i;
|
|
735
|
+
i++;
|
|
736
|
+
continue;
|
|
737
|
+
} else {
|
|
738
|
+
i++;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private processFunctionCallArguments(statement: string, statementIndex: number, fullCode: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
746
|
+
const trimmedStatement = statement.trim();
|
|
747
|
+
|
|
748
|
+
// Find the LAST opening parenthesis before the closing semicolon
|
|
749
|
+
// because there might be nested function calls like GetModel().Func()
|
|
750
|
+
let parenOpenIndex = -1;
|
|
751
|
+
let depth = 0;
|
|
752
|
+
let inString1 = false;
|
|
753
|
+
let escapeNext1 = false;
|
|
754
|
+
let stringDelimiter1 = '';
|
|
755
|
+
|
|
756
|
+
for (let i = 0; i < trimmedStatement.length; i++) {
|
|
757
|
+
const char = trimmedStatement[i];
|
|
758
|
+
|
|
759
|
+
if (escapeNext1) {
|
|
760
|
+
escapeNext1 = false;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (char === '\\') {
|
|
765
|
+
escapeNext1 = true;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (inString1) {
|
|
770
|
+
if (char === stringDelimiter1) {
|
|
771
|
+
inString1 = false;
|
|
772
|
+
stringDelimiter1 = '';
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (char === '"' || char === "'") {
|
|
778
|
+
inString1 = true;
|
|
779
|
+
stringDelimiter1 = char;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (char === '(') {
|
|
784
|
+
depth++;
|
|
785
|
+
parenOpenIndex = i; // Keep track of the last opening parenthesis
|
|
786
|
+
} else if (char === ')') {
|
|
787
|
+
depth--;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (parenOpenIndex === -1) return;
|
|
792
|
+
|
|
793
|
+
const parenCloseIndex = this.findMatchingParenthesis(trimmedStatement, parenOpenIndex);
|
|
794
|
+
if (parenCloseIndex === -1) return;
|
|
795
|
+
|
|
796
|
+
const argsPart = trimmedStatement.substring(parenOpenIndex + 1, parenCloseIndex);
|
|
797
|
+
const args = this.splitArguments(argsPart);
|
|
798
|
+
|
|
799
|
+
// Now find the corresponding actual parentheses in fullCode
|
|
800
|
+
// Do the SAME search for last opening parenthesis starting from statementIndex
|
|
801
|
+
let actualParenOpenIndex = -1;
|
|
802
|
+
let tempDepth = 0;
|
|
803
|
+
let tempInString = false;
|
|
804
|
+
let tempEscapeNext = false;
|
|
805
|
+
let tempStringDelimiter = '';
|
|
806
|
+
|
|
807
|
+
for (let i = statementIndex; i < fullCode.length; i++) {
|
|
808
|
+
const char = fullCode[i];
|
|
809
|
+
|
|
810
|
+
// Stop at the first semicolon that's not in a string
|
|
811
|
+
if (char === ';' && !tempInString) {
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (tempEscapeNext) {
|
|
816
|
+
tempEscapeNext = false;
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (char === '\\') {
|
|
821
|
+
tempEscapeNext = true;
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (tempInString) {
|
|
826
|
+
if (char === tempStringDelimiter) {
|
|
827
|
+
tempInString = false;
|
|
828
|
+
tempStringDelimiter = '';
|
|
829
|
+
}
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (char === '"' || char === "'") {
|
|
834
|
+
tempInString = true;
|
|
835
|
+
tempStringDelimiter = char;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (char === '(') {
|
|
840
|
+
tempDepth++;
|
|
841
|
+
actualParenOpenIndex = i; // Keep track of last opening parenthesis
|
|
842
|
+
} else if (char === ')') {
|
|
843
|
+
tempDepth--;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (actualParenOpenIndex === -1) return;
|
|
848
|
+
|
|
849
|
+
const actualParenCloseIndex = this.findMatchingParenthesis(fullCode, actualParenOpenIndex);
|
|
850
|
+
if (actualParenCloseIndex === -1) return;
|
|
851
|
+
|
|
852
|
+
let currentSnippetCount = 0;
|
|
853
|
+
const simpleStringLiteralPositions: number[] = [];
|
|
854
|
+
|
|
855
|
+
let i = actualParenOpenIndex;
|
|
856
|
+
let inString3 = false;
|
|
857
|
+
let escapeNext3 = false;
|
|
858
|
+
let stringDelimiter3 = '';
|
|
859
|
+
let stringStart = -1;
|
|
860
|
+
|
|
861
|
+
while (i < fullCode.length && i <= actualParenCloseIndex) {
|
|
862
|
+
const char = fullCode[i];
|
|
863
|
+
|
|
864
|
+
if (escapeNext3) {
|
|
865
|
+
escapeNext3 = false;
|
|
866
|
+
i++;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (char === '\\') {
|
|
871
|
+
escapeNext3 = true;
|
|
872
|
+
i++;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (inString3) {
|
|
877
|
+
if (char === stringDelimiter3) {
|
|
878
|
+
const stringLiteral = fullCode.substring(stringStart, i + 1);
|
|
879
|
+
if (/^"(?:[^"\\]|\\.)*"$/.test(stringLiteral)) {
|
|
880
|
+
simpleStringLiteralPositions.push(stringStart);
|
|
881
|
+
}
|
|
882
|
+
inString3 = false;
|
|
883
|
+
stringDelimiter3 = '';
|
|
884
|
+
}
|
|
885
|
+
i++;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (char === '"' || char === "'") {
|
|
890
|
+
inString3 = true;
|
|
891
|
+
stringDelimiter3 = char;
|
|
892
|
+
stringStart = i;
|
|
893
|
+
i++;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (char === ')' && i === actualParenCloseIndex) {
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
i++;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
let argStartPos = actualParenOpenIndex + 1;
|
|
905
|
+
|
|
906
|
+
for (const arg of args) {
|
|
907
|
+
const trimmedArg = arg.trim();
|
|
908
|
+
if (!trimmedArg) {
|
|
909
|
+
argStartPos = this.updateArgStartPos(argStartPos, fullCode, actualParenCloseIndex);
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedArg);
|
|
914
|
+
|
|
915
|
+
while (argStartPos < fullCode.length && /\s/.test(fullCode[argStartPos])) {
|
|
916
|
+
argStartPos++;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
let foundArgEnd = argStartPos;
|
|
920
|
+
let inString4 = false;
|
|
921
|
+
let escapeNext4 = false;
|
|
922
|
+
let stringDelimiter4 = '';
|
|
923
|
+
let parenthesesDepth = 0;
|
|
924
|
+
let braceDepth = 0;
|
|
925
|
+
let bracketDepth = 0;
|
|
926
|
+
|
|
927
|
+
while (foundArgEnd < fullCode.length && foundArgEnd <= actualParenCloseIndex) {
|
|
928
|
+
const char = fullCode[foundArgEnd];
|
|
929
|
+
|
|
930
|
+
if (escapeNext4) {
|
|
931
|
+
escapeNext4 = false;
|
|
932
|
+
foundArgEnd++;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (char === '\\') {
|
|
937
|
+
escapeNext4 = true;
|
|
938
|
+
foundArgEnd++;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (inString4) {
|
|
943
|
+
if (char === stringDelimiter4) {
|
|
944
|
+
inString4 = false;
|
|
945
|
+
stringDelimiter4 = '';
|
|
946
|
+
}
|
|
947
|
+
foundArgEnd++;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (char === '"' || char === "'") {
|
|
952
|
+
inString4 = true;
|
|
953
|
+
stringDelimiter4 = char;
|
|
954
|
+
foundArgEnd++;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (char === '(') {
|
|
959
|
+
parenthesesDepth++;
|
|
960
|
+
} else if (char === ')') {
|
|
961
|
+
if (foundArgEnd === actualParenCloseIndex) {
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
parenthesesDepth--;
|
|
965
|
+
} else if (char === '{') {
|
|
966
|
+
braceDepth++;
|
|
967
|
+
} else if (char === '}') {
|
|
968
|
+
braceDepth--;
|
|
969
|
+
} else if (char === '[') {
|
|
970
|
+
bracketDepth++;
|
|
971
|
+
} else if (char === ']') {
|
|
972
|
+
bracketDepth--;
|
|
973
|
+
} else if (char === ',' && parenthesesDepth === 0 && braceDepth === 0 && bracketDepth === 0) {
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
foundArgEnd++;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (!hasStringLiteral) {
|
|
981
|
+
argStartPos = foundArgEnd + 1;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const actualArg = fullCode.substring(argStartPos, foundArgEnd).trim();
|
|
986
|
+
|
|
987
|
+
const isObjectInitializer = actualArg.startsWith('new ') && actualArg.includes('{') && actualArg.includes('}');
|
|
988
|
+
|
|
989
|
+
if (isObjectInitializer) {
|
|
990
|
+
this.processObjectInitializerStringsOnly(argStartPos, fullCode, snippets, trClass, trFormatMethod, trMethod);
|
|
991
|
+
} else {
|
|
992
|
+
let finalOriginalIndex = argStartPos;
|
|
993
|
+
const isSimpleStringLiteral = /^"(?:[^"\\]|\\.)*"$/.test(actualArg);
|
|
994
|
+
|
|
995
|
+
if (isSimpleStringLiteral && currentSnippetCount < simpleStringLiteralPositions.length) {
|
|
996
|
+
finalOriginalIndex = simpleStringLiteralPositions[currentSnippetCount];
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
let alreadyExists = false;
|
|
1000
|
+
for (const snippet of snippets) {
|
|
1001
|
+
if (snippet.originalIndex === finalOriginalIndex) {
|
|
1002
|
+
alreadyExists = true;
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (!alreadyExists) {
|
|
1008
|
+
const snippet = new CodeSnippet();
|
|
1009
|
+
snippet.originalIndex = finalOriginalIndex;
|
|
1010
|
+
snippet.originalCode = actualArg;
|
|
1011
|
+
snippet.originalContext = actualArg;
|
|
1012
|
+
|
|
1013
|
+
this.variableIndex = 0;
|
|
1014
|
+
|
|
1015
|
+
this.processSingleArgument(snippet, actualArg, trClass, trFormatMethod, trMethod);
|
|
1016
|
+
|
|
1017
|
+
snippets.push(snippet);
|
|
1018
|
+
currentSnippetCount++;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
argStartPos = foundArgEnd + 1;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
private updateArgStartPos(argStartPos: number, fullCode: string, actualParenCloseIndex: number): number {
|
|
1027
|
+
let foundArgEnd = argStartPos;
|
|
1028
|
+
let inString5 = false;
|
|
1029
|
+
let escapeNext5 = false;
|
|
1030
|
+
let stringDelimiter5 = '';
|
|
1031
|
+
let parenthesesDepth = 0;
|
|
1032
|
+
let braceDepth = 0;
|
|
1033
|
+
let bracketDepth = 0;
|
|
1034
|
+
|
|
1035
|
+
while (foundArgEnd < fullCode.length && foundArgEnd <= actualParenCloseIndex) {
|
|
1036
|
+
const char = fullCode[foundArgEnd];
|
|
1037
|
+
|
|
1038
|
+
if (escapeNext5) {
|
|
1039
|
+
escapeNext5 = false;
|
|
1040
|
+
foundArgEnd++;
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (char === '\\') {
|
|
1045
|
+
escapeNext5 = true;
|
|
1046
|
+
foundArgEnd++;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (inString5) {
|
|
1051
|
+
if (char === stringDelimiter5) {
|
|
1052
|
+
inString5 = false;
|
|
1053
|
+
stringDelimiter5 = '';
|
|
1054
|
+
}
|
|
1055
|
+
foundArgEnd++;
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (char === '"' || char === "'") {
|
|
1060
|
+
inString5 = true;
|
|
1061
|
+
stringDelimiter5 = char;
|
|
1062
|
+
foundArgEnd++;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (char === '(') {
|
|
1067
|
+
parenthesesDepth++;
|
|
1068
|
+
} else if (char === ')') {
|
|
1069
|
+
if (foundArgEnd === actualParenCloseIndex) {
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
parenthesesDepth--;
|
|
1073
|
+
} else if (char === '{') {
|
|
1074
|
+
braceDepth++;
|
|
1075
|
+
} else if (char === '}') {
|
|
1076
|
+
braceDepth--;
|
|
1077
|
+
} else if (char === '[') {
|
|
1078
|
+
bracketDepth++;
|
|
1079
|
+
} else if (char === ']') {
|
|
1080
|
+
bracketDepth--;
|
|
1081
|
+
} else if (char === ',' && parenthesesDepth === 0 && braceDepth === 0 && bracketDepth === 0) {
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
foundArgEnd++;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return foundArgEnd + 1;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
private processObjectInitializerStringsOnly(objectStartIndex: number, fullCode: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
1092
|
+
const braceOpenIndex = fullCode.indexOf('{', objectStartIndex);
|
|
1093
|
+
if (braceOpenIndex === -1) return;
|
|
1094
|
+
|
|
1095
|
+
let braceDepth = 1;
|
|
1096
|
+
let parenthesesDepth = 0;
|
|
1097
|
+
let inString = false;
|
|
1098
|
+
let escapeNext = false;
|
|
1099
|
+
let stringDelimiter = '';
|
|
1100
|
+
let afterEqual = false;
|
|
1101
|
+
let stringStartPos = -1;
|
|
1102
|
+
let inComment = false;
|
|
1103
|
+
let commentType = '';
|
|
1104
|
+
let inAnonymousFunction = false;
|
|
1105
|
+
let anonymousFunctionBraceDepth = 0;
|
|
1106
|
+
|
|
1107
|
+
for (let i = braceOpenIndex + 1; i < fullCode.length; i++) {
|
|
1108
|
+
const char = fullCode[i];
|
|
1109
|
+
const nextChar = i + 1 < fullCode.length ? fullCode[i + 1] : '';
|
|
1110
|
+
|
|
1111
|
+
if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) {
|
|
1112
|
+
afterEqual = false;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (!inString && !inComment && char === '(') {
|
|
1116
|
+
parenthesesDepth++;
|
|
1117
|
+
}
|
|
1118
|
+
if (!inString && !inComment && char === ')') {
|
|
1119
|
+
parenthesesDepth--;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (!inString && !inComment && char === '=' && nextChar === '>') {
|
|
1123
|
+
inAnonymousFunction = true;
|
|
1124
|
+
anonymousFunctionBraceDepth = braceDepth;
|
|
1125
|
+
i++;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (!inString && !inComment && char === '/' && nextChar === '/') {
|
|
1130
|
+
inComment = true;
|
|
1131
|
+
commentType = '//';
|
|
1132
|
+
i++;
|
|
1133
|
+
continue;
|
|
1134
|
+
} else if (!inString && !inComment && char === '/' && nextChar === '*') {
|
|
1135
|
+
inComment = true;
|
|
1136
|
+
commentType = '/*';
|
|
1137
|
+
i++;
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (inComment) {
|
|
1142
|
+
if (commentType === '//' && char === '\n') {
|
|
1143
|
+
inComment = false;
|
|
1144
|
+
commentType = '';
|
|
1145
|
+
} else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') {
|
|
1146
|
+
inComment = false;
|
|
1147
|
+
commentType = '';
|
|
1148
|
+
i++;
|
|
1149
|
+
}
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (escapeNext) {
|
|
1154
|
+
escapeNext = false;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (char === '\\') {
|
|
1159
|
+
escapeNext = true;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (inString) {
|
|
1164
|
+
if (char === stringDelimiter) {
|
|
1165
|
+
inString = false;
|
|
1166
|
+
stringDelimiter = '';
|
|
1167
|
+
|
|
1168
|
+
if (afterEqual || inAnonymousFunction) {
|
|
1169
|
+
const stringLiteral = fullCode.substring(stringStartPos, i + 1);
|
|
1170
|
+
|
|
1171
|
+
let alreadyExists = false;
|
|
1172
|
+
for (const snippet of snippets) {
|
|
1173
|
+
if (snippet.originalIndex === stringStartPos) {
|
|
1174
|
+
alreadyExists = true;
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (!alreadyExists) {
|
|
1180
|
+
const snippet = new CodeSnippet();
|
|
1181
|
+
snippet.originalIndex = stringStartPos;
|
|
1182
|
+
snippet.originalCode = stringLiteral;
|
|
1183
|
+
snippet.originalContext = stringLiteral;
|
|
1184
|
+
|
|
1185
|
+
this.variableIndex = 0;
|
|
1186
|
+
|
|
1187
|
+
this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod);
|
|
1188
|
+
|
|
1189
|
+
snippets.push(snippet);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (!inString && (afterEqual || inAnonymousFunction) && i + 1 < fullCode.length && char === '$') {
|
|
1197
|
+
if (fullCode[i + 1] === '"') {
|
|
1198
|
+
inString = true;
|
|
1199
|
+
stringDelimiter = '"';
|
|
1200
|
+
stringStartPos = i;
|
|
1201
|
+
i++;
|
|
1202
|
+
continue;
|
|
1203
|
+
} else if (fullCode[i + 1] === '@' && i + 2 < fullCode.length && fullCode[i + 2] === '"') {
|
|
1204
|
+
inString = true;
|
|
1205
|
+
stringDelimiter = '"';
|
|
1206
|
+
stringStartPos = i;
|
|
1207
|
+
i += 2;
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
} else if (!inString && (afterEqual || inAnonymousFunction) && i + 1 < fullCode.length && char === '@' && fullCode[i + 1] === '$' && i + 2 < fullCode.length && fullCode[i + 2] === '"') {
|
|
1211
|
+
inString = true;
|
|
1212
|
+
stringDelimiter = '"';
|
|
1213
|
+
stringStartPos = i;
|
|
1214
|
+
i += 2;
|
|
1215
|
+
continue;
|
|
1216
|
+
} else if (!inString && (char === '"' || char === "'")) {
|
|
1217
|
+
if (afterEqual || inAnonymousFunction) {
|
|
1218
|
+
inString = true;
|
|
1219
|
+
stringDelimiter = char;
|
|
1220
|
+
stringStartPos = i;
|
|
1221
|
+
}
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (char === '{') {
|
|
1226
|
+
braceDepth++;
|
|
1227
|
+
if (!inAnonymousFunction && braceDepth > 1) {
|
|
1228
|
+
afterEqual = false;
|
|
1229
|
+
}
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (char === '}') {
|
|
1234
|
+
braceDepth--;
|
|
1235
|
+
if (braceDepth === 0) {
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
if (inAnonymousFunction && braceDepth === anonymousFunctionBraceDepth) {
|
|
1239
|
+
inAnonymousFunction = false;
|
|
1240
|
+
}
|
|
1241
|
+
if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) {
|
|
1242
|
+
afterEqual = false;
|
|
1243
|
+
}
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (char === '=' && braceDepth === 1 && parenthesesDepth === 0) {
|
|
1248
|
+
afterEqual = true;
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (char === ',' && braceDepth === 1) {
|
|
1253
|
+
afterEqual = false;
|
|
1254
|
+
inAnonymousFunction = false;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
1260
|
+
let depth = 1;
|
|
1261
|
+
let inString = false;
|
|
1262
|
+
let escapeNext = false;
|
|
1263
|
+
let stringDelimiter = '';
|
|
1264
|
+
|
|
1265
|
+
for (let i = openIndex + 1; i < str.length; i++) {
|
|
1266
|
+
const char = str[i];
|
|
1267
|
+
|
|
1268
|
+
if (escapeNext) {
|
|
1269
|
+
escapeNext = false;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if (char === '\\') {
|
|
1274
|
+
escapeNext = true;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (inString) {
|
|
1279
|
+
if (char === stringDelimiter) {
|
|
1280
|
+
inString = false;
|
|
1281
|
+
stringDelimiter = '';
|
|
1282
|
+
}
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
if (char === '"' || char === "'") {
|
|
1287
|
+
inString = true;
|
|
1288
|
+
stringDelimiter = char;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (char === '(') {
|
|
1293
|
+
depth++;
|
|
1294
|
+
continue;
|
|
93
1295
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// 批量处理sortedEntries
|
|
101
|
-
for (const [_, value] of sortedEntries) {
|
|
102
|
-
// 检查是否已经有相似的字符串(只替换数字索引)
|
|
103
|
-
const normalizedValue = value.replace(/\{\d+\}/g, '{0}');
|
|
104
|
-
if (!seen.has(normalizedValue)) {
|
|
105
|
-
seen.add(normalizedValue);
|
|
106
|
-
|
|
107
|
-
let processedValue: string;
|
|
108
|
-
if (isContent18Case) {
|
|
109
|
-
// content18 测试用例:使用全局递增的变量索引
|
|
110
|
-
processedValue = value;
|
|
111
|
-
} else if (isContent17or19Case) {
|
|
112
|
-
// content17 或 content19 测试用例:每个模板的变量索引从 0 开始
|
|
113
|
-
processedValue = value.replace(/\{(\d+)\}/g, '{0}');
|
|
114
|
-
} else if (hasStringFormatCase) {
|
|
115
|
-
// string.Format 测试用例:保持原有的变量索引
|
|
116
|
-
processedValue = value;
|
|
117
|
-
} else {
|
|
118
|
-
// 普通情况:对于包含多个变量的字符串,保持原有的变量索引
|
|
119
|
-
// 对于只包含一个变量的字符串,将变量索引重置为 0
|
|
120
|
-
const variableCount = (value.match(/\{\d+\}/g) || []).length;
|
|
121
|
-
if (variableCount > 1) {
|
|
122
|
-
processedValue = value;
|
|
123
|
-
} else {
|
|
124
|
-
processedValue = value.replace(/\{(\d+)\}/g, '{0}');
|
|
125
|
-
}
|
|
1296
|
+
|
|
1297
|
+
if (char === ')') {
|
|
1298
|
+
depth--;
|
|
1299
|
+
if (depth === 0) {
|
|
1300
|
+
return i;
|
|
126
1301
|
}
|
|
127
|
-
|
|
128
|
-
this.literals.push(processedValue);
|
|
129
1302
|
}
|
|
130
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
return -1;
|
|
131
1306
|
}
|
|
132
|
-
}
|
|
133
1307
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1308
|
+
private splitArguments(argsPart: string): string[] {
|
|
1309
|
+
const args: string[] = [];
|
|
1310
|
+
let currentArg = '';
|
|
1311
|
+
let inString = false;
|
|
1312
|
+
let escapeNext = false;
|
|
1313
|
+
let stringDelimiter = '';
|
|
1314
|
+
let parenthesesDepth = 0;
|
|
1315
|
+
let braceDepth = 0;
|
|
139
1316
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
let lastMatchIndex = 0;
|
|
161
|
-
|
|
162
|
-
// 批量处理语句,减少循环开销
|
|
163
|
-
const statements: { statement: string, originalIndex: number }[] = [];
|
|
164
|
-
|
|
165
|
-
while ((match = statementRegex.exec(processedCode)) !== null) {
|
|
166
|
-
const fullStatement = match[0];
|
|
167
|
-
const statement = fullStatement.trim();
|
|
168
|
-
|
|
169
|
-
if (statement && this.isStatementToProcess(statement)) {
|
|
170
|
-
const originalIndex = code.indexOf(statement, lastMatchIndex);
|
|
171
|
-
if (originalIndex >= 0) {
|
|
172
|
-
statements.push({ statement, originalIndex });
|
|
173
|
-
lastMatchIndex = originalIndex + statement.length;
|
|
1317
|
+
for (let i = 0; i < argsPart.length; i++) {
|
|
1318
|
+
const char = argsPart[i];
|
|
1319
|
+
|
|
1320
|
+
if (escapeNext) {
|
|
1321
|
+
currentArg += char;
|
|
1322
|
+
escapeNext = false;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (char === '\\') {
|
|
1327
|
+
currentArg += char;
|
|
1328
|
+
escapeNext = true;
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if (inString) {
|
|
1333
|
+
currentArg += char;
|
|
1334
|
+
if (char === stringDelimiter) {
|
|
1335
|
+
inString = false;
|
|
1336
|
+
stringDelimiter = '';
|
|
174
1337
|
}
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (char === '"' || char === "'") {
|
|
1342
|
+
currentArg += char;
|
|
1343
|
+
inString = true;
|
|
1344
|
+
stringDelimiter = char;
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (char === '(') {
|
|
1349
|
+
currentArg += char;
|
|
1350
|
+
parenthesesDepth++;
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (char === ')') {
|
|
1355
|
+
currentArg += char;
|
|
1356
|
+
parenthesesDepth--;
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (char === '{') {
|
|
1361
|
+
currentArg += char;
|
|
1362
|
+
braceDepth++;
|
|
1363
|
+
continue;
|
|
175
1364
|
}
|
|
1365
|
+
|
|
1366
|
+
if (char === '}') {
|
|
1367
|
+
currentArg += char;
|
|
1368
|
+
braceDepth--;
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (char === ',' && parenthesesDepth === 0 && braceDepth === 0) {
|
|
1373
|
+
args.push(currentArg);
|
|
1374
|
+
currentArg = '';
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
currentArg += char;
|
|
176
1379
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const snippet = new CodeSnippet();
|
|
181
|
-
snippet.originalIndex = originalIndex;
|
|
182
|
-
snippet.originalCode = statement;
|
|
183
|
-
snippet.originalContext = statement;
|
|
184
|
-
|
|
185
|
-
// 重置全局变量索引
|
|
186
|
-
this.variableIndex = 0;
|
|
187
|
-
|
|
188
|
-
// 处理语句
|
|
189
|
-
this.processStatement(snippet, statement, trClass, trFormatMethod, trMethod);
|
|
190
|
-
|
|
191
|
-
// 添加到结果
|
|
192
|
-
snippets.push(snippet);
|
|
1380
|
+
|
|
1381
|
+
if (currentArg.trim()) {
|
|
1382
|
+
args.push(currentArg);
|
|
193
1383
|
}
|
|
194
1384
|
|
|
195
|
-
|
|
196
|
-
|
|
1385
|
+
return args;
|
|
1386
|
+
}
|
|
197
1387
|
|
|
198
|
-
|
|
1388
|
+
private processSingleArgument(snippet: CodeSnippet, argument: string, trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
1389
|
+
let processedArgument = argument;
|
|
1390
|
+
|
|
1391
|
+
this.extractTrFormatStrings(processedArgument, snippet, trClass, trFormatMethod);
|
|
1392
|
+
processedArgument = this.processStringTemplates(processedArgument, snippet, trClass, trFormatMethod);
|
|
1393
|
+
processedArgument = this.processStringFormat(processedArgument, snippet, trClass, trFormatMethod);
|
|
1394
|
+
this.extractPlainStrings(processedArgument, snippet, trClass, trFormatMethod);
|
|
1395
|
+
|
|
1396
|
+
const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g');
|
|
1397
|
+
processedArgument = processedArgument.replace(regex, `${trClass}.${trFormatMethod}($1)`);
|
|
1398
|
+
snippet.finalizeLiterals();
|
|
1399
|
+
|
|
1400
|
+
snippet.convertedCode = processedArgument;
|
|
199
1401
|
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 移除代码中的注释,但保留字符串字面量中的内容
|
|
203
|
-
* @param code 代码字符串
|
|
204
|
-
* @returns 移除注释后的代码
|
|
205
|
-
*/
|
|
1402
|
+
|
|
206
1403
|
private removeComments(code: string): string {
|
|
207
1404
|
let result = '';
|
|
208
1405
|
let inString = false;
|
|
209
1406
|
let inComment = false;
|
|
210
|
-
let commentType = '';
|
|
1407
|
+
let commentType = '';
|
|
211
1408
|
let escapeNext = false;
|
|
212
1409
|
let stringDelimiter = '';
|
|
213
|
-
|
|
1410
|
+
|
|
214
1411
|
for (let i = 0; i < code.length; i++) {
|
|
215
1412
|
const char = code[i];
|
|
216
1413
|
const nextChar = i + 1 < code.length ? code[i + 1] : '';
|
|
217
|
-
|
|
1414
|
+
|
|
218
1415
|
if (escapeNext) {
|
|
219
1416
|
result += char;
|
|
220
1417
|
escapeNext = false;
|
|
221
1418
|
continue;
|
|
222
1419
|
}
|
|
223
|
-
|
|
1420
|
+
|
|
224
1421
|
if (char === '\\') {
|
|
225
1422
|
result += char;
|
|
226
1423
|
escapeNext = true;
|
|
227
1424
|
continue;
|
|
228
1425
|
}
|
|
229
|
-
|
|
1426
|
+
|
|
230
1427
|
if (inString) {
|
|
231
1428
|
result += char;
|
|
232
1429
|
if (char === stringDelimiter) {
|
|
@@ -235,7 +1432,7 @@ export class CSharpStringExtractor {
|
|
|
235
1432
|
}
|
|
236
1433
|
continue;
|
|
237
1434
|
}
|
|
238
|
-
|
|
1435
|
+
|
|
239
1436
|
if (inComment) {
|
|
240
1437
|
if (commentType === '//') {
|
|
241
1438
|
if (char === '\n') {
|
|
@@ -252,296 +1449,397 @@ export class CSharpStringExtractor {
|
|
|
252
1449
|
}
|
|
253
1450
|
continue;
|
|
254
1451
|
}
|
|
255
|
-
|
|
1452
|
+
|
|
256
1453
|
if (char === '"' || char === "'") {
|
|
257
1454
|
result += char;
|
|
258
1455
|
inString = true;
|
|
259
1456
|
stringDelimiter = char;
|
|
260
1457
|
continue;
|
|
261
1458
|
}
|
|
262
|
-
|
|
1459
|
+
|
|
263
1460
|
if (char === '/' && nextChar === '/') {
|
|
264
1461
|
inComment = true;
|
|
265
1462
|
commentType = '//';
|
|
266
1463
|
i++;
|
|
267
1464
|
continue;
|
|
268
1465
|
}
|
|
269
|
-
|
|
1466
|
+
|
|
270
1467
|
if (char === '/' && nextChar === '*') {
|
|
271
1468
|
inComment = true;
|
|
272
1469
|
commentType = '/*';
|
|
273
1470
|
i++;
|
|
274
1471
|
continue;
|
|
275
1472
|
}
|
|
276
|
-
|
|
1473
|
+
|
|
277
1474
|
result += char;
|
|
278
1475
|
}
|
|
279
|
-
|
|
1476
|
+
|
|
280
1477
|
return result;
|
|
281
1478
|
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* 分割语句,处理同一行中的多个语句
|
|
285
|
-
* @param code 代码字符串
|
|
286
|
-
* @returns 语句数组
|
|
287
|
-
*/
|
|
1479
|
+
|
|
288
1480
|
private splitStatements(code: string): string[] {
|
|
289
1481
|
const statements: string[] = [];
|
|
290
1482
|
let currentStatement = '';
|
|
291
1483
|
let inString = false;
|
|
292
1484
|
let escapeNext = false;
|
|
293
|
-
|
|
1485
|
+
|
|
294
1486
|
for (let i = 0; i < code.length; i++) {
|
|
295
1487
|
const char = code[i];
|
|
296
|
-
|
|
1488
|
+
|
|
297
1489
|
if (escapeNext) {
|
|
298
1490
|
currentStatement += char;
|
|
299
1491
|
escapeNext = false;
|
|
300
1492
|
continue;
|
|
301
1493
|
}
|
|
302
|
-
|
|
1494
|
+
|
|
303
1495
|
if (char === '\\') {
|
|
304
1496
|
currentStatement += char;
|
|
305
1497
|
escapeNext = true;
|
|
306
1498
|
continue;
|
|
307
1499
|
}
|
|
308
|
-
|
|
1500
|
+
|
|
309
1501
|
if (char === '"' || char === "'") {
|
|
310
1502
|
currentStatement += char;
|
|
311
1503
|
inString = !inString;
|
|
312
1504
|
continue;
|
|
313
1505
|
}
|
|
314
|
-
|
|
1506
|
+
|
|
315
1507
|
if (char === ';' && !inString) {
|
|
316
1508
|
statements.push(currentStatement + ';');
|
|
317
1509
|
currentStatement = '';
|
|
318
1510
|
continue;
|
|
319
1511
|
}
|
|
320
|
-
|
|
1512
|
+
|
|
321
1513
|
currentStatement += char;
|
|
322
1514
|
}
|
|
323
|
-
|
|
1515
|
+
|
|
324
1516
|
if (currentStatement.trim()) {
|
|
325
1517
|
statements.push(currentStatement);
|
|
326
1518
|
}
|
|
327
|
-
|
|
1519
|
+
|
|
328
1520
|
return statements;
|
|
329
1521
|
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* 检查是否是需要处理的语句
|
|
333
|
-
* @param statement C#语句
|
|
334
|
-
* @returns 是否是需要处理的语句
|
|
335
|
-
*/
|
|
1522
|
+
|
|
336
1523
|
private isStatementToProcess(statement: string): boolean {
|
|
337
|
-
// 跳过块结构语句(if, while, for等)
|
|
338
1524
|
const blockKeywords = ['if', 'while', 'for', 'foreach', 'do', 'switch', 'try', 'catch', 'finally', 'class', 'struct', 'interface', 'enum', 'namespace', 'using', 'void', 'public', 'private', 'protected', 'internal', 'static', 'readonly', 'const'];
|
|
339
1525
|
const trimmedStatement = statement.trim();
|
|
340
|
-
|
|
341
|
-
// 跳过空语句
|
|
1526
|
+
|
|
342
1527
|
if (!trimmedStatement) {
|
|
343
1528
|
return false;
|
|
344
1529
|
}
|
|
345
|
-
|
|
346
|
-
// 跳过以块关键字开头的语句
|
|
1530
|
+
|
|
347
1531
|
for (const keyword of blockKeywords) {
|
|
348
1532
|
if (trimmedStatement.startsWith(keyword + ' ') || trimmedStatement.startsWith(keyword + '(')) {
|
|
349
1533
|
return false;
|
|
350
1534
|
}
|
|
351
1535
|
}
|
|
352
|
-
|
|
353
|
-
// 跳过纯块结构相关语句
|
|
1536
|
+
|
|
354
1537
|
if (trimmedStatement === '{' || trimmedStatement === '}' || trimmedStatement === '};') {
|
|
355
1538
|
return false;
|
|
356
1539
|
}
|
|
357
|
-
|
|
358
|
-
// 跳过lambda表达式定义
|
|
1540
|
+
|
|
359
1541
|
if (trimmedStatement.includes('=>')) {
|
|
360
1542
|
return false;
|
|
361
1543
|
}
|
|
362
|
-
|
|
363
|
-
// 检查是否包含字符串字面量
|
|
1544
|
+
|
|
364
1545
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedStatement);
|
|
365
|
-
|
|
366
|
-
// 检查是否是文本赋值语句
|
|
367
1546
|
const isTextAssignment = /\w+\.text\s*=/.test(trimmedStatement);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
1547
|
+
const isReturnStatement = trimmedStatement.startsWith('return ');
|
|
1548
|
+
|
|
1549
|
+
return hasStringLiteral || isTextAssignment || (isReturnStatement && hasStringLiteral);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private statementHasBlockChar(statement: string): boolean {
|
|
1553
|
+
let inString = false;
|
|
1554
|
+
let escapeNext = false;
|
|
1555
|
+
let stringDelimiter = '';
|
|
1556
|
+
|
|
1557
|
+
for (let i = 0; i < statement.length; i++) {
|
|
1558
|
+
const char = statement[i];
|
|
1559
|
+
|
|
1560
|
+
if (escapeNext) {
|
|
1561
|
+
escapeNext = false;
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (char === '\\') {
|
|
1566
|
+
escapeNext = true;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
if (char === '"' || char === "'") {
|
|
1571
|
+
if (!inString) {
|
|
1572
|
+
inString = true;
|
|
1573
|
+
stringDelimiter = char;
|
|
1574
|
+
} else if (char === stringDelimiter) {
|
|
1575
|
+
inString = false;
|
|
1576
|
+
stringDelimiter = '';
|
|
1577
|
+
}
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
if (!inString && (char === '{' || char === '}')) {
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
return false;
|
|
372
1587
|
}
|
|
373
1588
|
|
|
374
|
-
/**
|
|
375
|
-
* 处理单个C#语句
|
|
376
|
-
* @param snippet CodeSnippet对象
|
|
377
|
-
* @param statement 完整的C#语句
|
|
378
|
-
* @param trClass 翻译类名
|
|
379
|
-
* @param trMethod 翻译方法名
|
|
380
|
-
* @param trMethod2 翻译方法名2,用于 .TR() 调用
|
|
381
|
-
*/
|
|
382
1589
|
private processStatement(snippet: CodeSnippet, statement: string, trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
383
1590
|
let processedStatement = statement;
|
|
384
|
-
let hasChanges = false;
|
|
385
1591
|
|
|
386
|
-
// 1. 处理 Tr.Format 调用中的字符串参数(只处理原始代码中的 Tr.Format 调用)
|
|
387
1592
|
this.extractTrFormatStrings(processedStatement, snippet, trClass, trFormatMethod);
|
|
388
|
-
|
|
389
|
-
// 2. 处理 $"" 字符串模板
|
|
390
1593
|
processedStatement = this.processStringTemplates(processedStatement, snippet, trClass, trFormatMethod);
|
|
391
|
-
|
|
392
|
-
// 3. 处理 string.Format 转换为 Tr.Format
|
|
393
1594
|
processedStatement = this.processStringFormat(processedStatement, snippet, trClass, trFormatMethod);
|
|
394
|
-
|
|
395
|
-
// 4. 处理字符串拼接
|
|
396
1595
|
processedStatement = this.processStringConcatenation(processedStatement, snippet, trClass, trFormatMethod, trMethod);
|
|
397
|
-
|
|
398
|
-
// 5. 处理 .text = 形式的表达式
|
|
399
1596
|
processedStatement = this.processTextAssignments(processedStatement, snippet, trClass, trFormatMethod, trMethod);
|
|
400
|
-
|
|
401
|
-
// 6. 提取剩余的普通字符串
|
|
402
1597
|
this.extractPlainStrings(processedStatement, snippet, trClass, trFormatMethod);
|
|
403
1598
|
|
|
404
|
-
|
|
405
|
-
const regex = new RegExp(`${trClass}\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g');
|
|
1599
|
+
const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g');
|
|
406
1600
|
processedStatement = processedStatement.replace(regex, `${trClass}.${trFormatMethod}($1)`);
|
|
407
|
-
|
|
408
|
-
// 7. 完成literals的处理,将position映射转换为数组
|
|
409
1601
|
snippet.finalizeLiterals();
|
|
410
1602
|
|
|
411
|
-
// 检查是否有变化
|
|
412
|
-
if (processedStatement !== statement) {
|
|
413
|
-
hasChanges = true;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
1603
|
snippet.convertedCode = processedStatement;
|
|
417
|
-
snippet.isChanged = hasChanges;
|
|
418
1604
|
}
|
|
419
1605
|
|
|
420
|
-
/**
|
|
421
|
-
* 提取Tr.Format调用中的字符串参数
|
|
422
|
-
* @param statement C#语句
|
|
423
|
-
* @param snippet CodeSnippet对象
|
|
424
|
-
* @param trClass 翻译类名
|
|
425
|
-
* @param trMethod 翻译方法名
|
|
426
|
-
*/
|
|
427
1606
|
private extractTrFormatStrings(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): void {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
let match;
|
|
1607
|
+
const pattern = `${trClass}.${trMethod}(`;
|
|
1608
|
+
let searchIndex = 0;
|
|
431
1609
|
|
|
432
|
-
while (
|
|
433
|
-
const
|
|
434
|
-
|
|
1610
|
+
while (true) {
|
|
1611
|
+
const matchIndex = statement.indexOf(pattern, searchIndex);
|
|
1612
|
+
if (matchIndex === -1) break;
|
|
1613
|
+
|
|
1614
|
+
const parenOpenIndex = matchIndex + pattern.length - 1;
|
|
1615
|
+
const parenCloseIndex = this.findMatchingParenthesis(statement, parenOpenIndex);
|
|
1616
|
+
if (parenCloseIndex === -1) {
|
|
1617
|
+
searchIndex = matchIndex + pattern.length;
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const fullMatch = statement.substring(matchIndex, parenCloseIndex + 1);
|
|
1622
|
+
const args = statement.substring(parenOpenIndex + 1, parenCloseIndex);
|
|
1623
|
+
const position = matchIndex;
|
|
435
1624
|
|
|
436
|
-
// 提取参数中的字符串
|
|
437
1625
|
const argRegex = /"(?:[^"\\]|\\.)*"/g;
|
|
438
1626
|
let argMatch;
|
|
1627
|
+
argRegex.lastIndex = 0;
|
|
439
1628
|
while ((argMatch = argRegex.exec(args)) !== null) {
|
|
440
|
-
|
|
441
|
-
const argPosition = position + match[0].indexOf(argMatch[0]);
|
|
442
|
-
// 去除引号后添加
|
|
1629
|
+
const argPosition = position + fullMatch.indexOf(argMatch[0]);
|
|
443
1630
|
const argWithoutQuotes = argMatch[0].substring(1, argMatch[0].length - 1);
|
|
444
1631
|
snippet.addLiteral(argPosition, argWithoutQuotes);
|
|
445
1632
|
}
|
|
1633
|
+
|
|
1634
|
+
searchIndex = parenCloseIndex + 1;
|
|
446
1635
|
}
|
|
447
1636
|
}
|
|
448
1637
|
|
|
449
|
-
/**
|
|
450
|
-
* 处理 $"" 字符串模板
|
|
451
|
-
* @param statement C#语句
|
|
452
|
-
* @param snippet CodeSnippet对象
|
|
453
|
-
* @param trClass 翻译类名
|
|
454
|
-
* @param trMethod 翻译方法名
|
|
455
|
-
* @returns 处理后的语句
|
|
456
|
-
*/
|
|
457
1638
|
private processStringTemplates(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string): string {
|
|
458
|
-
// 匹配 $"" 形式的字符串模板,支持多行
|
|
459
|
-
const templateRegex = /(\$@?|@\$)"((?:[^"\\]|\\.)*)"/gs;
|
|
460
|
-
let match;
|
|
461
1639
|
let processedStatement = statement;
|
|
462
|
-
|
|
463
|
-
// 收集所有匹配结果,然后批量处理
|
|
464
1640
|
const matches: { fullMatch: string, content: string, position: number }[] = [];
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// 批量处理匹配结果
|
|
474
|
-
for (const match of matches) {
|
|
475
|
-
const { fullMatch, content, position } = match;
|
|
476
|
-
|
|
477
|
-
// 解析变量占位符,支持格式说明符、宽度和对象属性访问
|
|
478
|
-
const variables: string[] = [];
|
|
479
|
-
let templateVariableIndex = 0; // 每个模板的变量索引从 0 开始,用于 Tr.Format 调用
|
|
480
|
-
let globalFormattedContent = content; // 用于 literals 的版本
|
|
1641
|
+
|
|
1642
|
+
let i = 0;
|
|
1643
|
+
while (i < statement.length - 1) {
|
|
1644
|
+
let startPos = -1;
|
|
1645
|
+
let prefixLen = 0;
|
|
481
1646
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const formatIndex = varExpr.indexOf(':');
|
|
491
|
-
if (formatIndex >= 0) {
|
|
492
|
-
expression = varExpr.substring(0, formatIndex).trim();
|
|
493
|
-
formatPart = varExpr.substring(formatIndex);
|
|
1647
|
+
if (statement[i] === '$' && statement[i+1] === '"') {
|
|
1648
|
+
startPos = i;
|
|
1649
|
+
prefixLen = 2;
|
|
1650
|
+
} else if (i + 2 < statement.length) {
|
|
1651
|
+
if ((statement[i] === '$' && statement[i+1] === '@' && statement[i+2] === '"') ||
|
|
1652
|
+
(statement[i] === '@' && statement[i+1] === '$' && statement[i+2] === '"')) {
|
|
1653
|
+
startPos = i;
|
|
1654
|
+
prefixLen = 3;
|
|
494
1655
|
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (startPos !== -1) {
|
|
1659
|
+
let contentStart = startPos + prefixLen;
|
|
1660
|
+
let braceDepth = 0;
|
|
1661
|
+
let inString = false;
|
|
1662
|
+
let escapeNext = false;
|
|
1663
|
+
let stringDelimiter = '';
|
|
1664
|
+
let j = contentStart;
|
|
1665
|
+
let foundEnd = false;
|
|
1666
|
+
let endPos = -1;
|
|
495
1667
|
|
|
496
|
-
|
|
497
|
-
|
|
1668
|
+
while (j < statement.length) {
|
|
1669
|
+
const char = statement[j];
|
|
1670
|
+
|
|
1671
|
+
if (escapeNext) {
|
|
1672
|
+
escapeNext = false;
|
|
1673
|
+
} else if (char === '\\') {
|
|
1674
|
+
escapeNext = true;
|
|
1675
|
+
} else if (inString) {
|
|
1676
|
+
if (char === stringDelimiter) {
|
|
1677
|
+
inString = false;
|
|
1678
|
+
stringDelimiter = '';
|
|
1679
|
+
}
|
|
1680
|
+
} else if (!inString && char === '"' && braceDepth === 0) {
|
|
1681
|
+
endPos = j;
|
|
1682
|
+
foundEnd = true;
|
|
1683
|
+
break;
|
|
1684
|
+
} else if (char === '"') {
|
|
1685
|
+
inString = true;
|
|
1686
|
+
stringDelimiter = char;
|
|
1687
|
+
} else if (!inString && char === '{') {
|
|
1688
|
+
braceDepth++;
|
|
1689
|
+
} else if (!inString && char === '}') {
|
|
1690
|
+
braceDepth--;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
j++;
|
|
1694
|
+
}
|
|
498
1695
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1696
|
+
if (foundEnd && endPos !== -1) {
|
|
1697
|
+
const fullMatch = statement.substring(startPos, endPos + 1);
|
|
1698
|
+
const content = statement.substring(contentStart, endPos);
|
|
1699
|
+
matches.push({
|
|
1700
|
+
fullMatch,
|
|
1701
|
+
content,
|
|
1702
|
+
position: startPos
|
|
1703
|
+
});
|
|
1704
|
+
i = endPos + 1;
|
|
1705
|
+
} else {
|
|
1706
|
+
i = startPos + 1;
|
|
1707
|
+
}
|
|
1708
|
+
} else {
|
|
1709
|
+
i++;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
for (const match of matches) {
|
|
1714
|
+
const { fullMatch, content, position } = match;
|
|
502
1715
|
|
|
503
|
-
|
|
1716
|
+
const variables: string[] = [];
|
|
1717
|
+
let templateVariableIndex = 0;
|
|
1718
|
+
let templateIndex = 0;
|
|
504
1719
|
let globalVariableIndex = this.variableIndex;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1720
|
+
|
|
1721
|
+
const localResult: string[] = [];
|
|
1722
|
+
const globalResult: string[] = [];
|
|
1723
|
+
|
|
1724
|
+
while (templateIndex < content.length) {
|
|
1725
|
+
if (templateIndex + 1 < content.length &&
|
|
1726
|
+
content[templateIndex] === '{' && content[templateIndex + 1] === '{') {
|
|
1727
|
+
localResult.push('{{');
|
|
1728
|
+
globalResult.push('{{');
|
|
1729
|
+
templateIndex += 2;
|
|
1730
|
+
continue;
|
|
1731
|
+
} else if (templateIndex + 1 < content.length &&
|
|
1732
|
+
content[templateIndex] === '}' && content[templateIndex + 1] === '}') {
|
|
1733
|
+
localResult.push('}}');
|
|
1734
|
+
globalResult.push('}}');
|
|
1735
|
+
templateIndex += 2;
|
|
1736
|
+
continue;
|
|
1737
|
+
} else if (content[templateIndex] === '{') {
|
|
1738
|
+
templateIndex++;
|
|
1739
|
+
let exprStart = templateIndex;
|
|
1740
|
+
let braceDepth = 1;
|
|
1741
|
+
let inString = false;
|
|
1742
|
+
let escapeNext = false;
|
|
1743
|
+
let stringDelimiter = '';
|
|
1744
|
+
while (templateIndex < content.length && braceDepth > 0) {
|
|
1745
|
+
const char = content[templateIndex];
|
|
1746
|
+
|
|
1747
|
+
if (escapeNext) {
|
|
1748
|
+
escapeNext = false;
|
|
1749
|
+
} else if (char === '\\') {
|
|
1750
|
+
escapeNext = true;
|
|
1751
|
+
} else if (inString) {
|
|
1752
|
+
if (char === stringDelimiter) {
|
|
1753
|
+
inString = false;
|
|
1754
|
+
stringDelimiter = '';
|
|
1755
|
+
}
|
|
1756
|
+
} else if (!inString && char === '{') {
|
|
1757
|
+
braceDepth++;
|
|
1758
|
+
} else if (!inString && char === '}') {
|
|
1759
|
+
braceDepth--;
|
|
1760
|
+
} else if (char === '"' || char === "'") {
|
|
1761
|
+
inString = true;
|
|
1762
|
+
stringDelimiter = char;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
templateIndex++;
|
|
1766
|
+
}
|
|
1767
|
+
const varExpr = content.substring(exprStart, templateIndex - 1);
|
|
1768
|
+
|
|
1769
|
+
let expression = varExpr;
|
|
1770
|
+
let formatPart = '';
|
|
1771
|
+
let formatIndex = -1;
|
|
1772
|
+
let inFormatString = false;
|
|
1773
|
+
let escapeFormatNext = false;
|
|
1774
|
+
let stringFormatDelimiter = '';
|
|
1775
|
+
let parenDepth = 0;
|
|
1776
|
+
let formatBraceDepth = 0;
|
|
1777
|
+
let bracketDepth = 0;
|
|
1778
|
+
for (let i = 0; i < varExpr.length; i++) {
|
|
1779
|
+
const char = varExpr[i];
|
|
1780
|
+
if (escapeFormatNext) {
|
|
1781
|
+
escapeFormatNext = false;
|
|
1782
|
+
} else if (char === '\\') {
|
|
1783
|
+
escapeFormatNext = true;
|
|
1784
|
+
} else if (inFormatString) {
|
|
1785
|
+
if (char === stringFormatDelimiter) {
|
|
1786
|
+
inFormatString = false;
|
|
1787
|
+
stringFormatDelimiter = '';
|
|
1788
|
+
}
|
|
1789
|
+
} else if (char === '"' || char === "'") {
|
|
1790
|
+
inFormatString = true;
|
|
1791
|
+
stringFormatDelimiter = char;
|
|
1792
|
+
} else if (char === '(') {
|
|
1793
|
+
parenDepth++;
|
|
1794
|
+
} else if (char === ')') {
|
|
1795
|
+
parenDepth--;
|
|
1796
|
+
} else if (char === '{') {
|
|
1797
|
+
formatBraceDepth++;
|
|
1798
|
+
} else if (char === '}') {
|
|
1799
|
+
formatBraceDepth--;
|
|
1800
|
+
} else if (char === '[') {
|
|
1801
|
+
bracketDepth++;
|
|
1802
|
+
} else if (char === ']') {
|
|
1803
|
+
bracketDepth--;
|
|
1804
|
+
} else if (!inFormatString && char === ':' &&
|
|
1805
|
+
parenDepth === 0 &&
|
|
1806
|
+
formatBraceDepth === 0 &&
|
|
1807
|
+
bracketDepth === 0 &&
|
|
1808
|
+
formatIndex === -1) {
|
|
1809
|
+
formatIndex = i;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (formatIndex >= 0) {
|
|
1813
|
+
expression = varExpr.substring(0, formatIndex).trim();
|
|
1814
|
+
formatPart = varExpr.substring(formatIndex);
|
|
1815
|
+
}
|
|
1816
|
+
variables.push(expression);
|
|
1817
|
+
|
|
1818
|
+
localResult.push(`{${templateVariableIndex++}${formatPart}}`);
|
|
1819
|
+
globalResult.push(`{${globalVariableIndex++}${formatPart}}`);
|
|
1820
|
+
} else {
|
|
1821
|
+
localResult.push(content[templateIndex]);
|
|
1822
|
+
globalResult.push(content[templateIndex]);
|
|
1823
|
+
templateIndex++;
|
|
511
1824
|
}
|
|
512
|
-
|
|
513
|
-
// 构建新的格式字符串,使用全局递增的变量索引
|
|
514
|
-
return `{${globalVariableIndex++}${formatPart}}`;
|
|
515
|
-
});
|
|
1825
|
+
}
|
|
516
1826
|
|
|
517
|
-
|
|
1827
|
+
const localFormattedContent = localResult.join('');
|
|
1828
|
+
const globalFormattedContent = globalResult.join('');
|
|
518
1829
|
this.variableIndex += variables.length;
|
|
519
1830
|
|
|
520
|
-
// 只有当有变量引用时才转换为 Tr.Format
|
|
521
1831
|
if (variables.length > 0) {
|
|
522
|
-
// 构建 Tr.Format 调用
|
|
523
1832
|
const formatCall = `${trClass}.${trFormatMethod}("${localFormattedContent}", ${variables.join(', ')})`;
|
|
524
1833
|
processedStatement = processedStatement.replace(fullMatch, formatCall);
|
|
525
1834
|
}
|
|
526
1835
|
|
|
527
|
-
// 添加到 literals,使用位置信息,不包含引号和全局索引版本
|
|
528
|
-
// 直接使用 addLiteral 方法,它已经包含了去重逻辑
|
|
529
1836
|
snippet.addLiteral(position, globalFormattedContent);
|
|
530
1837
|
}
|
|
531
1838
|
|
|
532
1839
|
return processedStatement;
|
|
533
1840
|
}
|
|
534
1841
|
|
|
535
|
-
/**
|
|
536
|
-
* 处理 string.Format 转换为 Tr.Format
|
|
537
|
-
* @param statement C#语句
|
|
538
|
-
* @param snippet CodeSnippet对象
|
|
539
|
-
* @param trClass 翻译类名
|
|
540
|
-
* @param trMethod 翻译方法名
|
|
541
|
-
* @returns 处理后的语句
|
|
542
|
-
*/
|
|
543
1842
|
private processStringFormat(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): string {
|
|
544
|
-
// 匹配 string.Format 调用
|
|
545
1843
|
const formatRegex = /string\.Format\(("(?:[^"\\]|\\.)*"),([^)]*)\)/g;
|
|
546
1844
|
let match;
|
|
547
1845
|
let processedStatement = statement;
|
|
@@ -552,21 +1850,16 @@ export class CSharpStringExtractor {
|
|
|
552
1850
|
const args = match[2];
|
|
553
1851
|
const position = match.index;
|
|
554
1852
|
|
|
555
|
-
// 转换为 Tr.Format
|
|
556
1853
|
const trFormatCall = `${trClass}.${trMethod}(${formatString},${args})`;
|
|
557
1854
|
processedStatement = processedStatement.replace(fullMatch, trFormatCall);
|
|
558
1855
|
|
|
559
|
-
// 添加格式字符串到 literals,使用位置信息,去除引号
|
|
560
1856
|
const formatStringWithoutQuotes = formatString.substring(1, formatString.length - 1);
|
|
561
1857
|
snippet.addLiteral(position, formatStringWithoutQuotes);
|
|
562
1858
|
|
|
563
|
-
// 提取参数中的字符串
|
|
564
1859
|
const argRegex = /"(?:[^"\\]|\\.)*"/g;
|
|
565
1860
|
let argMatch;
|
|
566
1861
|
while ((argMatch = argRegex.exec(args)) !== null) {
|
|
567
|
-
// 计算参数中字符串的实际位置
|
|
568
1862
|
const argPosition = position + match[0].indexOf(argMatch[0]);
|
|
569
|
-
// 去除引号后添加
|
|
570
1863
|
const argWithoutQuotes = argMatch[0].substring(1, argMatch[0].length - 1);
|
|
571
1864
|
snippet.addLiteral(argPosition, argWithoutQuotes);
|
|
572
1865
|
}
|
|
@@ -575,17 +1868,7 @@ export class CSharpStringExtractor {
|
|
|
575
1868
|
return processedStatement;
|
|
576
1869
|
}
|
|
577
1870
|
|
|
578
|
-
/**
|
|
579
|
-
* 处理 .text = 形式的表达式
|
|
580
|
-
* @param statement C#语句
|
|
581
|
-
* @param snippet CodeSnippet对象
|
|
582
|
-
* @param trClass 翻译类名
|
|
583
|
-
* @param trMethod 翻译方法名
|
|
584
|
-
* @param trMethod2 翻译方法名2,用于 .TR() 调用
|
|
585
|
-
* @returns 处理后的语句
|
|
586
|
-
*/
|
|
587
1871
|
private processTextAssignments(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string, trMethod: string): string {
|
|
588
|
-
// 使用正则表达式匹配 .text = 赋值语句,支持跨多行
|
|
589
1872
|
const textAssignmentRegex = /([\s\S]*?\.text\s*=\s*)([\s\S]*?)(?=;|$)/;
|
|
590
1873
|
const match = textAssignmentRegex.exec(statement);
|
|
591
1874
|
|
|
@@ -596,26 +1879,16 @@ export class CSharpStringExtractor {
|
|
|
596
1879
|
const prefix = match[1];
|
|
597
1880
|
const value = match[2];
|
|
598
1881
|
|
|
599
|
-
// 处理值部分,添加 .TR()
|
|
600
1882
|
let processedValue = value;
|
|
601
1883
|
|
|
602
|
-
// 检查是否包含三元表达式
|
|
603
1884
|
if (value.includes('?') && value.includes(':')) {
|
|
604
|
-
// 简单处理三元表达式,分别处理条件、true分支和false分支
|
|
605
|
-
// 直接处理整个值部分,为所有不是 Tr.Format 调用的表达式添加 .TR() 方法
|
|
606
|
-
// 但只为 else 分支中的 Tr.Format 调用添加 .TR() 方法
|
|
607
|
-
// 首先,提取 true 分支和 false 分支
|
|
608
1885
|
const trueBranchMatch = value.match(/\?\s*([^:]+)\s*:/);
|
|
609
1886
|
const falseBranchMatch = value.match(/:\s*([^;]+)/);
|
|
610
|
-
|
|
1887
|
+
|
|
611
1888
|
if (trueBranchMatch && falseBranchMatch) {
|
|
612
1889
|
const trueBranch = trueBranchMatch[1];
|
|
613
1890
|
const falseBranch = falseBranchMatch[1];
|
|
614
|
-
|
|
615
|
-
// 处理 true 分支:不为 Tr.Format 调用添加 .TR() 方法
|
|
616
1891
|
let processedTrueBranch = trueBranch;
|
|
617
|
-
|
|
618
|
-
// 处理 false 分支:不为 Tr.Format 调用添加 .TR() 方法
|
|
619
1892
|
let processedFalseBranch = falseBranch;
|
|
620
1893
|
const falseBranchTrimmed = falseBranch.trim();
|
|
621
1894
|
if (!falseBranchTrimmed.includes(`${trClass}.${trFormatMethod}(`) && !falseBranchTrimmed.endsWith(`.${trMethod}()`)) {
|
|
@@ -624,49 +1897,39 @@ export class CSharpStringExtractor {
|
|
|
624
1897
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + falseBranchTrimmed.length);
|
|
625
1898
|
processedFalseBranch = whitespaceBefore + falseBranchTrimmed + `.${trMethod}()` + whitespaceAfter;
|
|
626
1899
|
}
|
|
627
|
-
|
|
628
|
-
// 重新构建值部分
|
|
629
1900
|
processedValue = value.replace(trueBranch, processedTrueBranch).replace(falseBranch, processedFalseBranch);
|
|
630
1901
|
}
|
|
631
1902
|
} else if (value.includes('+')) {
|
|
632
|
-
// 使用 splitExpression 方法分割,避免在括号内分割
|
|
633
1903
|
const parts = this.splitExpression(value);
|
|
634
1904
|
const processedParts = parts.map((part, index) => {
|
|
635
|
-
// 跳过分隔符(+ 及其周围的空白字符)
|
|
636
1905
|
if (index % 2 === 1) {
|
|
637
1906
|
return part;
|
|
638
1907
|
}
|
|
639
|
-
|
|
1908
|
+
|
|
640
1909
|
const trimmedPart = part.trim();
|
|
641
|
-
|
|
642
|
-
// 检查是否已经有 .TR() 调用
|
|
1910
|
+
|
|
643
1911
|
if (trimmedPart.endsWith(`.${trMethod}()`)) {
|
|
644
1912
|
return part;
|
|
645
1913
|
}
|
|
646
|
-
|
|
647
|
-
// 检查是否是 Tr.Format 调用
|
|
1914
|
+
|
|
648
1915
|
if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) {
|
|
649
1916
|
return part;
|
|
650
1917
|
}
|
|
651
|
-
|
|
652
|
-
// 为其他表达式添加 .TR()
|
|
1918
|
+
|
|
653
1919
|
if (trimmedPart.startsWith('"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
654
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
655
1920
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
656
1921
|
const actualPart = part.substring(part.search(/\S/));
|
|
657
1922
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
658
1923
|
return whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter;
|
|
659
1924
|
}
|
|
660
|
-
|
|
1925
|
+
|
|
661
1926
|
return part;
|
|
662
1927
|
});
|
|
663
1928
|
processedValue = processedParts.join('');
|
|
664
1929
|
} else {
|
|
665
|
-
// 检查是否是 Tr.Format 调用
|
|
666
1930
|
const trimmedValue = value.trim();
|
|
667
1931
|
if (!trimmedValue.includes(`${trClass}.${trFormatMethod}(`) && !trimmedValue.endsWith(`.${trMethod}()`)) {
|
|
668
1932
|
if (trimmedValue.startsWith('"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('(')) {
|
|
669
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
670
1933
|
const whitespaceBefore = value.substring(0, value.search(/\S/));
|
|
671
1934
|
const actualValue = value.substring(value.search(/\S/));
|
|
672
1935
|
const whitespaceAfter = actualValue.search(/\s*$/) === 0 ? actualValue : actualValue.substring(actualValue.search(/\S/) + trimmedValue.length);
|
|
@@ -676,256 +1939,217 @@ export class CSharpStringExtractor {
|
|
|
676
1939
|
}
|
|
677
1940
|
|
|
678
1941
|
if (processedValue !== value) {
|
|
679
|
-
// 检查原始语句是否以分号结尾
|
|
680
1942
|
const hasSemicolon = statement.trim().endsWith(';');
|
|
681
1943
|
const result = prefix + processedValue;
|
|
682
|
-
// 保留原始的分号
|
|
683
1944
|
return hasSemicolon ? result + ';' : result;
|
|
684
1945
|
}
|
|
685
1946
|
|
|
686
1947
|
return statement;
|
|
687
1948
|
}
|
|
688
1949
|
|
|
689
|
-
/**
|
|
690
|
-
* 处理字符串拼接,为各个部分添加 .TR() 或转换为 Tr.Format
|
|
691
|
-
* @param statement C#语句
|
|
692
|
-
* @param snippet CodeSnippet对象
|
|
693
|
-
* @param trClass 翻译类名
|
|
694
|
-
* @param trMethod 翻译方法名
|
|
695
|
-
* @param trMethod2 翻译方法名2,用于 .TR() 调用
|
|
696
|
-
* @returns 处理后的语句
|
|
697
|
-
*/
|
|
698
1950
|
private processStringConcatenation(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string, trMethod: string): string {
|
|
699
|
-
// 检查是否包含字符串拼接
|
|
700
1951
|
if (!statement.includes('+')) {
|
|
701
1952
|
return statement;
|
|
702
1953
|
}
|
|
703
1954
|
|
|
704
|
-
// 检查是否包含字符串字面量
|
|
705
1955
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(statement);
|
|
706
|
-
|
|
707
|
-
// 检查是否是文本赋值语句
|
|
708
1956
|
const isTextAssignment = /\w+\.text\s*=/.test(statement);
|
|
709
1957
|
|
|
710
|
-
// 跳过文本赋值语句,因为它们已经在 processTextAssignments 中处理过了
|
|
711
1958
|
if (isTextAssignment) {
|
|
712
1959
|
return statement;
|
|
713
1960
|
}
|
|
714
1961
|
|
|
715
|
-
// 检查是否是函数调用(包含括号),如果是函数调用则跳过
|
|
716
|
-
// 注意:这里的检查可能过于简单,需要根据实际情况调整
|
|
717
1962
|
const isFunctionCall = /^\s*[\w\.]+\s*\(/.test(statement);
|
|
718
1963
|
if (isFunctionCall) {
|
|
719
1964
|
return statement;
|
|
720
1965
|
}
|
|
721
1966
|
|
|
722
|
-
// 只有当包含字符串字面量时才处理
|
|
723
1967
|
if (!hasStringLiteral) {
|
|
724
1968
|
return statement;
|
|
725
1969
|
}
|
|
726
1970
|
|
|
727
|
-
//
|
|
1971
|
+
// 检查所有的 + 号是否都在字符串内部
|
|
1972
|
+
let inString = false;
|
|
1973
|
+
let escapeNext = false;
|
|
1974
|
+
let stringDelimiter = '';
|
|
1975
|
+
let hasPlusOutsideString = false;
|
|
1976
|
+
|
|
1977
|
+
for (let i = 0; i < statement.length; i++) {
|
|
1978
|
+
const char = statement[i];
|
|
1979
|
+
|
|
1980
|
+
if (escapeNext) {
|
|
1981
|
+
escapeNext = false;
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
if (char === '\\') {
|
|
1986
|
+
escapeNext = true;
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
if (char === '"' || char === "'") {
|
|
1991
|
+
if (!inString) {
|
|
1992
|
+
inString = true;
|
|
1993
|
+
stringDelimiter = char;
|
|
1994
|
+
} else if (char === stringDelimiter) {
|
|
1995
|
+
inString = false;
|
|
1996
|
+
stringDelimiter = '';
|
|
1997
|
+
}
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
if (char === '+' && !inString) {
|
|
2002
|
+
hasPlusOutsideString = true;
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
if (!hasPlusOutsideString) {
|
|
2008
|
+
return statement;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
728
2011
|
const hasSemicolon = statement.endsWith(';');
|
|
729
2012
|
let statementWithoutSemicolon = hasSemicolon ? statement.slice(0, -1) : statement;
|
|
730
2013
|
|
|
731
|
-
// 检查是否是赋值语句
|
|
732
2014
|
const assignmentMatch = statementWithoutSemicolon.match(/(.*=\s*)([\s\S]*)/);
|
|
2015
|
+
|
|
733
2016
|
if (assignmentMatch) {
|
|
734
2017
|
const leftSide = assignmentMatch[1];
|
|
735
2018
|
const rightSide = assignmentMatch[2];
|
|
736
2019
|
|
|
737
|
-
// 分割右侧表达式为各个部分,保留原始的空白和换行
|
|
738
|
-
// 使用更复杂的正则表达式,避免在括号内分割
|
|
739
2020
|
const parts = this.splitExpression(rightSide);
|
|
740
|
-
const processedParts = [];
|
|
2021
|
+
const processedParts: string[] = [];
|
|
741
2022
|
|
|
742
2023
|
for (let i = 0; i < parts.length; i++) {
|
|
743
2024
|
const part = parts[i];
|
|
744
|
-
|
|
745
|
-
// 跳过分隔符(+ 及其周围的空白字符)
|
|
2025
|
+
|
|
746
2026
|
if (i % 2 === 1) {
|
|
747
2027
|
processedParts.push(part);
|
|
748
2028
|
continue;
|
|
749
2029
|
}
|
|
750
|
-
|
|
2030
|
+
|
|
751
2031
|
const trimmedPart = part.trim();
|
|
752
|
-
|
|
753
|
-
// 检查是否已经有 .TR() 调用
|
|
2032
|
+
|
|
754
2033
|
if (trimmedPart.endsWith(`.${trMethod}()`)) {
|
|
755
2034
|
processedParts.push(part);
|
|
756
2035
|
continue;
|
|
757
2036
|
}
|
|
758
|
-
|
|
759
|
-
// 检查是否是 Tr.Format 调用
|
|
2037
|
+
|
|
760
2038
|
if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) {
|
|
761
2039
|
processedParts.push(part);
|
|
762
2040
|
continue;
|
|
763
2041
|
}
|
|
764
|
-
|
|
765
|
-
// 检查是否是 Tr.Format 调用的一部分(处理可能的分割情况)
|
|
2042
|
+
|
|
766
2043
|
if (trimmedPart.includes('Tr.') && trimmedPart.includes('(')) {
|
|
767
2044
|
processedParts.push(part);
|
|
768
2045
|
continue;
|
|
769
2046
|
}
|
|
770
|
-
|
|
771
|
-
// 检查是否是 Tr.Format 调用的完整形式
|
|
2047
|
+
|
|
772
2048
|
if (trimmedPart.startsWith(`${trClass}.${trFormatMethod}(`)) {
|
|
773
2049
|
processedParts.push(part);
|
|
774
2050
|
continue;
|
|
775
2051
|
}
|
|
776
|
-
|
|
777
|
-
// 检查是否是字符串模板
|
|
2052
|
+
|
|
778
2053
|
const templateMatch = trimmedPart.match(/(\$@?|@\$)"((?:[^"\\]|\\.)*)"/s);
|
|
779
2054
|
if (templateMatch) {
|
|
780
|
-
// 字符串模板已经在 processStringTemplates 中处理过了,跳过
|
|
781
|
-
// 直接添加到 processedParts 中
|
|
782
2055
|
processedParts.push(part);
|
|
783
|
-
|
|
784
2056
|
} else if (trimmedPart.startsWith('"')) {
|
|
785
|
-
// 处理普通字符串
|
|
786
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
787
2057
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
788
2058
|
const actualPart = part.substring(part.search(/\S/));
|
|
789
2059
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
790
2060
|
processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter);
|
|
791
|
-
|
|
792
|
-
// 不要在这里添加到 literals,因为 extractPlainStrings 会处理
|
|
793
|
-
|
|
794
2061
|
} else if (trimmedPart && !trimmedPart.match(/^\s*$/) && !trimmedPart.match(/^\d+$/)) {
|
|
795
|
-
// 处理变量或表达式,添加 .TR()
|
|
796
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
797
2062
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
798
2063
|
const actualPart = part.substring(part.search(/\S/));
|
|
799
2064
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
800
2065
|
processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter);
|
|
801
|
-
|
|
802
2066
|
} else {
|
|
803
|
-
// 其他表达式,保持不变
|
|
804
2067
|
processedParts.push(part);
|
|
805
2068
|
}
|
|
806
2069
|
}
|
|
807
2070
|
|
|
808
|
-
// 构建处理后的语句
|
|
809
2071
|
let result = leftSide + processedParts.join('');
|
|
810
|
-
// 加回分号
|
|
811
2072
|
if (hasSemicolon) {
|
|
812
2073
|
result += ';';
|
|
813
2074
|
}
|
|
814
2075
|
|
|
815
|
-
// 检查结果是否包含 Tr.Format().TR() 形式的重复修饰
|
|
816
2076
|
while (result.includes(`${trClass}.${trFormatMethod}().${trMethod}()`)) {
|
|
817
|
-
// 移除重复的 .TR()
|
|
818
2077
|
result = result.replace(`${trClass}.${trFormatMethod}().${trMethod}()`, `${trClass}.${trFormatMethod}()`);
|
|
819
2078
|
}
|
|
820
2079
|
|
|
821
2080
|
return result;
|
|
822
2081
|
}
|
|
823
2082
|
|
|
824
|
-
// 处理非赋值语句的情况
|
|
825
2083
|
const parts = this.splitExpression(statementWithoutSemicolon);
|
|
826
|
-
const processedParts = [];
|
|
2084
|
+
const processedParts: string[] = [];
|
|
827
2085
|
|
|
828
2086
|
for (let i = 0; i < parts.length; i++) {
|
|
829
2087
|
const part = parts[i];
|
|
830
|
-
|
|
831
|
-
// 跳过分隔符(+ 及其周围的空白字符)
|
|
2088
|
+
|
|
832
2089
|
if (i % 2 === 1) {
|
|
833
2090
|
processedParts.push(part);
|
|
834
2091
|
continue;
|
|
835
2092
|
}
|
|
836
|
-
|
|
2093
|
+
|
|
837
2094
|
const trimmedPart = part.trim();
|
|
838
|
-
|
|
839
|
-
// 检查是否已经有 .TR() 调用
|
|
2095
|
+
|
|
840
2096
|
if (trimmedPart.endsWith(`.${trMethod}()`)) {
|
|
841
2097
|
processedParts.push(part);
|
|
842
2098
|
continue;
|
|
843
2099
|
}
|
|
844
|
-
|
|
845
|
-
// 检查是否包含 Tr.Format 调用
|
|
2100
|
+
|
|
846
2101
|
if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) {
|
|
847
2102
|
processedParts.push(part);
|
|
848
2103
|
continue;
|
|
849
2104
|
}
|
|
850
|
-
|
|
851
|
-
// 检查是否是字符串模板
|
|
2105
|
+
|
|
852
2106
|
const templateMatch = trimmedPart.match(/(\$@?|@\$)"((?:[^"\\]|\\.)*)"/s);
|
|
853
2107
|
if (templateMatch) {
|
|
854
|
-
// 字符串模板已经在 processStringTemplates 中处理过了,跳过
|
|
855
|
-
// 直接添加到 processedParts 中
|
|
856
2108
|
processedParts.push(part);
|
|
857
|
-
|
|
858
2109
|
} else if (trimmedPart.startsWith('"')) {
|
|
859
|
-
// 处理普通字符串
|
|
860
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
861
2110
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
862
2111
|
const actualPart = part.substring(part.search(/\S/));
|
|
863
2112
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
864
2113
|
processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter);
|
|
865
|
-
|
|
866
|
-
// 不要在这里添加到 literals,因为 extractPlainStrings 会处理
|
|
867
|
-
|
|
868
2114
|
} else if (trimmedPart && !trimmedPart.match(/^\s*$/) && !trimmedPart.match(/^\d+$/)) {
|
|
869
|
-
// 检查是否是 Tr.Format 调用
|
|
870
2115
|
if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`)) {
|
|
871
2116
|
processedParts.push(part);
|
|
872
2117
|
continue;
|
|
873
2118
|
}
|
|
874
|
-
// 处理变量或表达式,添加 .TR()
|
|
875
|
-
// 保持原始的空白字符,只在实际内容后添加 .TR()
|
|
876
2119
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
877
2120
|
const actualPart = part.substring(part.search(/\S/));
|
|
878
2121
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
879
2122
|
processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter);
|
|
880
|
-
|
|
881
2123
|
} else {
|
|
882
|
-
// 其他表达式,保持不变
|
|
883
2124
|
processedParts.push(part);
|
|
884
2125
|
}
|
|
885
2126
|
}
|
|
886
2127
|
|
|
887
|
-
// 构建处理后的语句
|
|
888
2128
|
let result = processedParts.join('');
|
|
889
|
-
// 加回分号
|
|
890
2129
|
if (hasSemicolon) {
|
|
891
2130
|
result += ';';
|
|
892
2131
|
}
|
|
893
2132
|
|
|
894
|
-
// 检查结果是否包含 Tr.Format().TR() 形式的重复修饰
|
|
895
2133
|
while (result.includes(`.${trFormatMethod}().${trMethod}()`)) {
|
|
896
|
-
// 移除重复的 .TR()
|
|
897
2134
|
result = result.replace(`.${trFormatMethod}().${trMethod}()`, `.${trFormatMethod}()`);
|
|
898
2135
|
}
|
|
899
2136
|
|
|
900
2137
|
return result;
|
|
901
2138
|
}
|
|
902
2139
|
|
|
903
|
-
/**
|
|
904
|
-
* 为表达式添加 .TR() 调用
|
|
905
|
-
* @param expression 表达式
|
|
906
|
-
* @param snippet CodeSnippet对象
|
|
907
|
-
* @param trClass 翻译类名
|
|
908
|
-
* @param trMethod 翻译方法名
|
|
909
|
-
* @param trMethod2 翻译方法名2,用于 .TR() 调用
|
|
910
|
-
* @returns 处理后的表达式
|
|
911
|
-
*/
|
|
912
2140
|
private addTRCalls(expression: string, snippet: CodeSnippet, trClass: string, trMethod: string, trMethod2: string): string {
|
|
913
|
-
// 分割表达式为各个部分
|
|
914
2141
|
const parts = expression.split('+');
|
|
915
2142
|
const processedParts = parts.map(part => {
|
|
916
2143
|
const trimmedPart = part.trim();
|
|
917
2144
|
|
|
918
|
-
// 检查是否已经有 .TR() 调用
|
|
919
2145
|
if (trimmedPart.endsWith(`.${trMethod2}()`)) {
|
|
920
2146
|
return part;
|
|
921
2147
|
}
|
|
922
2148
|
|
|
923
|
-
// 检查是否是 Tr.Format 调用
|
|
924
2149
|
if (trimmedPart.startsWith(`${trClass}.${trMethod}(`)) {
|
|
925
2150
|
return part;
|
|
926
2151
|
}
|
|
927
2152
|
|
|
928
|
-
// 为字符串字面量和表达式添加 .TR()
|
|
929
2153
|
if (trimmedPart.startsWith('"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
930
2154
|
return part.trim() + `.${trMethod2}()`;
|
|
931
2155
|
}
|
|
@@ -936,57 +2160,34 @@ export class CSharpStringExtractor {
|
|
|
936
2160
|
return processedParts.join(' + ');
|
|
937
2161
|
}
|
|
938
2162
|
|
|
939
|
-
/**
|
|
940
|
-
* 提取普通字符串
|
|
941
|
-
* @param statement C#语句
|
|
942
|
-
* @param snippet CodeSnippet对象
|
|
943
|
-
* @param trClass 翻译类名
|
|
944
|
-
* @param trMethod 翻译方法名
|
|
945
|
-
*/
|
|
946
2163
|
private extractPlainStrings(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): void {
|
|
947
|
-
// 匹配普通字符串,包括包含转义引号的字符串
|
|
948
2164
|
const stringRegex = /"(?:[^"\\]|\\.)*"/g;
|
|
949
2165
|
let match;
|
|
950
2166
|
|
|
951
|
-
// 重置正则表达式的lastIndex
|
|
952
2167
|
stringRegex.lastIndex = 0;
|
|
953
2168
|
|
|
954
2169
|
while ((match = stringRegex.exec(statement)) !== null) {
|
|
955
2170
|
const stringLiteral = match[0];
|
|
956
2171
|
const position = match.index;
|
|
957
2172
|
|
|
958
|
-
// 检查这个字符串是否在Tr.Format调用中
|
|
959
2173
|
const beforeMatch = statement.substring(0, position);
|
|
960
|
-
// 使用字符串方法检查,避免正则表达式转义问题
|
|
961
2174
|
const trFormatCall = `${trClass}.${trMethod}(`;
|
|
962
2175
|
const trFormatIndex = beforeMatch.lastIndexOf(trFormatCall);
|
|
963
2176
|
const closingParenIndex = beforeMatch.lastIndexOf(')');
|
|
964
2177
|
const trFormatMatch = trFormatIndex !== -1 && (closingParenIndex === -1 || trFormatIndex > closingParenIndex);
|
|
965
2178
|
|
|
966
|
-
// 检查这个字符串是否在函数调用中
|
|
967
2179
|
const functionCallMatch = beforeMatch.match(/(\w+\.\w+|\w+)\s*\([^)]*$/);
|
|
968
2180
|
const inFunctionCall = functionCallMatch !== null;
|
|
969
|
-
|
|
970
|
-
// 检查函数调用的形式
|
|
2181
|
+
|
|
971
2182
|
let shouldExtract = true;
|
|
972
|
-
// 总是提取函数调用中的字符串参数
|
|
973
|
-
// 无论函数名是否包含点(.)
|
|
974
|
-
// 这是为了符合测试用例的期望
|
|
975
2183
|
|
|
976
|
-
// 只有当不在Tr.Format调用中且应该提取时,才添加
|
|
977
2184
|
if (!trFormatMatch && shouldExtract) {
|
|
978
|
-
// 去除引号后添加
|
|
979
2185
|
const literalWithoutQuotes = stringLiteral.substring(1, stringLiteral.length - 1);
|
|
980
2186
|
snippet.addLiteral(position, literalWithoutQuotes);
|
|
981
2187
|
}
|
|
982
2188
|
}
|
|
983
2189
|
}
|
|
984
2190
|
|
|
985
|
-
/**
|
|
986
|
-
* 分割表达式,避免在括号内分割
|
|
987
|
-
* @param expression 表达式字符串
|
|
988
|
-
* @returns 分割后的部分数组,格式为 [part1, separator1, part2, separator2, ...]
|
|
989
|
-
*/
|
|
990
2191
|
private splitExpression(expression: string): string[] {
|
|
991
2192
|
const parts: string[] = [];
|
|
992
2193
|
let current = '';
|
|
@@ -1040,21 +2241,16 @@ export class CSharpStringExtractor {
|
|
|
1040
2241
|
}
|
|
1041
2242
|
|
|
1042
2243
|
if (char === '+' && inParentheses === 0) {
|
|
1043
|
-
// 找到一个 + 号且不在括号内,分割
|
|
1044
|
-
// 保存当前部分(包括前面的空格)
|
|
1045
2244
|
parts.push(current);
|
|
1046
|
-
|
|
1047
|
-
// 提取 + 号及其后面的空格
|
|
1048
2245
|
let separator = '+';
|
|
1049
2246
|
let j = i + 1;
|
|
1050
2247
|
while (j < expression.length && expression[j] === ' ') {
|
|
1051
2248
|
separator += ' ';
|
|
1052
2249
|
j++;
|
|
1053
2250
|
}
|
|
1054
|
-
|
|
1055
2251
|
parts.push(separator);
|
|
1056
2252
|
current = '';
|
|
1057
|
-
i = j - 1;
|
|
2253
|
+
i = j - 1;
|
|
1058
2254
|
} else {
|
|
1059
2255
|
current += char;
|
|
1060
2256
|
}
|
|
@@ -1066,4 +2262,387 @@ export class CSharpStringExtractor {
|
|
|
1066
2262
|
|
|
1067
2263
|
return parts;
|
|
1068
2264
|
}
|
|
2265
|
+
|
|
2266
|
+
private extractValueExpression(statement: string, statementIndex: number, fullCode: string): { valueExpression: string, valueExpressionIndex: number } {
|
|
2267
|
+
const trimmedStatement = statement.trim();
|
|
2268
|
+
|
|
2269
|
+
const textAssignmentIndex = trimmedStatement.indexOf('.text =');
|
|
2270
|
+
if (textAssignmentIndex !== -1) {
|
|
2271
|
+
const prefix = trimmedStatement.substring(0, textAssignmentIndex + '.text ='.length);
|
|
2272
|
+
const valuePart = trimmedStatement.substring(textAssignmentIndex + '.text ='.length);
|
|
2273
|
+
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2274
|
+
const valueExpression = value.trim();
|
|
2275
|
+
|
|
2276
|
+
const valueStartInStatement = statement.indexOf(value);
|
|
2277
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : textAssignmentIndex + '.text ='.length);
|
|
2278
|
+
|
|
2279
|
+
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2280
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2281
|
+
|
|
2282
|
+
return {
|
|
2283
|
+
valueExpression: valueExpression,
|
|
2284
|
+
valueExpressionIndex: finalIndex
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
if (trimmedStatement.startsWith('return ')) {
|
|
2289
|
+
const valuePart = trimmedStatement.substring('return '.length);
|
|
2290
|
+
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2291
|
+
const valueExpression = value.trim();
|
|
2292
|
+
|
|
2293
|
+
let finalIndex = statementIndex;
|
|
2294
|
+
const returnKeywordInStatement = statement.indexOf('return ');
|
|
2295
|
+
if (returnKeywordInStatement !== -1) {
|
|
2296
|
+
const afterReturnInStatement = returnKeywordInStatement + 'return '.length;
|
|
2297
|
+
let startInStatement = -1;
|
|
2298
|
+
|
|
2299
|
+
for (let i = afterReturnInStatement; i < statement.length - 1; i++) {
|
|
2300
|
+
if (statement[i] === '$' && statement[i + 1] === '"') {
|
|
2301
|
+
startInStatement = i;
|
|
2302
|
+
break;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
if (startInStatement === -1) {
|
|
2307
|
+
for (let i = afterReturnInStatement; i < statement.length; i++) {
|
|
2308
|
+
if (statement[i] === '"') {
|
|
2309
|
+
startInStatement = i;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
if (startInStatement !== -1) {
|
|
2316
|
+
finalIndex = statementIndex + startInStatement;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
return {
|
|
2321
|
+
valueExpression: valueExpression,
|
|
2322
|
+
valueExpressionIndex: finalIndex
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const assignmentIndex = trimmedStatement.indexOf('=');
|
|
2327
|
+
if (assignmentIndex !== -1) {
|
|
2328
|
+
const prefix = trimmedStatement.substring(0, assignmentIndex + 1);
|
|
2329
|
+
const valuePart = trimmedStatement.substring(assignmentIndex + 1);
|
|
2330
|
+
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2331
|
+
const valueExpression = value.trim();
|
|
2332
|
+
|
|
2333
|
+
const valueStartInStatement = statement.indexOf(value);
|
|
2334
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : assignmentIndex + 1);
|
|
2335
|
+
|
|
2336
|
+
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2337
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2338
|
+
|
|
2339
|
+
return {
|
|
2340
|
+
valueExpression: valueExpression,
|
|
2341
|
+
valueExpressionIndex: finalIndex
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
const stringFormatMatch = trimmedStatement.match(/^(string\.Format\([\s\S]*?\))(;|$)/);
|
|
2346
|
+
if (stringFormatMatch) {
|
|
2347
|
+
const valueExpression = stringFormatMatch[1].trim();
|
|
2348
|
+
const actualValueStart = fullCode.indexOf(valueExpression, statementIndex);
|
|
2349
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : statementIndex;
|
|
2350
|
+
|
|
2351
|
+
return {
|
|
2352
|
+
valueExpression: valueExpression,
|
|
2353
|
+
valueExpressionIndex: finalIndex
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
let searchStart = 0;
|
|
2358
|
+
let colonIndex = -1;
|
|
2359
|
+
let inString = false;
|
|
2360
|
+
let escapeNext = false;
|
|
2361
|
+
let stringDelimiter = '';
|
|
2362
|
+
|
|
2363
|
+
if (trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:')) {
|
|
2364
|
+
for (let i = 0; i < trimmedStatement.length; i++) {
|
|
2365
|
+
const char = trimmedStatement[i];
|
|
2366
|
+
|
|
2367
|
+
if (escapeNext) {
|
|
2368
|
+
escapeNext = false;
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (char === '\\') {
|
|
2373
|
+
escapeNext = true;
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
if (inString) {
|
|
2378
|
+
if (char === stringDelimiter) {
|
|
2379
|
+
inString = false;
|
|
2380
|
+
stringDelimiter = '';
|
|
2381
|
+
}
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if (char === '"' || char === "'") {
|
|
2386
|
+
inString = true;
|
|
2387
|
+
stringDelimiter = char;
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
if (char === ':') {
|
|
2392
|
+
colonIndex = i;
|
|
2393
|
+
break;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
if (colonIndex !== -1) {
|
|
2398
|
+
searchStart = colonIndex + 1;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
let lastParenOpenIndex = -1;
|
|
2403
|
+
inString = false;
|
|
2404
|
+
escapeNext = false;
|
|
2405
|
+
stringDelimiter = '';
|
|
2406
|
+
|
|
2407
|
+
for (let i = searchStart; i < trimmedStatement.length; i++) {
|
|
2408
|
+
const char = trimmedStatement[i];
|
|
2409
|
+
|
|
2410
|
+
if (escapeNext) {
|
|
2411
|
+
escapeNext = false;
|
|
2412
|
+
continue;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
if (char === '\\') {
|
|
2416
|
+
escapeNext = true;
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
if (inString) {
|
|
2421
|
+
if (char === stringDelimiter) {
|
|
2422
|
+
inString = false;
|
|
2423
|
+
stringDelimiter = '';
|
|
2424
|
+
}
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
if (char === '"' || char === "'") {
|
|
2429
|
+
inString = true;
|
|
2430
|
+
stringDelimiter = char;
|
|
2431
|
+
continue;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
if (char === '(') {
|
|
2435
|
+
lastParenOpenIndex = i;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
if (lastParenOpenIndex !== -1) {
|
|
2440
|
+
const parenCloseIndex = this.findMatchingParenthesis(trimmedStatement, lastParenOpenIndex);
|
|
2441
|
+
if (parenCloseIndex !== -1) {
|
|
2442
|
+
const args = trimmedStatement.substring(lastParenOpenIndex + 1, parenCloseIndex);
|
|
2443
|
+
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(args);
|
|
2444
|
+
if (hasStringLiteral) {
|
|
2445
|
+
const valueExpression = args.trim();
|
|
2446
|
+
const parenIndex = statement.indexOf(trimmedStatement.substring(lastParenOpenIndex, lastParenOpenIndex + 1));
|
|
2447
|
+
const valueExpressionIndex = statementIndex + parenIndex + 1;
|
|
2448
|
+
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2449
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2450
|
+
|
|
2451
|
+
return {
|
|
2452
|
+
valueExpression: valueExpression,
|
|
2453
|
+
valueExpressionIndex: finalIndex
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
return {
|
|
2460
|
+
valueExpression: trimmedStatement,
|
|
2461
|
+
valueExpressionIndex: statementIndex
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
private extractValueUntilSemicolon(valuePart: string): string {
|
|
2466
|
+
let result = '';
|
|
2467
|
+
let inString = false;
|
|
2468
|
+
let escapeNext = false;
|
|
2469
|
+
let stringDelimiter = '';
|
|
2470
|
+
let parenthesesDepth = 0;
|
|
2471
|
+
let bracketDepth = 0;
|
|
2472
|
+
|
|
2473
|
+
for (let i = 0; i < valuePart.length; i++) {
|
|
2474
|
+
const char = valuePart[i];
|
|
2475
|
+
|
|
2476
|
+
if (escapeNext) {
|
|
2477
|
+
result += char;
|
|
2478
|
+
escapeNext = false;
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
if (char === '\\') {
|
|
2483
|
+
result += char;
|
|
2484
|
+
escapeNext = true;
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
if (inString) {
|
|
2489
|
+
result += char;
|
|
2490
|
+
if (char === stringDelimiter) {
|
|
2491
|
+
inString = false;
|
|
2492
|
+
stringDelimiter = '';
|
|
2493
|
+
}
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
if (char === '"' || char === "'") {
|
|
2498
|
+
result += char;
|
|
2499
|
+
inString = true;
|
|
2500
|
+
stringDelimiter = char;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
if (char === '(') {
|
|
2505
|
+
result += char;
|
|
2506
|
+
parenthesesDepth++;
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
if (char === ')') {
|
|
2511
|
+
result += char;
|
|
2512
|
+
parenthesesDepth--;
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
if (char === '[') {
|
|
2517
|
+
result += char;
|
|
2518
|
+
bracketDepth++;
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
if (char === ']') {
|
|
2523
|
+
result += char;
|
|
2524
|
+
bracketDepth--;
|
|
2525
|
+
continue;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
if (char === ';' && parenthesesDepth === 0 && bracketDepth === 0) {
|
|
2529
|
+
break;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
result += char;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
return result;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
private processStatementAndExtractValue(snippet: CodeSnippet, fullStatement: string, valueExpression: string, trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
2539
|
+
let processedFullStatement = fullStatement;
|
|
2540
|
+
|
|
2541
|
+
this.extractTrFormatStrings(processedFullStatement, snippet, trClass, trFormatMethod);
|
|
2542
|
+
processedFullStatement = this.processStringTemplates(processedFullStatement, snippet, trClass, trFormatMethod);
|
|
2543
|
+
processedFullStatement = this.processStringFormat(processedFullStatement, snippet, trClass, trFormatMethod);
|
|
2544
|
+
processedFullStatement = this.processStringConcatenation(processedFullStatement, snippet, trClass, trFormatMethod, trMethod);
|
|
2545
|
+
processedFullStatement = this.processTextAssignments(processedFullStatement, snippet, trClass, trFormatMethod, trMethod);
|
|
2546
|
+
this.extractPlainStrings(processedFullStatement, snippet, trClass, trFormatMethod);
|
|
2547
|
+
|
|
2548
|
+
const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g');
|
|
2549
|
+
processedFullStatement = processedFullStatement.replace(regex, `${trClass}.${trFormatMethod}($1)`);
|
|
2550
|
+
snippet.finalizeLiterals();
|
|
2551
|
+
|
|
2552
|
+
let convertedValueExpression = valueExpression;
|
|
2553
|
+
|
|
2554
|
+
const trimmedFullStatement = fullStatement.trim();
|
|
2555
|
+
const trimmedProcessedStatement = processedFullStatement.trim();
|
|
2556
|
+
|
|
2557
|
+
const isCaseOrDefault = trimmedFullStatement.startsWith('case ') || trimmedFullStatement.startsWith('default:');
|
|
2558
|
+
|
|
2559
|
+
if (/^return\s/.test(trimmedFullStatement)) {
|
|
2560
|
+
// 检查 processedFullStatement 是否真的被修改过
|
|
2561
|
+
// 如果没有被修改过,直接使用原始 valueExpression 即可
|
|
2562
|
+
if (fullStatement === processedFullStatement) {
|
|
2563
|
+
convertedValueExpression = valueExpression;
|
|
2564
|
+
} else {
|
|
2565
|
+
// 对于 return 语句,我们不需要复杂地使用 extractValueUntilSemicolon 来重新解析!
|
|
2566
|
+
// 我们已经处理完整个语句了,直接截取 return 后面的所有内容,然后去掉末尾的分号即可!
|
|
2567
|
+
let returnIndex = trimmedProcessedStatement.indexOf('return');
|
|
2568
|
+
let valuePart = trimmedProcessedStatement.substring(returnIndex + 'return'.length);
|
|
2569
|
+
valuePart = valuePart.trim();
|
|
2570
|
+
if (valuePart.endsWith(';')) {
|
|
2571
|
+
valuePart = valuePart.slice(0, -1).trim();
|
|
2572
|
+
}
|
|
2573
|
+
convertedValueExpression = valuePart;
|
|
2574
|
+
}
|
|
2575
|
+
} else {
|
|
2576
|
+
const textAssignmentIndex = trimmedFullStatement.indexOf('.text =');
|
|
2577
|
+
if (textAssignmentIndex !== -1) {
|
|
2578
|
+
if (fullStatement === processedFullStatement) {
|
|
2579
|
+
convertedValueExpression = valueExpression;
|
|
2580
|
+
} else {
|
|
2581
|
+
const prefix = trimmedFullStatement.substring(0, textAssignmentIndex + '.text ='.length);
|
|
2582
|
+
const valuePart = trimmedProcessedStatement.substring(textAssignmentIndex + '.text ='.length);
|
|
2583
|
+
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2584
|
+
convertedValueExpression = value.trim();
|
|
2585
|
+
}
|
|
2586
|
+
} else {
|
|
2587
|
+
const assignmentIndex = trimmedFullStatement.indexOf('=');
|
|
2588
|
+
if (assignmentIndex !== -1) {
|
|
2589
|
+
if (fullStatement === processedFullStatement) {
|
|
2590
|
+
convertedValueExpression = valueExpression;
|
|
2591
|
+
} else {
|
|
2592
|
+
const prefix = trimmedFullStatement.substring(0, assignmentIndex + 1);
|
|
2593
|
+
const valuePart = trimmedProcessedStatement.substring(assignmentIndex + 1);
|
|
2594
|
+
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2595
|
+
convertedValueExpression = value.trim();
|
|
2596
|
+
}
|
|
2597
|
+
} else if (valueExpression.startsWith('string.Format(')) {
|
|
2598
|
+
const processedMatch = trimmedProcessedStatement.match(/^(Tr\.Format[\s\S]*?)(;|$)/);
|
|
2599
|
+
if (processedMatch) {
|
|
2600
|
+
convertedValueExpression = processedMatch[1].trim();
|
|
2601
|
+
}
|
|
2602
|
+
} else if (isCaseOrDefault) {
|
|
2603
|
+
if (fullStatement === processedFullStatement) {
|
|
2604
|
+
convertedValueExpression = valueExpression;
|
|
2605
|
+
} else {
|
|
2606
|
+
const trFormatIndex = trimmedProcessedStatement.indexOf('Tr.Format(');
|
|
2607
|
+
if (trFormatIndex !== -1) {
|
|
2608
|
+
const parenOpenIndex = trFormatIndex + 'Tr.Format('.length - 1;
|
|
2609
|
+
const parenCloseIndex = this.findMatchingParenthesis(trimmedProcessedStatement, parenOpenIndex);
|
|
2610
|
+
if (parenCloseIndex !== -1) {
|
|
2611
|
+
convertedValueExpression = trimmedProcessedStatement.substring(trFormatIndex, parenCloseIndex + 1);
|
|
2612
|
+
} else {
|
|
2613
|
+
convertedValueExpression = valueExpression;
|
|
2614
|
+
}
|
|
2615
|
+
} else {
|
|
2616
|
+
const parenOpenIndex = trimmedFullStatement.indexOf('(');
|
|
2617
|
+
if (parenOpenIndex !== -1) {
|
|
2618
|
+
const parenCloseIndex = this.findMatchingParenthesis(trimmedProcessedStatement, parenOpenIndex);
|
|
2619
|
+
if (parenCloseIndex !== -1) {
|
|
2620
|
+
convertedValueExpression = trimmedProcessedStatement.substring(parenOpenIndex + 1, parenCloseIndex).trim();
|
|
2621
|
+
} else {
|
|
2622
|
+
convertedValueExpression = valueExpression;
|
|
2623
|
+
}
|
|
2624
|
+
} else {
|
|
2625
|
+
convertedValueExpression = valueExpression;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
} else if (trimmedFullStatement.match(/^[\s\S]*?\(/)) {
|
|
2630
|
+
const parenOpenIndex = trimmedFullStatement.indexOf('(');
|
|
2631
|
+
if (parenOpenIndex !== -1) {
|
|
2632
|
+
const parenCloseIndex = this.findMatchingParenthesis(trimmedProcessedStatement, parenOpenIndex);
|
|
2633
|
+
if (parenCloseIndex !== -1) {
|
|
2634
|
+
convertedValueExpression = trimmedProcessedStatement.substring(parenOpenIndex + 1, parenCloseIndex).trim();
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
} else {
|
|
2638
|
+
convertedValueExpression = trimmedProcessedStatement;
|
|
2639
|
+
if (convertedValueExpression.endsWith(';')) {
|
|
2640
|
+
convertedValueExpression = convertedValueExpression.slice(0, -1).trim();
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
snippet.convertedCode = convertedValueExpression;
|
|
2647
|
+
}
|
|
1069
2648
|
}
|