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
|
@@ -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'] //
|
|
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('❗ 检查未通过,请修复上述问题')
|