@oceanbase/codemod 1.0.0-alpha.0 → 1.0.0-alpha.10

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.
Files changed (72) hide show
  1. package/README.md +158 -0
  2. package/bin/cli.js +196 -30
  3. package/bin/upgrade-list.json +8 -5
  4. package/package.json +7 -6
  5. package/transforms/__testfixtures__/less-to-cssvar/basic.input.less +16 -0
  6. package/transforms/__testfixtures__/less-to-cssvar/basic.output.less +14 -0
  7. package/transforms/__testfixtures__/less-to-cssvar/color-scales.input.less +23 -0
  8. package/transforms/__testfixtures__/less-to-cssvar/color-scales.output.less +21 -0
  9. package/transforms/__testfixtures__/less-to-cssvar/complex-values.input.less +22 -0
  10. package/transforms/__testfixtures__/less-to-cssvar/complex-values.output.less +20 -0
  11. package/transforms/__testfixtures__/less-to-cssvar/control-tokens.input.less +29 -0
  12. package/transforms/__testfixtures__/less-to-cssvar/control-tokens.output.less +27 -0
  13. package/transforms/__testfixtures__/less-to-cssvar/css-modules-global.input.less +21 -0
  14. package/transforms/__testfixtures__/less-to-cssvar/css-modules-global.output.less +19 -0
  15. package/transforms/__testfixtures__/less-to-cssvar/custom-prefix.input.less +9 -0
  16. package/transforms/__testfixtures__/less-to-cssvar/custom-prefix.output.less +7 -0
  17. package/transforms/__testfixtures__/less-to-cssvar/fill-tokens.input.less +36 -0
  18. package/transforms/__testfixtures__/less-to-cssvar/fill-tokens.output.less +34 -0
  19. package/transforms/__testfixtures__/less-to-cssvar/mixed-values.input.less +21 -0
  20. package/transforms/__testfixtures__/less-to-cssvar/mixed-values.output.less +19 -0
  21. package/transforms/__testfixtures__/less-to-cssvar/multiple-imports.input.less +9 -0
  22. package/transforms/__testfixtures__/less-to-cssvar/multiple-imports.output.less +8 -0
  23. package/transforms/__testfixtures__/less-to-cssvar/nested-selectors.input.less +24 -0
  24. package/transforms/__testfixtures__/less-to-cssvar/nested-selectors.output.less +22 -0
  25. package/transforms/__testfixtures__/less-to-cssvar/no-transform.input.less +8 -0
  26. package/transforms/__testfixtures__/less-to-cssvar/no-transform.output.less +8 -0
  27. package/transforms/__testfixtures__/less-to-cssvar/obui-import.input.less +7 -0
  28. package/transforms/__testfixtures__/less-to-cssvar/obui-import.output.less +5 -0
  29. package/transforms/__testfixtures__/less-to-cssvar/status-colors.input.less +25 -0
  30. package/transforms/__testfixtures__/less-to-cssvar/status-colors.output.less +23 -0
  31. package/transforms/__testfixtures__/less-to-token/antd-v4-less-to-token.input.less +2 -0
  32. package/transforms/__testfixtures__/less-to-token/antd-v4-less-to-token.output.less +2 -0
  33. package/transforms/__testfixtures__/less-to-token/case-insensitive.input.less +4 -0
  34. package/transforms/__testfixtures__/less-to-token/case-insensitive.output.less +4 -0
  35. package/transforms/__testfixtures__/less-to-token/exist-import-url.input.less +10 -0
  36. package/transforms/__testfixtures__/less-to-token/exist-import-url.output.less +10 -0
  37. package/transforms/__testfixtures__/less-to-token/exist-import.input.less +10 -0
  38. package/transforms/__testfixtures__/less-to-token/exist-import.output.less +10 -0
  39. package/transforms/__testfixtures__/sass-to-cssvar/basic.input.scss +18 -0
  40. package/transforms/__testfixtures__/sass-to-cssvar/basic.output.scss +18 -0
  41. package/transforms/__testfixtures__/sass-to-cssvar/custom-prefix.input.scss +5 -0
  42. package/transforms/__testfixtures__/sass-to-cssvar/custom-prefix.output.scss +5 -0
  43. package/transforms/__testfixtures__/sass-to-cssvar/no-transform.input.scss +6 -0
  44. package/transforms/__testfixtures__/sass-to-cssvar/no-transform.output.scss +6 -0
  45. package/transforms/__testfixtures__/style-to-token/anonymous-function.input.js +13 -0
  46. package/transforms/__testfixtures__/style-to-token/anonymous-function.output.js +14 -0
  47. package/transforms/__testfixtures__/style-to-token/antd-style.input.js +3 -2
  48. package/transforms/__testfixtures__/style-to-token/antd-style.output.js +2 -1
  49. package/transforms/__testfixtures__/style-to-token/class-component.input.js +1 -1
  50. package/transforms/__testfixtures__/style-to-token/existed-useToken.input.js +1 -1
  51. package/transforms/__testfixtures__/style-to-token/function-component.input.js +3 -3
  52. package/transforms/__testfixtures__/style-to-token/function-component.output.js +2 -2
  53. package/transforms/__testfixtures__/style-to-token/function.input.js +2 -2
  54. package/transforms/__testfixtures__/style-to-token/hooks.input.js +1 -1
  55. package/transforms/__testfixtures__/style-to-token/nested-object.input.js +12 -0
  56. package/transforms/__testfixtures__/style-to-token/nested-object.output.js +13 -0
  57. package/transforms/__testfixtures__/style-to-token/single-function.input.js +5 -0
  58. package/transforms/__testfixtures__/style-to-token/single-function.output.js +6 -0
  59. package/transforms/__testfixtures__/style-to-token/template-string.input.js +23 -0
  60. package/transforms/__testfixtures__/style-to-token/template-string.output.js +25 -0
  61. package/transforms/__tests__/less-to-cssvar.test.ts +180 -0
  62. package/transforms/__tests__/less-to-token.test.ts +2 -0
  63. package/transforms/__tests__/sass-to-cssvar.test.ts +67 -0
  64. package/transforms/__tests__/style-to-token.test.ts +4 -0
  65. package/transforms/less-to-cssvar.js +505 -0
  66. package/transforms/less-to-token.js +47 -7
  67. package/transforms/obui-to-oceanbase-design-and-ui.js +0 -4
  68. package/transforms/sass-to-cssvar.js +194 -0
  69. package/transforms/style-to-token.js +236 -25
  70. package/transforms/utils/index.js +8 -0
  71. package/transforms/utils/path-utils.js +40 -0
  72. package/transforms/utils/token.js +24 -2
