td-octopus 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-octopus",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "I18N tool",
5
5
  "author": "Anthony Li",
6
6
  "bin": {
@@ -16,7 +16,6 @@
16
16
  "baidu-translate": "^1.3.0",
17
17
  "colors": "^1.4.0",
18
18
  "eslint": "^8.15.0",
19
- "eslint-config-tongdun": "^1.1.11",
20
19
  "eslint-plugin-prettier": "^4.2.1",
21
20
  "eslint-plugin-td-rules-plugin": "^1.0.1",
22
21
  "eslint-plugin-unused-imports": "^4.3.0",
@@ -1,4 +1,3 @@
1
-
2
1
  const fs = require('fs');
3
2
  const path = require('path');
4
3
  const parser = require('@babel/parser');
@@ -9,36 +8,96 @@ const { ESLint } = require('eslint');
9
8
  const prettier = require('eslint-plugin-prettier');
10
9
  const unusedImports = require('eslint-plugin-unused-imports');
11
10
 
12
- // ===== 工具函数 =====
13
- const astNodeToString = (node) => {
11
+ // ===== 工具函数:判断 key 是否匹配指定名称 =====
12
+ function isKeyNamed(node, name) {
13
+ if (t.isIdentifier(node)) {
14
+ return node.name === name;
15
+ }
14
16
  if (t.isStringLiteral(node)) {
15
- return node.value;
17
+ return node.value === name;
16
18
  }
17
- if (t.isIdentifier(node)) {
18
- return node.name;
19
+ return false;
20
+ }
21
+
22
+ function extractCnKeyDirect(node) {
23
+ if (t.isStringLiteral(node)) {
24
+ return node.value;
19
25
  }
26
+
20
27
  if (t.isMemberExpression(node)) {
21
- const objectStr = astNodeToString(node.object);
22
- const propertyStr = astNodeToString(node.property);
23
- if (objectStr === 'I18N') {
24
- return propertyStr;
28
+ let parts = [];
29
+ let current = node;
30
+
31
+ while (t.isMemberExpression(current)) {
32
+ if (current.computed) return null;
33
+ const prop = current.property;
34
+ if (!t.isIdentifier(prop)) return null;
35
+ parts.unshift(prop.name);
36
+ current = current.object;
37
+ }
38
+
39
+ if (t.isIdentifier(current) && current.name === 'I18N') {
40
+ parts.unshift('I18N');
41
+ } else {
42
+ return null;
43
+ }
44
+
45
+ if (parts.length === 4) {
46
+ return parts.join('.');
25
47
  }
26
- return `${objectStr}.${propertyStr}`;
27
48
  }
28
- return generate(node, { concise: true, comments: false }).code;
49
+
50
+ return null;
29
51
  }
30
52
 
31
- function isKeyNamed(node, name) {
53
+ // ===== 提取 cn 的 key(仅支持:字符串字面量 或 I18N.a.b.c 四层)=====
54
+ function extractCnKey(node, scope) {
55
+ // 情况1: 字符串字面量
56
+ if (t.isStringLiteral(node)) {
57
+ return node.value;
58
+ }
59
+
60
+ // 情况2: Identifier → 查找其定义
32
61
  if (t.isIdentifier(node)) {
33
- return node.name === name;
62
+ const binding = scope.getBinding(node.name);
63
+
64
+ if (binding && binding.path.isVariableDeclarator()) {
65
+ const init = binding.path.node.init;
66
+
67
+ if (init) {
68
+ // 递归解析初始化表达式(不再传 scope,避免无限递归)
69
+ return extractCnKeyDirect(init);
70
+ }
71
+ }
72
+ return null;
34
73
  }
74
+
75
+ // 情况3: 检查是否为 I18N.a.b.c(必须是四层 MemberExpression,非 computed)
76
+ return extractCnKeyDirect(node);
77
+ }
78
+
79
+ function extractEnValue(node, scope) {
80
+ // 直接是字符串字面量
35
81
  if (t.isStringLiteral(node)) {
36
- return node.value === name;
82
+ return node.value;
37
83
  }
38
- return false;
84
+
85
+ // 是变量(Identifier),尝试回溯
86
+ if (t.isIdentifier(node)) {
87
+ const binding = scope.getBinding(node.name);
88
+ if (binding && binding.path.isVariableDeclarator()) {
89
+ const init = binding.path.node.init;
90
+ if (init && t.isStringLiteral(init)) {
91
+ return init.value;
92
+ }
93
+ }
94
+ }
95
+
96
+ // 其他情况(表达式、模板、多变量等)不支持
97
+ return null;
39
98
  }
40
99
 
41
- // ===== 单文件转换函数(接受 code 和 filePath)=====
100
+ // ===== 单文件转换函数 =====
42
101
  const transformToChinese = (code, globalList) => {
43
102
  let localList = {}; // 每个文件独立的 list
44
103
 
@@ -49,19 +108,22 @@ const transformToChinese = (code, globalList) => {
49
108
 
50
109
  const langVariableNames = new Set();
51
110
 
52
- // 第一阶段:找 getLanguage()
111
+ // 第一阶段:收集 getLang / getLanguage 的变量名
53
112
  traverse(ast, {
54
113
  VariableDeclarator(path) {
55
114
  const { id, init } = path.node;
56
- const a = t.isIdentifier(id) && t.isCallExpression(init) && t.isIdentifier(init.callee, { name: 'getLanguage' })
57
- const b = t.isIdentifier(id) && t.isCallExpression(init) && t.isIdentifier(init.callee, { name: 'getLang' })
58
- if (a || b) {
115
+ if (
116
+ t.isIdentifier(id) &&
117
+ t.isCallExpression(init) &&
118
+ t.isIdentifier(init.callee) &&
119
+ ['getLanguage', 'getLang'].includes(init.callee.name)
120
+ ) {
59
121
  langVariableNames.add(id.name);
60
122
  }
61
123
  }
62
124
  });
63
125
 
64
- // 第二阶段:处理多语言对象 和 语言索引
126
+ // 第二阶段:处理对象和成员表达式
65
127
  traverse(ast, {
66
128
  ObjectExpression(path) {
67
129
  const props = path.node.properties;
@@ -70,53 +132,40 @@ const transformToChinese = (code, globalList) => {
70
132
  const cnProp = props.find(
71
133
  (prop) => t.isObjectProperty(prop) && !prop.computed && isKeyNamed(prop.key, 'cn') && !prop.method
72
134
  );
73
-
74
135
  const enProp = props.find(
75
136
  (prop) => t.isObjectProperty(prop) && !prop.computed && isKeyNamed(prop.key, 'en') && !prop.method
76
137
  );
77
-
78
- // 仅当 cn en 都是字符串字面量时,才记录映射
79
- if (cnProp && enProp && t.isStringLiteral(cnProp.value) && t.isStringLiteral(enProp.value)) {
80
- localList[cnProp.value.value] = enProp.value.value;
81
- }
82
-
83
- if (cnProp) {
84
- path.replaceWith(cnProp.value); // 替换 { cn: ..., en: ... } 为 cn 值
138
+
139
+ // 仅当 en 是字符串字面量,且 cn 符合提取规则时,才记录
140
+ if (cnProp && enProp) {
141
+ const enStr = extractEnValue(enProp.value, path.scope);
142
+ const cnKey = extractCnKey(cnProp.value, path.scope);
143
+
144
+ if (cnKey !== null && enStr !== null) {
145
+ localList[cnKey] = enStr;
146
+ }
85
147
  }
86
148
  },
87
149
 
88
150
  MemberExpression(path) {
89
-
90
151
  const node = path.node;
91
152
  if (!node.computed) return;
92
-
93
- // 处理 obj[langVar]
94
- if (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) {
95
- path.replaceWith(node.object);
96
- return;
97
- }
98
153
 
99
- // 处理 obj[getLang()] 或 obj[getLanguage()]
100
- if (
101
- t.isCallExpression(node.property) &&
102
- t.isIdentifier(node.property.callee) &&
103
- ['getLang', 'getLanguage'].includes(node.property.callee.name)
104
- ) {
105
- path.replaceWith(node.object);
106
- return;
107
- }
108
- },
154
+ const isLangAccess =
155
+ (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) ||
156
+ (t.isCallExpression(node.property) &&
157
+ t.isIdentifier(node.property.callee) &&
158
+ ['getLang', 'getLanguage'].includes(node.property.callee.name));
109
159
 
110
- // 清理 getLang() 相关的变量声明和导入(保持不变)
111
- Identifier(path) {
112
- if (langVariableNames.has(path.node.name)) {
113
- const parent = path.parentPath;
114
- if (parent.isExpressionStatement()) {
115
- parent.remove();
116
- }
160
+ if (isLangAccess) {
161
+ // 替换为 obj['cn']
162
+ path.replaceWith(
163
+ t.memberExpression(node.object, t.stringLiteral('cn'), true)
164
+ );
117
165
  }
118
166
  },
119
167
 
168
+ // 清理无用的 lang 变量声明
120
169
  VariableDeclarator(path) {
121
170
  const { id } = path.node;
122
171
  if (t.isIdentifier(id) && langVariableNames.has(id.name)) {
@@ -129,16 +178,26 @@ const transformToChinese = (code, globalList) => {
129
178
  }
130
179
  }
131
180
  }
181
+ },
182
+
183
+ Identifier(path) {
184
+ if (langVariableNames.has(path.node.name)) {
185
+ const parent = path.parentPath;
186
+ if (parent.isExpressionStatement()) {
187
+ parent.remove();
188
+ }
189
+ }
132
190
  }
133
191
  });
134
192
 
135
- // 第三阶段:清理 getLanguage 导入
193
+ // 第三阶段:清理 getLang / getLanguage 导入
136
194
  traverse(ast, {
137
195
  ImportDeclaration(path) {
138
196
  const specifiers = path.node.specifiers;
139
197
  const newSpecifiers = specifiers.filter((spec) => {
140
198
  if (t.isImportSpecifier(spec)) {
141
- return !t.isIdentifier(spec.imported, { name: 'getLanguage' }) && !t.isIdentifier(spec.imported, { name: 'getLang' });
199
+ return !t.isIdentifier(spec.imported, { name: 'getLanguage' }) &&
200
+ !t.isIdentifier(spec.imported, { name: 'getLang' });
142
201
  }
143
202
  return true;
144
203
  });
@@ -157,17 +216,15 @@ const transformToChinese = (code, globalList) => {
157
216
  comments: false
158
217
  }).code;
159
218
 
160
- // 合并到全局 list
161
-
219
+ // 合并到全局翻译表
162
220
  Object.assign(globalList, localList);
163
221
  return transformedCode;
164
- }
222
+ };
165
223
 
166
- // ===== ESLint 实例(复用)=====
224
+ // ===== ESLint 实例(用于格式化和清理未使用导入)=====
167
225
  const eslint = new ESLint({
168
226
  fix: true,
169
227
  cwd: process.cwd(),
170
- useEslintrc: false,
171
228
  plugins: {
172
229
  'unused-imports': unusedImports
173
230
  },
@@ -180,7 +237,6 @@ const eslint = new ESLint({
180
237
  }
181
238
  },
182
239
  overrideConfig: {
183
- extends: ["tongdun/react"],
184
240
  plugins: ["td-rules-plugin"],
185
241
  rules: {
186
242
  'prettier/prettier': [
@@ -199,7 +255,7 @@ const eslint = new ESLint({
199
255
  }
200
256
  });
201
257
 
202
- // ===== 递归读取所有 .js 文件 =====
258
+ // ===== 递归读取所有 .js 文件(排除 index.js)=====
203
259
  const getAllJsFiles = (dir) => {
204
260
  let results = [];
205
261
  const files = fs.readdirSync(dir);
@@ -208,14 +264,13 @@ const getAllJsFiles = (dir) => {
208
264
  const fullPath = path.join(dir, file);
209
265
  const stat = fs.statSync(fullPath);
210
266
  if (stat.isDirectory()) {
211
- results = results.concat(getAllJsFiles(fullPath)); // 递归
212
- } else if (file.endsWith('.js') && !['index.js'].includes(file)) {
267
+ results = results.concat(getAllJsFiles(fullPath));
268
+ } else if (file.endsWith('.js') && file !== 'index.js') {
213
269
  results.push(fullPath);
214
270
  }
215
271
  }
216
-
217
272
  return results;
218
- }
273
+ };
219
274
 
220
275
  // ===== 处理单个文件 =====
221
276
  const processFile = async (filePath, globalList) => {
@@ -226,9 +281,11 @@ const processFile = async (filePath, globalList) => {
226
281
  const finalCode = results[0].output || transformedCode;
227
282
 
228
283
  fs.writeFileSync(filePath, finalCode, 'utf-8');
229
- }
284
+ };
230
285
 
286
+ // ===== 导出接口 =====
231
287
  module.exports = {
232
288
  processFile,
233
- getAllJsFiles
234
- }
289
+ getAllJsFiles,
290
+ transformToChinese // 便于单元测试
291
+ };