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
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
export class CodeSnippet {
|
|
2
|
+
/**
|
|
3
|
+
* 代码片段在原始源代码中的起始位置索引
|
|
4
|
+
*/
|
|
2
5
|
originalIndex: number;
|
|
6
|
+
/**
|
|
7
|
+
* 原始上下文,与 originalCode 相同,保存原始的代码片段内容
|
|
8
|
+
*/
|
|
3
9
|
originalContext: string;
|
|
10
|
+
/**
|
|
11
|
+
* 从源代码中提取的原始代码片段
|
|
12
|
+
*/
|
|
4
13
|
originalCode: string;
|
|
14
|
+
/**
|
|
15
|
+
* 经过国际化处理后的代码片段,可能添加了 .TR() 调用或转换为 Tr.Format(...) 形式
|
|
16
|
+
*/
|
|
5
17
|
convertedCode: string;
|
|
18
|
+
/**
|
|
19
|
+
* 从代码片段中提取出的字符串字面量数组,已进行去重和标准化处理
|
|
20
|
+
*/
|
|
6
21
|
literals: string[];
|
|
22
|
+
/**
|
|
23
|
+
* 意外情况记录数组
|
|
24
|
+
*/
|
|
7
25
|
unexpects: string[];
|
|
8
26
|
private literalPositions: Map<number, string>;
|
|
9
27
|
|
|
@@ -17,6 +35,10 @@ export class CodeSnippet {
|
|
|
17
35
|
this.literalPositions = new Map();
|
|
18
36
|
}
|
|
19
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 判断代码片段是否经过修改
|
|
40
|
+
* @returns 如果 convertedCode 与 originalCode 不同则返回 true,否则返回 false
|
|
41
|
+
*/
|
|
20
42
|
get isChanged(): boolean {
|
|
21
43
|
return this.originalCode !== this.convertedCode;
|
|
22
44
|
}
|
|
@@ -85,6 +107,10 @@ export class CodeSnippet {
|
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
export class CSharpStringExtractor {
|
|
110
|
+
private static readonly TEXT_ASSIGNMENT_PATTERN = /\b[\p{L}\p{N}_]+\.text\s*=/u;
|
|
111
|
+
private static readonly TITLE_ASSIGNMENT_PATTERN = /\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title\s*=/u;
|
|
112
|
+
private static readonly SPECIAL_ASSIGNMENT_PATTERN = /(?:\b[\p{L}\p{N}_]+\.text|\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title)\s*=/u;
|
|
113
|
+
|
|
88
114
|
private variableIndex: number = 0;
|
|
89
115
|
|
|
90
116
|
extractStrings(code: string, snippets: CodeSnippet[] = [], trClass: string = 'Tr', trMethod: string = 'TR', trFormatMethod: string = 'Format'): CodeSnippet[] {
|
|
@@ -192,7 +218,7 @@ export class CSharpStringExtractor {
|
|
|
192
218
|
for (const { statement, originalIndex } of statements) {
|
|
193
219
|
const trimmedStatement = statement.trim();
|
|
194
220
|
const isStringFormatCall = trimmedStatement.startsWith('string.Format(');
|
|
195
|
-
const isTextAssignment =
|
|
221
|
+
const isTextAssignment = this.isSpecialAssignment(trimmedStatement);
|
|
196
222
|
const isRegularAssignment = /^[\s\S]*?=/.test(trimmedStatement);
|
|
197
223
|
const isCaseOrDefault = trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:');
|
|
198
224
|
const isFunctionCall = /^\s*[\w\.<>]+[\s\w\.<>]*\(/.test(trimmedStatement) && !isStringFormatCall && !isCaseOrDefault;
|
|
@@ -1572,6 +1598,11 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
1572
1598
|
return statements;
|
|
1573
1599
|
}
|
|
1574
1600
|
|
|
1601
|
+
private isSpecialAssignment(statement: string): boolean {
|
|
1602
|
+
const trimmedStatement = statement.trim();
|
|
1603
|
+
return CSharpStringExtractor.SPECIAL_ASSIGNMENT_PATTERN.test(trimmedStatement);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1575
1606
|
private isStatementToProcess(statement: string): boolean {
|
|
1576
1607
|
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'];
|
|
1577
1608
|
const trimmedStatement = statement.trim();
|
|
@@ -1595,7 +1626,7 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
1595
1626
|
}
|
|
1596
1627
|
|
|
1597
1628
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedStatement);
|
|
1598
|
-
const isTextAssignment =
|
|
1629
|
+
const isTextAssignment = this.isSpecialAssignment(trimmedStatement);
|
|
1599
1630
|
const isReturnStatement = trimmedStatement.startsWith('return ');
|
|
1600
1631
|
|
|
1601
1632
|
return hasStringLiteral || isTextAssignment || (isReturnStatement && hasStringLiteral);
|
|
@@ -1921,8 +1952,8 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
1921
1952
|
}
|
|
1922
1953
|
|
|
1923
1954
|
private processTextAssignments(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string, trMethod: string): string {
|
|
1924
|
-
const
|
|
1925
|
-
const match =
|
|
1955
|
+
const specialAssignmentRegex = /([\s\S]*?(?:\.text|\.title)\s*=\s*)([\s\S]*?)(?=;|$)/;
|
|
1956
|
+
const match = specialAssignmentRegex.exec(statement);
|
|
1926
1957
|
|
|
1927
1958
|
let prefix: string | null = null;
|
|
1928
1959
|
let value: string;
|
|
@@ -1971,7 +2002,7 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
1971
2002
|
return part;
|
|
1972
2003
|
}
|
|
1973
2004
|
|
|
1974
|
-
if (trimmedPart.startsWith('"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
2005
|
+
if (trimmedPart.startsWith('"') || trimmedPart.startsWith('$"') || trimmedPart.startsWith('$@"') || trimmedPart.startsWith('@$"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) {
|
|
1975
2006
|
const whitespaceBefore = part.substring(0, part.search(/\S/));
|
|
1976
2007
|
const actualPart = part.substring(part.search(/\S/));
|
|
1977
2008
|
const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length);
|
|
@@ -1984,7 +2015,7 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
1984
2015
|
} else {
|
|
1985
2016
|
const trimmedValue = value.trim();
|
|
1986
2017
|
if (!trimmedValue.includes(`${trClass}.${trFormatMethod}(`) && !trimmedValue.endsWith(`.${trMethod}()`)) {
|
|
1987
|
-
if (trimmedValue !== 'null' && (trimmedValue.startsWith('"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('('))) {
|
|
2018
|
+
if (trimmedValue !== 'null' && (trimmedValue.startsWith('"') || trimmedValue.startsWith('$"') || trimmedValue.startsWith('$@"') || trimmedValue.startsWith('@$"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('('))) {
|
|
1988
2019
|
const whitespaceBefore = value.substring(0, value.search(/\S/));
|
|
1989
2020
|
const actualValue = value.substring(value.search(/\S/));
|
|
1990
2021
|
const whitespaceAfter = actualValue.search(/\s*$/) === 0 ? actualValue : actualValue.substring(actualValue.search(/\S/) + trimmedValue.length);
|
|
@@ -2011,7 +2042,7 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
2011
2042
|
}
|
|
2012
2043
|
|
|
2013
2044
|
const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(statement);
|
|
2014
|
-
const isTextAssignment =
|
|
2045
|
+
const isTextAssignment = this.isSpecialAssignment(statement);
|
|
2015
2046
|
|
|
2016
2047
|
if (isTextAssignment) {
|
|
2017
2048
|
return statement;
|
|
@@ -2374,15 +2405,29 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
2374
2405
|
private extractValueExpression(statement: string, statementIndex: number, fullCode: string): { valueExpression: string, valueExpressionIndex: number } {
|
|
2375
2406
|
const trimmedStatement = statement.trim();
|
|
2376
2407
|
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2408
|
+
let assignmentMatch: RegExpExecArray | null = null;
|
|
2409
|
+
let assignmentSuffix = '';
|
|
2410
|
+
|
|
2411
|
+
const textMatch = CSharpStringExtractor.TEXT_ASSIGNMENT_PATTERN.exec(trimmedStatement);
|
|
2412
|
+
const titleMatch = CSharpStringExtractor.TITLE_ASSIGNMENT_PATTERN.exec(trimmedStatement);
|
|
2413
|
+
|
|
2414
|
+
if (textMatch) {
|
|
2415
|
+
assignmentMatch = textMatch;
|
|
2416
|
+
assignmentSuffix = '.text =';
|
|
2417
|
+
} else if (titleMatch) {
|
|
2418
|
+
assignmentMatch = titleMatch;
|
|
2419
|
+
assignmentSuffix = '.title =';
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
if (assignmentMatch) {
|
|
2423
|
+
const assignmentIndex = assignmentMatch.index + assignmentMatch[0].indexOf(assignmentSuffix);
|
|
2424
|
+
const prefix = trimmedStatement.substring(0, assignmentIndex + assignmentSuffix.length);
|
|
2425
|
+
const valuePart = trimmedStatement.substring(assignmentIndex + assignmentSuffix.length);
|
|
2381
2426
|
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2382
2427
|
const valueExpression = value.trim();
|
|
2383
2428
|
|
|
2384
2429
|
const valueStartInStatement = statement.indexOf(value);
|
|
2385
|
-
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement :
|
|
2430
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : assignmentIndex + assignmentSuffix.length);
|
|
2386
2431
|
|
|
2387
2432
|
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2388
2433
|
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
@@ -2398,32 +2443,11 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
2398
2443
|
const value = this.extractValueUntilSemicolon(valuePart);
|
|
2399
2444
|
const valueExpression = value.trim();
|
|
2400
2445
|
|
|
2401
|
-
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
for (let i = afterReturnInStatement; i < statement.length - 1; i++) {
|
|
2408
|
-
if (statement[i] === '$' && statement[i + 1] === '"') {
|
|
2409
|
-
startInStatement = i;
|
|
2410
|
-
break;
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
if (startInStatement === -1) {
|
|
2415
|
-
for (let i = afterReturnInStatement; i < statement.length; i++) {
|
|
2416
|
-
if (statement[i] === '"') {
|
|
2417
|
-
startInStatement = i;
|
|
2418
|
-
break;
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
if (startInStatement !== -1) {
|
|
2424
|
-
finalIndex = statementIndex + startInStatement;
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2446
|
+
const valueStartInStatement = statement.indexOf(value);
|
|
2447
|
+
const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : 'return '.length);
|
|
2448
|
+
|
|
2449
|
+
const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex);
|
|
2450
|
+
const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex;
|
|
2427
2451
|
|
|
2428
2452
|
return {
|
|
2429
2453
|
valueExpression: valueExpression,
|
|
@@ -2731,7 +2755,7 @@ private findMatchingParenthesis(str: string, openIndex: number): number {
|
|
|
2731
2755
|
|
|
2732
2756
|
private processStatementAndExtractValue(snippet: CodeSnippet, fullStatement: string, valueExpression: string, trClass: string, trFormatMethod: string, trMethod: string): void {
|
|
2733
2757
|
let processedValueExpression = valueExpression;
|
|
2734
|
-
const isTextAssignment =
|
|
2758
|
+
const isTextAssignment = this.isSpecialAssignment(fullStatement);
|
|
2735
2759
|
|
|
2736
2760
|
this.extractTrFormatStrings(processedValueExpression, snippet, trClass, trFormatMethod);
|
|
2737
2761
|
processedValueExpression = this.processStringTemplates(processedValueExpression, snippet, trClass, trFormatMethod);
|
package/src/CmdExecutor.ts
CHANGED
|
@@ -10,13 +10,12 @@ export class CmdExecutor {
|
|
|
10
10
|
static testConvert() {
|
|
11
11
|
let cwd = "E:/DATA/Projects/ZhiYou/ProjectFClient/GameClient/"
|
|
12
12
|
let cscodeFolders =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
13
|
+
[
|
|
14
|
+
cwd + "Assets/Bundles/FGUI/",
|
|
15
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Bundles/UI/",
|
|
16
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Bundles/Battle/",
|
|
17
|
+
// "E:/DATA/Projects/ZhiYou/DXTSProject/Client/Assets/Scripts/",
|
|
18
|
+
]
|
|
20
19
|
|
|
21
20
|
let gameConfigFolders = [cwd + "Assets/Bundles/GameConfigs/"]
|
|
22
21
|
let outCsvFile = "E:/DATA/Projects/e-gbl-client/client/Assets/Bundles/GameConfigs/Translation/hello.csv"
|
|
@@ -93,6 +93,74 @@ describe('CSharpStringExtractor', () => {
|
|
|
93
93
|
expect(snippets[0].literals).toContain('sub');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
// 形如 `m_btn_xxx.title = yyy;` 的赋值语句要和形如 `xxx.text = yyy;` 的赋值语句完全等同处理, 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式, `m_btn_` 为引用符号名固定前缀; 为了便于理解, 用正则表达式表示, 也就是相当于需要同时匹配 `/^m_btn_\w+\.title\s*=/` 形式的表达式和 `/\w+\.text\s*=/` 形式的表达式, 而其中对于 `xxx` 和 `yyy` 部分的处理逻辑是完全一致的
|
|
97
|
+
test('should handle m_btn_xxx.title = yyy', () => {
|
|
98
|
+
const code = `
|
|
99
|
+
buy.title = "lkwjelkfj1";
|
|
100
|
+
m_btn1_buy.title = "lkwjelkfj2";
|
|
101
|
+
am_btn_buy.title = "lkwjelkfj3";
|
|
102
|
+
m_btn_buy.title = "lkwjelkfj4";
|
|
103
|
+
m_btn_wwefHwref.title = $"Hello";
|
|
104
|
+
m_btn_fxx_wf.title = $"Hello, {name}!";
|
|
105
|
+
`;
|
|
106
|
+
const snippets = extractor.extractStrings(code);
|
|
107
|
+
{
|
|
108
|
+
let snippet = snippets[0];
|
|
109
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj1"'));
|
|
110
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj1"');
|
|
111
|
+
// `buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
112
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj1"');
|
|
113
|
+
expect(snippet.literals).toContain('lkwjelkfj1');
|
|
114
|
+
expect(snippet.isChanged).toBe(false);
|
|
115
|
+
}
|
|
116
|
+
{
|
|
117
|
+
let snippet = snippets[1];
|
|
118
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj2"'));
|
|
119
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj2"');
|
|
120
|
+
// `m_btn1_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
121
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj2"');
|
|
122
|
+
expect(snippet.literals).toContain('lkwjelkfj2');
|
|
123
|
+
expect(snippet.isChanged).toBe(false);
|
|
124
|
+
}
|
|
125
|
+
{
|
|
126
|
+
let snippet = snippets[2];
|
|
127
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj3"'));
|
|
128
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj3"');
|
|
129
|
+
// `am_btn_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()`
|
|
130
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj3"');
|
|
131
|
+
expect(snippet.literals).toContain('lkwjelkfj3');
|
|
132
|
+
expect(snippet.isChanged).toBe(false);
|
|
133
|
+
}
|
|
134
|
+
{
|
|
135
|
+
let snippet = snippets[3];
|
|
136
|
+
expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj4"'));
|
|
137
|
+
expect(snippet.originalCode).toBe('"lkwjelkfj4"');
|
|
138
|
+
// `m_btn_buy` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()`
|
|
139
|
+
expect(snippet.convertedCode).toBe('"lkwjelkfj4".TR()');
|
|
140
|
+
expect(snippet.literals).toContain('lkwjelkfj4');
|
|
141
|
+
expect(snippet.isChanged).toBe(true);
|
|
142
|
+
}
|
|
143
|
+
{
|
|
144
|
+
let snippet = snippets[4];
|
|
145
|
+
expect(snippet.originalIndex).toBe(code.indexOf('$"Hello"'));
|
|
146
|
+
expect(snippet.originalCode).toBe('$"Hello"');
|
|
147
|
+
// `m_btn_wwefHwref` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()`
|
|
148
|
+
expect(snippet.convertedCode).toBe('$"Hello".TR()');
|
|
149
|
+
expect(snippet.literals).toContain('Hello');
|
|
150
|
+
expect(snippet.isChanged).toBe(true);
|
|
151
|
+
}
|
|
152
|
+
{
|
|
153
|
+
let snippet = snippets[5];
|
|
154
|
+
expect(snippet.originalIndex).toBe(code.indexOf('$"Hello, {name}!"'));
|
|
155
|
+
expect(snippet.originalCode).toBe('$"Hello, {name}!"');
|
|
156
|
+
// `m_btn_fxx_wf` 符合 `m_btn_` 开头的条件
|
|
157
|
+
expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)');
|
|
158
|
+
expect(snippet.literals).toContain('Hello, {0}!');
|
|
159
|
+
expect(snippet.isChanged).toBe(true);
|
|
160
|
+
}
|
|
161
|
+
expect(snippets.length).toBe(6);
|
|
162
|
+
});
|
|
163
|
+
|
|
96
164
|
// 测试用 `+` 连接的字符串表达式, 确保用 `+` 符号拼接的字符串表达式被整体处理, 从中提取字符串表达式信息, 而不是分别处理 `+` 两边每个字符串表达式; 并且给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式
|
|
97
165
|
test('should handle string concatenation', () => {
|
|
98
166
|
const code = 'var label2 = "Hello, " + name + "!";';
|
|
@@ -103,7 +171,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
103
171
|
expect(snippets[0].convertedCode).toBe('"Hello, ".TR() + name.TR() + "!".TR()');
|
|
104
172
|
});
|
|
105
173
|
|
|
106
|
-
// 测试 `xxx.text = yyy
|
|
174
|
+
// 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的字符串表达式信息提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式
|
|
107
175
|
test('should handle .text = assignments with function calls', () => {
|
|
108
176
|
const code = 'label.text = Func();';
|
|
109
177
|
const snippets = extractor.extractStrings(code);
|
|
@@ -113,7 +181,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
113
181
|
expect(snippets[0].convertedCode).toBe('Func().TR()');
|
|
114
182
|
});
|
|
115
183
|
|
|
116
|
-
// 测试 `xxx.text = xxx + xxx
|
|
184
|
+
// 测试 `xxx.text = xxx + xxx;` 形式的赋值语句, 确保将 `xxx.text` 中的 `xxx` 字符串表达式提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式
|
|
117
185
|
test('should handle .text = assignments with multiple function calls', () => {
|
|
118
186
|
const code = 'label.text = Func() + Func();';
|
|
119
187
|
const snippets = extractor.extractStrings(code);
|
|
@@ -123,7 +191,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
123
191
|
expect(snippets[0].convertedCode).toBe('Func().TR() + Func().TR()');
|
|
124
192
|
});
|
|
125
193
|
|
|
126
|
-
// 测试 `xxx.text = yyy
|
|
194
|
+
// 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的 `xxx` 内插字符串提取出来, 并转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式
|
|
127
195
|
test('should handle .text = assignments with string templates', () => {
|
|
128
196
|
const code = 'label.text = $"pre{Func()}sub";';
|
|
129
197
|
const snippets = extractor.extractStrings(code);
|
|
@@ -215,7 +283,7 @@ describe('CSharpStringExtractor', () => {
|
|
|
215
283
|
expect(snippet.isChanged).toBe(true);
|
|
216
284
|
});
|
|
217
285
|
|
|
218
|
-
// 测试处理 `xxx.text = yyy
|
|
286
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式, 需要从 `yyy` 中正确提取字符串表达式信息, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式
|
|
219
287
|
test('should handle .text = assignments', () => {
|
|
220
288
|
const code = 'label.text = "Test";';
|
|
221
289
|
const snippets = extractor.extractStrings(code);
|
|
@@ -329,9 +397,9 @@ describe('CSharpStringExtractor', () => {
|
|
|
329
397
|
|
|
330
398
|
const snippet = snippets[0];
|
|
331
399
|
expect(snippet.originalCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]"');
|
|
332
|
-
expect(snippet.convertedCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]"');
|
|
400
|
+
expect(snippet.convertedCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]".TR()');
|
|
333
401
|
expect(snippet.literals).toEqual(['[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]']);
|
|
334
|
-
expect(snippet.isChanged).toBe(
|
|
402
|
+
expect(snippet.isChanged).toBe(true);
|
|
335
403
|
});
|
|
336
404
|
|
|
337
405
|
// 处理C#多行语句
|
|
@@ -1967,7 +2035,7 @@ namespace TestNamespace{
|
|
|
1967
2035
|
}
|
|
1968
2036
|
});
|
|
1969
2037
|
// 正确识别C# 行注释语句边界, 行注释不能影响上下行的字符串提取
|
|
1970
|
-
test('should handle comment boundary
|
|
2038
|
+
test('should handle comment boundary 3', () => {
|
|
1971
2039
|
const code = `
|
|
1972
2040
|
// klwjfe
|
|
1973
2041
|
namespace TestNamespace{
|
|
@@ -2166,7 +2234,7 @@ namespace FaBao.UI.Comp
|
|
|
2166
2234
|
});
|
|
2167
2235
|
|
|
2168
2236
|
// 正确提取C#方法参数中的字符串表达式信息, 支持1到多个形参有默认值的情形
|
|
2169
|
-
test('should handle string expression in method call parameter
|
|
2237
|
+
test('should handle string expression in method call parameter 4', () => {
|
|
2170
2238
|
const code = `
|
|
2171
2239
|
namespace FaBao.UI.Comp
|
|
2172
2240
|
{
|
|
@@ -2393,7 +2461,7 @@ namespace FaBao.UI.MainUI
|
|
|
2393
2461
|
});
|
|
2394
2462
|
|
|
2395
2463
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2396
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2464
|
+
test('should handle class member initialization assignment with anonymous function 3', () => {
|
|
2397
2465
|
const code = `
|
|
2398
2466
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2399
2467
|
{
|
|
@@ -2418,7 +2486,7 @@ namespace FaBao.UI.MainUI
|
|
|
2418
2486
|
});
|
|
2419
2487
|
|
|
2420
2488
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2421
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2489
|
+
test('should handle class member initialization assignment with anonymous function 4', () => {
|
|
2422
2490
|
const code = `
|
|
2423
2491
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2424
2492
|
{
|
|
@@ -2440,7 +2508,7 @@ namespace FaBao.UI.MainUI
|
|
|
2440
2508
|
});
|
|
2441
2509
|
|
|
2442
2510
|
// 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现.
|
|
2443
|
-
test('should handle class member initialization assignment with anonymous function
|
|
2511
|
+
test('should handle class member initialization assignment with anonymous function 5', () => {
|
|
2444
2512
|
const code = `
|
|
2445
2513
|
FUISys.Instance.ShowLayer<MainConfirmDialog>(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam()
|
|
2446
2514
|
{
|
|
@@ -2461,7 +2529,7 @@ namespace FaBao.UI.MainUI
|
|
|
2461
2529
|
}
|
|
2462
2530
|
});
|
|
2463
2531
|
|
|
2464
|
-
// 测试处理 `xxx.text = yyy
|
|
2532
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。
|
|
2465
2533
|
test('should handle .text = format 1', () => {
|
|
2466
2534
|
const code = `m_text_iwff.text = info ;`;
|
|
2467
2535
|
const snippets = extractor.extractStrings(code);
|
|
@@ -2476,8 +2544,8 @@ namespace FaBao.UI.MainUI
|
|
|
2476
2544
|
}
|
|
2477
2545
|
});
|
|
2478
2546
|
|
|
2479
|
-
// 测试处理 `xxx.text = yyy
|
|
2480
|
-
test('should handle .text = format
|
|
2547
|
+
// 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。
|
|
2548
|
+
test('should handle .text = format 2', () => {
|
|
2481
2549
|
const code = `m_text_info.text = info1.member1 + info2.member2;`;
|
|
2482
2550
|
const snippets = extractor.extractStrings(code);
|
|
2483
2551
|
{
|
|
@@ -2559,7 +2627,7 @@ namespace FaBao.UI.MainUI
|
|
|
2559
2627
|
});
|
|
2560
2628
|
|
|
2561
2629
|
// 支持处理字符串里出现各种特殊符号的情况
|
|
2562
|
-
test('should handle special characters in string
|
|
2630
|
+
test('should handle special characters in string 9', () => {
|
|
2563
2631
|
const code = `
|
|
2564
2632
|
(GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word);
|
|
2565
2633
|
`;
|
|
@@ -2578,7 +2646,7 @@ namespace FaBao.UI.MainUI
|
|
|
2578
2646
|
});
|
|
2579
2647
|
|
|
2580
2648
|
// 支持处理字符串里出现各种特殊符号的情况
|
|
2581
|
-
test('should handle special characters in string
|
|
2649
|
+
test('should handle special characters in string 10', () => {
|
|
2582
2650
|
const code = `
|
|
2583
2651
|
(GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word);
|
|
2584
2652
|
`;
|
|
@@ -2676,4 +2744,62 @@ namespace FaBao.UI.MainUI
|
|
|
2676
2744
|
expect(snippets.length).toBe(0);
|
|
2677
2745
|
});
|
|
2678
2746
|
|
|
2747
|
+
// 需要正确获取 `originalIndex`, `originalIndex` 的定义为 `originalIndex=code.indexOf(originalCode)`, 其中 `code` 表示原始代码, `originalCode` 表示匹配出的原始代码中的字符串表达式
|
|
2748
|
+
test('should handle calculate originalIndex', () => {
|
|
2749
|
+
const code = `
|
|
2750
|
+
namespace FaBao
|
|
2751
|
+
{
|
|
2752
|
+
public partial class UserInfoModel
|
|
2753
|
+
{
|
|
2754
|
+
public bool HasJoinedGuild()
|
|
2755
|
+
{
|
|
2756
|
+
return !string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0";
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
public void UpdateBargain(BargainShopData bargainShopData)
|
|
2760
|
+
{
|
|
2761
|
+
MyGuildInfo.IsBargainShopBargain = true;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
`;
|
|
2767
|
+
const snippets = extractor.extractStrings(code);
|
|
2768
|
+
{
|
|
2769
|
+
let targetSnippet = snippets[0];
|
|
2770
|
+
expect(targetSnippet.originalCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`);
|
|
2771
|
+
expect(targetSnippet.originalIndex).toBe(code.indexOf(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`));
|
|
2772
|
+
expect(targetSnippet.convertedCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`);
|
|
2773
|
+
expect(targetSnippet.literals).toEqual([
|
|
2774
|
+
'0'
|
|
2775
|
+
]);
|
|
2776
|
+
expect(targetSnippet.isChanged).toBe(false);
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
|
|
2780
|
+
// 测试 `xxx.text = yyy;` 和 `m_btn_xxx.title = yyy;` 形式的赋值语句都需要支持中文, 包括字符串表达式中也要支持包含中文的情况
|
|
2781
|
+
test('should handle .text = assignments contains chinese', () => {
|
|
2782
|
+
const code = `
|
|
2783
|
+
为栓饭cxs12.text = 南方eflkj.d文件 + "完善ew";
|
|
2784
|
+
m_btn_为栓饭cxs12.title = bw尅kljekl.完善 + "栏栏nfc";
|
|
2785
|
+
`;
|
|
2786
|
+
const snippets = extractor.extractStrings(code);
|
|
2787
|
+
{
|
|
2788
|
+
let snippet = snippets[0];
|
|
2789
|
+
expect(snippet.originalCode).toBe('南方eflkj.d文件 + "完善ew"');
|
|
2790
|
+
expect(snippet.convertedCode).toBe('南方eflkj.d文件.TR() + "完善ew".TR()');
|
|
2791
|
+
expect(snippet.literals).toEqual([
|
|
2792
|
+
'完善ew'
|
|
2793
|
+
]);
|
|
2794
|
+
}
|
|
2795
|
+
{
|
|
2796
|
+
let snippet = snippets[1];
|
|
2797
|
+
expect(snippet.originalCode).toBe('bw尅kljekl.完善 + "栏栏nfc"');
|
|
2798
|
+
expect(snippet.convertedCode).toBe('bw尅kljekl.完善.TR() + "栏栏nfc".TR()');
|
|
2799
|
+
expect(snippet.literals).toEqual([
|
|
2800
|
+
'栏栏nfc'
|
|
2801
|
+
]);
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
|
|
2679
2805
|
});
|
package/test/TestConvert.test.ts
CHANGED