@@ -0,0 +1,194 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const postcss = require('postcss');
4
+ const postcssScss = require('postcss-scss');
5
+ const isDirectory = require('is-directory');
6
+ const { shouldExcludePath } = require('./utils/path-utils');
7
+
8
+ /**
9
+ * Get SASS/SCSS tokens from @oceanbase/design theme Less file
10
+ * Since we're converting SASS variables to CSS variables based on the same design tokens,
11
+ * we can reuse the Less token list
12
+ */
13
+ function getSassTokensFromTheme() {
14
+ try {
15
+ // Try to find the Less theme file from @oceanbase/design
16
+ const themeLessPath = require.resolve('@oceanbase/design/es/theme/style/default.less');
17
+ const content = fs.readFileSync(themeLessPath, 'utf-8');
18
+
19
+ // Extract all @variableName: definitions
20
+ const tokenRegex = /@([a-zA-Z][a-zA-Z0-9]*):/g;
21
+ const tokens = new Set();
22
+ let match;
23
+ while ((match = tokenRegex.exec(content)) !== null) {
24
+ tokens.add(match[1]);
25
+ }
26
+ return Array.from(tokens);
27
+ } catch (e) {
28
+ // Fallback: try lib path
29
+ try {
30
+ const themeLessPath = require.resolve('@oceanbase/design/lib/theme/style/default.less');
31
+ const content = fs.readFileSync(themeLessPath, 'utf-8');
32
+
33
+ const tokenRegex = /@([a-zA-Z][a-zA-Z0-9]*):/g;
34
+ const tokens = new Set();
35
+ let match;
36
+ while ((match = tokenRegex.exec(content)) !== null) {
37
+ tokens.add(match[1]);
38
+ }
39
+ return Array.from(tokens);
40
+ } catch (e2) {
41
+ // Final fallback: use theme token object
42
+ try {
43
+ const { default: defaultTheme } = require('@oceanbase/design/lib/theme/default');
44
+ return Object.keys(defaultTheme.token || {});
45
+ } catch (e3) {
46
+ console.warn('Warning: Could not load tokens from @oceanbase/design, using empty list');
47
+ return [];
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ // Get SASS variable tokens dynamically from @oceanbase/design theme
54
+ const SASS_TOKENS = getSassTokensFromTheme();
55
+
56
+ /**
57
+ * Convert camelCase to kebab-case
58
+ * @param {string} str - camelCase string
59
+ * @returns {string} - kebab-case string
60
+ */
61
+ function camelToKebab(str) {
62
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
63
+ }
64
+
65
+ /**
66
+ * Find all sass/scss files in the directory
67
+ * @param {string} dir - Directory or file path
68
+ * @returns {string[]} - Array of file paths
69
+ */
70
+ const findAllSassFiles = dir => {
71
+ const sassFiles = [];
72
+ const isDir = isDirectory.sync(dir);
73
+ if (isDir) {
74
+ const files = fs.readdirSync(dir);
75
+ files.forEach(file => {
76
+ const filePath = path.join(dir, file);
77
+ if (isDirectory.sync(filePath)) {
78
+ if (shouldExcludePath(filePath)) {
79
+ return;
80
+ }
81
+ sassFiles.push(...findAllSassFiles(filePath));
82
+ } else if (file.endsWith('.sass') || file.endsWith('.scss')) {
83
+ sassFiles.push(filePath);
84
+ }
85
+ });
86
+ } else if (dir.endsWith('.sass') || dir.endsWith('.scss')) {
87
+ sassFiles.push(dir);
88
+ }
89
+ return sassFiles;
90
+ };
91
+
92
+ /**
93
+ * Transform SASS variable to CSS variable
94
+ * @param {string} value - CSS value that may contain SASS variables
95
+ * @param {string} prefix - CSS variable prefix (default: 'ant')
96
+ * @returns {string} - Transformed value with CSS variables
97
+ */
98
+ function transformSassVarToCssVar(value, prefix = 'ant') {
99
+ if (!value || typeof value !== 'string') {
100
+ return value;
101
+ }
102
+
103
+ let result = value;
104
+
105
+ // Match SASS variables like $colorPrimary, $fontSize, etc.
106
+ // Support both $tokenName and #{tokenName} syntax
107
+ const sassVarRegex = /\$([a-zA-Z][a-zA-Z0-9]*)/g;
108
+
109
+ result = result.replace(sassVarRegex, (match, varName) => {
110
+ // Check if this is a known token
111
+ if (SASS_TOKENS.includes(varName)) {
112
+ const kebabName = camelToKebab(varName);
113
+ return `var(--${prefix}-${kebabName})`;
114
+ }
115
+ // Return original if not a known token
116
+ return match;
117
+ });
118
+
119
+ return result;
120
+ }
121
+
122
+ /**
123
+ * Transform a single SASS/SCSS file to use CSS variables
124
+ * @param {string} file - File path
125
+ * @param {object} options - Transform options
126
+ * @param {string} options.prefix - CSS variable prefix (default: 'ant')
127
+ * @returns {Promise<{content: string, hasTransformations: boolean}>} - Transformed content
128
+ */
129
+ async function transform(file, options = {}) {
130
+ const { prefix = 'ant' } = options;
131
+ const content = fs.readFileSync(file, 'utf-8');
132
+ const { root: ast } = await postcss([]).process(content, {
133
+ syntax: postcssScss,
134
+ from: file,
135
+ });
136
+
137
+ // Track whether any transformations were made
138
+ let hasTransformations = false;
139
+
140
+ // Traverse AST
141
+ ast.walk(node => {
142
+ if (node.type === 'decl') {
143
+ // Transform property values that contain SASS variables
144
+ const newValue = transformSassVarToCssVar(node.value, prefix);
145
+ if (newValue !== node.value) {
146
+ node.value = newValue;
147
+ hasTransformations = true;
148
+ }
149
+ } else if (node.type === 'atrule') {
150
+ // Transform SASS variables in at-rule params (e.g., media queries)
151
+ if (node.params) {
152
+ const newParams = transformSassVarToCssVar(node.params, prefix);
153
+ if (newParams !== node.params) {
154
+ node.params = newParams;
155
+ hasTransformations = true;
156
+ }
157
+ }
158
+ }
159
+ });
160
+
161
+ return {
162
+ content: ast.toString(postcssScss.stringify),
163
+ hasTransformations,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Run the sass-to-cssvar transformation
169
+ * @param {string} file - File or directory path
170
+ * @param {object} options - Transform options
171
+ * @param {string} options.prefix - CSS variable prefix (default: 'ant')
172
+ */
173
+ async function sassToCssvar(file, options = {}) {
174
+ const { prefix = 'ant' } = options;
175
+ const allSassFiles = findAllSassFiles(file);
176
+
177
+ for (const item of allSassFiles) {
178
+ const { content, hasTransformations } = await transform(item, { prefix });
179
+
180
+ if (hasTransformations) {
181
+ fs.writeFileSync(item, content);
182
+ console.log(` Transformed: ${item}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ module.exports = {
188
+ transform,
189
+ sassToCssvar,
190
+ transformSassVarToCssVar,
191
+ getSassTokensFromTheme,
192
+ camelToKebab,
193
+ SASS_TOKENS,
194
+ };
@@ -38,15 +38,35 @@ function wrapJSXValue(value, isJSXAttribute) {
38
38
  }
39
39
 
40
40
  // 检查是否为 React 组件或 Hook
41
- function isReactComponentOrHook(functionName) {
42
- return functionName && (isFirstUpperCase(functionName) || functionName.startsWith('use'));
41
+ function isReactComponentOrHook(functionName, path) {
42
+ // 如果有函数名,检查是否以大写字母开头或以 use 开头
43
+ if (functionName) {
44
+ return isFirstUpperCase(functionName) || functionName.startsWith('use');
45
+ }
46
+
47
+ // 对于匿名函数,检查是否是 export default function () {} 的形式
48
+ // 而不是 export default () => {} 的形式
49
+ if (path) {
50
+ const parentType = path.parentPath.value?.type;
51
+ if (parentType === 'FunctionDeclaration') {
52
+ // export default function () {} - 认为是 React 组件
53
+ return true;
54
+ }
55
+ if (parentType === 'ArrowFunctionExpression') {
56
+ // export default () => {} - 不认为是 React 组件
57
+ return false;
58
+ }
59
+ }
60
+
61
+ // 其他情况,默认不认为是 React 组件
62
+ return false;
43
63
  }
44
64
 
45
65
  // 检查 BlockStatement 中是否包含 token 使用
46
66
  function hasTokenUsage(j, path) {
47
67
  return (
48
- j(path).find(j.Identifier, {
49
- name: name => name?.includes('token.'),
68
+ j(path).find(j.MemberExpression, {
69
+ object: { name: 'token' },
50
70
  }).length > 0
51
71
  );
52
72
  }
@@ -122,7 +142,7 @@ function addTokenImportToBlockStatement(j, root, path) {
122
142
  const includeJSXElement = j(path).find(j.JSXElement).length > 0;
123
143
  const functionName = getFunctionName(path);
124
144
 
125
- if (includeJSXElement && isReactComponentOrHook(functionName)) {
145
+ if (includeJSXElement && isReactComponentOrHook(functionName, path)) {
126
146
  const insertString = 'const { token } = theme.useToken()';
127
147
  path.get('body').value.unshift(j.expressionStatement(j.identifier(insertString)));
128
148
  addSubmoduleImport(j, root, {
@@ -130,6 +150,13 @@ function addTokenImportToBlockStatement(j, root, path) {
130
150
  importedName: 'theme',
131
151
  importKind: 'value',
132
152
  });
153
+ } else if (includeJSXElement) {
154
+ // 对于非 React 组件的函数,直接导入 token
155
+ addSubmoduleImport(j, root, {
156
+ moduleName: '@oceanbase/design',
157
+ importedName: 'token',
158
+ importKind: 'value',
159
+ });
133
160
  }
134
161
  }
135
162
 
@@ -201,17 +228,6 @@ function processStringLiterals(j, root) {
201
228
  const { key, token, formattedValue } = tokenParse(path.value.value);
202
229
  const isJSXAttribute = path.parentPath.value.type === 'JSXAttribute';
203
230
 
204
- console.log(
205
- 'Processing string:',
206
- path.value.value,
207
- 'isJSXAttribute:',
208
- isJSXAttribute,
209
- 'formattedValue:',
210
- formattedValue,
211
- 'key:',
212
- key
213
- );
214
-
215
231
  if (formattedValue === key) {
216
232
  // 完全匹配的情况,直接返回 token.xxx
217
233
  const memberExpression = j.memberExpression(j.identifier('token'), j.identifier(token));
@@ -244,12 +260,130 @@ function processStringLiterals(j, root) {
244
260
  return hasChanged;
245
261
  }
246
262
 
263
+ // 处理模板字符串中的颜色值
264
+ function processTemplateLiterals(j, root) {
265
+ let hasChanged = false;
266
+
267
+ const templateList = root.find(j.TemplateLiteral);
268
+
269
+ if (templateList.length > 0) {
270
+ templateList.forEach(path => {
271
+ const templateLiteral = path.value;
272
+ const quasis = templateLiteral.quasis;
273
+ const expressions = templateLiteral.expressions || [];
274
+
275
+ // 检查每个模板字符串片段是否包含需要转换的颜色值
276
+ let needsReconstruction = false;
277
+ const newQuasis = [];
278
+ const newExpressions = [];
279
+
280
+ for (let i = 0; i < quasis.length; i++) {
281
+ const quasi = quasis[i];
282
+ let value = quasi.value.raw;
283
+
284
+ // 查找需要转换的颜色值
285
+ const colorMatch = value.match(
286
+ /rgba?\([^)]+\)|#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|hsl\([^)]+\)|hsla?\([^)]+\)/g
287
+ );
288
+ if (colorMatch) {
289
+ hasChanged = true;
290
+ needsReconstruction = true;
291
+
292
+ // 收集所有需要替换的匹配项及其 token
293
+ const replacements = [];
294
+ colorMatch.forEach(match => {
295
+ const { token } = tokenParse(match);
296
+ if (token) {
297
+ const index = value.indexOf(match);
298
+ replacements.push({ index, match, token });
299
+ }
300
+ });
301
+
302
+ // 按位置排序,从后往前处理以避免位置偏移
303
+ replacements.sort((a, b) => b.index - a.index);
304
+
305
+ let processedValue = value;
306
+ replacements.forEach(({ index, match, token }) => {
307
+ const before = processedValue.substring(0, index);
308
+ const after = processedValue.substring(index + match.length);
309
+
310
+ // 如果前面有内容,创建一个 quasi
311
+ if (before) {
312
+ newQuasis.push(j.templateElement({ raw: before, cooked: before }, false));
313
+ }
314
+
315
+ // 创建 token 表达式
316
+ newExpressions.push(j.memberExpression(j.identifier('token'), j.identifier(token)));
317
+
318
+ // 剩余部分继续处理
319
+ processedValue = after;
320
+ });
321
+
322
+ // 如果有剩余部分,添加到 quasis
323
+ if (processedValue || newQuasis.length === 0) {
324
+ const isTail = i === quasis.length - 1 && expressions.length === 0;
325
+ newQuasis.push(
326
+ j.templateElement({ raw: processedValue || '', cooked: processedValue || '' }, isTail)
327
+ );
328
+ } else if (newQuasis.length > 0) {
329
+ // 确保最后一个 quasi 标记为 tail(如果这是最后一个 quasi 且没有更多表达式)
330
+ const isTail = i === quasis.length - 1 && expressions.length === 0;
331
+ if (isTail) {
332
+ const lastQuasi = newQuasis[newQuasis.length - 1];
333
+ lastQuasi.tail = true;
334
+ }
335
+ }
336
+ } else {
337
+ // 如果没有颜色值需要转换,保持原样
338
+ newQuasis.push(quasi);
339
+ if (i < expressions.length) {
340
+ newExpressions.push(expressions[i]);
341
+ }
342
+ }
343
+ }
344
+
345
+ // 如果需要重构,替换整个模板字符串
346
+ if (needsReconstruction) {
347
+ // 确保最后一个 quasi 标记为 tail
348
+ if (newQuasis.length > 0) {
349
+ const lastQuasi = newQuasis[newQuasis.length - 1];
350
+ lastQuasi.tail = true;
351
+ }
352
+
353
+ // 合并原有的表达式(如果有)
354
+ const allExpressions = [...newExpressions];
355
+ // 添加原有的表达式(在 quasis 之后)
356
+ for (let i = 0; i < expressions.length; i++) {
357
+ allExpressions.push(expressions[i]);
358
+ }
359
+
360
+ path.replace(j.templateLiteral(newQuasis, allExpressions));
361
+ }
362
+ });
363
+
364
+ // 为包含 token 使用的顶级 BlockStatement 添加导入
365
+ root
366
+ .find(j.BlockStatement)
367
+ .filter(path => isTopBlockStatement(path))
368
+ .forEach(path => {
369
+ if (hasTokenUsage(j, path) && !hasUseTokenStatement(j, path)) {
370
+ addTokenImportToBlockStatement(j, root, path);
371
+ }
372
+ });
373
+ }
374
+
375
+ return hasChanged;
376
+ }
377
+
247
378
  function importComponent(j, root, options) {
248
379
  let hasChanged = false;
249
380
 
250
381
  // 处理字符串字面量
251
382
  hasChanged = processStringLiterals(j, root) || hasChanged;
252
383
 
384
+ // 处理模板字符串中的颜色值
385
+ hasChanged = processTemplateLiterals(j, root) || hasChanged;
386
+
253
387
  // 处理对象属性值(如 fontSize: 14)
254
388
  const objectPropertyChanged = processObjectProperties(j, root);
255
389
  hasChanged = objectPropertyChanged || hasChanged;
@@ -261,13 +395,14 @@ function importComponent(j, root, options) {
261
395
  function processObjectProperties(j, root) {
262
396
  let hasChanged = false;
263
397
 
264
- const objectPropertyList = root.find(j.ObjectProperty, {
398
+ // 处理数字字面量
399
+ const numericPropertyList = root.find(j.ObjectProperty, {
265
400
  key: { type: 'Identifier' },
266
401
  value: { type: 'NumericLiteral' },
267
402
  });
268
403
 
269
- if (objectPropertyList.length > 0) {
270
- objectPropertyList.replaceWith(path => {
404
+ if (numericPropertyList.length > 0) {
405
+ numericPropertyList.replaceWith(path => {
271
406
  const propertyName = path.value.key.name;
272
407
  const propertyValue = path.value.value.value;
273
408
 
@@ -275,16 +410,39 @@ function processObjectProperties(j, root) {
275
410
  if (tokenResult) {
276
411
  hasChanged = true;
277
412
  const isJSXAttribute = path.parentPath.value.type === 'JSXAttribute';
278
- const stringValue = wrapJSXValue(`token.${tokenResult.token}`, isJSXAttribute);
279
- return j.objectProperty(j.identifier(propertyName), j.identifier(stringValue));
413
+ const memberExpression = j.memberExpression(
414
+ j.identifier('token'),
415
+ j.identifier(tokenResult.token)
416
+ );
417
+ return j.objectProperty(j.identifier(propertyName), memberExpression);
280
418
  }
281
419
  return path.value;
282
420
  });
421
+ }
283
422
 
284
- // 如果发生了替换,需要添加 token 导入
285
- if (hasChanged) {
286
- addTokenImportsForObjectProperties(j, root);
287
- }
423
+ // 处理字符串字面量(如 fontSize: '14px')
424
+ const stringPropertyList = root.find(j.ObjectProperty, {
425
+ key: { type: 'Identifier' },
426
+ value: { type: 'StringLiteral' },
427
+ });
428
+
429
+ if (stringPropertyList.length > 0) {
430
+ stringPropertyList.replaceWith(path => {
431
+ const propertyName = path.value.key.name;
432
+ const propertyValue = path.value.value.value;
433
+
434
+ const tokenResult = propertyTokenParse(propertyName, propertyValue);
435
+ if (tokenResult) {
436
+ hasChanged = true;
437
+ const isJSXAttribute = path.parentPath.value.type === 'JSXAttribute';
438
+ const memberExpression = j.memberExpression(
439
+ j.identifier('token'),
440
+ j.identifier(tokenResult.token)
441
+ );
442
+ return j.objectProperty(j.identifier(propertyName), memberExpression);
443
+ }
444
+ return path.value;
445
+ });
288
446
  }
289
447
 
290
448
  return hasChanged;
@@ -292,6 +450,7 @@ function processObjectProperties(j, root) {
292
450
 
293
451
  // 为对象属性添加 token 导入
294
452
  function addTokenImportsForObjectProperties(j, root) {
453
+ // 处理 BlockStatement 中的 token 使用
295
454
  root
296
455
  .find(j.BlockStatement)
297
456
  .filter(path => isTopBlockStatement(path))
@@ -304,6 +463,31 @@ function addTokenImportsForObjectProperties(j, root) {
304
463
  }
305
464
  }
306
465
  });
466
+
467
+ // 处理顶层导出语句中的 token 使用
468
+ const hasTokenUsageInRoot =
469
+ root.find(j.MemberExpression, {
470
+ object: { name: 'token' },
471
+ }).length > 0;
472
+
473
+ if (hasTokenUsageInRoot) {
474
+ // 检查是否应该添加顶层 token 导入
475
+ if (shouldAddTopLevelTokenImport(j, root)) {
476
+ // 检查是否有 @oceanbase/design 的导入
477
+ const hasOceanbaseImport =
478
+ root.find(j.ImportDeclaration, {
479
+ source: { value: '@oceanbase/design' },
480
+ }).length > 0;
481
+
482
+ if (hasOceanbaseImport) {
483
+ // 如果有 @oceanbase/design 导入,添加到现有导入
484
+ addTokenToExistingImport(j, root);
485
+ } else {
486
+ // 如果没有 @oceanbase/design 导入,添加顶层 token 导入
487
+ addTopLevelTokenImport(j, root);
488
+ }
489
+ }
490
+ }
307
491
  }
308
492
 
309
493
  // 检查是否应该添加顶层 token 导入
@@ -385,6 +569,33 @@ module.exports = (file, api, options) => {
385
569
  processCreateStylesParams(j, root);
386
570
  }
387
571
 
572
+ // 为对象属性添加 token 导入(只在没有其他 token 导入逻辑时调用)
573
+ if (hasChanged) {
574
+ // 检查是否已经有其他 token 导入逻辑在处理
575
+ const hasOtherTokenLogic =
576
+ root.find(j.CallExpression, {
577
+ callee: { name: 'createStyles' },
578
+ }).length > 0 ||
579
+ root.find(j.CallExpression, {
580
+ callee: {
581
+ type: 'MemberExpression',
582
+ object: { name: 'theme' },
583
+ property: { name: 'useToken' },
584
+ },
585
+ }).length > 0 ||
586
+ root
587
+ .find(j.ImportDeclaration, {
588
+ source: { value: '@oceanbase/design' },
589
+ })
590
+ .find(j.ImportSpecifier, {
591
+ imported: { name: 'theme' },
592
+ }).length > 0;
593
+
594
+ if (!hasOtherTokenLogic) {
595
+ addTokenImportsForObjectProperties(j, root);
596
+ }
597
+ }
598
+
388
599
  // 如果有变化,检查是否需要添加 token 导入
389
600
  if (hasChanged) {
390
601
  // 检查是否有 token 使用
@@ -136,6 +136,14 @@ function addModuleImport(j, root, { pkgName, importSpecifier, importKind, before
136
136
  return 1;
137
137
  }
138
138
 
139
+ // 特殊处理:token 总是放在最后
140
+ if (a.imported && a.imported.name === 'token') {
141
+ return 1;
142
+ }
143
+ if (b.imported && b.imported.name === 'token') {
144
+ return -1;
145
+ }
146
+
139
147
  return a.imported.name.localeCompare(b.imported.name);
140
148
  });
141
149
  const importStatement = j.importDeclaration(mergedImportSpecifiers, j.literal(pkgName));
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Common directories to exclude when traversing files
3
+ * These are typically build outputs, caches, dependencies, and temporary directories
4
+ */
5
+ const EXCLUDED_DIRS = [
6
+ 'node_modules',
7
+ '.umi',
8
+ '.umi-production',
9
+ '.git',
10
+ 'dist',
11
+ 'build',
12
+ 'coverage',
13
+ '.cache',
14
+ '.next',
15
+ '.turbo',
16
+ '.vite',
17
+ '.nuxt',
18
+ '.output',
19
+ '.temp',
20
+ '.tmp',
21
+ 'temp',
22
+ 'tmp',
23
+ ];
24
+
25
+ /**
26
+ * Check if a path should be excluded
27
+ * @param {string} filePath - File path to check
28
+ * @returns {boolean} - True if should be excluded
29
+ */
30
+ function shouldExcludePath(filePath) {
31
+ const normalizedPath = filePath.replace(/\\/g, '/');
32
+ return EXCLUDED_DIRS.some(
33
+ dir => normalizedPath.includes(`/${dir}/`) || normalizedPath.endsWith(`/${dir}`)
34
+ );
35
+ }
36
+
37
+ module.exports = {
38
+ EXCLUDED_DIRS,
39
+ shouldExcludePath,
40
+ };
@@ -36,17 +36,24 @@ const TOKEN_MAP = {
36
36
  '#1890ff': 'colorInfo',
37
37
  '#40a9ff': 'colorInfo',
38
38
  '#006aff': 'colorInfo',
39
+ '#1843ff': 'colorInfo',
40
+ '#597ef7': 'colorInfo',
39
41
  '#f7f9fb': 'colorInfoBg',
42
+ '#91a9f8': 'colorInfoBg',
40
43
  '#e6f7ff': 'colorInfoBgHover',
41
44
  '#f3f9ff': 'colorInfoBgHover',
42
45
  '#e6f7ff': 'colorInfoBgHover',
43
46
  '#73d13d': 'colorSuccess',
44
47
  '#52c41a': 'colorSuccess',
45
48
  '#faad14': 'colorWarning',
49
+ '#ffa940': 'colorWarning',
46
50
  '#fef6e7': 'colorWarningBg',
51
+ '#fed59c': 'colorWarningBg',
47
52
  '#ff4d4f': 'colorError',
48
53
  '#f5222d': 'colorError',
49
54
  '#f8636b': 'colorError',
55
+ '#f93939': 'colorError',
56
+ '#eb4444': 'colorError',
50
57
  '#d9d9d9': 'colorBorder',
51
58
  '#bfbfbf': 'colorBorder',
52
59
  '#e8e8e8': 'colorBorder',
@@ -67,7 +74,9 @@ const TOKEN_MAP = {
67
74
  'rgba(0,0,0,0.85)': 'colorText',
68
75
  'rgba(0,0,0,0.65)': 'colorTextSecondary',
69
76
  'rgba(0,0,0,0.45)': 'colorTextTertiary',
70
- '#5c6b8a': 'colorTextTertiary',
77
+ '#5c6b8a': 'colorTextSecondary',
78
+ '#5C6B8A': 'colorTextSecondary',
79
+ '#ced5e3': '@colorTextPlaceholder',
71
80
  'rgba(0,0,0,0.25)': 'colorTextQuaternary',
72
81
  'rgba(0,0,0,.85)': 'colorText',
73
82
  'rgba(0,0,0,.65)': 'colorTextSecondary',
@@ -97,6 +106,7 @@ const TOKEN_MAP = {
97
106
  'rgba(0,0,0,0.06)': 'colorFillSecondary',
98
107
  'rgba(0,0,0,0.04)': 'colorFillTertiary',
99
108
  'rgba(0,0,0,0.02)': 'colorFillQuaternary',
109
+ 'rgba(0,0,0,0.03)': 'colorFillQuaternary',
100
110
  '#f5f6fa': 'colorBgLayout',
101
111
  '#edeff2': 'colorBgLayout',
102
112
  // obui legacy style => token
@@ -122,12 +132,12 @@ const TOKEN_MAP = {
122
132
  '#cdd5e4': 'colorBorder',
123
133
  '#f5f8fe': 'colorBgLayout',
124
134
  '#f5f7fa': 'colorBgLayout',
135
+ '#f8fafe': 'colorBgLayout',
125
136
  'rgba(140,140,140,0.1)': 'colorBgLayout',
126
137
  'rgb(240,242,245)': 'colorBgLayout',
127
138
  '#132039': 'colorText',
128
139
  '#364563': 'colorTextSecondary',
129
140
  '#8592ad': 'colorTextTertiary',
130
- '#f8fafe': 'colorFillQuaternary',
131
141
  };
132
142
 
133
143
  const TOKEN_MAP_KEYS = Object.keys(TOKEN_MAP).map(key => formatValue(key));
@@ -170,6 +180,18 @@ const PROPERTY_TOKEN_MAP = {
170
180
  15: 'fontSizeLG',
171
181
  16: 'fontSizeLG',
172
182
  },
183
+ fontWeight: {
184
+ 300: 'fontWeightWeak',
185
+ 400: 'fontWeight',
186
+ 500: 'fontWeightStrong',
187
+ 600: 'fontWeightStrong',
188
+ },
189
+ borderRadius: {
190
+ 2: 'borderRadiusSM',
191
+ 4: 'borderRadius',
192
+ 6: 'borderRadiusMD',
193
+ 8: 'borderRadiusLG',
194
+ },
173
195
  };
174
196
 
175
197
  function propertyTokenParse(propertyName, value) {