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.
Files changed (55) hide show
  1. package/.trae/specs/fix-doc-comment-boundary/checklist.md +7 -0
  2. package/.trae/specs/fix-doc-comment-boundary/spec.md +52 -0
  3. package/.trae/specs/fix-doc-comment-boundary/tasks.md +34 -0
  4. package/.trae/specs/fix-interpolated-string-nested-literals/checklist.md +7 -0
  5. package/.trae/specs/fix-interpolated-string-nested-literals/spec.md +55 -0
  6. package/.trae/specs/fix-interpolated-string-nested-literals/tasks.md +25 -0
  7. package/.trae/specs/fix-remaining-interpolated-string-index/checklist.md +9 -0
  8. package/.trae/specs/fix-remaining-interpolated-string-index/spec.md +59 -0
  9. package/.trae/specs/fix-remaining-interpolated-string-index/tasks.md +41 -0
  10. package/.trae/specs/fix-return-interpolated-string/checklist.md +8 -0
  11. package/.trae/specs/fix-return-interpolated-string/spec.md +60 -0
  12. package/.trae/specs/fix-return-interpolated-string/tasks.md +39 -0
  13. package/.trae/specs/handle-interpolated-string-double-braces/checklist.md +9 -0
  14. package/.trae/specs/handle-interpolated-string-double-braces/spec.md +61 -0
  15. package/.trae/specs/handle-interpolated-string-double-braces/tasks.md +41 -0
  16. package/.trae/specs/handle-return-statement/checklist.md +11 -0
  17. package/.trae/specs/handle-return-statement/spec.md +76 -0
  18. package/.trae/specs/handle-return-statement/tasks.md +44 -0
  19. package/.trae/specs/handle-special-string-characters/checklist.md +13 -0
  20. package/.trae/specs/handle-special-string-characters/spec.md +94 -0
  21. package/.trae/specs/handle-special-string-characters/tasks.md +74 -0
  22. package/.trae/specs/unify-return-statement-string-extraction/checklist.md +10 -0
  23. package/.trae/specs/unify-return-statement-string-extraction/spec.md +70 -0
  24. package/.trae/specs/unify-return-statement-string-extraction/tasks.md +54 -0
  25. package/bin/scanliterals.js +3 -3
  26. package/bin/slimlangs.js +3 -3
  27. package/dist/src/CSharpStringExtractor.js +1755 -358
  28. package/dist/src/CmdExecutor.js +6 -8
  29. package/dist/test/CSharpStringExtractor.test.js +1484 -207
  30. 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
  31. package/jest.config.js +9 -9
  32. package/package.json +1 -1
  33. package/src/CSCodeScanner.ts +305 -305
  34. package/src/CSVUtils.ts +181 -181
  35. package/src/CSharpStringExtractor.ts +2019 -479
  36. package/src/CmdExecutor.ts +107 -106
  37. package/src/LiteralCollector.ts +143 -143
  38. package/src/RunConvert.ts +3 -3
  39. package/src/RunSlimLangs.ts +3 -3
  40. package/src/TableScanner.ts +92 -92
  41. package/test/CSharpStringExtractor.test.ts +1564 -208
  42. package/test/KeeperDialog.cs +114 -0
  43. package/test/TestSpecialString.cs +24 -0
  44. package/tsconfig.json +109 -109
  45. package/dist/CSCodeScanner.js +0 -271
  46. package/dist/CSVUtils.js +0 -218
  47. package/dist/CmdExecutor.js +0 -101
  48. package/dist/CodeSnippet.js +0 -2
  49. package/dist/LiteralCollector.js +0 -124
  50. package/dist/RunConvert.js +0 -4
  51. package/dist/RunSlimLangs.js +0 -4
  52. package/dist/TableScanner.js +0 -107
  53. package/dist/TestConvert.test.js +0 -10
  54. package/dist/TestSlimCsv.test.js +0 -10
  55. 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
- * 添加字符串到literals,并确保同一个位置的字符串会合并
48
- * @param position 字符串在代码中的位置
49
- * @param value 字符串值
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
- const isContent18Case = templateCount > 1 && multiVariableTemplates > 0;
98
- const isContent17or19Case = templateCount > 1 && templateCount === singleVariableTemplates;
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
- export class CSharpStringExtractor {
135
- /**
136
- * 用于跟踪全局变量索引,确保整个语句中索引连续递增
137
- */
138
- private variableIndex: number = 0;
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
- * 从C#代码中提取字符串并进行转换
142
- * @param code C#代码文本
143
- * @param trClass 翻译类名
144
- * @param trMethod 翻译方法名2,用于 .TR() 调用
145
- * @param trFormatMethod 翻译方法名
146
- * @returns CodeSnippet数组
147
- */
148
- extractStrings(code: string, snippets: CodeSnippet[] = [], trClass: string = 'Tr', trMethod: string = 'TR', trFormatMethod: string = 'Format'): CodeSnippet[] {
149
- // 处理代码,移除注释并保留格式
150
- let processedCode = code;
151
-
152
- // 移除注释,但保留字符串字面量中的内容
153
- processedCode = this.removeComments(processedCode);
154
-
155
- // 预编译正则表达式,避免重复创建
156
- const statementRegex = /([^;{}"']*(?:"(?:[^"\\]|\\.)*"[^;{}"']*|'(?:[^'\\]|\\.)*'[^;{}"']*)*);/g;
157
- let match;
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
- for (const { statement, originalIndex } of statements) {
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
- // 按originalIndex排序,确保snippets中的originalIndex是自然递增的
196
- snippets.sort((a, b) => a.originalIndex - b.originalIndex);
1346
+ return args;
1347
+ }
197
1348
 
198
- return snippets;
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 = ''; // '' for none, '//' for single line, '/*' for multi line
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
- return hasStringLiteral || isTextAssignment;
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
- // 检查并移除 Tr.Format().TR() 形式的重复修饰
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
- // 匹配 Tr.Format 调用
429
- const trFormatRegex = new RegExp(`${trClass}\\.${trMethod}\\(([^)]*)\\)`, 'g');
430
- let match;
1568
+ const pattern = `${trClass}.${trMethod}(`;
1569
+ let searchIndex = 0;
431
1570
 
432
- while ((match = trFormatRegex.exec(statement)) !== null) {
433
- const args = match[1];
434
- const position = match.index;
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
- while ((match = templateRegex.exec(statement)) !== null) {
466
- matches.push({
467
- fullMatch: match[0],
468
- content: match[2],
469
- position: match.index
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
- const localFormattedContent = content.replace(/\{([^}]*?)\}/g, (_, varExpr) => {
484
- // 提取变量表达式,忽略格式说明符和宽度
485
- // 格式:{expression[,width][:format]}
486
- let expression = varExpr;
487
- let formatPart = '';
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
- variables.push(expression);
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
- // 构建新的格式字符串,每个模板的变量索引从 0 开始
500
- return `{${templateVariableIndex++}${formatPart}}`;
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
- // 构建用于 literals 的版本,使用全局递增的变量索引
1677
+ const variables: string[] = [];
1678
+ let templateVariableIndex = 0;
1679
+ let templateIndex = 0;
504
1680
  let globalVariableIndex = this.variableIndex;
505
- globalFormattedContent = content.replace(/\{([^}]*?)\}/g, (_, varExpr) => {
506
- // 提取格式说明符
507
- let formatPart = '';
508
- const formatIndex = varExpr.indexOf(':');
509
- if (formatIndex >= 0) {
510
- formatPart = varExpr.substring(formatIndex);
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; // 更新 i 的位置
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
  }