feima-shortcuts 0.3.0-beta.12 → 0.3.0-beta.13

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": "feima-shortcuts",
3
- "version": "0.3.0-beta.12",
3
+ "version": "0.3.0-beta.13",
4
4
  "description": "快捷指令",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -6,12 +6,16 @@ const ts = require('typescript')
6
6
  const { parse } = require('@vue/compiler-sfc')
7
7
 
8
8
  const ROOT = process.cwd()
9
- const SCAN_DIRS = ['src/views'] // components 先预留
9
+ const SCAN_DIRS = ['src/views'] // views 目录
10
+ const COMPONENTS_DIR = 'src/components' // components 目录
10
11
  const FILE_EXT = ['vue', 'ts', 'js', 'tsx', 'jsx']
11
12
 
12
13
  // 匹配 t() 调用:t('key') 或 t('key', 'default')
13
14
  const T_FUNCTION_REG = /\bt\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`][^'"`]*['"`])?\s*\)/g
14
15
 
16
+ // 匹配 $t() 调用:$t('key') 或 $t('key', 'default')
17
+ const DOLLAR_T_FUNCTION_REG = /\$t\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`][^'"`]*['"`])?\s*\)/g
18
+
15
19
  /**
16
20
  * 扁平化 locale 对象,返回所有 key 的集合
17
21
  */
@@ -79,6 +83,18 @@ function extractTKeys(code) {
79
83
  return keys
80
84
  }
81
85
 
86
+ /**
87
+ * 从代码中提取所有 $t() 调用的 key
88
+ */
89
+ function extractDollarTKeys(code) {
90
+ const keys = new Set()
91
+ let match
92
+ while ((match = DOLLAR_T_FUNCTION_REG.exec(code)) !== null) {
93
+ keys.add(match[1])
94
+ }
95
+ return keys
96
+ }
97
+
82
98
  /**
83
99
  * 检查 locale 导入方式
84
100
  * @returns {Object} { hasCorrectImport, hasOtherImport, importPaths }
@@ -234,6 +250,106 @@ function scanFile(filePath, results) {
234
250
  }
235
251
  }
236
252
 
253
+ /**
254
+ * 检查组件目录(使用 $t 注入方式)
255
+ */
256
+ function checkComponent(componentDir, results) {
257
+ const localePath = path.join(componentDir, 'locale.ts')
258
+ if (!fs.existsSync(localePath)) {
259
+ return // 组件没有 locale.ts,跳过
260
+ }
261
+
262
+ const localeKeys = loadLocaleKeys(localePath)
263
+ if (!localeKeys) {
264
+ console.warn(`⚠️ 无法加载组件 locale: ${path.relative(ROOT, localePath)}`)
265
+ return
266
+ }
267
+
268
+ // 递归扫描组件目录下的所有文件
269
+ const allUsedKeys = new Set()
270
+ const componentFiles = []
271
+
272
+ FILE_EXT.forEach(ext => {
273
+ const pattern = `${componentDir}/**/*.${ext}`
274
+ const files = glob.sync(pattern, { nodir: true })
275
+ files.forEach(file => {
276
+ componentFiles.push(file)
277
+ let code = ''
278
+
279
+ if (file.endsWith('.vue')) {
280
+ code = parseVueFile(file)
281
+ } else {
282
+ try {
283
+ code = fs.readFileSync(file, 'utf-8')
284
+ } catch (error) {
285
+ console.warn(`⚠️ 读取文件失败: ${file}`, error.message)
286
+ return
287
+ }
288
+ }
289
+
290
+ if (code) {
291
+ // 提取 $t() 调用的 key
292
+ const dollarTKeys = extractDollarTKeys(code)
293
+ dollarTKeys.forEach(key => allUsedKeys.add(key))
294
+ }
295
+ })
296
+ })
297
+
298
+ if (allUsedKeys.size === 0) {
299
+ return // 组件中没有使用 $t()
300
+ }
301
+
302
+ // 检查哪些 key 在 locale 中不存在
303
+ const missingKeys = Array.from(allUsedKeys).filter(key => !localeKeys.has(key))
304
+
305
+ if (missingKeys.length > 0) {
306
+ const relativeComponentDir = path.relative(ROOT, componentDir)
307
+ const relativeLocalePath = path.relative(ROOT, localePath)
308
+
309
+ if (!results.componentMissingKeys.has(relativeComponentDir)) {
310
+ results.componentMissingKeys.set(relativeComponentDir, {
311
+ componentDir: relativeComponentDir,
312
+ localePath: relativeLocalePath,
313
+ missingKeys: new Set(),
314
+ files: []
315
+ })
316
+ }
317
+
318
+ missingKeys.forEach(key => {
319
+ results.componentMissingKeys.get(relativeComponentDir).missingKeys.add(key)
320
+ })
321
+
322
+ // 记录使用了这些 key 的文件
323
+ componentFiles.forEach(file => {
324
+ let code = ''
325
+ if (file.endsWith('.vue')) {
326
+ code = parseVueFile(file)
327
+ } else {
328
+ try {
329
+ code = fs.readFileSync(file, 'utf-8')
330
+ } catch (error) {
331
+ return
332
+ }
333
+ }
334
+
335
+ if (code) {
336
+ const dollarTKeys = extractDollarTKeys(code)
337
+ const fileMissingKeys = Array.from(dollarTKeys).filter(key => missingKeys.includes(key))
338
+ if (fileMissingKeys.length > 0) {
339
+ const relativeFile = path.relative(ROOT, file)
340
+ const info = results.componentMissingKeys.get(relativeComponentDir)
341
+ if (!info.files.find(f => f.file === relativeFile)) {
342
+ info.files.push({
343
+ file: relativeFile,
344
+ missingKeys: fileMissingKeys
345
+ })
346
+ }
347
+ }
348
+ }
349
+ })
350
+ }
351
+ }
352
+
237
353
  /**
238
354
  * 主函数
239
355
  */
@@ -242,10 +358,11 @@ function run() {
242
358
  invalidImports: new Map(), // 使用了非 './locale' 的导入
243
359
  noLocaleImport: new Map(), // 没有导入 locale 但使用了 t()
244
360
  missingLocale: new Map(), // locale.ts 文件不存在
245
- missingKeys: new Map() // locale.ts 存在但缺少某些 key
361
+ missingKeys: new Map(), // locale.ts 存在但缺少某些 key
362
+ componentMissingKeys: new Map() // 组件中 $t() 的 key 在 locale.ts 中缺失
246
363
  }
247
364
 
248
- // 扫描所有文件
365
+ // 扫描 views 目录
249
366
  SCAN_DIRS.forEach(dir => {
250
367
  const absDir = path.join(ROOT, dir)
251
368
  if (!fs.existsSync(absDir)) {
@@ -260,6 +377,19 @@ function run() {
260
377
  })
261
378
  })
262
379
 
380
+ // 扫描 components 目录
381
+ const componentsDir = path.join(ROOT, COMPONENTS_DIR)
382
+ if (fs.existsSync(componentsDir)) {
383
+ // 找到所有有 locale.ts 的组件目录
384
+ const localeFiles = glob.sync(`${componentsDir}/**/locale.ts`, { nodir: true })
385
+ localeFiles.forEach(localeFile => {
386
+ const componentDir = path.dirname(localeFile)
387
+ checkComponent(componentDir, results)
388
+ })
389
+ } else {
390
+ console.log(`⚠️ 目录不存在: ${COMPONENTS_DIR}`)
391
+ }
392
+
263
393
  // 输出结果
264
394
  console.log('\n📋 i18n 使用检查结果\n')
265
395
 
@@ -318,6 +448,30 @@ function run() {
318
448
  console.log()
319
449
  }
320
450
 
451
+ // 5. 输出组件中缺失的 key
452
+ if (results.componentMissingKeys.size > 0) {
453
+ hasError = true
454
+ console.log('❌ 以下组件使用的 $t() key 在 locale.ts 中未定义:\n')
455
+ results.componentMissingKeys.forEach((info, componentDir) => {
456
+ console.log(` 组件: ${componentDir}`)
457
+ console.log(` locale 文件: ${info.localePath}`)
458
+ console.log(` 缺失的 key:`)
459
+ Array.from(info.missingKeys).forEach(key => {
460
+ console.log(` - ${key}`)
461
+ })
462
+ if (info.files && info.files.length > 0) {
463
+ console.log(` 使用位置:`)
464
+ info.files.forEach(fileInfo => {
465
+ console.log(` ${fileInfo.file}`)
466
+ if (fileInfo.missingKeys && fileInfo.missingKeys.length > 0) {
467
+ console.log(` 缺失的 key: ${fileInfo.missingKeys.join(', ')}`)
468
+ }
469
+ })
470
+ }
471
+ console.log()
472
+ })
473
+ }
474
+
321
475
  // 总结
322
476
  if (hasError) {
323
477
  console.log('❗ 检查未通过,请修复上述问题')