feima-shortcuts 0.3.0-beta.6 → 0.3.0-beta.8

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/bin/feima.js CHANGED
@@ -4,7 +4,8 @@ const { Command } = require('commander')
4
4
  const feima = require("../src/generate");
5
5
 
6
6
  const checkLocale = require('../src/scripts/check-locale')
7
- const checkI18nDefault = require('../src/scripts/check-i18n-default')
7
+ const checkI18nDefault = require('../src/scripts/check-i18n-default')
8
+ const checkI18nViews = require('../src/scripts/check-i18n-views')
8
9
  const { version } = require("../package.json"); // 读取 package.json 中的版本号
9
10
 
10
11
  const program = new Command()
@@ -22,11 +23,18 @@ program
22
23
 
23
24
  program
24
25
  .command('check-i18n-default')
25
- .description('检查未使用的 locale')
26
+ .description('检查未设置默认值的 t')
26
27
  .action(() => {
27
28
  checkI18nDefault.run();
28
29
  })
29
30
 
31
+ program
32
+ .command('check-i18n-views')
33
+ .description('检查已使用未创建的 key')
34
+ .action(() => {
35
+ checkI18nViews.run();
36
+ })
37
+
30
38
 
31
39
  const run = () => {
32
40
  console.log(`🚀 当前版本:v${version}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feima-shortcuts",
3
- "version": "0.3.0-beta.6",
3
+ "version": "0.3.0-beta.8",
4
4
  "description": "快捷指令",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -15,6 +15,9 @@
15
15
  "author": "1198810568@qq.com",
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
+ "@babel/parser": "^7.28.5",
19
+ "@babel/traverse": "^7.28.5",
20
+ "@vue/compiler-sfc": "^3.5.26",
18
21
  "chalk": "^4.1.0",
19
22
  "commander": "^5.1.0",
20
23
  "download-git-repo": "^3.0.2",
@@ -0,0 +1,171 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const glob = require('glob')
4
+ const { parse } = require('@vue/compiler-sfc')
5
+ const babelParser = require('@babel/parser')
6
+ const traverse = require('@babel/traverse').default
7
+
8
+ /* ---------------- defaults ---------------- */
9
+
10
+ const DEFAULT_OPTIONS = {
11
+ cwd: process.cwd(),
12
+ viewsDir: 'src/views',
13
+ silent: false,
14
+ }
15
+
16
+ /* ---------------- utils ---------------- */
17
+
18
+ function flattenLocale(obj, prefix = '') {
19
+ let res = []
20
+ for (const k in obj) {
21
+ const key = prefix ? `${prefix}.${k}` : k
22
+ if (typeof obj[k] === 'object') {
23
+ res = res.concat(flattenLocale(obj[k], key))
24
+ } else {
25
+ res.push(key)
26
+ }
27
+ }
28
+ return res
29
+ }
30
+
31
+ function loadLocaleKeys(localePath) {
32
+ if (!fs.existsSync(localePath)) return null
33
+ const mod = require(localePath)
34
+ const localeObj = mod.default || mod
35
+ return flattenLocale(localeObj)
36
+ }
37
+
38
+ function parseCode(code) {
39
+ return babelParser.parse(code, {
40
+ sourceType: 'module',
41
+ plugins: ['typescript', 'jsx'],
42
+ })
43
+ }
44
+
45
+ /* ---------------- core ---------------- */
46
+
47
+ function run(userOptions = {}) {
48
+ // ✅ 保证 options 永远有值
49
+ const options = { ...DEFAULT_OPTIONS, ...userOptions }
50
+ const { cwd, viewsDir, silent } = options
51
+
52
+ const absViewsDir = path.join(cwd, viewsDir)
53
+
54
+ if (!fs.existsSync(absViewsDir)) {
55
+ if (!silent) {
56
+ console.warn(`⚠️ views 目录不存在:${viewsDir}`)
57
+ }
58
+ return {
59
+ success: true,
60
+ missingKeys: [],
61
+ noLocaleUsage: [],
62
+ scannedFiles: 0,
63
+ }
64
+ }
65
+
66
+ const files = glob.sync('**/*.{vue,ts,tsx}', {
67
+ cwd: absViewsDir,
68
+ absolute: true,
69
+ })
70
+
71
+ const missingKeys = []
72
+ const noLocaleUsage = []
73
+
74
+ for (const file of files) {
75
+ const content = fs.readFileSync(file, 'utf-8')
76
+
77
+ let scriptCode = content
78
+
79
+ if (file.endsWith('.vue')) {
80
+ const { descriptor } = parse(content)
81
+ scriptCode =
82
+ descriptor.scriptSetup?.content ||
83
+ descriptor.script?.content ||
84
+ ''
85
+ }
86
+
87
+ if (!scriptCode) continue
88
+
89
+ const ast = parseCode(scriptCode)
90
+
91
+ let usedLocale = false
92
+ const usedKeys = []
93
+
94
+ traverse(ast, {
95
+ ImportDeclaration(p) {
96
+ if (p.node.source.value === './locale') {
97
+ usedLocale = true
98
+ }
99
+ },
100
+
101
+ CallExpression(p) {
102
+ const callee = p.node.callee
103
+ if (
104
+ (callee.type === 'Identifier' && callee.name === 't') ||
105
+ (callee.type === 'Identifier' && callee.name === '$t')
106
+ ) {
107
+ const arg = p.node.arguments[0]
108
+ if (arg && arg.type === 'StringLiteral') {
109
+ usedKeys.push(arg.value)
110
+ }
111
+ }
112
+ },
113
+ })
114
+
115
+ if (!usedKeys.length) continue
116
+
117
+ const localePath = path.join(path.dirname(file), 'locale.ts')
118
+ const localeKeys = loadLocaleKeys(localePath)
119
+
120
+ if (!usedLocale || !localeKeys) {
121
+ noLocaleUsage.push({
122
+ file: path.relative(cwd, file),
123
+ keys: usedKeys,
124
+ })
125
+ continue
126
+ }
127
+
128
+ for (const key of usedKeys) {
129
+ if (!localeKeys.includes(key)) {
130
+ missingKeys.push({
131
+ file: path.relative(cwd, file),
132
+ key,
133
+ })
134
+ }
135
+ }
136
+ }
137
+
138
+ /* ---------------- output ---------------- */
139
+
140
+ if (!silent) {
141
+ if (missingKeys.length) {
142
+ console.error('\n❌ 缺失的国际化 key:')
143
+ missingKeys.forEach(i =>
144
+ console.error(` ${i.file} → ${i.key}`)
145
+ )
146
+ }
147
+
148
+ if (noLocaleUsage.length) {
149
+ console.warn('\n⚠️ 使用了 t/$t 但未使用 ./locale:')
150
+ noLocaleUsage.forEach(i =>
151
+ console.warn(` ${i.file}`)
152
+ )
153
+ }
154
+
155
+ if (!missingKeys.length && !noLocaleUsage.length) {
156
+ console.log('✅ views 国际化检查通过')
157
+ }
158
+ }
159
+
160
+ return {
161
+ success: missingKeys.length === 0,
162
+ missingKeys,
163
+ noLocaleUsage,
164
+ scannedFiles: files.length,
165
+ options, // 🔍 调试/日志时很有用
166
+ }
167
+ }
168
+
169
+ /* ---------------- exports ---------------- */
170
+
171
+ exports.run = run