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