scancscode 1.0.31 → 1.0.34

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