scancscode 1.0.37 → 1.0.39

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