scancscode 1.0.37 → 1.0.39
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/documents/refactor-codesnippet-text-assignments_plan.md +128 -0
- package/.trae/documents/refactor-summary.md +188 -0
- package/.trae/specs/add-codesnippet-field-comments/checklist.md +11 -0
- package/.trae/specs/add-codesnippet-field-comments/spec.md +59 -0
- package/.trae/specs/add-codesnippet-field-comments/tasks.md +14 -0
- package/.trae/specs/fix-mbtn-title-pattern/checklist.md +9 -0
- package/.trae/specs/fix-mbtn-title-pattern/spec.md +69 -0
- package/.trae/specs/fix-mbtn-title-pattern/tasks.md +50 -0
- package/.trae/specs/fix-return-original-index/checklist.md +4 -0
- package/.trae/specs/fix-return-original-index/spec.md +74 -0
- package/.trae/specs/fix-return-original-index/tasks.md +14 -0
- package/dist/src/CSharpStringExtractor.js +58 -36
- package/dist/src/CmdExecutor.js +5 -6
- package/dist/test/CSharpStringExtractor.test.js +139 -16
- package/dist/test/TestConvert.test.js +1 -2
- package/package.json +1 -1
- package/src/CSharpStringExtractor.ts +63 -39
- package/src/CmdExecutor.ts +6 -7
- package/test/CSharpStringExtractor.test.ts +142 -16
- package/test/TestConvert.test.ts +1 -1
|
@@ -2,11 +2,29 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CSharpStringExtractor = exports.CodeSnippet = void 0;
|
|
4
4
|
class CodeSnippet {
|
|
5
|
+
/**
|
|
6
|
+
* 代码片段在原始源代码中的起始位置索引
|
|
7
|
+
*/
|
|
5
8
|
originalIndex;
|
|
9
|
+
/**
|
|
10
|
+
* 原始上下文,与 originalCode 相同,保存原始的代码片段内容
|
|
11
|
+
*/
|
|
6
12
|
originalContext;
|
|
13
|
+
/**
|
|
14
|
+
* 从源代码中提取的原始代码片段
|
|
15
|
+
*/
|
|
7
16
|
originalCode;
|
|
17
|
+
/**
|
|
18
|
+
* 经过国际化处理后的代码片段,可能添加了 .TR() 调用或转换为 Tr.Format(...) 形式
|
|
19
|
+
*/
|
|
8
20
|
convertedCode;
|
|
21
|
+
/**
|
|
22
|
+
* 从代码片段中提取出的字符串字面量数组,已进行去重和标准化处理
|
|
23
|
+
*/
|
|
9
24
|
literals;
|
|
25
|
+
/**
|
|
26
|
+
* 意外情况记录数组
|
|
27
|
+
*/
|
|
10
28
|
unexpects;
|
|
11
29
|
literalPositions;
|
|
12
30
|
constructor() {
|
|
@@ -18,6 +36,10 @@ class CodeSnippet {
|
|
|
18
36
|
this.unexpects = [];
|
|
19
37
|
this.literalPositions = new Map();
|
|
20
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 判断代码片段是否经过修改
|
|
41
|
+
* @returns 如果 convertedCode 与 originalCode 不同则返回 true,否则返回 false
|
|
42
|
+
*/
|
|
21
43
|
get isChanged() {
|
|
22
44
|
return this.originalCode !== this.convertedCode;
|
|
23
45
|
}
|
|
@@ -82,6 +104,9 @@ class CodeSnippet {
|
|
|
82
104
|
}
|
|
83
105
|
exports.CodeSnippet = CodeSnippet;
|
|
84
106
|
class CSharpStringExtractor {
|
|
107
|
+
static TEXT_ASSIGNMENT_PATTERN = /\b[\p{L}\p{N}_]+\.text\s*=/u;
|
|
108
|
+
static TITLE_ASSIGNMENT_PATTERN = /\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title\s*=/u;
|
|
109
|
+
static SPECIAL_ASSIGNMENT_PATTERN = /(?:\b[\p{L}\p{N}_]+\.text|\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title)\s*=/u;
|
|
85
110
|
variableIndex = 0;
|
|
86
111
|
extractStrings(code, snippets = [], trClass = 'Tr', trMethod = 'TR', trFormatMethod = 'Format') {
|
|
87
112
|
const statements = [];
|
|
@@ -180,7 +205,7 @@ class CSharpStringExtractor {
|
|
|
180
205
|
for (const { statement, originalIndex } of statements) {
|
|
181
206
|
const trimmedStatement = statement.trim();
|
|
182
207
|
const isStringFormatCall = trimmedStatement.startsWith('string.Format(');
|
|
183
|
-
const isTextAssignment =
|
|
208
|
+
const isTextAssignment = this.isSpecialAssignment(trimmedStatement);
|
|
184
209
|
const isRegularAssignment = /^[\s\S]*?=/.test(trimmedStatement);
|
|
185
210
|
const isCaseOrDefault = trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:');
|
|
186
211
|
const isFunctionCall = /^\s*[\w\.<>]+[\s\w\.<>]*\(/.test(trimmedStatement) && !isStringFormatCall && !isCaseOrDefault;
|
|
@@ -1411,6 +1436,10 @@ class CSharpStringExtractor {
|
|
|
1411
1436
|
}
|
|
1412
1437
|
return statements;
|
|
1413
1438
|
}
|
|
1439
|
+
isSpecialAssignment(statement) {
|
|
1440
|
+
const trimmedStatement = statement.trim();
|
|
1441
|
+
return CSharpStringExtractor.SPECIAL_ASSIGNMENT_PATTERN.test(trimmedStatement);
|
|
1442
|
+
}
|
|
1414
1443
|
isStatementToProcess(statement) {
|
|
1415
1444
|
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'];
|
|
1416
1445
|
const trimmedStatement = statement.trim();
|
|
@@ -1429,7 +1458,7 @@ class CSharpStringExtractor {
|
|
|
1429
1458
|
return false;
|
|
1430
1459
|
}
|
|
1431
1460
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedStatement);
|
|
1432
|
-
const isTextAssignment =
|
|
1461
|
+
const isTextAssignment = this.isSpecialAssignment(trimmedStatement);
|
|
1433
1462
|
const isReturnStatement = trimmedStatement.startsWith('return ');
|
|
1434
1463
|
return hasStringLiteral || isTextAssignment || (isReturnStatement && hasStringLiteral);
|
|
1435
1464
|
}
|
|
@@ -1739,8 +1768,8 @@ class CSharpStringExtractor {
|
|
|
1739
1768
|
return processedStatement;
|
|
1740
1769
|
}
|
|
1741
1770
|
processTextAssignments(statement, snippet, trClass, trFormatMethod, trMethod) {
|
|
1742
|
-
const
|
|
1743
|
-
const match =
|
|
1771
|
+
const specialAssignmentRegex = /([\s\S]*?(?:\.text|\.title)\s*=\s*)([\s\S]*?)(?=;|$)/;
|
|
1772
|
+
const match = specialAssignmentRegex.exec(statement);
|
|
1744
1773
|
let prefix = null;
|
|
1745
1774
|
let value;
|
|
1746
1775
|
if (match) {
|
|
@@ -1782,7 +1811,7 @@ class CSharpStringExtractor {
|
|
|
1782
1811
|
if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) {
|
|
1783
1812
|
return part;
|
|
1784
1813
|
}
|
|
1785
|
-
if (trimmedPart.startsWith('"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
1814
|
+
if (trimmedPart.startsWith('"') || trimmedPart.startsWith('$"') || trimmedPart.startsWith('$@"') || trimmedPart.startsWith('@$"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
1786
1815
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
1787
1816
|
const actualPart = part.substring(part.search(/\S/));
|
|
1788
1817
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
@@ -1795,7 +1824,7 @@ class CSharpStringExtractor {
|
|
|
1795
1824
|
else {
|
|
1796
1825
|
const trimmedValue = value.trim();
|
|
1797
1826
|
if (!trimmedValue.includes(`${trClass}.${trFormatMethod}(`) && !trimmedValue.endsWith(`.${trMethod}()`)) {
|
|
1798
|
-
if (trimmedValue !== 'null' && (trimmedValue.startsWith('"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('('))) {
|
|
1827
|
+
if (trimmedValue !== 'null' && (trimmedValue.startsWith('"') || trimmedValue.startsWith('$"') || trimmedValue.startsWith('$@"') || trimmedValue.startsWith('@$"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('('))) {
|
|
1799
1828
|
const whitespaceBefore = value.substring(0, value.search(/\S/));
|
|
1800
1829
|
const actualValue = value.substring(value.search(/\S/));
|
|
1801
1830
|
const whitespaceAfter = actualValue.search(/\s*$/) === 0 ? actualValue : actualValue.substring(actualValue.search(/\S/) + trimmedValue.length);
|
|
@@ -1820,7 +1849,7 @@ class CSharpStringExtractor {
|
|
|
1820
1849
|
return statement;
|
|
1821
1850
|
}
|
|
1822
1851
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(statement);
|
|
1823
|
-
const isTextAssignment =
|
|
1852
|
+
const isTextAssignment = this.isSpecialAssignment(statement);
|
|
1824
1853
|
if (isTextAssignment) {
|
|
1825
1854
|
return statement;
|
|
1826
1855
|
}
|
|
@@ -2137,14 +2166,26 @@ class CSharpStringExtractor {
|
|
|
2137
2166
|
}
|
|
2138
2167
|
extractValueExpression(statement, statementIndex, fullCode) {
|
|
2139
2168
|
const trimmedStatement = statement.trim();
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2169
|
+
let assignmentMatch = null;
|
|
2170
|
+
let assignmentSuffix = '';
|
|
2171
|
+
const textMatch = CSharpStringExtractor.TEXT_ASSIGNMENT_PATTERN.exec(trimmedStatement);
|
|
2172
|
+
const titleMatch = CSharpStringExtractor.TITLE_ASSIGNMENT_PATTERN.exec(trimmedStatement);
|
|
2173
|
+
if (textMatch) {
|
|
2174
|
+
assignmentMatch = textMatch;
|
|
2175
|
+
assignmentSuffix = '.text =';
|
|
2176
|
+
}
|
|
2177
|
+
else if (titleMatch) {
|
|
2178
|
+
assignmentMatch = titleMatch;
|
|
2179
|
+
assignmentSuffix = '.title =';
|
|
2180
|
+
}
|
|
2181
|
+
if (assignmentMatch) {
|
|
2182
|
+
const assignmentIndex = assignmentMatch.index + assignmentMatch[0].indexOf(assignmentSuffix);
|
|
2183
|
+
const prefix = trimmedStatement.substring(0, assignmentIndex + assignmentSuffix.length);
|
|
2184
|
+
const valuePart = trimmedStatement.substring(assignmentIndex + assignmentSuffix.length);
|
|
2144
2185
|
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2145
2186
|
const valueExpression = value.trim();
|
|
2146
2187
|
const valueStartInStatement = statement.indexOf(value);
|
|
2147
|
-
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement :
|
|
2188
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : assignmentIndex + assignmentSuffix.length);
|
|
2148
2189
|
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2149
2190
|
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2150
2191
|
return {
|
|
@@ -2156,29 +2197,10 @@ class CSharpStringExtractor {
|
|
|
2156
2197
|
const valuePart = trimmedStatement.substring('return '.length);
|
|
2157
2198
|
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2158
2199
|
const valueExpression = value.trim();
|
|
2159
|
-
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
let startInStatement = -1;
|
|
2164
|
-
for (let i = afterReturnInStatement; i < statement.length - 1; i++) {
|
|
2165
|
-
if (statement[i] === '$' && statement[i + 1] === '"') {
|
|
2166
|
-
startInStatement = i;
|
|
2167
|
-
break;
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
if (startInStatement === -1) {
|
|
2171
|
-
for (let i = afterReturnInStatement; i < statement.length; i++) {
|
|
2172
|
-
if (statement[i] === '"') {
|
|
2173
|
-
startInStatement = i;
|
|
2174
|
-
break;
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
if (startInStatement !== -1) {
|
|
2179
|
-
finalIndex = statementIndex + startInStatement;
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2200
|
+
const valueStartInStatement = statement.indexOf(value);
|
|
2201
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : 'return '.length);
|
|
2202
|
+
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2203
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2182
2204
|
return {
|
|
2183
2205
|
valueExpression: valueExpression,
|
|
2184
2206
|
valueExpressionIndex: finalIndex
|
|
@@ -2442,7 +2464,7 @@ class CSharpStringExtractor {
|
|
|
2442
2464
|
}
|
|
2443
2465
|
processStatementAndExtractValue(snippet, fullStatement, valueExpression, trClass, trFormatMethod, trMethod) {
|
|
2444
2466
|
let processedValueExpression = valueExpression;
|
|
2445
|
-
const isTextAssignment =
|
|
2467
|
+
const isTextAssignment = this.isSpecialAssignment(fullStatement);
|
|
2446
2468
|
this.extractTrFormatStrings(processedValueExpression, snippet, trClass, trFormatMethod);
|
|
2447
2469
|
processedValueExpression = this.processStringTemplates(processedValueExpression, snippet, trClass, trFormatMethod);
|
|
2448
2470
|
processedValueExpression = this.processStringFormat(processedValueExpression, snippet, trClass, trFormatMethod);
|
package/dist/src/CmdExecutor.js
CHANGED
|
@@ -13,12 +13,11 @@ function isNullOrEmpty(arr) {
|
|
|
13
13
|
class CmdExecutor {
|
|
14
14
|
static testConvert() {
|
|
15
15
|
let cwd = "E:/DATA/Projects/ZhiYou/ProjectFClient/GameClient/";
|
|
16
|
-
let cscodeFolders =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Bundles/
|
|
20
|
-
"E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/
|
|
21
|
-
"E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Scripts/",
|
|
16
|
+
let cscodeFolders = [
|
|
17
|
+
cwd + "Assets/Bundles/FGUI/",
|
|
18
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Bundles/UI/",
|
|
19
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Bundles/Battle/",
|
|
20
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Scripts/",
|
|
22
21
|
];
|
|
23
22
|
let gameConfigFolders = [cwd + "Assets/Bundles/GameConfigs/"];
|
|
24
23
|
let outCsvFile = "E:/DATA/Projects/e-gbl-client/client/Assets/Bundles/GameConfigs/Translation/hello.csv";
|
|
@@ -76,6 +76,73 @@ describe('CSharpStringExtractor', () => {
|
|
|
76
76
|
expect(snippets[0].literals).toContain('pre');
|
|
77
77
|
expect(snippets[0].literals).toContain('sub');
|
|
78
78
|
});
|
|
79
|
+
// 形如 `m_btn_xxx.title = yyy;` 的赋值语句要和形如 `xxx.text = yyy;` 的赋值语句完全等同处理, 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式, `m_btn_` 为引用符号名固定前缀; 为了便于理解, 用正则表达式表示, 也就是相当于需要同时匹配 `/^m_btn_\w+\.title\s*=/` 形式的表达式和 `/\w+\.text\s*=/` 形式的表达式, 而其中对于 `xxx` 和 `yyy` 部分的处理逻辑是完全一致的
|
|
80
|
+
test('should handle m_btn_xxx.title = yyy', () => {
|
|
81
|
+
const code = `
|
|
82
|
+
buy.title = "lkwjelkfj1";
|
|
83
|
+
m_btn1_buy.title = "lkwjelkfj2";
|
|
84
|
+
am_btn_buy.title = "lkwjelkfj3";
|
|
85
|
+
m_btn_buy.title = "lkwjelkfj4";
|
|
86
|
+
m_btn_wwefHwref.title = $"Hello";
|
|
87
|
+
m_btn_fxx_wf.title = $"Hello, {name}!";
|
|
88
|
+
`;
|
|
89
|
+
const snippets = extractor.extractStrings(code);
|
|
90
|
+
{
|
|
91
|
+
let snippet = snippets[0];
|
|
92
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj1"'));
|
|
93
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj1"');
|
|
94
|
+
// `buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
95
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj1"');
|
|
96
|
+
expect(snippet.literals).toContain('lkwjelkfj1');
|
|
97
|
+
expect(snippet.isChanged).toBe(false);
|
|
98
|
+
}
|
|
99
|
+
{
|
|
100
|
+
let snippet = snippets[1];
|
|
101
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj2"'));
|
|
102
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj2"');
|
|
103
|
+
// `m_btn1_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
104
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj2"');
|
|
105
|
+
expect(snippet.literals).toContain('lkwjelkfj2');
|
|
106
|
+
expect(snippet.isChanged).toBe(false);
|
|
107
|
+
}
|
|
108
|
+
{
|
|
109
|
+
let snippet = snippets[2];
|
|
110
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj3"'));
|
|
111
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj3"');
|
|
112
|
+
// `am_btn_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
113
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj3"');
|
|
114
|
+
expect(snippet.literals).toContain('lkwjelkfj3');
|
|
115
|
+
expect(snippet.isChanged).toBe(false);
|
|
116
|
+
}
|
|
117
|
+
{
|
|
118
|
+
let snippet = snippets[3];
|
|
119
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj4"'));
|
|
120
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj4"');
|
|
121
|
+
// `m_btn_buy` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()`
|
|
122
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj4".TR()');
|
|
123
|
+
expect(snippet.literals).toContain('lkwjelkfj4');
|
|
124
|
+
expect(snippet.isChanged).toBe(true);
|
|
125
|
+
}
|
|
126
|
+
{
|
|
127
|
+
let snippet = snippets[4];
|
|
128
|
+
expect(snippet.originalIndex).toBe(code.indexOf('$"Hello"'));
|
|
129
|
+
expect(snippet.originalCode).toBe('$"Hello"');
|
|
130
|
+
// `m_btn_wwefHwref` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()`
|
|
131
|
+
expect(snippet.convertedCode).toBe('$"Hello".TR()');
|
|
132
|
+
expect(snippet.literals).toContain('Hello');
|
|
133
|
+
expect(snippet.isChanged).toBe(true);
|
|
134
|
+
}
|
|
135
|
+
{
|
|
136
|
+
let snippet = snippets[5];
|
|
137
|
+
expect(snippet.originalIndex).toBe(code.indexOf('$"Hello, {name}!"'));
|
|
138
|
+
expect(snippet.originalCode).toBe('$"Hello, {name}!"');
|
|
139
|
+
// `m_btn_fxx_wf` 符合 `m_btn_` 开头的条件
|
|
140
|
+
expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)');
|
|
141
|
+
expect(snippet.literals).toContain('Hello, {0}!');
|
|
142
|
+
expect(snippet.isChanged).toBe(true);
|
|
143
|
+
}
|
|
144
|
+
expect(snippets.length).toBe(6);
|
|
145
|
+
});
|
|
79
146
|
// 测试用 `+` 连接的字符串表达式, 确保用 `+` 符号拼接的字符串表达式被整体处理, 从中提取字符串表达式信息, 而不是分别处理 `+` 两边每个字符串表达式; 并且给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式
|
|
80
147
|
test('should handle string concatenation', () => {
|
|
81
148
|
const code = 'var label2 = "Hello, " + name + "!";';
|
|
@@ -84,7 +151,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
84
151
|
expect(snippets[0].originalCode).toBe('"Hello, " + name + "!"');
|
|
85
152
|
expect(snippets[0].convertedCode).toBe('"Hello, ".TR() + name.TR() + "!".TR()');
|
|
86
153
|
});
|
|
87
|
-
// 测试 `xxx.text = yyy
|
|
154
|
+
// 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的字符串表达式信息提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式
|
|
88
155
|
test('should handle .text = assignments with function calls', () => {
|
|
89
156
|
const code = 'label.text = Func();';
|
|
90
157
|
const snippets = extractor.extractStrings(code);
|
|
@@ -92,7 +159,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
92
159
|
expect(snippets[0].originalCode).toBe('Func()');
|
|
93
160
|
expect(snippets[0].convertedCode).toBe('Func().TR()');
|
|
94
161
|
});
|
|
95
|
-
// 测试 `xxx.text = xxx + xxx
|
|
162
|
+
// 测试 `xxx.text = xxx + xxx;` 形式的赋值语句, 确保将 `xxx.text` 中的 `xxx` 字符串表达式提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式
|
|
96
163
|
test('should handle .text = assignments with multiple function calls', () => {
|
|
97
164
|
const code = 'label.text = Func() + Func();';
|
|
98
165
|
const snippets = extractor.extractStrings(code);
|
|
@@ -100,7 +167,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
100
167
|
expect(snippets[0].originalCode).toBe('Func() + Func()');
|
|
101
168
|
expect(snippets[0].convertedCode).toBe('Func().TR() + Func().TR()');
|
|
102
169
|
});
|
|
103
|
-
// 测试 `xxx.text = yyy
|
|
170
|
+
// 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的 `xxx` 内插字符串提取出来, 并转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式
|
|
104
171
|
test('should handle .text = assignments with string templates', () => {
|
|
105
172
|
const code = 'label.text = $"pre{Func()}sub";';
|
|
106
173
|
const snippets = extractor.extractStrings(code);
|
|
@@ -176,7 +243,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
176
243
|
expect(snippet.literals).toEqual(['Hello, {0}!']);
|
|
177
244
|
expect(snippet.isChanged).toBe(true);
|
|
178
245
|
});
|
|
179
|
-
// 测试处理 `xxx.text = yyy
|
|
246
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式, 需要从 `yyy` 中正确提取字符串表达式信息, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式
|
|
180
247
|
test('should handle .text = assignments', () => {
|
|
181
248
|
const code = 'label.text = "Test";';
|
|
182
249
|
const snippets = extractor.extractStrings(code);
|
|
@@ -273,9 +340,9 @@ describe('CSharpStringExtractor', () => {
|
|
|
273
340
|
const snippets = extractor.extractStrings(code);
|
|
274
341
|
const snippet = snippets[0];
|
|
275
342
|
expect(snippet.originalCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]"');
|
|
276
|
-
expect(snippet.convertedCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]"');
|
|
343
|
+
expect(snippet.convertedCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]".TR()');
|
|
277
344
|
expect(snippet.literals).toEqual(['[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]']);
|
|
278
|
-
expect(snippet.isChanged).toBe(
|
|
345
|
+
expect(snippet.isChanged).toBe(true);
|
|
279
346
|
});
|
|
280
347
|
// 处理C#多行语句
|
|
281
348
|
test('should handle non string assignment 1', () => {
|
|
@@ -1756,7 +1823,7 @@ namespace TestNamespace{
|
|
|
1756
1823
|
}
|
|
1757
1824
|
});
|
|
1758
1825
|
// 正确识别C# 行注释语句边界, 行注释不能影响上下行的字符串提取
|
|
1759
|
-
test('should handle comment boundary
|
|
1826
|
+
test('should handle comment boundary 3', () => {
|
|
1760
1827
|
const code = `
|
|
1761
1828
|
// klwjfe
|
|
1762
1829
|
namespace TestNamespace{
|
|
@@ -1950,7 +2017,7 @@ namespace FaBao.UI.Comp
|
|
|
1950
2017
|
expect(snippets.length).toBe(1);
|
|
1951
2018
|
});
|
|
1952
2019
|
// 正确提取C#方法参数中的字符串表达式信息, 支持1到多个形参有默认值的情形
|
|
1953
|
-
test('should handle string expression in method call parameter
|
|
2020
|
+
test('should handle string expression in method call parameter 4', () => {
|
|
1954
2021
|
const code = `
|
|
1955
2022
|
namespace FaBao.UI.Comp
|
|
1956
2023
|
{
|
|
@@ -2173,7 +2240,7 @@ namespace FaBao.UI.MainUI
|
|
|
2173
2240
|
}
|
|
2174
2241
|
});
|
|
2175
2242
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2176
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2243
|
+
test('should handle class member initialization assignment with anonymous function 3', () => {
|
|
2177
2244
|
const code = `
|
|
2178
2245
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2179
2246
|
{
|
|
@@ -2197,7 +2264,7 @@ namespace FaBao.UI.MainUI
|
|
|
2197
2264
|
}
|
|
2198
2265
|
});
|
|
2199
2266
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2200
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2267
|
+
test('should handle class member initialization assignment with anonymous function 4', () => {
|
|
2201
2268
|
const code = `
|
|
2202
2269
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2203
2270
|
{
|
|
@@ -2218,7 +2285,7 @@ namespace FaBao.UI.MainUI
|
|
|
2218
2285
|
}
|
|
2219
2286
|
});
|
|
2220
2287
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2221
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2288
|
+
test('should handle class member initialization assignment with anonymous function 5', () => {
|
|
2222
2289
|
const code = `
|
|
2223
2290
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2224
2291
|
{
|
|
@@ -2238,7 +2305,7 @@ namespace FaBao.UI.MainUI
|
|
|
2238
2305
|
expect(targetSnippet.isChanged).toBe(false);
|
|
2239
2306
|
}
|
|
2240
2307
|
});
|
|
2241
|
-
// 测试处理 `xxx.text = yyy
|
|
2308
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。
|
|
2242
2309
|
test('should handle .text = format 1', () => {
|
|
2243
2310
|
const code = `m_text_iwff.text = info ;`;
|
|
2244
2311
|
const snippets = extractor.extractStrings(code);
|
|
@@ -2251,8 +2318,8 @@ namespace FaBao.UI.MainUI
|
|
|
2251
2318
|
expect(targetSnippet.isChanged).toBe(true);
|
|
2252
2319
|
}
|
|
2253
2320
|
});
|
|
2254
|
-
// 测试处理 `xxx.text = yyy
|
|
2255
|
-
test('should handle .text = format
|
|
2321
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。
|
|
2322
|
+
test('should handle .text = format 2', () => {
|
|
2256
2323
|
const code = `m_text_info.text = info1.member1 + info2.member2;`;
|
|
2257
2324
|
const snippets = extractor.extractStrings(code);
|
|
2258
2325
|
{
|
|
@@ -2329,7 +2396,7 @@ namespace FaBao.UI.MainUI
|
|
|
2329
2396
|
expect(snippets.length).toBe(2);
|
|
2330
2397
|
});
|
|
2331
2398
|
// 支持处理字符串里出现各种特殊符号的情况
|
|
2332
|
-
test('should handle special characters in string
|
|
2399
|
+
test('should handle special characters in string 9', () => {
|
|
2333
2400
|
const code = `
|
|
2334
2401
|
(GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word);
|
|
2335
2402
|
`;
|
|
@@ -2347,7 +2414,7 @@ namespace FaBao.UI.MainUI
|
|
|
2347
2414
|
expect(snippets.length).toBe(1);
|
|
2348
2415
|
});
|
|
2349
2416
|
// 支持处理字符串里出现各种特殊符号的情况
|
|
2350
|
-
test('should handle special characters in string
|
|
2417
|
+
test('should handle special characters in string 10', () => {
|
|
2351
2418
|
const code = `
|
|
2352
2419
|
(GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word);
|
|
2353
2420
|
`;
|
|
@@ -2438,4 +2505,60 @@ namespace FaBao.UI.MainUI
|
|
|
2438
2505
|
const snippets = extractor.extractStrings(code);
|
|
2439
2506
|
expect(snippets.length).toBe(0);
|
|
2440
2507
|
});
|
|
2508
|
+
// 需要正确获取 `originalIndex`, `originalIndex` 的定义为 `originalIndex=code.indexOf(originalCode)`, 其中 `code` 表示原始代码, `originalCode` 表示匹配出的原始代码中的字符串表达式
|
|
2509
|
+
test('should handle calculate originalIndex', () => {
|
|
2510
|
+
const code = `
|
|
2511
|
+
namespace FaBao
|
|
2512
|
+
{
|
|
2513
|
+
public partial class UserInfoModel
|
|
2514
|
+
{
|
|
2515
|
+
public bool HasJoinedGuild()
|
|
2516
|
+
{
|
|
2517
|
+
return !string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0";
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
public void UpdateBargain(BargainShopData bargainShopData)
|
|
2521
|
+
{
|
|
2522
|
+
MyGuildInfo.IsBargainShopBargain = true;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
`;
|
|
2528
|
+
const snippets = extractor.extractStrings(code);
|
|
2529
|
+
{
|
|
2530
|
+
let targetSnippet = snippets[0];
|
|
2531
|
+
expect(targetSnippet.originalCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`);
|
|
2532
|
+
expect(targetSnippet.originalIndex).toBe(code.indexOf(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`));
|
|
2533
|
+
expect(targetSnippet.convertedCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`);
|
|
2534
|
+
expect(targetSnippet.literals).toEqual([
|
|
2535
|
+
'0'
|
|
2536
|
+
]);
|
|
2537
|
+
expect(targetSnippet.isChanged).toBe(false);
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
// 测试 `xxx.text = yyy;` 和 `m_btn_xxx.title = yyy;` 形式的赋值语句都需要支持中文, 包括字符串表达式中也要支持包含中文的情况
|
|
2541
|
+
test('should handle .text = assignments contains chinese', () => {
|
|
2542
|
+
const code = `
|
|
2543
|
+
为栓饭cxs12.text = 南方eflkj.d文件 + "完善ew";
|
|
2544
|
+
m_btn_为栓饭cxs12.title = bw尅kljekl.完善 + "栏栏nfc";
|
|
2545
|
+
`;
|
|
2546
|
+
const snippets = extractor.extractStrings(code);
|
|
2547
|
+
{
|
|
2548
|
+
let snippet = snippets[0];
|
|
2549
|
+
expect(snippet.originalCode).toBe('南方eflkj.d文件 + "完善ew"');
|
|
2550
|
+
expect(snippet.convertedCode).toBe('南方eflkj.d文件.TR() + "完善ew".TR()');
|
|
2551
|
+
expect(snippet.literals).toEqual([
|
|
2552
|
+
'完善ew'
|
|
2553
|
+
]);
|
|
2554
|
+
}
|
|
2555
|
+
{
|
|
2556
|
+
let snippet = snippets[1];
|
|
2557
|
+
expect(snippet.originalCode).toBe('bw尅kljekl.完善 + "栏栏nfc"');
|
|
2558
|
+
expect(snippet.convertedCode).toBe('bw尅kljekl.完善.TR() + "栏栏nfc".TR()');
|
|
2559
|
+
expect(snippet.literals).toEqual([
|
|
2560
|
+
'栏栏nfc'
|
|
2561
|
+
]);
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2441
2564
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const CmdExecutor_1 = require("../src/CmdExecutor");
|
|
4
3
|
describe('TestCmdExecutor', () => {
|
|
5
4
|
test("test convert", async () => {
|
|
6
|
-
await
|
|
5
|
+
// await CmdExecutor.testConvert();
|
|
7
6
|
expect(true).toBe(true);
|
|
8
7
|
}, 50000);
|
|
9
8
|
});
|