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