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