import { readFileSync } from 'fs'; import { CSharpStringExtractor, CodeSnippet } from '../src/CSharpStringExtractor'; describe('CSharpStringExtractor', () => { let extractor: CSharpStringExtractor; beforeEach(() => { extractor = new CSharpStringExtractor(); }); // 测试从普通函数调用参数中提取字符串表达式信息, 只需要提取参数中的字符串值表达式, 不需要对提取的字符串表达式进行处理 test('should extract plain strings', () => { const code = 'Console.WriteLine("Hello, world!");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].literals).toEqual(['Hello, world!']); }); // 已经追加 `.TR()` 后缀的字符串表达式不需要重复追加 `.TR()` test('should handle strings with .TR() already appended', () => { const code = 'obj.prop = "Hello, world!".TR();'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Hello, world!".TR()'); expect(snippets[0].convertedCode).toBe('"Hello, world!".TR()'); expect(snippets[0].isChanged).toBe(false); }); // 测试从普通函数调用参数中提取字符串表达式信息, 只需要提取参数中的字符串值表达式, 不需要对提取的字符串表达式进行处理, 也不需要追加 `.TR()` test('should not add .TR() to function arguments', () => { const code = 'CallFunc("Hello, world!");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Hello, world!"'); expect(snippets[0].convertedCode).toBe('"Hello, world!"'); }); // 测试从普通属性赋值语句中提取字符串表达式信息, 只需要提取赋值表达式中的字符串值表达式, 不需要对提取的字符串表达式进行处理, 也不需要追加 `.TR()` test('should not add .TR() to regular property assignments', () => { const code = 'obj.prop = "Hello, world!";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Hello, world!"'); expect(snippets[0].convertedCode).toBe('"Hello, world!"'); }); // 测试从 $"" 字符串模板(也叫内插字符串)中提取字符串表达式信息, 需要先将字符串模板转换为 `string.Format(...)` 格式, 然后将 `string.Format(...)` 替换为 `Tr.Format(...)`, 之后从 `Tr.Format(...)` 的函数调用参数中提取普通字符串、或进一步递归提取字符串表达式信息 test('should handle $"" string templates', () => { const code = 'var label1 = $"Hello, {name}!";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('$"Hello, {name}!"'); expect(snippets[0].literals).toContain('Hello, {0}!'); expect(snippets[0].convertedCode).toBe('Tr.Format("Hello, {0}!", name)'); }); // 测试从普通函数调用参数中提取 $"" 字符串模板(也叫内插字符串), 需要先将字符串模板转换为 `string.Format(...)` 格式, 然后将 `string.Format(...)` 替换为 `Tr.Format(...)`, 之后从 `Tr.Format(...)` 的函数调用参数中提取普通字符串、或进一步递归提取字符串表达式信息 test('should handle $"" string templates in function calls', () => { const code = 'CallFunc($"Hello, world: {obj.Func1()}");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('$"Hello, world: {obj.Func1()}"'); expect(snippets[0].convertedCode).toBe('Tr.Format("Hello, world: {0}", obj.Func1())'); }); // 测试遇到 `string.Format(...)` 形式的字符串表达式, 需要先将 `string.Format(...)` 替换为 `Tr.Format(...)`, 之后从 `Tr.Format(...)` 的函数调用参数中提取普通字符串、或进一步递归提取字符串表达式信息, 内插字符串概念参考: `https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/tokens/interpolated` test('should convert string.Format to Tr.Format', () => { const code = 'string.Format("Hello, ", name, "!");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); // string.Format(...) 形式的字符串表达式, 不需要对提取的字符串表达式进行处理, 也不需要追加 `.TR()` // string.Format(...) 形式包含的字符串表达式, 需要特殊处理, 连带 `string.Format()` 一起捕获存入 originalCode 成员 expect(snippets[0].originalCode).toBe('string.Format("Hello, ", name, "!")'); expect(snippets[0].convertedCode).toBe('Tr.Format("Hello, ", name, "!")'); }); // 测试遇到 `string.Format(...)` 形式的字符串表达式, 需要先将 `string.Format(...)` 替换为 `Tr.Format(...)`, 之后从 `Tr.Format(...)` 的函数调用参数中提取普通字符串、或进一步递归提取字符串表达式信息 // 同时测试用 `+` 连接的字符串表达式, 确保用 `+` 符号拼接的字符串表达式被整体处理, 从中提取字符串表达式信息, 而不是分别处理 `+` 两边每个字符串表达式 test('should handle string.Format in .text assignments', () => { const code = 'label.text = Tr.Format("pre", Func(), "sub") + other;'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('Tr.Format("pre", Func(), "sub") + other'); expect(snippets[0].literals).toContain('pre'); expect(snippets[0].literals).toContain('sub'); }); // 形如 `m_btn_xxx.title = yyy;` 的赋值语句要和形如 `xxx.text = yyy;` 的赋值语句完全等同处理, 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式, `m_btn_` 为引用符号名固定前缀; 为了便于理解, 用正则表达式表示, 也就是相当于需要同时匹配 `/^m_btn_\w+\.title\s*=/` 形式的表达式和 `/\w+\.text\s*=/` 形式的表达式, 而其中对于 `xxx` 和 `yyy` 部分的处理逻辑是完全一致的 test('should handle m_btn_xxx.title = yyy', () => { const code = ` buy.title = "lkwjelkfj1"; m_btn1_buy.title = "lkwjelkfj2"; am_btn_buy.title = "lkwjelkfj3"; m_btn_buy.title = "lkwjelkfj4"; m_btn_wwefHwref.title = $"Hello"; m_btn_fxx_wf.title = $"Hello, {name}!"; `; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj1"')); expect(snippet.originalCode).toBe('"lkwjelkfj1"'); // `buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()` expect(snippet.convertedCode).toBe('"lkwjelkfj1"'); expect(snippet.literals).toContain('lkwjelkfj1'); expect(snippet.isChanged).toBe(false); } { let snippet = snippets[1]; expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj2"')); expect(snippet.originalCode).toBe('"lkwjelkfj2"'); // `m_btn1_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()` expect(snippet.convertedCode).toBe('"lkwjelkfj2"'); expect(snippet.literals).toContain('lkwjelkfj2'); expect(snippet.isChanged).toBe(false); } { let snippet = snippets[2]; expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj3"')); expect(snippet.originalCode).toBe('"lkwjelkfj3"'); // `am_btn_buy` 不符合 `m_btn_` 开头的条件, 不需要强制加 `.TR()` expect(snippet.convertedCode).toBe('"lkwjelkfj3"'); expect(snippet.literals).toContain('lkwjelkfj3'); expect(snippet.isChanged).toBe(false); } { let snippet = snippets[3]; expect(snippet.originalIndex).toBe(code.indexOf('"lkwjelkfj4"')); expect(snippet.originalCode).toBe('"lkwjelkfj4"'); // `m_btn_buy` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()` expect(snippet.convertedCode).toBe('"lkwjelkfj4".TR()'); expect(snippet.literals).toContain('lkwjelkfj4'); expect(snippet.isChanged).toBe(true); } { let snippet = snippets[4]; expect(snippet.originalIndex).toBe(code.indexOf('$"Hello"')); expect(snippet.originalCode).toBe('$"Hello"'); // `m_btn_wwefHwref` 符合 `m_btn_` 开头的条件, 需要强制加 `.TR()` expect(snippet.convertedCode).toBe('$"Hello".TR()'); expect(snippet.literals).toContain('Hello'); expect(snippet.isChanged).toBe(true); } { let snippet = snippets[5]; expect(snippet.originalIndex).toBe(code.indexOf('$"Hello, {name}!"')); expect(snippet.originalCode).toBe('$"Hello, {name}!"'); // `m_btn_fxx_wf` 符合 `m_btn_` 开头的条件 expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)'); expect(snippet.literals).toContain('Hello, {0}!'); expect(snippet.isChanged).toBe(true); } expect(snippets.length).toBe(6); }); // 测试用 `+` 连接的字符串表达式, 确保用 `+` 符号拼接的字符串表达式被整体处理, 从中提取字符串表达式信息, 而不是分别处理 `+` 两边每个字符串表达式; 并且给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式 test('should handle string concatenation', () => { const code = 'var label2 = "Hello, " + name + "!";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Hello, " + name + "!"'); expect(snippets[0].convertedCode).toBe('"Hello, ".TR() + name.TR() + "!".TR()'); }); // 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的字符串表达式信息提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#字面量, 可以是对象、字符串、或其他表达式 test('should handle .text = assignments with function calls', () => { const code = 'label.text = Func();'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('Func()'); expect(snippets[0].convertedCode).toBe('Func().TR()'); }); // 测试 `xxx.text = xxx + xxx;` 形式的赋值语句, 确保将 `xxx.text` 中的 `xxx` 字符串表达式提取出来, 并追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式 test('should handle .text = assignments with multiple function calls', () => { const code = 'label.text = Func() + Func();'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('Func() + Func()'); expect(snippets[0].convertedCode).toBe('Func().TR() + Func().TR()'); }); // 测试 `xxx.text = yyy;` 形式的赋值语句, 确保将 `yyy` 中的 `xxx` 内插字符串提取出来, 并转换为 `Tr.Format(...)` 形式; 其中 `xxx`和`yyy` 代表某段C#表达式, 可以是对象、字符串、或其他表达式 test('should handle .text = assignments with string templates', () => { const code = 'label.text = $"pre{Func()}sub";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('$"pre{Func()}sub"'); expect(snippets[0].literals).toContain('pre{0}sub'); expect(snippets[0].convertedCode).toBe('Tr.Format("pre{0}sub", Func())'); }); // 测试从内插字符串中提取字符串表达式信息时, 需要注意转换和捕获内插字符串格式参数, 内插字符串和对应格式参数参考: `https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/tokens/interpolated` test('should handle .text = assignments with string.Format', () => { const code = 'label.text = string.Format("pre{0:F2}{1}", Func(), "sub") + other;'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('string.Format("pre{0:F2}{1}", Func(), "sub") + other'); expect(snippets[0].literals).toContain('pre{0:F2}{1}'); expect(snippets[0].literals).toContain('sub'); }); // 测试用 `+` 连接的字符串表达式, 确保用 `+` 符号拼接的字符串表达式被整体处理, 从中提取字符串表达式信息, 而不是分别处理 `+` 两边每个字符串表达式; 并且给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式或进一步递归处理 test('should handle complex concatenation with existing .TR() calls', () => { const code = 'var hello3 = "Hello, ".TR() + name.TR() + obj2.name.TR() + obj2.nam1_e.Fn1().Prop + "!";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Hello, ".TR() + name.TR() + obj2.name.TR() + obj2.nam1_e.Fn1().Prop + "!"'); expect(snippets[0].convertedCode).toBe('"Hello, ".TR() + name.TR() + obj2.name.TR() + obj2.nam1_e.Fn1().Prop.TR() + "!".TR()'); }); // 测试处理包含转义引号的字符串表达式, 确保能正确提取和转换转义引号, 而不会影响字符串边界的正常解析 test('should handle escaped quotes in strings', () => { const code = 'Debug.Log("aaa\\\"bbb\\\"c\\\"d\\\\\\"cc");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"aaa\\\"bbb\\\"c\\\"d\\\\\\"cc"'); expect(snippets[0].convertedCode).toBe('"aaa\\\"bbb\\\"c\\\"d\\\\\\"cc"'); // 由于JavaScript字符串转义的复杂性,我们只检查是否提取了字符串 expect(snippets[0].literals).toEqual(['aaa\\\"bbb\\\"c\\\"d\\\\\\"cc']); }); // 测试处理包含 `+` 连接的字符串表达式, 需要给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式或进一步递归处理 test('should capture statement boundaries correctly', () => { const code = 'obj.text = "Test property" + "Test property2";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(1); expect(snippets[0].originalCode).toBe('"Test property" + "Test property2"'); expect(snippets[0].convertedCode).toBe('"Test property".TR() + "Test property2".TR()'); }); // 测试处理包含多个语句的C#代码, 确保能从各个语句中正确提取和转换每个语句中的字符串表达式, 而不会影响其他语句的正常解析 test('should handle multiple statements', () => { const code = 'obj1.text = "Hello"; obj2.text = "World";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(2); expect(snippets[0].originalCode).toBe('"Hello"'); expect(snippets[0].convertedCode).toBe('"Hello".TR()'); expect(snippets[1].originalCode).toBe('"World"'); expect(snippets[1].convertedCode).toBe('"World".TR()'); }); // 测试处理作为函数参数的内插字符串表达式, 确保能从内插字符串中提取字符串表达式信息, 并转换为 `Tr.Format(...)` 形式 test('should extract and convert string templates', () => { const code = 'Ljk.Ilk($"Hello, {name}!");'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBeGreaterThan(0); const snippet = snippets[0]; expect(snippet.originalCode).toBe('$"Hello, {name}!"'); expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)'); expect(snippet.literals).toEqual(['Hello, {0}!']); expect(snippet.isChanged).toBe(true); }); // 测试处理 `string.Format` 调用, 确保能将其转换为 `Tr.Format(...)` 形式, 并正确提取字符串表达式信息 test('should convert string.Format to Tr.Format', () => { const code = 'string.Format("Hello, {0}!", name);'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBeGreaterThan(0); const snippet = snippets[0]; expect(snippet.originalCode).toBe('string.Format("Hello, {0}!", name)'); expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)'); expect(snippet.literals).toEqual(['Hello, {0}!']); expect(snippet.isChanged).toBe(true); }); // 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式, 需要从 `yyy` 中正确提取字符串表达式信息, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式 test('should handle .text = assignments', () => { const code = 'label.text = "Test";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBeGreaterThan(0); const snippet = snippets[0]; expect(snippet.originalCode).toBe('"Test"'); expect(snippet.convertedCode).toBe('"Test".TR()'); expect(snippet.literals).toEqual(['Test']); expect(snippet.isChanged).toBe(true); }); // 测试处理包含 `+` 连接的字符串表达式, 需要给 `+` 连接的每个成分追加 `.TR()` 或者转换为 `Tr.Format(...)` 形式或进一步递归处理 test('should handle string concatenations', () => { const code = 'var wkleee = "Hello, " + name + "!";'; const snippets = extractor.extractStrings(code); expect(snippets.length).toBeGreaterThan(0); const snippet = snippets[0]; expect(snippet.originalCode).toBe('"Hello, " + name + "!"'); expect(snippet.convertedCode).toBe('"Hello, ".TR() + name.TR() + "!".TR()'); expect(snippet.literals).toEqual(['Hello, ', '!']); expect(snippet.isChanged).toBe(true); }); // 测试处理 `+` 连接的字符串表达式时, 确保不会给已经包含 `.TR()` 的字符串表达式再次追加 `.TR()` test('should not add .TR() to strings already having it', () => { const code = 'var wkle = "Hello".TR();'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('"Hello".TR()'); expect(snippet.convertedCode).toBe('"Hello".TR()'); expect(snippet.literals).toEqual(['Hello']); expect(snippet.isChanged).toBe(false); }); // 测试处理复杂的 `.text =` 赋值语句, 确保能正确处理包含 `string.Format` 调用和 `+` 连接的字符串表达式, 并转换为 `Tr.Format(...)` 形式或进一步递归处理 test('should handle complex text assignments', () => { const code = 'label.text = string.Format("pre{0}sub", Func()) + other;'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('string.Format("pre{0}sub", Func()) + other'); expect(snippet.convertedCode).toBe('Tr.Format("pre{0}sub", Func()) + other.TR()'); expect(snippet.literals).toEqual(['pre{0}sub']); expect(snippet.isChanged).toBe(true); }); // 测试处理转义字符串, 确保能正确处理包含转义字符的字符串表达式, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式 test('should handle escaped strings', () => { const code = 'a.text = "aaa\\"bbb\\"c\\"d\\\\\\"cc";'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('"aaa\\"bbb\\"c\\"d\\\\\\"cc"'); expect(snippet.convertedCode).toBe('"aaa\\"bbb\\"c\\"d\\\\\\"cc".TR()'); expect(snippet.literals).toEqual(['aaa\\"bbb\\"c\\"d\\\\\\"cc']); expect(snippet.isChanged).toBe(true); }); // 测试处理 `@$""` 和 `$@""` 格式的字符串表达式, 确保能将其转换为 `Tr.Format(...)` 形式, 并正确提取字符串表达式信息 test('should handle @$"" and $@"" formats', () => { const code = 'Fcx.Kjl(@$"Hello, {name}!");'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('@$"Hello, {name}!"'); expect(snippet.convertedCode).toBe('Tr.Format("Hello, {0}!", name)'); expect(snippet.literals).toEqual(['Hello, {0}!']); expect(snippet.isChanged).toBe(true); }); // 测试处理函数调用时, 确保能正确提取参数中的字符串表达式, 不需要给字符串表达式追加 `.TR()` 或 转换为 `Tr.Format(...)` 形式, 除非遇到由 `+` 连接的字符串表达式 test('should handle function calls with string arguments', () => { const code = 'CallFunc("Hello", "World");'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"Hello"'); expect(snippet.convertedCode).toBe('"Hello"'); expect(snippet.literals).toEqual(['Hello']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[1]; expect(snippet.originalCode).toBe('"World"'); expect(snippet.convertedCode).toBe('"World"'); expect(snippet.literals).toEqual(['World']); expect(snippet.isChanged).toBe(false); } }); // 测试处理 `.text =` 赋值语句时, 确保能正确处理包含 `Tr.Format(...)` 调用的字符串表达式, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式 test('should handle .text = with Tr.Format', () => { const code = 'label.text = Tr.Format("pre", Func(), "sub") + other;'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('Tr.Format("pre", Func(), "sub") + other'); expect(snippet.convertedCode).toBe('Tr.Format("pre", Func(), "sub") + other.TR()'); expect(snippet.literals).toEqual(['pre', 'sub']); expect(snippet.isChanged).toBe(true); }); // 测试处理 `.text =` 赋值语句时, 确保能正确处理包含 `Tr.Format(...)` 调用的字符串表达式, 并给普通字符串表达式追加 `.TR()` 或者让内插字符串转换为 `Tr.Format(...)` 形式, 除非遇到由 `+` 连接的字符串表达式 test('should handle .text = with Tr.Format 2', () => { const code = 'm_text_lastAward.text = $"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]";'; const snippets = extractor.extractStrings(code); const snippet = snippets[0]; expect(snippet.originalCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]"'); expect(snippet.convertedCode).toBe('$"[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]".TR()'); expect(snippet.literals).toEqual(['[color=#FFFFFF]上期奖励:[/color][color=#FFE97E][/color][color=#FFFFFF]暂无获奖[/color]']); expect(snippet.isChanged).toBe(true); }); // 处理C#多行语句 test('should handle non string assignment 1', () => { const code = 'var d1 = 12;var d2 = 13;var d3 = d1 + d2;var aa = "aaa";'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"aaa"'); expect(snippet.convertedCode).toBe('"aaa"'); expect(snippet.literals).toEqual(['aaa']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle non string assignment 2', () => { const code = 'var d3 = d1 + d2;var aa = "aaa";'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"aaa"'); expect(snippet.convertedCode).toBe('"aaa"'); expect(snippet.literals).toEqual(['aaa']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 1', () => { const code = 'var aa = "aaa";\nvar bb = "bbb";\nvar cc = "ccc";'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"aaa"'); expect(snippet.convertedCode).toBe('"aaa"'); expect(snippet.literals).toEqual(['aaa']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[1]; expect(snippet.originalCode).toBe('"bbb"'); expect(snippet.convertedCode).toBe('"bbb"'); expect(snippet.literals).toEqual(['bbb']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[2]; expect(snippet.originalCode).toBe('"ccc"'); expect(snippet.convertedCode).toBe('"ccc"'); expect(snippet.literals).toEqual(['ccc']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 2', () => { const code = 'var d1 = 12;var d2 = 13;var d3 = d1 + d2;var aa = "aaa";\nvar bb = "bbb";\nvar dd = aa + bb;\nvar hh = aa + bb + "hhh";\ncc.text = "ccc" + aa + bb;var ii = "iii";jj.text = "jjj";CallFunc("jjj");CallFunc2("jjj", "kkk");jj.text = "jjj" + ll;'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"aaa"'); expect(snippet.convertedCode).toBe('"aaa"'); expect(snippet.literals).toEqual(['aaa']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[1]; expect(snippet.originalCode).toBe('"bbb"'); expect(snippet.convertedCode).toBe('"bbb"'); expect(snippet.literals).toEqual(['bbb']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[2]; expect(snippet.originalCode).toBe('aa + bb + "hhh"'); expect(snippet.convertedCode).toBe('aa.TR() + bb.TR() + "hhh".TR()'); expect(snippet.literals).toEqual(['hhh']); expect(snippet.isChanged).toBe(true); } { const snippet = snippets[3]; expect(snippet.originalCode).toBe('"ccc" + aa + bb'); expect(snippet.convertedCode).toBe('"ccc".TR() + aa.TR() + bb.TR()'); expect(snippet.literals).toEqual(['ccc']); expect(snippet.isChanged).toBe(true); } { const snippet = snippets[4]; expect(snippet.originalCode).toBe('"iii"'); expect(snippet.convertedCode).toBe('"iii"'); expect(snippet.literals).toEqual(['iii']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[5]; expect(snippet.originalCode).toBe('"jjj"'); expect(snippet.convertedCode).toBe('"jjj".TR()'); expect(snippet.literals).toEqual(['jjj']); expect(snippet.isChanged).toBe(true); } { const snippet = snippets[6]; expect(snippet.originalCode).toBe('"jjj"'); expect(snippet.convertedCode).toBe('"jjj"'); expect(snippet.literals).toEqual(['jjj']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[7]; expect(snippet.originalCode).toBe('"jjj"'); expect(snippet.convertedCode).toBe('"jjj"'); expect(snippet.literals).toEqual(['jjj']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[8]; expect(snippet.originalCode).toBe('"kkk"'); expect(snippet.convertedCode).toBe('"kkk"'); expect(snippet.literals).toEqual(['kkk']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[9]; expect(snippet.originalCode).toBe('"jjj" + ll'); expect(snippet.convertedCode).toBe('"jjj".TR() + ll.TR()'); expect(snippet.literals).toEqual(['jjj']); expect(snippet.isChanged).toBe(true); } }); // 处理C#多行语句 test('should handle multilne content 3', () => { const code = 'label.text = Tr.Format("pre", Func(), "sub") + other;\nlabel2.text = Tr.Format("pre2", Func(), "sub2") + other2;'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('Tr.Format("pre", Func(), "sub") + other'); expect(snippet.convertedCode).toBe('Tr.Format("pre", Func(), "sub") + other.TR()'); expect(snippet.literals).toEqual(['pre', 'sub']); expect(snippet.isChanged).toBe(true); } { const snippet = snippets[1]; expect(snippet.originalCode).toBe('Tr.Format("pre2", Func(), "sub2") + other2'); expect(snippet.convertedCode).toBe('Tr.Format("pre2", Func(), "sub2") + other2.TR()'); expect(snippet.literals).toEqual(['pre2', 'sub2']); expect(snippet.isChanged).toBe(true); } }); // 处理C#多行语句 test('should handle multilne content 4', () => { const code = 'label.text =\n Tr.Format(\n\t"pre", Func(),\n\t\t "sub") + other;'; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('Tr.Format(\n\t"pre", Func(),\n\t\t "sub") + other'); expect(snippet.convertedCode).toBe('Tr.Format(\n\t"pre", Func(),\n\t\t "sub") + other.TR()'); expect(snippet.literals).toEqual(['pre', 'sub']); expect(snippet.isChanged).toBe(true); } }); // 处理C#多行语句 test('should handle multilne content 5', () => { const code = ` // 注释中无字符串表达式时, 不需要包含在捕获部分里 m_text_name.text = cardTable.Name; `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('cardTable.Name'); expect(snippet.convertedCode).toBe('cardTable.Name.TR()'); expect(snippet.literals).toEqual([]); expect(snippet.isChanged).toBe(true); } }); // 处理C#多行语句 test('should handle multilne content 6', () => { const code = ` if (condition) { Toast.Info("卸载成功"); } `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.originalIndex).toBe(code.indexOf('"卸载成功"')); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 7', () => { const code = ` while (condition) { TT1.Fn1("卸载成功"); } `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.originalIndex).toBe(code.indexOf('"卸载成功"')); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 8', () => { const code = ` for (scope;condition;continuex) { QR2.Fn2("卸载成功"); } `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 9', () => { const code = ` { KK3.Ca3("卸载成功"); } `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 10', () => { const code = ` var lambda1 = () => { LJN4.Fn4("卸载成功"); }; `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 11', () => { const code = ` void lambda1() { KK5.Call5("卸载成功"); } `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 12', () => { const code = ` //星级 m_list_star.RemoveChildrenToPool(); for (int i = 0; i < _sutraCardData.LevelStar; i++) { var item = m_list_star.AddItemFromPool() as ItemSutraStarSComp; if (item == null) continue; item.SetSutraStar(_sutraCardData.Level); } m_text_quality.text = Tr.Format("{0}阶", _sutraCardData.Quality); //设置通玄值 m_text_tongXuan.text = "通玄:".TR() + (_sutraCardData.InheritAtkPercent / 100).ToString("0.0") + "%".TR(); `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalCode).toBe('Tr.Format("{0}阶", _sutraCardData.Quality)'); expect(snippet.convertedCode).toBe('Tr.Format("{0}阶", _sutraCardData.Quality)'); expect(snippet.literals).toEqual(['{0}阶']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 13', () => { const code = ` Toast.Info("卸载成功"); Toast.Info("卸载成功"); `; const snippets = extractor.extractStrings(code); { const snippet = snippets[0]; expect(snippet.originalIndex).toBe(code.indexOf('"卸载成功"')); expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } { const snippet = snippets[1]; expect(snippet.originalIndex).toBe(code.indexOf('"卸载成功"', snippets[0].originalIndex + 1)); expect(snippet.originalCode).toBe('"卸载成功"'); expect(snippet.convertedCode).toBe('"卸载成功"'); expect(snippet.literals).toEqual(['卸载成功']); expect(snippet.isChanged).toBe(false); } }); // 处理C#多行语句 test('should handle multilne content 14', () => { const code = readFileSync('./test/MainSutraDetailDialog.cs', 'utf8'); const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); // 针对每个snippet展开对每个属性值的断言 // Snippet 1 expect(snippets[0].originalIndex).toBe(code.indexOf('cardTable.Name.TR()')); expect(snippets[0].originalCode).toBe('cardTable.Name.TR()'); expect(snippets[0].convertedCode).toBe('cardTable.Name.TR()'); expect(snippets[0].literals).toEqual([]); expect(snippets[0].isChanged).toBe(false); // Snippet 2 expect(snippets[1].originalIndex).toBe(code.indexOf('sutraConfig.Poetry[0].TR()')); expect(snippets[1].originalCode).toBe('sutraConfig.Poetry[0].TR()'); expect(snippets[1].convertedCode).toBe('sutraConfig.Poetry[0].TR()'); expect(snippets[1].literals).toEqual([]); expect(snippets[1].isChanged).toBe(false); // Snippet 3 expect(snippets[2].originalIndex).toBe(code.indexOf('sutraConfig.Poetry[1].TR()')); expect(snippets[2].originalCode).toBe('sutraConfig.Poetry[1].TR()'); expect(snippets[2].convertedCode).toBe('sutraConfig.Poetry[1].TR()'); expect(snippets[2].literals).toEqual([]); expect(snippets[2].isChanged).toBe(false); // Snippet 4 expect(snippets[3].originalIndex).toBe(code.indexOf('(_sutraCardData.AttackUp() / 100).ToString("0.0") + "%".TR()')); expect(snippets[3].originalCode).toBe('(_sutraCardData.AttackUp() / 100).ToString("0.0") + "%".TR()'); expect(snippets[3].convertedCode).toBe('(_sutraCardData.AttackUp() / 100).ToString("0.0").TR() + "%".TR()'); expect(snippets[3].literals).toEqual(['0.0', '%']); expect(snippets[3].isChanged).toBe(true); // Snippet 5 expect(snippets[4].originalIndex).toBe(code.indexOf('(_sutraCardData.HpUp() / 100).ToString("0.0") + "%".TR()')); expect(snippets[4].originalCode).toBe('(_sutraCardData.HpUp() / 100).ToString("0.0") + "%".TR()'); expect(snippets[4].convertedCode).toBe('(_sutraCardData.HpUp() / 100).ToString("0.0").TR() + "%".TR()'); expect(snippets[4].literals).toEqual(['0.0', '%']); expect(snippets[4].isChanged).toBe(true); // Snippet 6 expect(snippets[5].originalIndex).toBe(code.indexOf('skillConfig.Name.TR()')); expect(snippets[5].originalCode).toBe('skillConfig.Name.TR()'); expect(snippets[5].convertedCode).toBe('skillConfig.Name.TR()'); expect(snippets[5].literals).toEqual([]); expect(snippets[5].isChanged).toBe(false); // Snippet 7 expect(snippets[6].originalIndex).toBe(code.indexOf('passiveSkillConfig != null ? passiveSkillConfig.Description.TR() : "被动技能未解锁".TR()')); expect(snippets[6].originalCode).toBe('passiveSkillConfig != null ? passiveSkillConfig.Description.TR() : "被动技能未解锁".TR()'); expect(snippets[6].convertedCode).toBe('passiveSkillConfig != null ? passiveSkillConfig.Description.TR() : "被动技能未解锁".TR()'); expect(snippets[6].literals).toEqual(['被动技能未解锁']); expect(snippets[6].isChanged).toBe(false); // Snippet 8 expect(snippets[7].originalIndex).toBe(code.indexOf('"Effect_FaBao_Unlock"')); expect(snippets[7].originalCode).toBe('"Effect_FaBao_Unlock"'); expect(snippets[7].convertedCode).toBe('"Effect_FaBao_Unlock"'); expect(snippets[7].literals).toEqual(['Effect_FaBao_Unlock']); expect(snippets[7].isChanged).toBe(false); // Snippet 9 expect(snippets[8].originalIndex).toBe(code.indexOf('Tr.Format("{0}阶", _sutraCardData.Quality)')); expect(snippets[8].originalCode).toBe('Tr.Format("{0}阶", _sutraCardData.Quality)'); expect(snippets[8].convertedCode).toBe('Tr.Format("{0}阶", _sutraCardData.Quality)'); expect(snippets[8].literals).toEqual(['{0}阶']); expect(snippets[8].isChanged).toBe(false); // Snippet 10 expect(snippets[9].originalIndex).toBe(code.indexOf('"通玄:".TR() + (_sutraCardData.InheritAtkPercent / 100).ToString("0.0") + "%".TR()')); expect(snippets[9].originalCode).toBe('"通玄:".TR() + (_sutraCardData.InheritAtkPercent / 100).ToString("0.0") + "%".TR()'); expect(snippets[9].convertedCode).toBe('"通玄:".TR() + (_sutraCardData.InheritAtkPercent / 100).ToString("0.0").TR() + "%".TR()'); expect(snippets[9].literals).toEqual(['通玄:', '0.0', '%']); expect(snippets[9].isChanged).toBe(true); // Snippet 11 expect(snippets[10].originalIndex).toBe(code.indexOf(`enough ? Tr.Format("[color=#1B8049]{0}/{1}[/color]", curCount, costCount) : Tr.Format("[color=#E55E5A]{0}/{1}[/color]", curCount, costCount)`)); expect(snippets[10].originalCode).toBe(`enough ? Tr.Format("[color=#1B8049]{0}/{1}[/color]", curCount, costCount) : Tr.Format("[color=#E55E5A]{0}/{1}[/color]", curCount, costCount)`); expect(snippets[10].convertedCode).toBe(`enough ? Tr.Format("[color=#1B8049]{0}/{1}[/color]", curCount, costCount) : Tr.Format("[color=#E55E5A]{0}/{1}[/color]", curCount, costCount)`); expect(snippets[10].literals).toEqual(['[color=#1B8049]{0}/{1}[/color]', '[color=#E55E5A]{0}/{1}[/color]']); expect(snippets[10].isChanged).toBe(false); // Snippet 12 expect(snippets[11].originalIndex).toBe(code.indexOf('dependLockInfo.Item2.TR()')); expect(snippets[11].originalCode).toBe('dependLockInfo.Item2.TR()'); expect(snippets[11].convertedCode).toBe('dependLockInfo.Item2.TR()'); expect(snippets[11].literals).toEqual([]); expect(snippets[11].isChanged).toBe(false); // Snippet 13 expect(snippets[12].originalIndex).toBe(code.indexOf('"Effect_FaBao_Unlock"', code.indexOf('"Effect_FaBao_Unlock"') + 1)); expect(snippets[12].originalCode).toBe('"Effect_FaBao_Unlock"'); expect(snippets[12].convertedCode).toBe('"Effect_FaBao_Unlock"'); expect(snippets[12].literals).toEqual(['Effect_FaBao_Unlock']); expect(snippets[12].isChanged).toBe(false); // Snippet 14 expect(snippets[13].originalIndex).toBe(code.indexOf('Tr.Format("({0}/6)", _sutraCardData.Card.RuneOpenCount)')); expect(snippets[13].originalCode).toBe('Tr.Format("({0}/6)", _sutraCardData.Card.RuneOpenCount)'); expect(snippets[13].convertedCode).toBe('Tr.Format("({0}/6)", _sutraCardData.Card.RuneOpenCount)'); expect(snippets[13].literals).toEqual(['({0}/6)']); expect(snippets[13].isChanged).toBe(false); // Snippet 15 expect(snippets[14].originalIndex).toBe(code.indexOf('$"Can not find tagId in sutra : {_sutraCardData.Card.Id}"')); expect(snippets[14].originalCode).toBe('$"Can not find tagId in sutra : {_sutraCardData.Card.Id}"'); expect(snippets[14].convertedCode).toBe('Tr.Format("Can not find tagId in sutra : {0}", _sutraCardData.Card.Id)'); expect(snippets[14].literals).toEqual(['Can not find tagId in sutra : {0}']); expect(snippets[14].isChanged).toBe(true); //#region 需要捕获类成员赋值表达式中, 出现在赋值操作符`=`右侧的字符串值表达式 // Snippet 16 expect(snippets[15].originalIndex).toBeGreaterThan(0); expect(snippets[15].originalIndex).toBe(code.indexOf('"提示"')); expect(snippets[15].originalCode).toBe('"提示"'); expect(snippets[15].convertedCode).toBe('"提示"'); expect(snippets[15].literals).toEqual(['提示']); expect(snippets[15].isChanged).toBe(false); // Snippet 17 expect(snippets[16].originalIndex).toBeGreaterThan(0); expect(snippets[16].originalIndex).toBe(code.indexOf('"是否一键卸下法宝当前镶嵌灵纹"')); expect(snippets[16].originalCode).toBe('"是否一键卸下法宝当前镶嵌灵纹"'); expect(snippets[16].convertedCode).toBe('"是否一键卸下法宝当前镶嵌灵纹"'); expect(snippets[16].literals).toEqual(['是否一键卸下法宝当前镶嵌灵纹']); expect(snippets[16].isChanged).toBe(false); // Snippet 18 expect(snippets[17].originalIndex).toBeGreaterThan(0); expect(snippets[17].originalIndex).toBe(code.indexOf('"确认"')); expect(snippets[17].originalCode).toBe('"确认"'); expect(snippets[17].convertedCode).toBe('"确认"'); expect(snippets[17].literals).toEqual(['确认']); expect(snippets[17].isChanged).toBe(false); //#endregion 需要捕获类成员赋值句式中, 出现在赋值操作符`=`右侧的字符串值表达式 // Snippet 19 expect(snippets[18].originalIndex).toBeGreaterThan(0); expect(snippets[18].originalIndex).toBe(code.indexOf('"卸载成功"')); expect(snippets[18].originalCode).toBe('"卸载成功"'); expect(snippets[18].convertedCode).toBe('"卸载成功"'); expect(snippets[18].literals).toEqual(['卸载成功']); expect(snippets[18].isChanged).toBe(false); //#region 需要捕获类成员赋值句式中, 出现在赋值操作符`=`右侧的字符串值表达式 // Snippet 20 expect(snippets[19].originalIndex).toBeGreaterThan(0); expect(snippets[19].originalIndex).toBe(code.indexOf('"取消"')); expect(snippets[19].originalCode).toBe('"取消"'); expect(snippets[19].convertedCode).toBe('"取消"'); expect(snippets[19].literals).toEqual(['取消']); expect(snippets[19].isChanged).toBe(false); // Snippet 21 expect(snippets[20].originalIndex).toBeGreaterThan(0); expect(snippets[20].originalIndex).toBe(code.indexOf('"本次登录不再提示"')); expect(snippets[20].originalCode).toBe('"本次登录不再提示"'); expect(snippets[20].convertedCode).toBe('"本次登录不再提示"'); expect(snippets[20].literals).toEqual(['本次登录不再提示']); expect(snippets[20].isChanged).toBe(false); //#endregion 需要捕获类成员赋值句式中, 出现在赋值操作符`=`右侧的字符串值表达式 // Snippet 22 expect(snippets[21].originalIndex).toBeGreaterThan(0); expect(snippets[21].originalIndex).toBe(code.indexOf('"卸载成功"', code.indexOf('"卸载成功"') + 1)); expect(snippets[21].originalCode).toBe('"卸载成功"'); expect(snippets[21].convertedCode).toBe('"卸载成功"'); expect(snippets[21].literals).toEqual(['卸载成功']); expect(snippets[21].isChanged).toBe(false); }); // 处理C#多行语句 test('should handle multilne content 15', () => { const code = readFileSync('./test/GuildDonateDialog.cs', 'utf8'); const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); // 针对每个snippet展开对每个属性值的断言 // 查找包含目标URL的snippet const targetSnippet = snippets.find(snippet => snippet.originalCode.includes('ui://a0w66rlc7bhfusff')); expect(targetSnippet).toBeDefined(); if (targetSnippet) { expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"{paidConfig.Quantity}"`)); expect(targetSnippet.originalCode).toBe(`$"{paidConfig.Quantity}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}", paidConfig.Quantity)`); expect(targetSnippet.literals).toEqual(["{0}"]); expect(targetSnippet.isChanged).toBe(true); } }); // 处理C#普通字符串包含 `//` 的情形 test('should handle string with `//`', () => { const code = `m_btn_paid.text = "//";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"//"`); expect(targetSnippet.convertedCode).toBe(`"//".TR()`); expect(targetSnippet.literals).toEqual(['//']); expect(targetSnippet.isChanged).toBe(true); }); // 处理C# `+=` 形式赋值语句 test('should handle string template with format specifier', () => { const code = `infoStr += $"最终抗性: {resistReduce:F}\\n";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"最终抗性: {resistReduce:F}\\n"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("最终抗性: {0:F}\\n", resistReduce)`); expect(targetSnippet.literals).toEqual(['最终抗性: {0:F}\\n']); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#多行语句, 特别是包含 `\n` 的字符串 test('should handle multilne content 17', () => { const code = ` infoStr += $"aaa: {aa}\n" + $"bbb = {bb}"; ` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"aaa: {aa}\n" + $"bbb = {bb}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("aaa: {0}\n", aa) + Tr.Format("bbb = {0}", bb)`); expect(targetSnippet.literals).toEqual([ 'aaa: {0}\n', 'bbb = {0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#多行语句, 特别是包含 `\\n` 的内插字符串 test('should handle multilne content 19', () => { const code = ` infoStr += $"aaa: {aa}\\n" + $"bbb = {bb}"; ` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"aaa: {aa}\\n" + $"bbb = {bb}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("aaa: {0}\\n", aa) + Tr.Format("bbb = {0}", bb)`); expect(targetSnippet.literals).toEqual([ 'aaa: {0}\\n', 'bbb = {0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中多行字符串拼接成的字符串表达式 test('should handle multilne content 18', () => { const code = ` infoStr += "伤害公式: \\n" + $"最终伤害[color={colorCode}][{(float)damageValue:F}][/color] = \\n" + $"基础伤害[b][{baseValue:F}][/b] X \\n" + $"暴击伤害加成[b][{damageInfo.CritDamageRatio:F}][/b] X \\n" + $"最终伤害加成[b][{1 + damageInfo.FinalDamageRatio:F}][/b] X \\n" + $"最终承伤加成[b][{1 + damageInfo.FinalInjuryRatio:F}][/b] / \\n" + $"最终伤害减免[b][{1 + damageInfo.FinalDamageReduce:F}][/b] X \\n" + $"抗性减免[b][{resistReduce:F}]"; ` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"伤害公式: \\n" + $"最终伤害[color={colorCode}][{(float)damageValue:F}][/color] = \\n" + $"基础伤害[b][{baseValue:F}][/b] X \\n" + $"暴击伤害加成[b][{damageInfo.CritDamageRatio:F}][/b] X \\n" + $"最终伤害加成[b][{1 + damageInfo.FinalDamageRatio:F}][/b] X \\n" + $"最终承伤加成[b][{1 + damageInfo.FinalInjuryRatio:F}][/b] / \\n" + $"最终伤害减免[b][{1 + damageInfo.FinalDamageReduce:F}][/b] X \\n" + $"抗性减免[b][{resistReduce:F}]"`); expect(targetSnippet.convertedCode).toBe(`"伤害公式: \\n".TR() + Tr.Format("最终伤害[color={0}][{1:F}][/color] = \\n", colorCode, (float)damageValue) + Tr.Format("基础伤害[b][{0:F}][/b] X \\n", baseValue) + Tr.Format("暴击伤害加成[b][{0:F}][/b] X \\n", damageInfo.CritDamageRatio) + Tr.Format("最终伤害加成[b][{0:F}][/b] X \\n", 1 + damageInfo.FinalDamageRatio) + Tr.Format("最终承伤加成[b][{0:F}][/b] / \\n", 1 + damageInfo.FinalInjuryRatio) + Tr.Format("最终伤害减免[b][{0:F}][/b] X \\n", 1 + damageInfo.FinalDamageReduce) + Tr.Format("抗性减免[b][{0:F}]", resistReduce)`); expect(targetSnippet.literals).toEqual([ '伤害公式: \\n', '最终伤害[color={0}][{1:F}][/color] = \\n', '基础伤害[b][{2:F}][/b] X \\n', '暴击伤害加成[b][{3:F}][/b] X \\n', '最终伤害加成[b][{4:F}][/b] X \\n', '最终承伤加成[b][{5:F}][/b] / \\n', '最终伤害减免[b][{6:F}][/b] X \\n', '抗性减免[b][{7:F}]' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中包含括号的内插字符串 test('should handle bracket 1', () => { const code = `m_text_time.text = $"下一轮神兽出现倒计时: {Date.GetIntweerpolatedTime(ServerTimer.Instance.Time, _startGameStamp)}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"下一轮神兽出现倒计时: {Date.GetIntweerpolatedTime(ServerTimer.Instance.Time, _startGameStamp)}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("下一轮神兽出现倒计时: {0}", Date.GetIntweerpolatedTime(ServerTimer.Instance.Time, _startGameStamp))`); expect(targetSnippet.literals).toEqual([ '下一轮神兽出现倒计时: {0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中包含括号的内插字符串 test('should handle bracket 2', () => { const code = `m_text_time.text = $"神兽离去时间: {Date.FeGetInterpolatedTime(ServerTimer.Instance.Time, _endGameStamp)}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"神兽离去时间: {Date.FeGetInterpolatedTime(ServerTimer.Instance.Time, _endGameStamp)}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("神兽离去时间: {0}", Date.FeGetInterpolatedTime(ServerTimer.Instance.Time, _endGameStamp))`); expect(targetSnippet.literals).toEqual([ '神兽离去时间: {0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中包含括号的内插字符串 test('should handle bracket 3', () => { const code = `desc += $"剩余{ToGetInterpolatedTime(_cardPoolData.FinishTime * 1000, ServerTimer.Instance.Time)}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"剩余{ToGetInterpolatedTime(_cardPoolData.FinishTime * 1000, ServerTimer.Instance.Time)}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("剩余{0}", ToGetInterpolatedTime(_cardPoolData.FinishTime * 1000, ServerTimer.Instance.Time))`); expect(targetSnippet.literals).toEqual([ '剩余{0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中包含括号的内插字符串 test('should handle bracket 4', () => { const code = `desc += $"剩余{ToGetInterpolatedTime(FF(_cardPoolData.FinishTime() * 1000, ServerTimer.Instance.Time()))}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"剩余{ToGetInterpolatedTime(FF(_cardPoolData.FinishTime() * 1000, ServerTimer.Instance.Time()))}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("剩余{0}", ToGetInterpolatedTime(FF(_cardPoolData.FinishTime() * 1000, ServerTimer.Instance.Time())))`); expect(targetSnippet.literals).toEqual([ '剩余{0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中包含`+=`符号的内插字符串 test('should handle duplicate tr handle 1', () => { const code = `infoStr = $"+={colorCode}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"+={colorCode}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("+={0}", colorCode)`); expect(targetSnippet.literals).toEqual([ '+={0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C# `+=` 形式的赋值语句后接 包含 `=` 符号的内插字符串 test('should handle duplicate tr handle 2', () => { const code = `infoStr += $"={colorCode}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"={colorCode}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("={0}", colorCode)`); expect(targetSnippet.literals).toEqual([ '={0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C# `+=` 形式的赋值语句后接 包含 `+=` 符号的内插字符串 test('should handle duplicate tr handle 3', () => { const code = `infoStr += $"+={colorCode}";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"+={colorCode}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("+={0}", colorCode)`); expect(targetSnippet.literals).toEqual([ '+={0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C# `+=` 形式的赋值语句后接包含 `=`、`[`、`]`、`{`、`}`、`=` 等特殊符号的内插字符串 test('should handle duplicate tr handle 4', () => { const code = `infoStr += $"伤害类型: [color={colorCode}][b]{_damageInfo.DamageType}[/b][/color]\n";` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`$"伤害类型: [color={colorCode}][b]{_damageInfo.DamageType}[/b][/color]\n"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType)`); expect(targetSnippet.literals).toEqual([ '伤害类型: [color={0}][b]{1}[/b][/color]\n' ]); expect(targetSnippet.isChanged).toBe(true); }); // 处理C#中函数调用参数中包含普通字符串的情况, 函数参数需要拆分, 逐个参数捕获 test('should handle para 1', () => { const code = `SetData("等级", level + 1);` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; // 函数参数需要拆分, 逐个参数捕获 expect(targetSnippet.originalCode).toBe(`"等级"`); expect(targetSnippet.convertedCode).toBe(`"等级"`); expect(targetSnippet.literals).toEqual([ "等级" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理C#中函数调用参数中包含普通字符串的情况 test('should handle para 2', () => { const code = `bbb.Func2("等级", level, level + 1, false, isMax);` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"等级"`); expect(targetSnippet.convertedCode).toBe(`"等级"`); expect(targetSnippet.literals).toEqual([ "等级" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理C#中switch语句中包含普通字符串的情况 test('should handle switch 1', () => { const code = ` switch(condition){ default: Log.Warning("实时更新自:" + rankData.Type); break; }` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"实时更新自:" + rankData.Type`); // 普通函数参数不需要追加 `.TR()`, 除非包含由 `+` 符号连接字符串等的情况 expect(targetSnippet.convertedCode).toBe(`"实时更新自:" + rankData.Type`); expect(targetSnippet.literals).toEqual([ "实时更新自:" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理C#中switch语句中包含普通字符串的情况 test('should handle switch 2', () => { const code = ` switch(kwjkwlje){ case (int)RANK_TYPE.天梯段位: Debug.Log1("jkwhfehwfjkh:" + rank2Data.T4zype); break; }` const snippets = extractor.extractStrings(code); // 验证提取的片段数量, switch 语句中包含普通字符串的情况 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"jkwhfehwfjkh:" + rank2Data.T4zype`); expect(targetSnippet.convertedCode).toBe(`"jkwhfehwfjkh:" + rank2Data.T4zype`); expect(targetSnippet.literals).toEqual([ "jkwhfehwfjkh:" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理C#中switch语句中包含普通字符串的情况 test('should handle switch 3', () => { const code = ` switch(lkw){ case V434: { Action2("wegwfw:" + varnws); break; } }` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"wegwfw:" + varnws`); expect(targetSnippet.convertedCode).toBe(`"wegwfw:" + varnws`); expect(targetSnippet.literals).toEqual([ "wegwfw:" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理C#中switch语句中包含普通字符串的情况 test('should handle switch 4', () => { const code = ` case V434: Action2("wegwfw:" + varnws); break;` const snippets = extractor.extractStrings(code); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThan(0); let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`"wegwfw:" + varnws`); expect(targetSnippet.convertedCode).toBe(`"wegwfw:" + varnws`); expect(targetSnippet.literals).toEqual([ "wegwfw:" ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理 C#内容中存在多个相同的包含字符串的语句时, 要每个都提取出来 test('should handle lost 1', () => { const code = readFileSync('./test/KeeperDialog.cs', 'utf8'); const snippets = extractor.extractStrings(code); let targetSnippet = snippets[3]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"已购买"; m_btn_buy.enabled = false; //购买后自动启用`)); expect(targetSnippet.originalCode).toBe(`"已购买"`); expect(targetSnippet.convertedCode).toBe(`"已购买".TR()`); expect(targetSnippet.literals).toEqual([ "已购买" ]); expect(targetSnippet.isChanged).toBe(true); // 验证提取的片段数量 expect(snippets.length).toBeGreaterThanOrEqual(5) }); // 字符串中包含 `)` 符号时, 也需要捕获该字符串 test('should handle lost 2', () => { const code = `m_text_runeSlot.text = Tr.Format(")", _sutraCardData.Card.RuneOpenCount);`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format(")", _sutraCardData.Card.RuneOpenCount)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format(")", _sutraCardData.Card.RuneOpenCount)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format(")", _sutraCardData.Card.RuneOpenCount)`); // 字符串中包含 `)` 符号时, 也需要捕获该字符串 expect(targetSnippet.literals).toEqual([ ')' ]); expect(targetSnippet.isChanged).toBe(false); }); // 处理 C#内容时, 需要匹配出包含 `(` 符号的字符串表达式 test('should handle lost 3', () => { const code = `m_text_runeSlot.text = ")";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`")"`)); expect(targetSnippet.originalCode).toBe(`")"`); expect(targetSnippet.convertedCode).toBe(`")".TR()`); expect(targetSnippet.literals).toEqual([ ')' ]); expect(targetSnippet.isChanged).toBe(true); }); // 测试处理C# `return` 语句中的字符串表达式, 提取的字符串表达式中不需要包含 `return` 关键字, 只需要包含字符串表达式 test('should handle return statement 1', () => { const code = `return "参数错误";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"参数错误"`)); expect(targetSnippet.originalCode).toBe(`"参数错误"`); expect(targetSnippet.convertedCode).toBe(`"参数错误"`); expect(targetSnippet.literals).toEqual([ '参数错误' ]); expect(targetSnippet.isChanged).toBe(false); }); // 测试处理C# `return` 语句中的字符串表达式, 提取的字符串表达式中不需要包含 `return` 关键字, 只需要包含字符串表达式 test('should handle return statement 2', () => { const code = `return "";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`""`)); expect(targetSnippet.originalCode).toBe(`""`); expect(targetSnippet.convertedCode).toBe(`""`); expect(targetSnippet.literals).toEqual([ '' ]); expect(targetSnippet.isChanged).toBe(false); }); // 测试处理C# `return` 语句中的字符串表达式, 提取的字符串表达式中不需要包含 `return` 关键字, 只需要包含字符串表达式 test('should handle return statement 3', () => { const code = `return $"";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$""`)); expect(targetSnippet.originalCode).toBe(`$""`); expect(targetSnippet.convertedCode).toBe(`$""`); expect(targetSnippet.literals).toEqual([ '' ]); expect(targetSnippet.isChanged).toBe(false); }); // 测试处理C# `return` 语句中的字符串表达式, 提取的字符串表达式中不需要包含 `return` 关键字, 只需要包含字符串表达式 test('should handle return statement 4', () => { const code = `return $"fwefwe";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"fwefwe"`)); expect(targetSnippet.originalCode).toBe(`$"fwefwe"`); expect(targetSnippet.convertedCode).toBe(`$"fwefwe"`); expect(targetSnippet.literals).toEqual([ 'fwefwe' ]); expect(targetSnippet.isChanged).toBe(false); }); // 测试处理C# `return` 语句中的字符串表达式, 提取的字符串表达式中不需要包含 `return` 关键字, 只需要包含字符串表达式 test('should handle return statement 5', () => { const code = `return $"wefwf{fwef}";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"wefwf{fwef}"`)); expect(targetSnippet.originalCode).toBe(`$"wefwf{fwef}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("wefwf{0}", fwef)`); expect(targetSnippet.literals).toEqual([ 'wefwf{0}' ]); expect(targetSnippet.isChanged).toBe(true); }); // 测试处理C#字符串表达式中包含各种特殊字符的情况 test('should handle special characters in string expression 1', () => { const code = `return "abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+{}|:\\"<>?1234567890\\\\";`; const snippets = extractor.extractStrings(code); let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+{}|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+{}|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+{}|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ 'abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+{}|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); }); // 测试处理在C#类成员初始赋值语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle member initial assignment', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"\\\\"`)); expect(targetSnippet.originalCode).toBe(`"\\\\"`); expect(targetSnippet.convertedCode).toBe(`"\\\\"`); expect(targetSnippet.literals).toEqual([ '\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[1]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"\\n"`)); expect(targetSnippet.originalCode).toBe(`"\\n"`); expect(targetSnippet.convertedCode).toBe(`"\\n"`); expect(targetSnippet.literals).toEqual([ '\\n' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[2]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"\\t"`)); expect(targetSnippet.originalCode).toBe(`$"\\t"`); expect(targetSnippet.convertedCode).toBe(`$"\\t"`); expect(targetSnippet.literals).toEqual([ '\\t' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理在C#各种语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle special characters in string expression 3', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[3]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[4]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"2. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"2. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"2. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '2. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[6]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"4. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"4. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"4. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '4. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理在C#各种语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle special characters in string expression 3.1', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[3]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '1. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理在C#各种语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle special characters in string expression of return sentence', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[7]; // 需要统一匹配 `return` 语句中的字符串表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"5. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"5. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"5. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '5. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理在C#各种语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle special characters in string expression 3.2', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[8]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"6. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"6. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"6. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '6. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[9]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"7. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"7. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"7. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '7. {}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理在C#各种语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#字符串边界识别和捕获 test('should handle special characters in string expression 4', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[5]; // 需要支持内插字符串中的特殊转义字符组合: `{{` 表示 `{`, `}}` 表示 `}`, 但是 `{{`和`}}` 从内插字符串转 `Tr.Format(...)` 形式时, 不需要转为 `{`和`}`, `{xxx}` 表示内插表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"3. {wef}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`$"3. {wef}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("3. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\", wef)`); expect(targetSnippet.literals).toEqual([ '3. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 测试处理在C#各种形式语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#内插字符串边界识别和捕获 test('should handle special characters in string expression 5', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[10]; // 需要支持内插字符串中的特殊转义字符组合: `{{` 表示 `{`, `}}` 表示 `}`, 但是 `{{`和`}}` 从内插字符串转 `Tr.Format(...)` 形式时, 不需要转为 `{`和`}`, `{xxx}` 表示内插表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"8. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`$"8. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("8. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\", wefff)`); expect(targetSnippet.literals).toEqual([ '8. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 测试处理在C#各种形式语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#内插字符串边界识别和捕获 test('should handle special characters in string expression 6', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[11]; // 需要支持内插字符串中的特殊转义字符组合: `{{` 表示 `{`, `}}` 表示 `}`, 但是 `{{`和`}}` 从内插字符串转 `Tr.Format(...)` 形式时, 不需要转为 `{`和`}`, `{xxx}` 表示内插表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"9. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`$"9. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("9. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\", wefff)`); expect(targetSnippet.literals).toEqual([ '9. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 测试处理在C#各种形式语句中, 字符串表达式中包含各种特殊字符的情况, 工具中需要统一处理C#内插字符串边界识别和捕获 test('should handle special characters in string expression 7', () => { const code = readFileSync("test/TestSpecialString.cs", "utf8"); const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[12]; // 需要支持内插字符串中的特殊转义字符组合: `{{` 表示 `{`, `}}` 表示 `}`, 但是 `{{`和`}}` 从内插字符串转 `Tr.Format(...)` 形式时, 不需要转为 `{`和`}`, `{xxx}` 表示内插表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"10. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`$"10. {wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); // 需要统一匹配 `return` 语句中的字符串表达式 expect(targetSnippet.convertedCode).toBe(`Tr.Format("10. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\", wefff)`); expect(targetSnippet.literals).toEqual([ '10. {0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(true); } }); test('should handle optional value assignment', () => { const code = `m_text_otherName.text = battleModel.TeammatePlayerData?.Name ?? "";`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`battleModel.TeammatePlayerData?.Name ?? ""`)); expect(targetSnippet.originalCode).toBe(`battleModel.TeammatePlayerData?.Name ?? ""`); expect(targetSnippet.convertedCode).toBe(`battleModel.TeammatePlayerData?.Name ?? "".TR()`); expect(targetSnippet.literals).toEqual([ '' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 1', () => { const code = ` protected override void OnShow(object param) { // Log.Info("AAA:ChooseSutraLayer显示1"); base.OnShow(param); }`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"AAA:ChooseSutraLayer显示1"`)); expect(targetSnippet.originalCode).toBe(`"AAA:ChooseSutraLayer显示1"`); expect(targetSnippet.convertedCode).toBe(`"AAA:ChooseSutraLayer显示1"`); expect(targetSnippet.literals).toEqual([ 'AAA:ChooseSutraLayer显示1' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 2', () => { const code = ` protected override void OnShow(object param) { // Log.Info($"BBB:Jowikwf"); base.OnShow(param); }`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"BBB:Jowikwf"`)); expect(targetSnippet.originalCode).toBe(`$"BBB:Jowikwf"`); expect(targetSnippet.convertedCode).toBe(`$"BBB:Jowikwf"`); expect(targetSnippet.literals).toEqual([ 'BBB:Jowikwf' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 3', () => { const code = ` protected override void OnShow(object param) { // Log.Info($"BBB:{wlek}Jowikwf"); base.OnShow(param); }`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"BBB:{wlek}Jowikwf"`)); expect(targetSnippet.originalCode).toBe(`$"BBB:{wlek}Jowikwf"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("BBB:{0}Jowikwf", wlek)`); expect(targetSnippet.literals).toEqual([ 'BBB:{0}Jowikwf' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 4', () => { const code = ` protected override void OnShow(object param) { // Log.Info("{}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"); base.OnShow(param); }`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"{}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`"{}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`"{}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.literals).toEqual([ '{}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 5', () => { const code = ` protected override void OnShow(object param) { // Log.Info($"{wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"); base.OnShow(param); }`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"{wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`)); expect(targetSnippet.originalCode).toBe(`$"{wefff}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\", wefff)`); expect(targetSnippet.literals).toEqual([ '{0}{{}}abcdefghijklmnopqrstuvwxyz\`-=[];\',./~!@#$%^&*()_+|:\\"<>?1234567890\\\\' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle complex string 1', () => { const code = ` infoStr += $"伤害类型: [color={colorCode}][b]{_damageInfo.DamageType}[/b][/color]\n"; infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n"; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"伤害类型: [color={colorCode}][b]{_damageInfo.DamageType}[/b][/color]\n"`)); expect(targetSnippet.originalCode).toBe(`$"伤害类型: [color={colorCode}][b]{_damageInfo.DamageType}[/b][/color]\n"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType)`); expect(targetSnippet.literals).toEqual([ '伤害类型: [color={0}][b]{1}[/b][/color]\n' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle complex string 2', () => { const code = ` infoStr += Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType); infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n"; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("伤害类型: [color={0}][b]{1}[/b][/color]\n", colorCode, _damageInfo.DamageType)`); expect(targetSnippet.literals).toEqual([ '伤害类型: [color={0}][b]{1}[/b][/color]\n' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C# 字符串表达式中递归内嵌字符串表达式的情况: 需要正确转换内插字符串格式 test('should handle fix interpolated string nested literals', () => { const code = `infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n";`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n"`)); expect(targetSnippet.originalCode).toBe(`$"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n"`); // 需要正确转换内插字符串格式 expect(targetSnippet.convertedCode).toBe(`Tr.Format("是否暴击: [b]{0}[/b]\n", (_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否"))`); expect(targetSnippet.literals).toEqual([ '是否暴击: [b]{0}[/b]\n' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle complex string with optional value 3', () => { const code = ` else if (battleModel.CurGameType == BattleModel.GameType.Teamwork) { //合作模式显示队友名称 m_text_otherName.text = battleModel.TeammatePlayerData?.Name ?? ""; } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`battleModel.TeammatePlayerData?.Name ?? ""`)); expect(targetSnippet.originalCode).toBe(`battleModel.TeammatePlayerData?.Name ?? ""`); expect(targetSnippet.convertedCode).toBe(`battleModel.TeammatePlayerData?.Name ?? "".TR()`); expect(targetSnippet.literals).toEqual([ '' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle complex string 4', () => { const code = ` private void OnBtnIaa(EventContext context) { var iaaConfig = IAAConfigTable.GetConfigById((int)IAAId.境界失败_加速冷却); if (iaaConfig == null) { return; } string des = $"观看视频后\n等待时间减少{iaaConfig.EffectValue / 60}分钟"; this.GetModel().ShowAdWithDialog(IAAId.境界失败_加速冷却, "加速时间", des, null); } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"观看视频后\n等待时间减少{iaaConfig.EffectValue / 60}分钟"`)); expect(targetSnippet.originalCode).toBe(`$"观看视频后\n等待时间减少{iaaConfig.EffectValue / 60}分钟"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("观看视频后\n等待时间减少{0}分钟", iaaConfig.EffectValue / 60)`); expect(targetSnippet.literals).toEqual([ '观看视频后\n等待时间减少{0}分钟' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 需要正确识别函数调用参数中每个参数的边界, 并识别每个参数中的字符串表达式 test('should handle strings in function call arguments', () => { const code = ` private void OnBtnIaa(EventContext context) { var iaaConfig = IAAConfigTable.GetConfigById((int)IAAId.境界失败_加速冷却); if (iaaConfig == null) { return; } string des = $"观看视频后\n等待时间减少{iaaConfig.EffectValue / 60}分钟"; this.GetModel().ShowAdWithDialog(IAAId.境界失败_加速冷却, "加速时间", des, null); } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[1]; // 需要正确识别函数调用参数中每个参数的边界, 并识别每个参数中的字符串表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"加速时间"`)); expect(targetSnippet.originalCode).toBe(`"加速时间"`); expect(targetSnippet.convertedCode).toBe(`"加速时间"`); expect(targetSnippet.literals).toEqual([ '加速时间' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C#注释中的字符串表达式 - 同样需要从字符串中提取字符串表达式信息 test('should handle string in comment 5', () => { const code = ` m_text_percent.text = $"{curGoodsConfig.RebateRate}%"; m_name_holder.url = FUISys.Instance.GetIconUrl(_curShopDetail.ShopConfig.Name); m_thumbnail_holder.url = FUISys.Instance.GetIconUrl(_curShopDetail.ShopConfig.Thumbnail); m_btn_buy.text = $"¥ {curGoodsConfig.Price}"; m_text_timeleft.text = curShop.TimeLimitData.RemainTime().TR() + "后消失".TR(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[2]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`curShop.TimeLimitData.RemainTime().TR() + "后消失".TR()`)); expect(targetSnippet.originalCode).toBe(`curShop.TimeLimitData.RemainTime().TR() + "后消失".TR()`); expect(targetSnippet.convertedCode).toBe(`curShop.TimeLimitData.RemainTime().TR() + "后消失".TR()`); expect(targetSnippet.literals).toEqual([ '后消失' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 正确处理C# `switch` 表达式形式中的字符串表达式 test('should handle switch expression string', () => { const code = ` case (int)State.Lock: Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"); break; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`)); expect(targetSnippet.originalCode).toBe(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("灵田{0}级解锁", _model.GetFarmLandUnlockLevel(_landId))`); expect(targetSnippet.literals).toEqual([ '灵田{0}级解锁' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确识别C# 行注释语句边界, 行注释不能影响上下行的字符串提取 test('should handle comment boundary 1', () => { const code = ` //上锁 case (int)State.Lock: Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"); break; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`)); expect(targetSnippet.originalCode).toBe(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("灵田{0}级解锁", _model.GetFarmLandUnlockLevel(_landId))`); expect(targetSnippet.literals).toEqual([ '灵田{0}级解锁' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确识别C# 行注释语句边界, 行注释不能影响上下行的字符串提取 test('should handle comment boundary 2', () => { const code = ` // klwjfe namespace TestNamespace{ // lkwjfe public class FK{ // klwf public void TestM(){ // 模拟状态变量 var _state = (int)State.Lock; // 模拟土地ID变量 switch (_state){ //上锁 case (int)State.Lock: // lkwjefl Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"); // klwejf break; // lkwjf } // klwjfel } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`)); expect(targetSnippet.originalCode).toBe(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("灵田{0}级解锁", _model.GetFarmLandUnlockLevel(_landId))`); expect(targetSnippet.literals).toEqual([ '灵田{0}级解锁' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确识别C# 行注释语句边界, 行注释不能影响上下行的字符串提取 test('should handle comment boundary 3', () => { const code = ` // klwjfe namespace TestNamespace{ // lkwjfe public class FK{ // wlkjelj private int Llkwe = 23; // lkjlkj private int Dwekl { get; set; } = 23; // jklwef private static int Dwekl { get; set; } = 23; // lkwjeflk public static void TestM(){} // wesd public static void TestM() { } // wesd public static void TestM(){ } // klwf public void TestM(){ // 模拟状态变量 var _state = (int)State.Lock; // 模拟土地ID变量 switch (_state){ //上锁 case (int)State.Lock: // lkwjefl Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"); // klwejf break; // lkwjf } // klwjfel } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`)); expect(targetSnippet.originalCode).toBe(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("灵田{0}级解锁", _model.GetFarmLandUnlockLevel(_landId))`); expect(targetSnippet.literals).toEqual([ '灵田{0}级解锁' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确识别C#文档注释边界, 类、方法、类成员的文档注释需要直接忽略捕获, 不参与字符串表达式提取; 文档注释通常位于类、方法、类成员的定义上方, 文档注释的格式形如: ` /// yyy\n /// ` 或 ` /// `, 其中xxx为标签, yyy为注释内容, ``和`` 不一定在同一行或不同行, ` /// yyy\n` 不一定只有一行; 文档注释通常以``标签开始, 以``标签结束, 或者以 `` 结束。 test('should handle doc comment boundary for class/method/members', () => { const code = ` /// /// "fwef 类注释1" /// $"fwef 类注释2" /// $"fwef 类注释2 {wef}" /// wef $"fwef 类注释2 {wef}" wef /// /// public partial class TimeLimitedChargeDialog { /// /// $" Property成员注释 dsvdf {wlpoe}" /// private int Ljfw { get; set; } = 342; /// /// $" Field成员注释 wfewe {vvwe}" /// $" Field成员注释 wfewe {vvwe}" wfe /// private int Ljfw = 342; /// /// private int Ljfw = 342; /// /// " wef方法注释 wef" /// $" wef方法注释 wef" /// $" wef方法注释 wef{wef}" /// wfe $" wef方法注释 wef{wef}" /// wfe $" wef方法注释 wef{wef}" wef ///wfe $" wef方法注释 wef{wef}" wef /// wefw $" wef方法注释 wef{wef}" we /// /// /// 是否重新创建页签列表 private void RefreshGoodsInfo() { m_text_timeleft.text = curShop.TimeLimitData.RemainTime() + "后消失"; } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`curShop.TimeLimitData.RemainTime() + "后消失"`)); expect(targetSnippet.originalCode).toBe(`curShop.TimeLimitData.RemainTime() + "后消失"`); expect(targetSnippet.convertedCode).toBe(`curShop.TimeLimitData.RemainTime().TR() + "后消失".TR()`); expect(targetSnippet.literals).toEqual([ '后消失' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 正确识别C#方法参数中的字符串表达式, 支持1到多个形参有默认值的情形 test('should handle string expression in method call parameter 1', () => { const code = ` namespace FaBao.UI.Comp { public partial class ItemCommonComp { public void BindData(bool needCompare = false) { m_text_level.text = Tr.Format("{0}阶", _itemConfig.PillLevel); } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("{0}阶", _itemConfig.PillLevel)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.literals).toEqual([ '{0}阶' ]); expect(targetSnippet.isChanged).toBe(false); } expect(snippets.length).toBe(1); }); // 正确识别C#方法参数中的字符串表达式, 支持1到多个形参有默认值的情形 test('should handle string expression in method call parameter 2', () => { const code = ` namespace FaBao.UI.Comp { public partial class ItemCommonComp { public void BindData(bool needCompare = false, long needCompare2 = 23, float needCompare2 = 324.0) { m_text_level.text = Tr.Format("{0}阶", _itemConfig.PillLevel); } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("{0}阶", _itemConfig.PillLevel)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.literals).toEqual([ '{0}阶' ]); expect(targetSnippet.isChanged).toBe(false); } expect(snippets.length).toBe(1); }); // 正确识别C#方法参数中的字符串表达式, 支持1到多个形参有默认值的情形 test('should handle string expression in method call parameter 3', () => { const code = ` namespace FaBao.UI.Comp { public partial class ItemCommonComp { public void BindData(string wkle, bool needCompare = false, int needCompare2 = false) { m_text_level.text = Tr.Format("{0}阶", _itemConfig.PillLevel); } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("{0}阶", _itemConfig.PillLevel)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.literals).toEqual([ '{0}阶' ]); expect(targetSnippet.isChanged).toBe(false); } expect(snippets.length).toBe(1); }); // 正确提取C#方法参数中的字符串表达式信息, 支持1到多个形参有默认值的情形 test('should handle string expression in method call parameter 4', () => { const code = ` namespace FaBao.UI.Comp { public partial class ItemCommonComp { public void BindData(string wkle = "11111", bool needCompare = false, int needCompare2 = false) { } public void BindData(string wkle = "22", string wkle = "werwer", bool needCompare = false, string wkle = "wefwewef31f", int needCompare2 = false, string wkle = "df223") { } public void BindData(string wkle = "(*&%\\\\*(@Fwfe))&(") { } public void BindData(string we, string wkle = "aaaaa", bool needCompare = false, int needCompare2 = false) { m_text_level.text = Tr.Format("{0}阶", _itemConfig.PillLevel); } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"11111"`)); expect(targetSnippet.originalCode).toBe(`"11111"`); expect(targetSnippet.convertedCode).toBe(`"11111"`); expect(targetSnippet.literals).toEqual([ '11111' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[1]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"22"`)); expect(targetSnippet.originalCode).toBe(`"22"`); expect(targetSnippet.convertedCode).toBe(`"22"`); expect(targetSnippet.literals).toEqual([ '22' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[2]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"werwer"`)); expect(targetSnippet.originalCode).toBe(`"werwer"`); expect(targetSnippet.convertedCode).toBe(`"werwer"`); expect(targetSnippet.literals).toEqual([ 'werwer' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[3]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"wefwewef31f"`)); expect(targetSnippet.originalCode).toBe(`"wefwewef31f"`); expect(targetSnippet.convertedCode).toBe(`"wefwewef31f"`); expect(targetSnippet.literals).toEqual([ 'wefwewef31f' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[4]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"df223"`)); expect(targetSnippet.originalCode).toBe(`"df223"`); expect(targetSnippet.convertedCode).toBe(`"df223"`); expect(targetSnippet.literals).toEqual([ 'df223' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[5]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"(*&%\\\\*(@Fwfe))&("`)); expect(targetSnippet.originalCode).toBe(`"(*&%\\\\*(@Fwfe))&("`); expect(targetSnippet.convertedCode).toBe(`"(*&%\\\\*(@Fwfe))&("`); expect(targetSnippet.literals).toEqual([ '(*&%\\\\*(@Fwfe))&(' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[6]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"aaaaa"`)); expect(targetSnippet.originalCode).toBe(`"aaaaa"`); expect(targetSnippet.convertedCode).toBe(`"aaaaa"`); expect(targetSnippet.literals).toEqual([ 'aaaaa' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[7]; // 类注释和方法注释不参与字符串表达式提取, 不影响字符串提取结果 expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("{0}阶", _itemConfig.PillLevel)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}阶", _itemConfig.PillLevel)`); expect(targetSnippet.literals).toEqual([ '{0}阶' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 需要正确识别C#代码中 `if` 语句边界 和 创建对象表达式的边界, 识别 `if` 语句前、中、后部分中的字符串表达式 test('should handle if border and object creation expression border', () => { const code = ` namespace FaBao.UI.MainUI { public partial class SpellUpgradeDialog { private async void OnBtnUpgrade() { // new SpellCard_Action_UpgradeSpellCard(_symbolCard.Id) 被方法调用传参的 () 包裹, 因此已经构成了完整的对象创建表达式 if (await this.GetModel().DispatchAction(new SpellCard_Action_UpgradeSpellCard(_symbolCard.Id))) { //升级成功后刷新界面 m_text_level.text = Tr.Format("{0}级", _symbolCard.Level); } } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 需要正确识别 `if` 语句边界, 识别 `if` 语句前、中、后部分中的字符串表达式 expect(targetSnippet.originalIndex).toBe(code.indexOf(`Tr.Format("{0}级", _symbolCard.Level)`)); expect(targetSnippet.originalCode).toBe(`Tr.Format("{0}级", _symbolCard.Level)`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}级", _symbolCard.Level)`); expect(targetSnippet.literals).toEqual([ '{0}级' ]); expect(targetSnippet.isChanged).toBe(false); } expect(snippets.length).toBe(1); }); // 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现. test('should handle class member initialization assignment with anonymous function 1', () => { const code = ` FUISys.Instance.ShowLayer(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam() { Title = "提示", ConfirmCallback = async () => { //灵纹一键下阵 if (await this.GetModel().DispatchAction(new Rune_Action_CleanRune(_sutraCardData.Card.Id))) { Toast.Info("卸载成功"); } }, CancelText = "取消", }).Forget(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"提示"`)); expect(targetSnippet.originalCode).toBe(`"提示"`); expect(targetSnippet.convertedCode).toBe(`"提示"`); expect(targetSnippet.literals).toEqual([ '提示' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[1]; // 需要正确识别C#代码中类成员初始化赋值时, 给成员赋值匿名函数时, 匿名函数中的字符串表达式也需要提取 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"卸载成功"`)); expect(targetSnippet.originalCode).toBe(`"卸载成功"`); expect(targetSnippet.convertedCode).toBe(`"卸载成功"`); expect(targetSnippet.literals).toEqual([ '卸载成功' ]); expect(targetSnippet.isChanged).toBe(false); } { let targetSnippet = snippets[2]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`"取消"`)); expect(targetSnippet.originalCode).toBe(`"取消"`); expect(targetSnippet.convertedCode).toBe(`"取消"`); expect(targetSnippet.literals).toEqual([ '取消' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现. test('should handle class member initialization assignment with anonymous function 2', () => { const code = ` FUISys.Instance.ShowLayer(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam() { ConfirmCallback = async () => { Toast.Info("卸载成功"); }, }).Forget(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 需要正确识别C#代码中类成员初始化赋值时, 给成员赋值匿名函数时, 匿名函数中的字符串表达式也需要提取 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"卸载成功"`)); expect(targetSnippet.originalCode).toBe(`"卸载成功"`); expect(targetSnippet.convertedCode).toBe(`"卸载成功"`); expect(targetSnippet.literals).toEqual([ '卸载成功' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现. test('should handle class member initialization assignment with anonymous function 3', () => { const code = ` FUISys.Instance.ShowLayer(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam() { ConfirmCallback = (int qwfewe) => { Toast.Info($"卸载成功: {qwfewe}"); }, }).Forget(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 需要正确识别C#代码中类成员初始化赋值时, 给成员赋值匿名函数时, 匿名函数中的字符串表达式也需要提取 expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"卸载成功: {qwfewe}"`)); expect(targetSnippet.originalCode).toBe(`$"卸载成功: {qwfewe}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("卸载成功: {0}", qwfewe)`); expect(targetSnippet.literals).toEqual([ '卸载成功: {0}' ]); expect(targetSnippet.isChanged).toBe(true); } }); // 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现. test('should handle class member initialization assignment with anonymous function 4', () => { const code = ` FUISys.Instance.ShowLayer(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam() { ConfirmCallback = async () => Toast.Info("卸载成功"), }).Forget(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 需要正确识别C#代码中类成员初始化赋值时, 给成员赋值匿名函数时, 匿名函数中的字符串表达式也需要提取 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"卸载成功"`)); expect(targetSnippet.originalCode).toBe(`"卸载成功"`); expect(targetSnippet.convertedCode).toBe(`"卸载成功"`); expect(targetSnippet.literals).toEqual([ '卸载成功' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 需要正确识别C#代码中的匿名函数, 匿名函数中的字符串表达式也同样需要提取; 匿名函数通常以 `0到多个修饰词 (可选参数列表) => { ... }` 或者 `0到多个修饰词 (可选参数列表) => 一句表达式` 形式定义实现. test('should handle class member initialization assignment with anonymous function 5', () => { const code = ` FUISys.Instance.ShowLayer(UISceneType.Main, new MainConfirmDialog.MainConfirmDialogParam() { ConfirmCallback = () => Toast.Info("卸载成功"), }).Forget(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; // 需要正确识别C#代码中类成员初始化赋值时, 给成员赋值匿名函数时, 匿名函数中的字符串表达式也需要提取 expect(targetSnippet.originalIndex).toBe(code.indexOf(`"卸载成功"`)); expect(targetSnippet.originalCode).toBe(`"卸载成功"`); expect(targetSnippet.convertedCode).toBe(`"卸载成功"`); expect(targetSnippet.literals).toEqual([ '卸载成功' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。 test('should handle .text = format 1', () => { const code = `m_text_iwff.text = info ;`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`info`)); expect(targetSnippet.originalCode).toBe(`info`); expect(targetSnippet.convertedCode).toBe(`info.TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } }); // 测试处理 `xxx.text = yyy;` 形式赋值语句, 以 `.text =` 给 `text` 成员赋值的表达式中, `yyy` 部分必定是字符串表达式; 如果`yyy`中不存在字符串, 则`yyy` 中以 `+` 连接的表达式需要加上 `.TR()`; 如果 `yyy` 是作为值表达式整体(必定返回字符串类型值), 也需要追加 `.TR()`。 test('should handle .text = format 2', () => { const code = `m_text_info.text = info1.member1 + info2.member2;`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`info1.member1 + info2.member2`)); expect(targetSnippet.originalCode).toBe(`info1.member1 + info2.member2`); expect(targetSnippet.convertedCode).toBe(`info1.member1.TR() + info2.member2.TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } }); // 处理字符串出现在赋值表达式的 `=` 左边(左值表达式)的情况 test('should handle right value expression', () => { const code = ` ((GLoader)GetChild($"m_loader_dailyItem{i + 1}")).url = FUISys.Instance.GetIconUrl(ItemTable.GetConfigById(_cardConfig.EveryDayAwards[i].ResId).Icon); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"m_loader_dailyItem{i + 1}"`)); expect(targetSnippet.originalCode).toBe(`$"m_loader_dailyItem{i + 1}"`); // 左边表达式函数调用参数中的字符串表达式 expect(targetSnippet.convertedCode).toBe(`Tr.Format("m_loader_dailyItem{0}", i + 1)`); expect(targetSnippet.literals).toEqual([ `m_loader_dailyItem{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 支持处理字符串里出现各种特殊符号的情况 test('should handle special characters in string 8', () => { const code = ` m_btn_paid.text = $"{paidConfig.Quantity}"; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"{paidConfig.Quantity}"`)); expect(targetSnippet.originalCode).toBe(`$"{paidConfig.Quantity}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("{0}", paidConfig.Quantity)`); expect(targetSnippet.literals).toEqual([ `{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 处理赋值表达式 `=` 左右两边(左值表达式和右值表达式)都存在字符串的情况 test('should handle assignment expression with both sides strings', () => { const code = ` ((GTextField)GetChild($"m_text_dailyCount{i + 1}")).text = $"x{_cardConfig.EveryDayAwards[i].Count}"; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"m_text_dailyCount{i + 1}"`)); expect(targetSnippet.originalCode).toBe(`$"m_text_dailyCount{i + 1}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("m_text_dailyCount{0}", i + 1)`); expect(targetSnippet.literals).toEqual([ `m_text_dailyCount{0}` ]); expect(targetSnippet.isChanged).toBe(true); } { let targetSnippet = snippets[1]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"x{_cardConfig.EveryDayAwards[i].Count}"`)); expect(targetSnippet.originalCode).toBe(`$"x{_cardConfig.EveryDayAwards[i].Count}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("x{0}", _cardConfig.EveryDayAwards[i].Count)`); expect(targetSnippet.literals).toEqual([ `x{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(2); }); // 支持处理字符串里出现各种特殊符号的情况 test('should handle special characters in string 9', () => { const code = ` (GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"m_textAnime{GetNextNumber()}"`)); expect(targetSnippet.originalCode).toBe(`$"m_textAnime{GetNextNumber()}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("m_textAnime{0}", GetNextNumber())`); expect(targetSnippet.literals).toEqual([ `m_textAnime{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 支持处理字符串里出现各种特殊符号的情况 test('should handle special characters in string 10', () => { const code = ` (GetChild($"m_textAnime{GetNextNumber()}") as TurtleTextAnimeComp)?.SetText(word); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"m_textAnime{GetNextNumber()}"`)); expect(targetSnippet.originalCode).toBe(`$"m_textAnime{GetNextNumber()}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("m_textAnime{0}", GetNextNumber())`); expect(targetSnippet.literals).toEqual([ `m_textAnime{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 修正字符串表达式捕获范围 test('should handle string expression capture range 1', () => { const code = ` (GetChild($"vwvwe{index + 1}") as LargeLotterySpellCardComp)?.InitData(spellCardId, true); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"vwvwe{index + 1}"`)); expect(targetSnippet.originalCode).toBe(`$"vwvwe{index + 1}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("vwvwe{0}", index + 1)`); expect(targetSnippet.literals).toEqual([ `vwvwe{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 修正字符串表达式捕获范围 test('should handle string expression capture range 2', () => { const code = ` (GetChild($"brrr_{index + 1}") as LargeLotterySpellCardComp)?.InitData(spellCardId, true); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"brrr_{index + 1}"`)); expect(targetSnippet.originalCode).toBe(`$"brrr_{index + 1}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("brrr_{0}", index + 1)`); expect(targetSnippet.literals).toEqual([ `brrr_{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 修正字符串表达式捕获范围 test('should handle string expression capture range 3', () => { const code = ` (GetChild($"m_card_{index}") as LargeLotterySpellCardComp)?.PlayEff(); `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`$"m_card_{index}"`)); expect(targetSnippet.originalCode).toBe(`$"m_card_{index}"`); expect(targetSnippet.convertedCode).toBe(`Tr.Format("m_card_{0}", index)`); expect(targetSnippet.literals).toEqual([ `m_card_{0}` ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(1); }); // 字符串表达式字面量直接为null时, 无需转换null test('should handle null 1', () => { const code = `m_text_notice.text = null;`; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`null`)); expect(targetSnippet.originalCode).toBe(`null`); expect(targetSnippet.convertedCode).toBe(`null`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(false); } expect(snippets.length).toBe(1); }); // 字符串表达式字面量直接为null时, 无需转换null test('should handle null 2', () => { const code = `m_text_notice.wfefe = null;`; const snippets = extractor.extractStrings(code); expect(snippets.length).toBe(0); }); // 需要正确获取 `originalIndex`, `originalIndex` 的定义为 `originalIndex=code.indexOf(originalCode)`, 其中 `code` 表示原始代码, `originalCode` 表示匹配出的原始代码中的字符串表达式 test('should handle calculate originalIndex', () => { const code = ` namespace FaBao { public partial class UserInfoModel { public bool HasJoinedGuild() { return !string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"; } public void UpdateBargain(BargainShopData bargainShopData) { MyGuildInfo.IsBargainShopBargain = true; } } } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`); expect(targetSnippet.originalIndex).toBe(code.indexOf(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`)); expect(targetSnippet.convertedCode).toBe(`!string.IsNullOrEmpty(MyGuildInfo.GuildId) && MyGuildInfo.GuildId != "0"`); expect(targetSnippet.literals).toEqual([ '0' ]); expect(targetSnippet.isChanged).toBe(false); } }); // 测试 `xxx.text = yyy;` 和 `m_btn_xxx.title = yyy;` 形式的赋值语句都需要支持中文, 包括字符串表达式中也要支持包含中文的情况 test('should handle .text = assignments contains chinese', () => { const code = ` 为栓饭cxs12.text = 南方eflkj.d文件 + "完善ew"; m_btn_为栓饭cxs12.title = bw尅kljekl.完善 + "栏栏nfc"; `; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('南方eflkj.d文件 + "完善ew"'); expect(snippet.convertedCode).toBe('南方eflkj.d文件.TR() + "完善ew".TR()'); expect(snippet.literals).toEqual([ '完善ew' ]); } { let snippet = snippets[1]; expect(snippet.originalCode).toBe('bw尅kljekl.完善 + "栏栏nfc"'); expect(snippet.convertedCode).toBe('bw尅kljekl.完善.TR() + "栏栏nfc".TR()'); expect(snippet.literals).toEqual([ '栏栏nfc' ]); } }); // 测试`m_btn_xxx.title = yyy;` 形式的赋值语句要和`xxx.text = yyy;`形式的赋值语句完全等同处理 test('should handle m_btn_xxx.title = yyy assignments contains chinese', () => { const code = ` switch (_roomData.RoomState) { case Data.Match.RoomState.IsDismiss: m_btn_join.title = "已解散"; m_btn_join.enabled = false; grayed = true; break; case Data.Match.RoomState.Rejected: { m_btn_join.title = $"被拒绝{Kff}"; m_btn_join.enabled = false; break; } case Data.Match.RoomState.OutOfDate: m_dd_join.text = "已到期"; break; default: m_btn_join.title = "申请"; m_btn_joi23n.title = $"申请{Kjf}"; break; } `; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('"已解散"'); expect(snippet.convertedCode).toBe('"已解散".TR()'); expect(snippet.literals).toEqual([ '已解散' ]); } { let snippet = snippets[1]; expect(snippet.originalCode).toBe('$"被拒绝{Kff}"'); expect(snippet.convertedCode).toBe('Tr.Format("被拒绝{0}", Kff)'); expect(snippet.literals).toEqual([ '被拒绝{0}' ]); } { let snippet = snippets[2]; expect(snippet.originalCode).toBe('"已到期"'); expect(snippet.convertedCode).toBe('"已到期".TR()'); expect(snippet.literals).toEqual([ '已到期' ]); } { let snippet = snippets[3]; expect(snippet.originalCode).toBe('"申请"'); expect(snippet.convertedCode).toBe('"申请".TR()'); expect(snippet.literals).toEqual([ '申请' ]); } { let snippet = snippets[4]; expect(snippet.originalCode).toBe('$"申请{Kjf}"'); expect(snippet.convertedCode).toBe('Tr.Format("申请{0}", Kjf)'); expect(snippet.literals).toEqual([ '申请{0}' ]); } }); // 测试三元表达式中的字符串表达式处理 test('should handle thriple expression with string expression 1', () => { const code = `m_btn_confirm.title = isUsed ? "使用中" : "使用";`; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.convertedCode).toBe('isUsed ? "使用中".TR() : "使用".TR()'); expect(snippet.literals).toEqual([ '使用中', '使用' ]); } }); // 测试三元表达式中的字符串表达式处理 test('should handle thriple expression with string expression 2', () => { const code = `wefe.text = isUsed ? "使用中" : "使用";`; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.convertedCode).toBe('isUsed ? "使用中".TR() : "使用".TR()'); expect(snippet.literals).toEqual([ '使用中', '使用' ]); } }); // 测试三元表达式中的字符串表达式处理 test('should handle thriple expression with string expression 3', () => { const code = `wefetext = isUsed ? "使用中" : "使用";`; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.convertedCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.literals).toEqual([ '使用中', '使用' ]); } }); // 测试三元表达式中的字符串表达式处理 test('should handle thriple expression with string expression 4', () => { const code = `wefetext(isUsed ? "使用中" : "使用");`; const snippets = extractor.extractStrings(code); { let snippet = snippets[0]; expect(snippet.originalCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.convertedCode).toBe('isUsed ? "使用中" : "使用"'); expect(snippet.literals).toEqual([ '使用中', '使用' ]); } }); // 测试if语句有花括号包围的单句表达式语句中的字符串表达式处理 test('should handle assignment expression within if{}else{}', () => { const code = ` if (_eventIdx < _table.MainDescription.Length) { m_text_desc.text = _table.MainDescription[_eventIdx]; } else { m_text_desc.text = _table.MainDescription[0]; } `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[_eventIdx]`)); expect(targetSnippet.originalCode).toBe(`_table.MainDescription[_eventIdx]`); expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[_eventIdx].TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } { let targetSnippet = snippets[1]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[0]`)); expect(targetSnippet.originalCode).toBe(`_table.MainDescription[0]`); expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[0].TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(2); }) // 测试if语句无花括号包围的单句表达式语句中的字符串表达式处理 test('should handle assignment expression within if-else-end', () => { const code = ` if (_eventIdx < _table.MainDescription.Length) m_text_desc.text = _table.MainDescription[_eventIdx]; else m_text_desc.text = _table.MainDescription[0]; `; const snippets = extractor.extractStrings(code); { let targetSnippet = snippets[0]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[_eventIdx]`)); expect(targetSnippet.originalCode).toBe(`_table.MainDescription[_eventIdx]`); expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[_eventIdx].TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } { let targetSnippet = snippets[1]; expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[0]`)); expect(targetSnippet.originalCode).toBe(`_table.MainDescription[0]`); expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[0].TR()`); expect(targetSnippet.literals).toEqual([ ]); expect(targetSnippet.isChanged).toBe(true); } expect(snippets.length).toBe(2); }) // // 测试if语句无花括号包围的单句表达式语句中的字符串表达式处理 // test('should handle assignment expression within if-else-end', () => { // const code = ` // private void RefreshRedDot() // { // m_redDot.visible = _data.NeedShowRedDot(); // } // private void OnSelfClick() // { // this.GetSystem().PlayAudioByName(AudioName.SoundUI_Button); // if (!_data.IsOpen) // { // string tip = _data.Config.LockTip; // if (_data.Config.TaskIds != null && _data.Config.TaskIds.Count > 0) // { // int taskId = App.Instance.GetModel().CurGuideMissionConfig.Id; // int remainingTaskCount = _data.Config.TaskIds[0] - taskId + 1; // remainingTaskCount = Mathf.Max(0, remainingTaskCount); // tip = string.Format(_data.Config.LockTip, remainingTaskCount); // } // Toast.Info(tip); // return; // } // `; // const snippets = extractor.extractStrings(code); // { // let targetSnippet = snippets[0]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[_eventIdx]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[_eventIdx]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[_eventIdx].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // { // let targetSnippet = snippets[1]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[0]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[0]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[0].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // expect(snippets.length).toBe(2); // }) // // 测试if语句无花括号包围的单句表达式语句中的字符串表达式处理 // test('should handle assignment expression within if-else-end', () => { // const code = ` // protected override void OnCreate() // { // base.OnCreate(); // } // protected override void OnShow(object param) // { // base.OnShow(param); // var mailContent = (MailContentDialogParam)param; // if (mailContent == null) return; // var mailData = this.GetModel().GetMail(mailContent.MailId); // if (mailData == null) return; // _mailId = mailData.MailId; // m_text_title.text = mailData.Title.TR(); // m_text_name.text = $"道友如晤:".TR(); // m_text_sender.text = Tr.Format("{0}稽首", mailData.Name); // //转换时区 // // var dateTimeOffset = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeSeconds(mailData.ReceiveTime), TimeZoneInfo.Local); // m_text_time.text = Date.ConvertTimestampToTimeString(mailData.ReceiveTime, "yyyy年M月d日 HH:mm").TR(); // m_scroll_content.title = mailData.Content; // `; // const snippets = extractor.extractStrings(code); // { // let targetSnippet = snippets[0]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[_eventIdx]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[_eventIdx]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[_eventIdx].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // { // let targetSnippet = snippets[1]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[0]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[0]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[0].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // expect(snippets.length).toBe(2); // }) // // 测试if语句无花括号包围的单句表达式语句中的字符串表达式处理 // test('should handle assignment expression within if-else-end', () => { // const code = ` // public void BindData(MailData mailData) // { // if (mailData == null) return; // _mailData = mailData; // //状态 // m_hasRead.selectedIndex = _mailData.HasRead ? (int)HasRead.True : (int)HasRead.False; // m_hasFetch.selectedIndex = mailData.Rewards.Count > 0 ? (int)HasFetch.True : (int)HasFetch.False; // m_text_title.text = _mailData.Title.TR(); // m_text_subTitle.text = _mailData.SubTitle.TR(); // //显示时间 // long interval = DateTimeOffset.Now.ToUnixTimeSeconds() - mailData.ReceiveTime; // if (interval <= 0) interval = 1; // if (interval < 60) m_text_time.text = "现在"; // else if (interval < 3600) m_text_time.text = $"{interval / 60}分钟前"; // else if (interval < 86400) m_text_time.text = $"{interval / 3600}小时前"; // else if (interval < 2592000) m_text_time.text = $"{interval / 86400}天前"; // else if (interval < 31104000) m_text_time.text = $"{interval / 2592000}月前"; // else m_text_time.text = Tr.Format("{0}年前", interval / 31104000); // } // `; // const snippets = extractor.extractStrings(code); // { // let targetSnippet = snippets[0]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[_eventIdx]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[_eventIdx]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[_eventIdx].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // { // let targetSnippet = snippets[1]; // expect(targetSnippet.originalIndex).toBe(code.indexOf(`_table.MainDescription[0]`)); // expect(targetSnippet.originalCode).toBe(`_table.MainDescription[0]`); // expect(targetSnippet.convertedCode).toBe(`_table.MainDescription[0].TR()`); // expect(targetSnippet.literals).toEqual([ // ]); // expect(targetSnippet.isChanged).toBe(true); // } // expect(snippets.length).toBe(2); // }) });