@ai-i18n/eslint-plugin 1.0.0-alpha.5 → 1.0.0-alpha.6
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/README.md +90 -21
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +562 -70
- package/dist/index.js.map +1 -1
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
# @ai-i18n/eslint-plugin
|
|
2
2
|
|
|
3
|
-
用于提前报告无法被 Vite/Yuku
|
|
4
|
-
`virtual:ai-i18n` 的 `t` binding,以及 Vue/React 模式下
|
|
5
|
-
`t`。其他库或局部同名函数不受影响。
|
|
3
|
+
用于提前报告无法被 Vite/Yuku 静态提取、不符合推荐语法,或不会随语言切换刷新的 `t()`
|
|
4
|
+
用法。规则检查解析到 `virtual:ai-i18n` 的 `t` binding,以及 Vue/React 模式下
|
|
5
|
+
`useI18n()` 解构或对象成员得到的 `t`。其他库或局部同名函数不受影响。
|
|
6
6
|
|
|
7
7
|
alpha 阶段请安装 `@ai-i18n/eslint-plugin@alpha`;peer 支持 ESLint 9 和 10。
|
|
8
8
|
|
|
9
9
|
## 按模式配置
|
|
10
10
|
|
|
11
|
-
显式 import
|
|
12
|
-
Vite `framework` 一致的 preset;它会声明对应只读全局,并启用静态参数报错与候选数量
|
|
13
|
-
警告:
|
|
11
|
+
显式 import 时,Vanilla / React 使用 `recommended`,Vue 使用覆盖 SFC 的 `vue`:
|
|
14
12
|
|
|
15
13
|
```js
|
|
16
14
|
import aiI18n from '@ai-i18n/eslint-plugin';
|
|
17
15
|
|
|
18
16
|
export default [
|
|
19
|
-
...aiI18n.configs.
|
|
17
|
+
...aiI18n.configs.recommended, // Vue 改用 .vue
|
|
20
18
|
];
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
启用 `aiI18n({ autoImport: true })` 时,改用与 Vite 框架模式一致的自动导入 preset:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
export default [
|
|
25
|
+
...aiI18n.configs['vanilla-auto-import'],
|
|
26
|
+
// Vue:...aiI18n.configs['vue-auto-import']
|
|
27
|
+
// React:...aiI18n.configs['react-auto-import']
|
|
28
|
+
];
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
只有 `*-auto-import` preset 会声明 Runtime 全局。所有 preset 都声明只读的
|
|
24
32
|
`defineI18nMessages` 编译宏。
|
|
25
33
|
|
|
26
34
|
## Vue SFC
|
|
27
35
|
|
|
28
36
|
`.vue` 是可选文件格式。Vue 项目应先通过 `eslint-plugin-vue` 配置
|
|
29
|
-
`vue-eslint-parser
|
|
37
|
+
`vue-eslint-parser`,再按 Vite 的 `autoImport` 值选择 preset:
|
|
30
38
|
|
|
31
39
|
```js
|
|
32
40
|
import aiI18n from '@ai-i18n/eslint-plugin';
|
|
@@ -41,22 +49,61 @@ export default [
|
|
|
41
49
|
parserOptions: { parser: tseslint.parser },
|
|
42
50
|
},
|
|
43
51
|
},
|
|
44
|
-
...aiI18n.configs.vue,
|
|
52
|
+
...aiI18n.configs.vue, // autoImport: false
|
|
53
|
+
// ...aiI18n.configs['vue-auto-import'], // autoImport: true
|
|
45
54
|
];
|
|
46
55
|
```
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
不会安装到 React/Vanilla 项目。
|
|
57
|
+
两个 Vue preset 都复用宿主的 Vue parser,并启用四条适用规则。语义分析使用
|
|
58
|
+
Vue 项目已有的 `vue/compiler-sfc` Node 入口,与 Vite 提取器复用相同分析语义和
|
|
59
|
+
source-map 映射,覆盖 `<script>`、`<script setup>`、模板插值和指令表达式。Vue、
|
|
60
|
+
TypeScript 与 SFC 编译相关依赖均为可选 peer,不会安装到 React/Vanilla 项目。
|
|
53
61
|
|
|
54
62
|
Vue preset 同时覆盖 Vue JSX/TSX,但宿主仍需用 `@vitejs/plugin-vue-jsx` 编译这些文件。
|
|
55
|
-
|
|
63
|
+
同一个 Vite build 不支持两种框架模式混用。
|
|
64
|
+
|
|
65
|
+
## 生命周期检查
|
|
66
|
+
|
|
67
|
+
静态提取成功不代表字符串会自动刷新。所有 preset 都启用
|
|
68
|
+
`ai-i18n/no-eager-translation`:在模块或 `<script setup>` 初始化期间保存 `t()` 结果会
|
|
69
|
+
收到 warning。Vue SFC 的 `export default { setup() {} }`,以及 `.vue` / `.ts` / `.tsx`
|
|
70
|
+
中从 `vue` 导入的 `defineComponent({ setup() {} })` 和函数签名也按一次性 setup
|
|
71
|
+
初始化检查。
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
export const label = t('保存'); // warning:只保存初始化时的译文
|
|
75
|
+
export const getLabel = () => t('保存'); // 允许:每次调用重新读取当前语言
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`recommended`、`vue`、`vue-auto-import` 与 `react-auto-import` 还启用
|
|
79
|
+
`ai-i18n/no-unsubscribed-t`。Vue / React JSX 或 TSX 的组件渲染函数不能只调用 Runtime
|
|
80
|
+
顶层 `t`,应从 `useI18n()` 获取 `t` 来建立框架订阅。React Compiler 的 `"use memo"`
|
|
81
|
+
与 `"use no memo"` 都不会替代订阅。
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
function SaveButton() {
|
|
85
|
+
return <button>{t('保存')}</button>; // warning
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function SaveButton() {
|
|
89
|
+
const { t } = useI18n();
|
|
90
|
+
return <button>{t('保存')}</button>; // 允许
|
|
91
|
+
}
|
|
92
|
+
```
|
|
56
93
|
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
|
|
94
|
+
事件回调和普通延迟函数中的 Runtime `t` 允许,独立的 `console.log` / `warn` / `error` /
|
|
95
|
+
`info` / `debug` 调用也视为即时消费。其他未知调用仍会 warning。第一版不追踪跨函数、
|
|
96
|
+
跨文件或 `useMemo` / `useState` 等任意数据流,因此规则提示的是可以确定的常见问题,
|
|
97
|
+
不代表覆盖所有译文生命周期错误。两条规则都不提供自动修复。
|
|
98
|
+
|
|
99
|
+
四条规则可独立启用。分析无法启动时,同一文件只报告一次双语错误;官方 preset 由
|
|
100
|
+
`t-static-args` 优先报告,避免次级规则重复提示。
|
|
101
|
+
|
|
102
|
+
规则与 Vite 共用静态参数语义,包括从 `useI18n()` 获得的对象成员调用
|
|
103
|
+
`i18n.t()`、`i18n['t']()`、省略式 `t('source', undefined)` 和 tagged template。
|
|
104
|
+
Vue 模板必须在 `<script setup>` 中绑定 `useI18n()` 返回的 `t`;自动导入只省略 import,
|
|
105
|
+
不会自动合成 Hook。`vue-auto-import` 会把裸 template-only `t()` 作为 error;模板中已
|
|
106
|
+
绑定到 Runtime 顶层 `t` 的调用则由 `no-unsubscribed-t` warning。
|
|
60
107
|
|
|
61
108
|
对象或数组成员只有在根集合由 `defineI18nMessages()` 标记后才属于推荐写法。字符串拼接、
|
|
62
109
|
逻辑表达式、`let` 文案、普通集合成员、`const tr = t`、命名空间调用、二次 Hook 解构、
|
|
@@ -70,14 +117,32 @@ import aiI18n from '@ai-i18n/eslint-plugin';
|
|
|
70
117
|
export default [
|
|
71
118
|
{
|
|
72
119
|
languageOptions: {
|
|
73
|
-
globals: {
|
|
120
|
+
globals: {
|
|
121
|
+
t: 'readonly',
|
|
122
|
+
useI18n: 'readonly',
|
|
123
|
+
defineI18nMessages: 'readonly',
|
|
124
|
+
},
|
|
74
125
|
},
|
|
75
126
|
plugins: { 'ai-i18n': aiI18n },
|
|
76
127
|
rules: {
|
|
128
|
+
'ai-i18n/no-eager-translation': [
|
|
129
|
+
'warn',
|
|
130
|
+
{
|
|
131
|
+
autoImport: ['t', 'useI18n'],
|
|
132
|
+
tsconfigPath: './tsconfig.json',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
'ai-i18n/no-unsubscribed-t': [
|
|
136
|
+
'warn',
|
|
137
|
+
{
|
|
138
|
+
autoImport: ['t', 'useI18n'],
|
|
139
|
+
tsconfigPath: './tsconfig.json',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
77
142
|
'ai-i18n/static-candidate-limit': [
|
|
78
143
|
'warn',
|
|
79
144
|
{
|
|
80
|
-
autoImport:
|
|
145
|
+
autoImport: ['t', 'useI18n'],
|
|
81
146
|
tsconfigPath: './tsconfig.json',
|
|
82
147
|
maxStaticCandidates: 2_000,
|
|
83
148
|
},
|
|
@@ -85,6 +150,7 @@ export default [
|
|
|
85
150
|
'ai-i18n/t-static-args': [
|
|
86
151
|
'error',
|
|
87
152
|
{
|
|
153
|
+
autoImport: ['t', 'useI18n'],
|
|
88
154
|
tsconfigPath: './tsconfig.json',
|
|
89
155
|
},
|
|
90
156
|
],
|
|
@@ -93,6 +159,9 @@ export default [
|
|
|
93
159
|
];
|
|
94
160
|
```
|
|
95
161
|
|
|
162
|
+
上例匹配 Vue / React 模式;Vanilla 手工配置只使用 `autoImport: ['t']`,并声明对应的顶层
|
|
163
|
+
Runtime globals。日常接入优先使用预设,避免 Vite 与 ESLint 的 API 集合不一致。
|
|
164
|
+
|
|
96
165
|
`ai-i18n/static-candidate-limit` 默认在单个 `t()` 的 source 与 options 组合超过 1000 个
|
|
97
166
|
时警告。`maxStaticCandidates` 必须是正整数,只改变 ESLint 的提示阈值;Vite 提取不设
|
|
98
167
|
上限,也没有对应插件选项。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { ESLint, Rule } from "eslint";
|
|
2
|
+
//#region src/rules/no-eager-translation.d.ts
|
|
3
|
+
declare const noEagerTranslation: Rule.RuleModule;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/rules/no-unsubscribed-t.d.ts
|
|
6
|
+
declare const noUnsubscribedT: Rule.RuleModule;
|
|
7
|
+
//#endregion
|
|
2
8
|
//#region src/rules/static-candidate-limit.d.ts
|
|
3
9
|
declare const staticCandidateLimit: Rule.RuleModule;
|
|
4
10
|
//#endregion
|
|
@@ -8,5 +14,5 @@ declare const tStaticArgs: Rule.RuleModule;
|
|
|
8
14
|
//#region src/index.d.ts
|
|
9
15
|
declare const plugin: ESLint.Plugin;
|
|
10
16
|
//#endregion
|
|
11
|
-
export { plugin as default, staticCandidateLimit, tStaticArgs };
|
|
17
|
+
export { plugin as default, noEagerTranslation, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
|
|
12
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/rules/static-candidate-limit.ts","../src/rules/t-static-args.ts","../src/index.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/rules/no-eager-translation.ts","../src/rules/no-unsubscribed-t.ts","../src/rules/static-candidate-limit.ts","../src/rules/t-static-args.ts","../src/index.ts"],"mappings":";;cAea,oBAAoB,KAAK;;;cCOzB,iBAAiB,KAAK;;;cCTtB,sBAAsB,KAAK;;;cC6C3B,aAAa,KAAK;;;cC/CzB,QAAQ,OAAO"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { AI_I18N_VIRTUAL_MODULE_ID, Analyzer, diagnosticMessage, extractMessages, findInvalidDefineI18nMessagesReferences, findTranslationCalls, findUnboundCalls, validateRecommendedUsage } from "@ai-i18n/analyzer";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import { AI_I18N_VIRTUAL_MODULE_ID, Analyzer, diagnosticMessage, extractMessages, findInvalidDefineI18nMessagesReferences, findUnboundCalls, validateRecommendedUsage } from "@ai-i18n/analyzer";
|
|
5
5
|
import { analyzeVueSource } from "@ai-i18n/analyzer/vue";
|
|
6
6
|
//#region src/resolve-import.ts
|
|
7
7
|
const SOURCE_EXTENSIONS = [
|
|
@@ -15,6 +15,11 @@ const SOURCE_EXTENSIONS = [
|
|
|
15
15
|
".mjs",
|
|
16
16
|
".cjs"
|
|
17
17
|
];
|
|
18
|
+
const DIRECTORY_STAMP_TTL_MS = 250;
|
|
19
|
+
const MAX_PROBE_CACHE_ENTRIES = 1e4;
|
|
20
|
+
const tsconfigCache = /* @__PURE__ */ new Map();
|
|
21
|
+
const directoryStampCache = /* @__PURE__ */ new Map();
|
|
22
|
+
const probeCache = /* @__PURE__ */ new Map();
|
|
18
23
|
function createImportResolver(tsconfigPath) {
|
|
19
24
|
const aliases = [];
|
|
20
25
|
let baseUrl = process.cwd();
|
|
@@ -47,15 +52,31 @@ function createImportResolver(tsconfigPath) {
|
|
|
47
52
|
};
|
|
48
53
|
}
|
|
49
54
|
function probeSource(candidate) {
|
|
55
|
+
candidate = path.normalize(candidate);
|
|
56
|
+
const cached = probeCache.get(candidate);
|
|
57
|
+
if (cached && cached.parentDirectory === readDirectoryStamp(path.dirname(candidate)) && cached.candidateDirectory === readDirectoryStamp(candidate)) return cached.resolved;
|
|
58
|
+
let resolved = null;
|
|
50
59
|
for (const extension of SOURCE_EXTENSIONS) {
|
|
51
60
|
const file = `${candidate}${extension}`;
|
|
52
|
-
if (isFile(file))
|
|
61
|
+
if (isFile(file)) {
|
|
62
|
+
resolved = path.normalize(file);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
|
-
for (const extension of SOURCE_EXTENSIONS.slice(1)) {
|
|
66
|
+
if (!resolved) for (const extension of SOURCE_EXTENSIONS.slice(1)) {
|
|
55
67
|
const file = path.join(candidate, `index${extension}`);
|
|
56
|
-
if (isFile(file))
|
|
68
|
+
if (isFile(file)) {
|
|
69
|
+
resolved = path.normalize(file);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
57
72
|
}
|
|
58
|
-
|
|
73
|
+
if (probeCache.size >= MAX_PROBE_CACHE_ENTRIES) probeCache.clear();
|
|
74
|
+
probeCache.set(candidate, {
|
|
75
|
+
parentDirectory: readDirectoryStamp(path.dirname(candidate)),
|
|
76
|
+
candidateDirectory: readDirectoryStamp(candidate),
|
|
77
|
+
resolved
|
|
78
|
+
});
|
|
79
|
+
return resolved;
|
|
59
80
|
}
|
|
60
81
|
function isFile(file) {
|
|
61
82
|
try {
|
|
@@ -65,12 +86,43 @@ function isFile(file) {
|
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
88
|
function readTsConfig(filename) {
|
|
89
|
+
const stamp = readFileStamp(filename);
|
|
90
|
+
const cached = tsconfigCache.get(filename);
|
|
91
|
+
if (cached?.stamp === stamp) return cached.value;
|
|
92
|
+
let value = {};
|
|
93
|
+
try {
|
|
94
|
+
value = JSON.parse(stripJsonComments(fs.readFileSync(filename, "utf8")));
|
|
95
|
+
} catch {}
|
|
96
|
+
tsconfigCache.set(filename, {
|
|
97
|
+
stamp,
|
|
98
|
+
value
|
|
99
|
+
});
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function readFileStamp(filename) {
|
|
68
103
|
try {
|
|
69
|
-
|
|
104
|
+
const stat = fs.statSync(filename);
|
|
105
|
+
return stat.isFile() ? `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}` : null;
|
|
70
106
|
} catch {
|
|
71
|
-
return
|
|
107
|
+
return null;
|
|
72
108
|
}
|
|
73
109
|
}
|
|
110
|
+
function readDirectoryStamp(directory) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const cached = directoryStampCache.get(directory);
|
|
113
|
+
if (cached && cached.expiresAt > now) return cached.value;
|
|
114
|
+
let value = null;
|
|
115
|
+
try {
|
|
116
|
+
const stat = fs.statSync(directory);
|
|
117
|
+
if (stat.isDirectory()) value = `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;
|
|
118
|
+
} catch {}
|
|
119
|
+
if (directoryStampCache.size >= MAX_PROBE_CACHE_ENTRIES) directoryStampCache.clear();
|
|
120
|
+
directoryStampCache.set(directory, {
|
|
121
|
+
expiresAt: now + DIRECTORY_STAMP_TTL_MS,
|
|
122
|
+
value
|
|
123
|
+
});
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
74
126
|
function stripJsonComments(source) {
|
|
75
127
|
let result = "";
|
|
76
128
|
let quote = "";
|
|
@@ -110,37 +162,66 @@ function stripJsonComments(source) {
|
|
|
110
162
|
}
|
|
111
163
|
//#endregion
|
|
112
164
|
//#region src/analyze.ts
|
|
113
|
-
|
|
165
|
+
const POTENTIAL_TRANSLATION_RE = /virtual:ai-i18n|\b(?:t|useI18n|defineI18nMessages)\b/;
|
|
166
|
+
function analyzeStaticSource(code, filename, tsconfigPath, lang, autoImport = false, maxStaticCandidates = Number.POSITIVE_INFINITY) {
|
|
167
|
+
if (!hasPotentialTranslationCandidate(code)) return {
|
|
168
|
+
warnings: [],
|
|
169
|
+
translationCalls: []
|
|
170
|
+
};
|
|
171
|
+
const autoImports = normalizeAutoImports(autoImport);
|
|
114
172
|
const resolve = createImportResolver(tsconfigPath);
|
|
115
173
|
const analyzer = new Analyzer({ resolve });
|
|
116
174
|
analyzer.addFile(AI_I18N_VIRTUAL_MODULE_ID, "export function t(source) { return source }");
|
|
117
175
|
const entryPath = normalizeFilename(filename);
|
|
118
176
|
const entry = analyzer.addFile(entryPath, code, lang ? { lang } : void 0);
|
|
119
|
-
if (!hasTranslationCandidate(entry,
|
|
177
|
+
if (!hasTranslationCandidate(entry, autoImports)) return {
|
|
178
|
+
warnings: [],
|
|
179
|
+
translationCalls: []
|
|
180
|
+
};
|
|
120
181
|
loadDependencies(analyzer, entry, resolve);
|
|
121
182
|
analyzer.link();
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
line,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
183
|
+
const hooks = translationHooks(autoImports);
|
|
184
|
+
const extraction = extractMessages(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, autoImports.t, maxStaticCandidates).warnings;
|
|
185
|
+
const recommended = validateRecommendedUsage(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, autoImports.t);
|
|
186
|
+
return {
|
|
187
|
+
warnings: (recommended.length ? [
|
|
188
|
+
...extraction.filter((warning) => warning.code === "parse-error"),
|
|
189
|
+
...extraction.filter((warning) => warning.code === "static-candidate-limit"),
|
|
190
|
+
...recommended
|
|
191
|
+
] : extraction).map(({ code: warningCode, line, column, message }) => ({
|
|
192
|
+
code: warningCode,
|
|
193
|
+
line,
|
|
194
|
+
column,
|
|
195
|
+
message
|
|
196
|
+
})),
|
|
197
|
+
translationCalls: findTranslationCalls(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, autoImports.t)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function hasPotentialTranslationCandidate(code) {
|
|
201
|
+
return POTENTIAL_TRANSLATION_RE.test(code);
|
|
202
|
+
}
|
|
203
|
+
function normalizeAutoImports(autoImport) {
|
|
204
|
+
if (typeof autoImport === "boolean") return {
|
|
205
|
+
t: autoImport,
|
|
206
|
+
useI18n: autoImport
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
t: autoImport?.includes("t") ?? false,
|
|
210
|
+
useI18n: autoImport?.includes("useI18n") ?? false
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function hasTranslationCandidate(module, autoImports) {
|
|
214
|
+
const unbound = ["defineI18nMessages"];
|
|
215
|
+
if (autoImports.t) unbound.push("t");
|
|
216
|
+
if (autoImports.useI18n) unbound.push("useI18n");
|
|
217
|
+
return module.imports.some((item) => !item.typeOnly && (item.specifier === AI_I18N_VIRTUAL_MODULE_ID || item.name === "t" || item.name === "useI18n")) || findInvalidDefineI18nMessagesReferences(module).length > 0 || findUnboundCalls(module, new Set(unbound)).length > 0;
|
|
218
|
+
}
|
|
219
|
+
function translationHooks(autoImports) {
|
|
139
220
|
return [{
|
|
140
221
|
module: AI_I18N_VIRTUAL_MODULE_ID,
|
|
141
222
|
hook: "useI18n",
|
|
142
223
|
property: "t",
|
|
143
|
-
autoImport
|
|
224
|
+
autoImport: autoImports.useI18n
|
|
144
225
|
}];
|
|
145
226
|
}
|
|
146
227
|
function loadDependencies(analyzer, entry, resolve) {
|
|
@@ -168,47 +249,98 @@ function normalizeFilename(filename) {
|
|
|
168
249
|
}
|
|
169
250
|
//#endregion
|
|
170
251
|
//#region src/vue-sfc.ts
|
|
171
|
-
const
|
|
252
|
+
const packageRequire = createRequire(import.meta.url);
|
|
172
253
|
function createVueAnalysisSource(source, filename, parserServices) {
|
|
173
254
|
if (!parserServices.getDocumentFragment?.()) throw new Error(diagnosticMessage("检查 .vue 文件需要配置 vue-eslint-parser。", "Configure vue-eslint-parser to lint .vue files."));
|
|
174
255
|
try {
|
|
175
|
-
return analyzeVueSource(source, filename,
|
|
256
|
+
return analyzeVueSource(source, filename, loadVueCompiler(filename));
|
|
176
257
|
} catch (error) {
|
|
177
258
|
if (isMissingVueCompiler(error)) throw new Error(diagnosticMessage("检查 .vue 文件需要安装 @vue/compiler-sfc。", "Install @vue/compiler-sfc to lint .vue files."));
|
|
178
259
|
throw error;
|
|
179
260
|
}
|
|
180
261
|
}
|
|
262
|
+
function loadVueCompiler(filename) {
|
|
263
|
+
const hostRequire = createRequire(path.resolve(filename));
|
|
264
|
+
const compiler = tryRequire(hostRequire, "vue/compiler-sfc") ?? tryRequire(packageRequire, "vue/compiler-sfc") ?? tryRequire(hostRequire, "@vue/compiler-sfc") ?? packageRequire("@vue/compiler-sfc");
|
|
265
|
+
compiler.registerTS?.(() => {
|
|
266
|
+
const typescript = tryResolve(hostRequire, "typescript") ?? tryResolve(packageRequire, "typescript");
|
|
267
|
+
return packageRequire(typescript ?? "typescript");
|
|
268
|
+
});
|
|
269
|
+
return compiler;
|
|
270
|
+
}
|
|
271
|
+
function tryRequire(require, id) {
|
|
272
|
+
const resolved = tryResolve(require, id);
|
|
273
|
+
return resolved ? require(resolved) : null;
|
|
274
|
+
}
|
|
275
|
+
function tryResolve(require, id) {
|
|
276
|
+
try {
|
|
277
|
+
return require.resolve(id);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (isMissingVueCompiler(error)) return null;
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
181
283
|
function isMissingVueCompiler(error) {
|
|
182
|
-
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
284
|
+
return error instanceof Error && "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
|
|
183
285
|
}
|
|
184
286
|
//#endregion
|
|
185
287
|
//#region src/rule-analysis.ts
|
|
186
288
|
const DEFAULT_CANDIDATE_LIMIT = 1e3;
|
|
187
289
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
290
|
+
const reportedAnalysisFailures = /* @__PURE__ */ new WeakSet();
|
|
291
|
+
const EMPTY_ANALYSIS = {
|
|
292
|
+
warnings: [],
|
|
293
|
+
translationCalls: []
|
|
294
|
+
};
|
|
188
295
|
function analyzeRuleContext(context, options, maxStaticCandidates = Number.POSITIVE_INFINITY) {
|
|
296
|
+
return analyzeRuleContextResult(context, options, maxStaticCandidates).warnings;
|
|
297
|
+
}
|
|
298
|
+
function analyzeTranslationCalls(context, options) {
|
|
299
|
+
return analyzeRuleContextResult(context, options, DEFAULT_CANDIDATE_LIMIT).translationCalls;
|
|
300
|
+
}
|
|
301
|
+
function reportAnalysisFailureOnce(context, node, error) {
|
|
302
|
+
if (reportedAnalysisFailures.has(context.sourceCode)) return;
|
|
303
|
+
reportedAnalysisFailures.add(context.sourceCode);
|
|
304
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
305
|
+
context.report({
|
|
306
|
+
node,
|
|
307
|
+
message: diagnosticMessage(`静态分析失败:${detail}`, `Static analysis failed: ${detail}`)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function analyzeRuleContextResult(context, options, maxStaticCandidates) {
|
|
311
|
+
if (!hasPotentialTranslationCandidate(context.sourceCode.text)) return EMPTY_ANALYSIS;
|
|
189
312
|
const cached = cache.get(context.sourceCode) ?? {
|
|
190
313
|
source: createAnalysisSource(context),
|
|
191
|
-
|
|
314
|
+
analyses: /* @__PURE__ */ new Map()
|
|
192
315
|
};
|
|
193
316
|
cache.set(context.sourceCode, cached);
|
|
194
317
|
const analyze = (limit) => {
|
|
318
|
+
const autoImports = normalizeAutoImports(options.autoImport);
|
|
195
319
|
const key = JSON.stringify([
|
|
196
320
|
options.tsconfigPath ?? null,
|
|
197
|
-
|
|
321
|
+
autoImports.t,
|
|
322
|
+
autoImports.useI18n,
|
|
198
323
|
limit
|
|
199
324
|
]);
|
|
200
|
-
const existing = cached.
|
|
325
|
+
const existing = cached.analyses.get(key);
|
|
201
326
|
if (existing) return existing;
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
327
|
+
const result = analyzeStaticSource(cached.source.code, context.filename, options.tsconfigPath, cached.source.lang, options.autoImport, limit);
|
|
328
|
+
const analysis = {
|
|
329
|
+
warnings: result.warnings.map((warning) => ({
|
|
330
|
+
...warning,
|
|
331
|
+
...cached.source.mapLocation(warning)
|
|
332
|
+
})),
|
|
333
|
+
translationCalls: result.translationCalls.map((call) => ({
|
|
334
|
+
...call,
|
|
335
|
+
...cached.source.mapLocation(call)
|
|
336
|
+
}))
|
|
337
|
+
};
|
|
338
|
+
cached.analyses.set(key, analysis);
|
|
339
|
+
return analysis;
|
|
208
340
|
};
|
|
209
341
|
if (Number.isFinite(maxStaticCandidates)) return analyze(maxStaticCandidates);
|
|
210
342
|
const preflight = analyze(DEFAULT_CANDIDATE_LIMIT);
|
|
211
|
-
return preflight.some((warning) => warning.code === "static-candidate-limit") ? analyze(Number.POSITIVE_INFINITY) : preflight;
|
|
343
|
+
return preflight.warnings.some((warning) => warning.code === "static-candidate-limit") ? analyze(Number.POSITIVE_INFINITY) : preflight;
|
|
212
344
|
}
|
|
213
345
|
function createAnalysisSource(context) {
|
|
214
346
|
return context.filename.endsWith(".vue") ? createVueAnalysisSource(context.sourceCode.text, context.filename, context.sourceCode.parserServices) : {
|
|
@@ -218,16 +350,288 @@ function createAnalysisSource(context) {
|
|
|
218
350
|
};
|
|
219
351
|
}
|
|
220
352
|
//#endregion
|
|
353
|
+
//#region src/rules/translation-call.ts
|
|
354
|
+
function matchTranslationCalls(context, options, candidates) {
|
|
355
|
+
const calls = new Map(analyzeTranslationCalls(context, options).map((call) => [locationKey(call.line, call.column, call.kind), call]));
|
|
356
|
+
return candidates.flatMap((candidate) => {
|
|
357
|
+
const location = candidate.node.loc?.start;
|
|
358
|
+
if (!location) return [];
|
|
359
|
+
const call = calls.get(locationKey(location.line, location.column, candidate.kind));
|
|
360
|
+
return call ? [{
|
|
361
|
+
...candidate,
|
|
362
|
+
call
|
|
363
|
+
}] : [];
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function locationKey(line, column, kind) {
|
|
367
|
+
return `${kind}:${line}:${column}`;
|
|
368
|
+
}
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/rules/no-eager-translation.ts
|
|
371
|
+
const FUNCTION_TYPES$1 = /* @__PURE__ */ new Set([
|
|
372
|
+
"ArrowFunctionExpression",
|
|
373
|
+
"FunctionDeclaration",
|
|
374
|
+
"FunctionExpression"
|
|
375
|
+
]);
|
|
376
|
+
const noEagerTranslation = {
|
|
377
|
+
meta: {
|
|
378
|
+
type: "problem",
|
|
379
|
+
docs: { description: "警告不会随语言切换更新的提前求值翻译结果" },
|
|
380
|
+
schema: [{
|
|
381
|
+
type: "object",
|
|
382
|
+
properties: {
|
|
383
|
+
tsconfigPath: { type: "string" },
|
|
384
|
+
autoImport: { anyOf: [{ type: "boolean" }, {
|
|
385
|
+
type: "array",
|
|
386
|
+
items: { enum: ["t", "useI18n"] },
|
|
387
|
+
uniqueItems: true
|
|
388
|
+
}] }
|
|
389
|
+
},
|
|
390
|
+
additionalProperties: false
|
|
391
|
+
}],
|
|
392
|
+
messages: { eagerTranslation: diagnosticMessage("初始化期间保存的 t() 结果不会随语言切换更新。请改为函数或 Getter,在使用时调用 t();组件视图请使用 useI18n()。", "A t() result stored during initialization will not update when the language changes. Evaluate it lazily in a function or getter; use useI18n() in component views.") }
|
|
393
|
+
},
|
|
394
|
+
create(context) {
|
|
395
|
+
const options = context.options[0] ?? {};
|
|
396
|
+
const isVueSfc = context.filename.toLowerCase().endsWith(".vue");
|
|
397
|
+
const candidates = [];
|
|
398
|
+
return {
|
|
399
|
+
CallExpression(node) {
|
|
400
|
+
candidates.push({
|
|
401
|
+
kind: "call",
|
|
402
|
+
node
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
TaggedTemplateExpression(node) {
|
|
406
|
+
candidates.push({
|
|
407
|
+
kind: "tagged-template",
|
|
408
|
+
node
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
"Program:exit"(program) {
|
|
412
|
+
let matches;
|
|
413
|
+
try {
|
|
414
|
+
matches = matchTranslationCalls(context, options, candidates);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
reportAnalysisFailureOnce(context, program, error);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
for (const { node } of matches) {
|
|
420
|
+
if (!storesOutsideFunction(node, context, isVueSfc)) continue;
|
|
421
|
+
context.report({
|
|
422
|
+
node,
|
|
423
|
+
messageId: "eagerTranslation"
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
function storesOutsideFunction(node, context, isVueSfc) {
|
|
431
|
+
let current = node;
|
|
432
|
+
let storesResult = false;
|
|
433
|
+
while (current.parent) {
|
|
434
|
+
const parent = current.parent;
|
|
435
|
+
if (FUNCTION_TYPES$1.has(parent.type)) {
|
|
436
|
+
const returnsExpression = parent.type === "ArrowFunctionExpression" && parent.body === current;
|
|
437
|
+
return isVueComponentSetup(parent, context, isVueSfc) && (storesResult || returnsExpression);
|
|
438
|
+
}
|
|
439
|
+
if (storesTranslationResult(parent, current)) storesResult = true;
|
|
440
|
+
if (parent.type === "Program") return storesResult;
|
|
441
|
+
current = parent;
|
|
442
|
+
}
|
|
443
|
+
return storesResult;
|
|
444
|
+
}
|
|
445
|
+
function isVueComponentSetup(node, context, isVueSfc) {
|
|
446
|
+
const directOwner = node.parent;
|
|
447
|
+
if (directOwner?.type === "CallExpression" && directOwner.arguments?.[0] === node) return isImportedDefineComponent(directOwner.callee, context);
|
|
448
|
+
const property = node.parent;
|
|
449
|
+
if (property?.type !== "Property" || property.value !== node || property.computed || propertyName(property.key) !== "setup") return false;
|
|
450
|
+
const options = property.parent;
|
|
451
|
+
if (options?.type !== "ObjectExpression") return false;
|
|
452
|
+
const owner = options.parent;
|
|
453
|
+
if (isVueSfc && owner?.type === "ExportDefaultDeclaration" && owner.declaration === options) return true;
|
|
454
|
+
return owner?.type === "CallExpression" && owner.arguments?.[0] === options && isImportedDefineComponent(owner.callee, context);
|
|
455
|
+
}
|
|
456
|
+
function propertyName(node) {
|
|
457
|
+
const key = node;
|
|
458
|
+
if (key?.type === "Identifier") return key.name;
|
|
459
|
+
return key?.type === "Literal" && typeof key.value === "string" ? key.value : void 0;
|
|
460
|
+
}
|
|
461
|
+
function isImportedDefineComponent(node, context) {
|
|
462
|
+
const callee = node;
|
|
463
|
+
if (callee?.type === "Identifier") return isVueImport(callee, context, "ImportSpecifier");
|
|
464
|
+
return callee?.type === "MemberExpression" && !callee.computed && propertyName(callee.property) === "defineComponent" && isVueImport(callee.object, context, "ImportNamespaceSpecifier");
|
|
465
|
+
}
|
|
466
|
+
function isVueImport(node, context, specifierType) {
|
|
467
|
+
const identifier = node;
|
|
468
|
+
if (identifier?.type !== "Identifier" || !identifier.name) return false;
|
|
469
|
+
let scope = context.sourceCode.getScope(identifier);
|
|
470
|
+
while (true) {
|
|
471
|
+
const variable = scope.set.get(identifier.name);
|
|
472
|
+
if (!variable) {
|
|
473
|
+
if (!scope.upper) return false;
|
|
474
|
+
scope = scope.upper;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
return variable.defs.some((definition) => {
|
|
478
|
+
if (definition.type !== "ImportBinding" || definition.node.type !== specifierType || definition.parent.source.value !== "vue") return false;
|
|
479
|
+
return specifierType === "ImportNamespaceSpecifier" || propertyName(definition.node.imported) === "defineComponent";
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function storesTranslationResult(parent, child) {
|
|
484
|
+
if (parent.type === "AccessorProperty" && parent.value === child) return true;
|
|
485
|
+
switch (parent.type) {
|
|
486
|
+
case "VariableDeclarator": return parent.init === child;
|
|
487
|
+
case "AssignmentExpression":
|
|
488
|
+
case "AssignmentPattern": return parent.right === child;
|
|
489
|
+
case "PropertyDefinition": return parent.value === child;
|
|
490
|
+
case "ReturnStatement": return parent.argument === child;
|
|
491
|
+
case "ExportDefaultDeclaration": return parent.declaration === child;
|
|
492
|
+
default: return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/rules/no-unsubscribed-t.ts
|
|
497
|
+
const FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
498
|
+
"ArrowFunctionExpression",
|
|
499
|
+
"FunctionDeclaration",
|
|
500
|
+
"FunctionExpression"
|
|
501
|
+
]);
|
|
502
|
+
const IMMEDIATE_CONSOLE_METHODS = /* @__PURE__ */ new Set([
|
|
503
|
+
"debug",
|
|
504
|
+
"error",
|
|
505
|
+
"info",
|
|
506
|
+
"log",
|
|
507
|
+
"warn"
|
|
508
|
+
]);
|
|
509
|
+
const noUnsubscribedT = {
|
|
510
|
+
meta: {
|
|
511
|
+
type: "problem",
|
|
512
|
+
docs: { description: "警告组件渲染路径中未订阅语言状态的顶层 t" },
|
|
513
|
+
schema: [{
|
|
514
|
+
type: "object",
|
|
515
|
+
properties: {
|
|
516
|
+
tsconfigPath: { type: "string" },
|
|
517
|
+
autoImport: { anyOf: [{ type: "boolean" }, {
|
|
518
|
+
type: "array",
|
|
519
|
+
items: { enum: ["t", "useI18n"] },
|
|
520
|
+
uniqueItems: true
|
|
521
|
+
}] }
|
|
522
|
+
},
|
|
523
|
+
additionalProperties: false
|
|
524
|
+
}],
|
|
525
|
+
messages: { unsubscribedT: diagnosticMessage("组件渲染期间使用顶层 t 不会订阅语言状态,语言切换不会主动刷新,缓存的渲染结果也可能继续使用旧译文。请使用 useI18n() 返回的 t。", "Top-level t does not subscribe the component to language updates, so language changes do not trigger a render and cached render results may remain stale. Use the t returned by useI18n().") }
|
|
526
|
+
},
|
|
527
|
+
create(context) {
|
|
528
|
+
const options = context.options[0] ?? {};
|
|
529
|
+
const candidates = [];
|
|
530
|
+
const jsxOwners = /* @__PURE__ */ new Set();
|
|
531
|
+
const templateNodes = /* @__PURE__ */ new Set();
|
|
532
|
+
const collectJsxOwner = (node) => {
|
|
533
|
+
const owner = nearestFunction(node);
|
|
534
|
+
if (owner) jsxOwners.add(owner);
|
|
535
|
+
};
|
|
536
|
+
const scriptVisitor = {
|
|
537
|
+
CallExpression(node) {
|
|
538
|
+
candidates.push({
|
|
539
|
+
kind: "call",
|
|
540
|
+
node
|
|
541
|
+
});
|
|
542
|
+
},
|
|
543
|
+
TaggedTemplateExpression(node) {
|
|
544
|
+
candidates.push({
|
|
545
|
+
kind: "tagged-template",
|
|
546
|
+
node
|
|
547
|
+
});
|
|
548
|
+
},
|
|
549
|
+
JSXElement: collectJsxOwner,
|
|
550
|
+
JSXFragment: collectJsxOwner,
|
|
551
|
+
"Program:exit"(program) {
|
|
552
|
+
let matches;
|
|
553
|
+
try {
|
|
554
|
+
matches = matchTranslationCalls(context, options, candidates);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
reportAnalysisFailureOnce(context, program, error);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
for (const { call, node } of matches) {
|
|
560
|
+
if (call.origin !== "runtime") continue;
|
|
561
|
+
if (templateNodes.has(node)) {
|
|
562
|
+
if (!isVueTemplateEventHandler(node)) context.report({
|
|
563
|
+
node,
|
|
564
|
+
messageId: "unsubscribedT"
|
|
565
|
+
});
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const owner = nearestFunction(node);
|
|
569
|
+
if (!owner || !jsxOwners.has(owner) || isImmediateConsoleEffect(node)) continue;
|
|
570
|
+
context.report({
|
|
571
|
+
node,
|
|
572
|
+
messageId: "unsubscribedT"
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
const parserServices = context.sourceCode.parserServices;
|
|
578
|
+
if (!parserServices.defineTemplateBodyVisitor) return scriptVisitor;
|
|
579
|
+
return parserServices.defineTemplateBodyVisitor({
|
|
580
|
+
CallExpression(node) {
|
|
581
|
+
candidates.push({
|
|
582
|
+
kind: "call",
|
|
583
|
+
node
|
|
584
|
+
});
|
|
585
|
+
templateNodes.add(node);
|
|
586
|
+
},
|
|
587
|
+
TaggedTemplateExpression(node) {
|
|
588
|
+
candidates.push({
|
|
589
|
+
kind: "tagged-template",
|
|
590
|
+
node
|
|
591
|
+
});
|
|
592
|
+
templateNodes.add(node);
|
|
593
|
+
}
|
|
594
|
+
}, scriptVisitor, { templateBodyTriggerSelector: "Program" });
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
function nearestFunction(node) {
|
|
598
|
+
let current = node;
|
|
599
|
+
while (current.parent) {
|
|
600
|
+
current = current.parent;
|
|
601
|
+
if (FUNCTION_TYPES.has(current.type)) return current;
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
function isImmediateConsoleEffect(node) {
|
|
606
|
+
const call = node.parent;
|
|
607
|
+
if (call?.type !== "CallExpression" || call.parent?.type !== "ExpressionStatement" || !call.arguments.some((argument) => argument === node) || call.callee.type !== "MemberExpression" || call.callee.object.type !== "Identifier" || call.callee.object.name !== "console") return false;
|
|
608
|
+
const property = call.callee.property;
|
|
609
|
+
const method = !call.callee.computed && property.type === "Identifier" ? property.name : call.callee.computed && property.type === "Literal" && typeof property.value === "string" ? property.value : null;
|
|
610
|
+
return method !== null && IMMEDIATE_CONSOLE_METHODS.has(method);
|
|
611
|
+
}
|
|
612
|
+
function isVueTemplateEventHandler(node) {
|
|
613
|
+
let current = node;
|
|
614
|
+
while (current.parent) {
|
|
615
|
+
current = current.parent;
|
|
616
|
+
if (current.type === "VOnExpression") return true;
|
|
617
|
+
}
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
//#endregion
|
|
221
621
|
//#region src/rules/static-candidate-limit.ts
|
|
222
622
|
const staticCandidateLimit = {
|
|
223
623
|
meta: {
|
|
224
|
-
type: "
|
|
624
|
+
type: "suggestion",
|
|
225
625
|
docs: { description: "警告单个 t() 展开的静态候选数量过多" },
|
|
226
626
|
schema: [{
|
|
227
627
|
type: "object",
|
|
228
628
|
properties: {
|
|
229
629
|
tsconfigPath: { type: "string" },
|
|
230
|
-
autoImport: { type: "boolean" },
|
|
630
|
+
autoImport: { anyOf: [{ type: "boolean" }, {
|
|
631
|
+
type: "array",
|
|
632
|
+
items: { enum: ["t", "useI18n"] },
|
|
633
|
+
uniqueItems: true
|
|
634
|
+
}] },
|
|
231
635
|
maxStaticCandidates: {
|
|
232
636
|
type: "integer",
|
|
233
637
|
minimum: 1,
|
|
@@ -258,7 +662,9 @@ const staticCandidateLimit = {
|
|
|
258
662
|
data: { reason: warning.message }
|
|
259
663
|
});
|
|
260
664
|
}
|
|
261
|
-
} catch {
|
|
665
|
+
} catch (error) {
|
|
666
|
+
reportAnalysisFailureOnce(context, node, error);
|
|
667
|
+
}
|
|
262
668
|
} };
|
|
263
669
|
}
|
|
264
670
|
};
|
|
@@ -272,28 +678,29 @@ const tStaticArgs = {
|
|
|
272
678
|
type: "object",
|
|
273
679
|
properties: {
|
|
274
680
|
tsconfigPath: { type: "string" },
|
|
275
|
-
autoImport: { type: "boolean" }
|
|
681
|
+
autoImport: { anyOf: [{ type: "boolean" }, {
|
|
682
|
+
type: "array",
|
|
683
|
+
items: { enum: ["t", "useI18n"] },
|
|
684
|
+
uniqueItems: true
|
|
685
|
+
}] }
|
|
276
686
|
},
|
|
277
687
|
additionalProperties: false
|
|
278
688
|
}],
|
|
279
689
|
messages: {
|
|
280
690
|
analysisFailed: "{{reason}}",
|
|
281
691
|
dynamicArg: "{{reason}}",
|
|
282
|
-
invalidUsage: "{{reason}}"
|
|
692
|
+
invalidUsage: "{{reason}}",
|
|
693
|
+
templateNeedsI18n: "{{reason}}"
|
|
283
694
|
}
|
|
284
695
|
},
|
|
285
696
|
create(context) {
|
|
286
697
|
const options = context.options[0] ?? {};
|
|
287
|
-
|
|
698
|
+
const scriptVisitor = { "Program:exit"(node) {
|
|
288
699
|
let warnings;
|
|
289
700
|
try {
|
|
290
701
|
warnings = analyzeRuleContext(context, options);
|
|
291
702
|
} catch (error) {
|
|
292
|
-
context
|
|
293
|
-
node,
|
|
294
|
-
messageId: "analysisFailed",
|
|
295
|
-
data: { reason: diagnosticMessage(`静态分析失败:${error instanceof Error ? error.message : String(error)}`, `Static analysis failed: ${error instanceof Error ? error.message : String(error)}`) }
|
|
296
|
-
});
|
|
703
|
+
reportAnalysisFailureOnce(context, node, error);
|
|
297
704
|
return;
|
|
298
705
|
}
|
|
299
706
|
for (const warning of warnings) {
|
|
@@ -309,12 +716,74 @@ const tStaticArgs = {
|
|
|
309
716
|
}
|
|
310
717
|
},
|
|
311
718
|
messageId: analysisFailed ? "analysisFailed" : invalidUsage ? "invalidUsage" : "dynamicArg",
|
|
312
|
-
data: { reason: analysisFailed ? diagnosticMessage(`静态分析失败:${warning.message}`, `Static analysis failed: ${warning.message}`) : invalidUsage ? warning.message : diagnosticMessage("t() 的参数无法静态提取。source 请使用静态字符串,options 请使用只包含
|
|
719
|
+
data: { reason: analysisFailed ? diagnosticMessage(`静态分析失败:${warning.message}`, `Static analysis failed: ${warning.message}`) : invalidUsage ? warning.message : diagnosticMessage("t() 的参数无法静态提取。source 请使用静态字符串,options 请使用只包含 comment 的静态对象。", "The t() arguments cannot be statically extracted. Use a static string for source and a static object containing only comment for options.") }
|
|
313
720
|
});
|
|
314
721
|
}
|
|
315
722
|
} };
|
|
723
|
+
if (!context.filename.toLowerCase().endsWith(".vue") || !normalizeAutoImports(options.autoImport).t) return scriptVisitor;
|
|
724
|
+
const services = context.sourceCode.parserServices;
|
|
725
|
+
const document = services.getDocumentFragment?.();
|
|
726
|
+
const scripts = document?.children?.filter(isScriptElement) ?? [];
|
|
727
|
+
const setupScript = scripts.find(isScriptSetup);
|
|
728
|
+
if (!setupScript && scripts.length > 0 || !document) return scriptVisitor;
|
|
729
|
+
if (setupScript && hasTopLevelBinding(context.sourceCode.ast, setupScript, "t")) return scriptVisitor;
|
|
730
|
+
if (!services.defineTemplateBodyVisitor) return scriptVisitor;
|
|
731
|
+
const reportTemplateT = (node) => {
|
|
732
|
+
const target = node.type === "CallExpression" ? node.callee : node.tag;
|
|
733
|
+
if (target?.type !== "Identifier" || target.name !== "t" || isTemplateLocal(target)) return;
|
|
734
|
+
context.report({
|
|
735
|
+
node: target,
|
|
736
|
+
messageId: "templateNeedsI18n",
|
|
737
|
+
data: { reason: diagnosticMessage("模板中的裸 t 无法绑定到 ai-i18n,也不会订阅语言变化。请在 <script setup> 中声明 const { t } = useI18n()。", "A bare t in the template is not bound to ai-i18n and does not subscribe to language changes. Declare const { t } = useI18n() in <script setup>.") }
|
|
738
|
+
});
|
|
739
|
+
};
|
|
740
|
+
return services.defineTemplateBodyVisitor({
|
|
741
|
+
CallExpression: reportTemplateT,
|
|
742
|
+
TaggedTemplateExpression: reportTemplateT
|
|
743
|
+
}, scriptVisitor);
|
|
316
744
|
}
|
|
317
745
|
};
|
|
746
|
+
function isScriptElement(node) {
|
|
747
|
+
return node.type === "VElement" && node.name === "script";
|
|
748
|
+
}
|
|
749
|
+
function isScriptSetup(node) {
|
|
750
|
+
return node.startTag?.attributes?.some((attribute) => !attribute.directive && attribute.key?.name === "setup") ?? false;
|
|
751
|
+
}
|
|
752
|
+
function hasTopLevelBinding(program, script, name) {
|
|
753
|
+
return program.body?.some((statement) => isInside(statement, script) && statementDeclares(statement, name)) ?? false;
|
|
754
|
+
}
|
|
755
|
+
function isInside(node, container) {
|
|
756
|
+
return Boolean(node.range && container.range && node.range[0] >= container.range[0] && node.range[1] <= container.range[1]);
|
|
757
|
+
}
|
|
758
|
+
function statementDeclares(node, name) {
|
|
759
|
+
switch (node.type) {
|
|
760
|
+
case "ImportDeclaration": return node.importKind !== "type" && (node.specifiers?.some((item) => item.importKind !== "type" && item.local?.name === name) ?? false);
|
|
761
|
+
case "VariableDeclaration": return node.declarations?.some((item) => patternDeclares(item.id, name)) ?? false;
|
|
762
|
+
case "FunctionDeclaration":
|
|
763
|
+
case "ClassDeclaration":
|
|
764
|
+
case "TSEnumDeclaration":
|
|
765
|
+
case "TSImportEqualsDeclaration": return node.id?.name === name;
|
|
766
|
+
case "ExportNamedDeclaration":
|
|
767
|
+
case "ExportDefaultDeclaration": return node.declaration ? statementDeclares(node.declaration, name) : false;
|
|
768
|
+
default: return false;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function patternDeclares(node, name) {
|
|
772
|
+
if (!node) return false;
|
|
773
|
+
switch (node.type) {
|
|
774
|
+
case "Identifier": return node.name === name;
|
|
775
|
+
case "RestElement": return patternDeclares(node.argument, name);
|
|
776
|
+
case "AssignmentPattern": return patternDeclares(node.left, name);
|
|
777
|
+
case "ArrayPattern": return node.elements?.some((item) => patternDeclares(item ?? void 0, name)) ?? false;
|
|
778
|
+
case "ObjectPattern": return node.properties?.some((item) => patternDeclares(item.type === "RestElement" ? item.argument : item.value, name)) ?? false;
|
|
779
|
+
default: return false;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function isTemplateLocal(node) {
|
|
783
|
+
let parent = node.parent;
|
|
784
|
+
while (parent && parent.type !== "VExpressionContainer") parent = parent.parent;
|
|
785
|
+
return (parent?.references)?.some((reference) => reference.variable && (reference.id === node || reference.id?.range?.[0] === node.range?.[0] && reference.id?.range?.[1] === node.range?.[1])) ?? false;
|
|
786
|
+
}
|
|
318
787
|
//#endregion
|
|
319
788
|
//#region src/index.ts
|
|
320
789
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
@@ -325,6 +794,8 @@ const plugin = {
|
|
|
325
794
|
namespace: "ai-i18n"
|
|
326
795
|
},
|
|
327
796
|
rules: {
|
|
797
|
+
"no-eager-translation": noEagerTranslation,
|
|
798
|
+
"no-unsubscribed-t": noUnsubscribedT,
|
|
328
799
|
"static-candidate-limit": staticCandidateLimit,
|
|
329
800
|
"t-static-args": tStaticArgs
|
|
330
801
|
},
|
|
@@ -335,51 +806,72 @@ plugin.configs.recommended = [{
|
|
|
335
806
|
plugins: { "ai-i18n": plugin },
|
|
336
807
|
languageOptions: { globals: { defineI18nMessages: "readonly" } },
|
|
337
808
|
rules: {
|
|
338
|
-
"ai-i18n/static-
|
|
339
|
-
"ai-i18n/
|
|
809
|
+
"ai-i18n/t-static-args": "error",
|
|
810
|
+
"ai-i18n/no-eager-translation": "warn",
|
|
811
|
+
"ai-i18n/no-unsubscribed-t": "warn",
|
|
812
|
+
"ai-i18n/static-candidate-limit": "warn"
|
|
340
813
|
}
|
|
341
814
|
}];
|
|
342
815
|
plugin.configs.vue = [{
|
|
343
816
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}"],
|
|
344
817
|
plugins: { "ai-i18n": plugin },
|
|
818
|
+
languageOptions: { globals: { defineI18nMessages: "readonly" } },
|
|
819
|
+
rules: {
|
|
820
|
+
"ai-i18n/t-static-args": "error",
|
|
821
|
+
"ai-i18n/no-eager-translation": "warn",
|
|
822
|
+
"ai-i18n/no-unsubscribed-t": "warn",
|
|
823
|
+
"ai-i18n/static-candidate-limit": "warn"
|
|
824
|
+
}
|
|
825
|
+
}];
|
|
826
|
+
plugin.configs["vanilla-auto-import"] = [{
|
|
827
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
828
|
+
plugins: { "ai-i18n": plugin },
|
|
345
829
|
languageOptions: { globals: {
|
|
346
|
-
|
|
830
|
+
t: "readonly",
|
|
831
|
+
setLang: "readonly",
|
|
832
|
+
getLang: "readonly",
|
|
833
|
+
getLangs: "readonly",
|
|
834
|
+
getLangLoadState: "readonly",
|
|
835
|
+
subscribe: "readonly",
|
|
347
836
|
defineI18nMessages: "readonly"
|
|
348
837
|
} },
|
|
349
838
|
rules: {
|
|
350
|
-
"ai-i18n/static-
|
|
351
|
-
"ai-i18n/
|
|
839
|
+
"ai-i18n/t-static-args": ["error", { autoImport: ["t"] }],
|
|
840
|
+
"ai-i18n/no-eager-translation": ["warn", { autoImport: ["t"] }],
|
|
841
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: ["t"] }]
|
|
352
842
|
}
|
|
353
843
|
}];
|
|
354
|
-
plugin.configs
|
|
355
|
-
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
844
|
+
plugin.configs["vue-auto-import"] = [{
|
|
845
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}"],
|
|
356
846
|
plugins: { "ai-i18n": plugin },
|
|
357
847
|
languageOptions: { globals: {
|
|
848
|
+
t: "readonly",
|
|
358
849
|
useI18n: "readonly",
|
|
359
850
|
defineI18nMessages: "readonly"
|
|
360
851
|
} },
|
|
361
852
|
rules: {
|
|
362
|
-
"ai-i18n/static-
|
|
363
|
-
"ai-i18n/
|
|
853
|
+
"ai-i18n/t-static-args": ["error", { autoImport: ["t", "useI18n"] }],
|
|
854
|
+
"ai-i18n/no-eager-translation": ["warn", { autoImport: ["t", "useI18n"] }],
|
|
855
|
+
"ai-i18n/no-unsubscribed-t": ["warn", { autoImport: ["t", "useI18n"] }],
|
|
856
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: ["t", "useI18n"] }]
|
|
364
857
|
}
|
|
365
858
|
}];
|
|
366
|
-
plugin.configs
|
|
367
|
-
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
859
|
+
plugin.configs["react-auto-import"] = [{
|
|
860
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
368
861
|
plugins: { "ai-i18n": plugin },
|
|
369
862
|
languageOptions: { globals: {
|
|
370
863
|
t: "readonly",
|
|
371
|
-
|
|
372
|
-
getLang: "readonly",
|
|
373
|
-
getLangs: "readonly",
|
|
374
|
-
subscribe: "readonly",
|
|
864
|
+
useI18n: "readonly",
|
|
375
865
|
defineI18nMessages: "readonly"
|
|
376
866
|
} },
|
|
377
867
|
rules: {
|
|
378
|
-
"ai-i18n/static-
|
|
379
|
-
"ai-i18n/
|
|
868
|
+
"ai-i18n/t-static-args": ["error", { autoImport: ["t", "useI18n"] }],
|
|
869
|
+
"ai-i18n/no-eager-translation": ["warn", { autoImport: ["t", "useI18n"] }],
|
|
870
|
+
"ai-i18n/no-unsubscribed-t": ["warn", { autoImport: ["t", "useI18n"] }],
|
|
871
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: ["t", "useI18n"] }]
|
|
380
872
|
}
|
|
381
873
|
}];
|
|
382
874
|
//#endregion
|
|
383
|
-
export { plugin as default, staticCandidateLimit, tStaticArgs };
|
|
875
|
+
export { plugin as default, noEagerTranslation, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
|
|
384
876
|
|
|
385
877
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/resolve-import.ts","../src/analyze.ts","../src/vue-sfc.ts","../src/rule-analysis.ts","../src/rules/static-candidate-limit.ts","../src/rules/t-static-args.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SOURCE_EXTENSIONS = [\n '',\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mts',\n '.cts',\n '.mjs',\n '.cjs',\n] as const;\n\ninterface TsConfig {\n compilerOptions?: {\n baseUrl?: string;\n paths?: Record<string, string[]>;\n };\n}\n\ninterface Alias {\n prefix: string;\n suffix: string;\n targets: string[];\n}\n\nexport function createImportResolver(tsconfigPath?: string) {\n const aliases: Alias[] = [];\n let baseUrl = process.cwd();\n if (tsconfigPath) {\n const absoluteConfig = path.resolve(tsconfigPath);\n const config = readTsConfig(absoluteConfig);\n baseUrl = path.resolve(\n path.dirname(absoluteConfig),\n config.compilerOptions?.baseUrl ?? '.',\n );\n for (const [pattern, targets] of Object.entries(\n config.compilerOptions?.paths ?? {},\n )) {\n const star = pattern.indexOf('*');\n aliases.push({\n prefix: star < 0 ? pattern : pattern.slice(0, star),\n suffix: star < 0 ? '' : pattern.slice(star + 1),\n targets,\n });\n }\n }\n\n return (specifier: string, importer: string): string | null => {\n if (specifier === 'virtual:ai-i18n') return specifier;\n if (specifier.startsWith('.')) {\n return probeSource(path.resolve(path.dirname(importer), specifier));\n }\n for (const alias of aliases) {\n if (\n !specifier.startsWith(alias.prefix) ||\n !specifier.endsWith(alias.suffix)\n ) {\n continue;\n }\n const value = specifier.slice(\n alias.prefix.length,\n specifier.length - alias.suffix.length || undefined,\n );\n for (const target of alias.targets) {\n const candidate = target.includes('*')\n ? target.replace('*', value)\n : target;\n const resolved = probeSource(path.resolve(baseUrl, candidate));\n if (resolved) return resolved;\n }\n }\n return null;\n };\n}\n\nfunction probeSource(candidate: string): string | null {\n for (const extension of SOURCE_EXTENSIONS) {\n const file = `${candidate}${extension}`;\n if (isFile(file)) return path.normalize(file);\n }\n for (const extension of SOURCE_EXTENSIONS.slice(1)) {\n const file = path.join(candidate, `index${extension}`);\n if (isFile(file)) return path.normalize(file);\n }\n return null;\n}\n\nfunction isFile(file: string) {\n try {\n return fs.statSync(file).isFile();\n } catch {\n return false;\n }\n}\n\nfunction readTsConfig(filename: string): TsConfig {\n try {\n return JSON.parse(stripJsonComments(fs.readFileSync(filename, 'utf8'))) as TsConfig;\n } catch {\n return {};\n }\n}\n\nfunction stripJsonComments(source: string): string {\n let result = '';\n let quote = '';\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const current = source[index]!;\n const next = source[index + 1];\n if (quote) {\n result += current;\n if (escaped) escaped = false;\n else if (current === '\\\\') escaped = true;\n else if (current === quote) quote = '';\n continue;\n }\n if (current === '\"' || current === \"'\") {\n quote = current;\n result += current;\n continue;\n }\n if (current === '/' && next === '/') {\n while (index < source.length && source[index] !== '\\n') index += 1;\n result += '\\n';\n continue;\n }\n if (current === '/' && next === '*') {\n index += 2;\n while (\n index < source.length &&\n !(source[index] === '*' && source[index + 1] === '/')\n ) {\n if (source[index] === '\\n') result += '\\n';\n index += 1;\n }\n index += 1;\n continue;\n }\n result += current;\n }\n return result.replace(/,\\s*([}\\]])/g, '$1');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n AI_I18N_VIRTUAL_MODULE_ID,\n Analyzer,\n extractMessages,\n findInvalidDefineI18nMessagesReferences,\n findUnboundCalls,\n validateRecommendedUsage,\n type Module,\n type ExtractWarningCode,\n type RecommendedUsageCode,\n type AnalysisLanguage,\n type TranslationHookBinding,\n} from '@ai-i18n/analyzer';\nimport { createImportResolver } from './resolve-import.js';\n\nexport interface StaticArgsWarning {\n code: ExtractWarningCode | RecommendedUsageCode;\n line: number;\n column: number;\n message: string;\n}\n\nexport function analyzeStaticArgs(\n code: string,\n filename: string,\n tsconfigPath?: string,\n lang?: AnalysisLanguage,\n autoImport = false,\n maxStaticCandidates = Number.POSITIVE_INFINITY,\n): StaticArgsWarning[] {\n const resolve = createImportResolver(tsconfigPath);\n const analyzer = new Analyzer({ resolve });\n analyzer.addFile(\n AI_I18N_VIRTUAL_MODULE_ID,\n 'export function t(source) { return source }',\n );\n const entryPath = normalizeFilename(filename);\n const entry = analyzer.addFile(entryPath, code, lang ? { lang } : undefined);\n if (!hasTranslationCandidate(entry, autoImport)) return [];\n loadDependencies(analyzer, entry, resolve);\n analyzer.link();\n const extraction = extractMessages(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n translationHooks(autoImport),\n autoImport,\n maxStaticCandidates,\n ).warnings;\n const recommended = validateRecommendedUsage(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n translationHooks(autoImport),\n autoImport,\n );\n const warnings = recommended.length\n ? [\n ...extraction.filter((warning) => warning.code === 'parse-error'),\n ...extraction.filter(\n (warning) => warning.code === 'static-candidate-limit',\n ),\n ...recommended,\n ]\n : extraction;\n return warnings.map(({ code: warningCode, line, column, message }) => ({\n code: warningCode,\n line,\n column,\n message,\n }));\n}\n\nfunction hasTranslationCandidate(module: Module, autoImport: boolean): boolean {\n return (\n module.imports.some(\n (item) =>\n !item.typeOnly &&\n (item.specifier === AI_I18N_VIRTUAL_MODULE_ID ||\n item.name === 't' ||\n item.name === 'useI18n'),\n ) ||\n findInvalidDefineI18nMessagesReferences(module).length > 0 ||\n findUnboundCalls(\n module,\n new Set(['defineI18nMessages', ...(autoImport ? ['t', 'useI18n'] : [])]),\n ).length > 0\n );\n}\n\nfunction translationHooks(\n autoImport: boolean,\n): readonly TranslationHookBinding[] {\n return [\n {\n module: AI_I18N_VIRTUAL_MODULE_ID,\n hook: 'useI18n',\n property: 't',\n autoImport,\n },\n ];\n}\n\nfunction loadDependencies(\n analyzer: Analyzer,\n entry: Module,\n resolve: (specifier: string, importer: string) => string | null,\n) {\n const queue = [entry];\n const visited = new Set<string>();\n while (queue.length) {\n const module = queue.shift()!;\n if (visited.has(module.path)) continue;\n visited.add(module.path);\n for (const item of module.imports) {\n const resolved = resolve(item.specifier, module.path);\n if (!resolved || resolved === AI_I18N_VIRTUAL_MODULE_ID) continue;\n let dependency = analyzer.module(resolved);\n if (!dependency) {\n try {\n dependency = analyzer.addFile(\n resolved,\n fs.readFileSync(resolved, 'utf8'),\n );\n } catch {\n continue;\n }\n }\n queue.push(dependency);\n }\n }\n}\n\nfunction normalizeFilename(filename: string) {\n return filename.startsWith('<')\n ? path.resolve('eslint-input.ts')\n : path.resolve(filename);\n}\n","import { createRequire } from 'node:module';\nimport { diagnosticMessage } from '@ai-i18n/analyzer';\nimport {\n analyzeVueSource,\n type VueAnalysisSource,\n type VueCompiler,\n} from '@ai-i18n/analyzer/vue';\n\ninterface VueParserServices {\n getDocumentFragment?: () => unknown;\n}\n\nconst require = createRequire(import.meta.url);\n\nexport function createVueAnalysisSource(\n source: string,\n filename: string,\n parserServices: VueParserServices,\n): VueAnalysisSource {\n if (!parserServices.getDocumentFragment?.()) {\n throw new Error(\n diagnosticMessage(\n '检查 .vue 文件需要配置 vue-eslint-parser。',\n 'Configure vue-eslint-parser to lint .vue files.',\n ),\n );\n }\n\n try {\n // Vue 编译器仅在 configs.vue 真正处理 SFC 时加载,不影响 React/Vanilla 项目。\n const compiler = require('@vue/compiler-sfc') as VueCompiler;\n return analyzeVueSource(source, filename, compiler);\n } catch (error) {\n if (isMissingVueCompiler(error)) {\n throw new Error(\n diagnosticMessage(\n '检查 .vue 文件需要安装 @vue/compiler-sfc。',\n 'Install @vue/compiler-sfc to lint .vue files.',\n ),\n );\n }\n throw error;\n }\n}\n\nfunction isMissingVueCompiler(error: unknown): boolean {\n return (\n error instanceof Error &&\n 'code' in error &&\n error.code === 'MODULE_NOT_FOUND'\n );\n}\n","import type { Rule } from 'eslint';\nimport { analyzeStaticArgs, type StaticArgsWarning } from './analyze.js';\nimport { createVueAnalysisSource } from './vue-sfc.js';\n\ninterface RuleAnalysisOptions {\n tsconfigPath?: string;\n autoImport?: boolean;\n}\n\ninterface CachedAnalysis {\n source: ReturnType<typeof createAnalysisSource>;\n warnings: Map<string, StaticArgsWarning[]>;\n}\n\nconst DEFAULT_CANDIDATE_LIMIT = 1_000;\nconst cache = new WeakMap<object, CachedAnalysis>();\n\nexport function analyzeRuleContext(\n context: Rule.RuleContext,\n options: RuleAnalysisOptions,\n maxStaticCandidates = Number.POSITIVE_INFINITY,\n): StaticArgsWarning[] {\n const cached: CachedAnalysis = cache.get(context.sourceCode) ?? {\n source: createAnalysisSource(context),\n warnings: new Map<string, StaticArgsWarning[]>(),\n };\n cache.set(context.sourceCode, cached);\n\n const analyze = (limit: number) => {\n const key = JSON.stringify([\n options.tsconfigPath ?? null,\n options.autoImport ?? false,\n limit,\n ]);\n const existing = cached.warnings.get(key);\n if (existing) return existing;\n const warnings = analyzeStaticArgs(\n cached.source.code,\n context.filename,\n options.tsconfigPath,\n cached.source.lang,\n options.autoImport,\n limit,\n ).map((warning) => ({\n ...warning,\n ...cached.source.mapLocation(warning),\n }));\n cached.warnings.set(key, warnings);\n return warnings;\n };\n\n if (Number.isFinite(maxStaticCandidates)) {\n return analyze(maxStaticCandidates);\n }\n const preflight = analyze(DEFAULT_CANDIDATE_LIMIT);\n return preflight.some((warning) => warning.code === 'static-candidate-limit')\n ? analyze(Number.POSITIVE_INFINITY)\n : preflight;\n}\n\nfunction createAnalysisSource(context: Rule.RuleContext) {\n return context.filename.endsWith('.vue')\n ? createVueAnalysisSource(\n context.sourceCode.text,\n context.filename,\n context.sourceCode.parserServices,\n )\n : {\n code: context.sourceCode.text,\n lang: undefined,\n mapLocation: (location: { line: number; column: number }) => location,\n };\n}\n","import type { Rule } from 'eslint';\nimport { analyzeRuleContext } from '../rule-analysis.js';\n\ninterface RuleOptions {\n tsconfigPath?: string;\n autoImport?: boolean;\n maxStaticCandidates?: number;\n}\n\nexport const staticCandidateLimit: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: '警告单个 t() 展开的静态候选数量过多',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: { type: 'boolean' },\n maxStaticCandidates: {\n type: 'integer',\n minimum: 1,\n maximum: Number.MAX_SAFE_INTEGER,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n candidateLimit: '{{reason}}',\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as RuleOptions;\n return {\n 'Program:exit'(node) {\n try {\n const warnings = analyzeRuleContext(\n context,\n options,\n options.maxStaticCandidates ?? 1_000,\n );\n for (const warning of warnings) {\n if (warning.code !== 'static-candidate-limit') continue;\n context.report({\n node,\n loc: {\n start: warning,\n end: { line: warning.line, column: warning.column + 1 },\n },\n messageId: 'candidateLimit',\n data: { reason: warning.message },\n });\n }\n } catch {\n // 主规则负责报告分析失败;本规则只补充候选数量警告。\n }\n },\n };\n },\n};\n","import type { Rule } from 'eslint';\nimport { diagnosticMessage } from '@ai-i18n/analyzer';\nimport { analyzeRuleContext } from '../rule-analysis.js';\n\ninterface RuleOptions {\n tsconfigPath?: string;\n autoImport?: boolean;\n}\n\nexport const tStaticArgs: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: '要求 virtual:ai-i18n 的 t() 参数可被静态提取',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: { type: 'boolean' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n analysisFailed: '{{reason}}',\n dynamicArg: '{{reason}}',\n invalidUsage: '{{reason}}',\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as RuleOptions;\n return {\n 'Program:exit'(node) {\n let warnings;\n try {\n warnings = analyzeRuleContext(context, options);\n } catch (error) {\n context.report({\n node,\n messageId: 'analysisFailed',\n data: {\n reason: diagnosticMessage(\n `静态分析失败:${error instanceof Error ? error.message : String(error)}`,\n `Static analysis failed: ${error instanceof Error ? error.message : String(error)}`,\n ),\n },\n });\n return;\n }\n for (const warning of warnings) {\n const analysisFailed = warning.code === 'parse-error';\n const invalidUsage =\n warning.code !== 'dynamic-argument' &&\n warning.code !== 'unresolved-argument' &&\n !analysisFailed;\n context.report({\n node,\n loc: {\n start: warning,\n end: { line: warning.line, column: warning.column + 1 },\n },\n messageId: analysisFailed\n ? 'analysisFailed'\n : invalidUsage\n ? 'invalidUsage'\n : 'dynamicArg',\n data: {\n reason: analysisFailed\n ? diagnosticMessage(\n `静态分析失败:${warning.message}`,\n `Static analysis failed: ${warning.message}`,\n )\n : invalidUsage\n ? warning.message\n : diagnosticMessage(\n 't() 的参数无法静态提取。source 请使用静态字符串,options 请使用只包含 id、comment 的静态对象。',\n 'The t() arguments cannot be statically extracted. Use a static string for source and a static object containing only id and comment for options.',\n ),\n },\n });\n }\n },\n };\n },\n};\n","import { createRequire } from 'node:module';\nimport type { ESLint } from 'eslint';\nimport { staticCandidateLimit } from './rules/static-candidate-limit.js';\nimport { tStaticArgs } from './rules/t-static-args.js';\n\nconst { version } = createRequire(import.meta.url)('../package.json') as {\n version: string;\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: '@ai-i18n/eslint-plugin',\n version,\n namespace: 'ai-i18n',\n },\n rules: {\n 'static-candidate-limit': staticCandidateLimit,\n 't-static-args': tStaticArgs,\n },\n configs: {},\n};\n\n// 规则集必须由使用者显式引入,不会修改宿主项目的 ESLint 配置。\nplugin.configs!.recommended = [\n {\n ignores: ['**/*.vue'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: { defineI18nMessages: 'readonly' },\n },\n rules: {\n 'ai-i18n/static-candidate-limit': 'warn',\n 'ai-i18n/t-static-args': 'error',\n },\n },\n];\n\nplugin.configs!.vue = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n useI18n: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/static-candidate-limit': ['warn', { autoImport: true }],\n 'ai-i18n/t-static-args': ['error', { autoImport: true }],\n },\n },\n];\n\nplugin.configs!.react = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n useI18n: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/static-candidate-limit': ['warn', { autoImport: true }],\n 'ai-i18n/t-static-args': ['error', { autoImport: true }],\n },\n },\n];\n\nplugin.configs!.vanilla = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n t: 'readonly',\n setLang: 'readonly',\n getLang: 'readonly',\n getLangs: 'readonly',\n subscribe: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/static-candidate-limit': ['warn', { autoImport: true }],\n 'ai-i18n/t-static-args': ['error', { autoImport: true }],\n },\n },\n];\n\nexport { staticCandidateLimit, tStaticArgs };\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["FUNCTION_TYPES"],"sources":["../src/resolve-import.ts","../src/analyze.ts","../src/vue-sfc.ts","../src/rule-analysis.ts","../src/rules/translation-call.ts","../src/rules/no-eager-translation.ts","../src/rules/no-unsubscribed-t.ts","../src/rules/static-candidate-limit.ts","../src/rules/t-static-args.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SOURCE_EXTENSIONS = [\n '',\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mts',\n '.cts',\n '.mjs',\n '.cjs',\n] as const;\nconst DIRECTORY_STAMP_TTL_MS = 250;\nconst MAX_PROBE_CACHE_ENTRIES = 10_000;\n\ninterface TsConfig {\n compilerOptions?: {\n baseUrl?: string;\n paths?: Record<string, string[]>;\n };\n}\n\ninterface Alias {\n prefix: string;\n suffix: string;\n targets: string[];\n}\n\ninterface CachedTsConfig {\n stamp: string | null;\n value: TsConfig;\n}\n\ninterface CachedDirectoryStamp {\n expiresAt: number;\n value: string | null;\n}\n\ninterface CachedProbe {\n candidateDirectory: string | null;\n parentDirectory: string | null;\n resolved: string | null;\n}\n\nconst tsconfigCache = new Map<string, CachedTsConfig>();\nconst directoryStampCache = new Map<string, CachedDirectoryStamp>();\nconst probeCache = new Map<string, CachedProbe>();\n\nexport function createImportResolver(tsconfigPath?: string) {\n const aliases: Alias[] = [];\n let baseUrl = process.cwd();\n if (tsconfigPath) {\n const absoluteConfig = path.resolve(tsconfigPath);\n const config = readTsConfig(absoluteConfig);\n baseUrl = path.resolve(\n path.dirname(absoluteConfig),\n config.compilerOptions?.baseUrl ?? '.',\n );\n for (const [pattern, targets] of Object.entries(\n config.compilerOptions?.paths ?? {},\n )) {\n const star = pattern.indexOf('*');\n aliases.push({\n prefix: star < 0 ? pattern : pattern.slice(0, star),\n suffix: star < 0 ? '' : pattern.slice(star + 1),\n targets,\n });\n }\n }\n\n return (specifier: string, importer: string): string | null => {\n if (specifier === 'virtual:ai-i18n') return specifier;\n if (specifier.startsWith('.')) {\n return probeSource(path.resolve(path.dirname(importer), specifier));\n }\n for (const alias of aliases) {\n if (\n !specifier.startsWith(alias.prefix) ||\n !specifier.endsWith(alias.suffix)\n ) {\n continue;\n }\n const value = specifier.slice(\n alias.prefix.length,\n specifier.length - alias.suffix.length || undefined,\n );\n for (const target of alias.targets) {\n const candidate = target.includes('*')\n ? target.replace('*', value)\n : target;\n const resolved = probeSource(path.resolve(baseUrl, candidate));\n if (resolved) return resolved;\n }\n }\n return null;\n };\n}\n\nfunction probeSource(candidate: string): string | null {\n candidate = path.normalize(candidate);\n const cached = probeCache.get(candidate);\n if (\n cached &&\n cached.parentDirectory === readDirectoryStamp(path.dirname(candidate)) &&\n cached.candidateDirectory === readDirectoryStamp(candidate)\n ) {\n return cached.resolved;\n }\n\n let resolved: string | null = null;\n for (const extension of SOURCE_EXTENSIONS) {\n const file = `${candidate}${extension}`;\n if (isFile(file)) {\n resolved = path.normalize(file);\n break;\n }\n }\n if (!resolved) {\n for (const extension of SOURCE_EXTENSIONS.slice(1)) {\n const file = path.join(candidate, `index${extension}`);\n if (isFile(file)) {\n resolved = path.normalize(file);\n break;\n }\n }\n }\n if (probeCache.size >= MAX_PROBE_CACHE_ENTRIES) probeCache.clear();\n probeCache.set(candidate, {\n parentDirectory: readDirectoryStamp(path.dirname(candidate)),\n candidateDirectory: readDirectoryStamp(candidate),\n resolved,\n });\n return resolved;\n}\n\nfunction isFile(file: string) {\n try {\n return fs.statSync(file).isFile();\n } catch {\n return false;\n }\n}\n\nfunction readTsConfig(filename: string): TsConfig {\n const stamp = readFileStamp(filename);\n const cached = tsconfigCache.get(filename);\n if (cached?.stamp === stamp) return cached.value;\n let value: TsConfig = {};\n try {\n value = JSON.parse(\n stripJsonComments(fs.readFileSync(filename, 'utf8')),\n ) as TsConfig;\n } catch {\n // 无效或不存在的 tsconfig 与未配置路径别名等价。\n }\n tsconfigCache.set(filename, { stamp, value });\n return value;\n}\n\nfunction readFileStamp(filename: string): string | null {\n try {\n const stat = fs.statSync(filename);\n return stat.isFile()\n ? `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`\n : null;\n } catch {\n return null;\n }\n}\n\nfunction readDirectoryStamp(directory: string): string | null {\n const now = Date.now();\n const cached = directoryStampCache.get(directory);\n if (cached && cached.expiresAt > now) return cached.value;\n let value: string | null = null;\n try {\n const stat = fs.statSync(directory);\n if (stat.isDirectory()) {\n value = `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;\n }\n } catch {\n // 不存在的目录也要缓存,避免重复的失败探测。\n }\n if (directoryStampCache.size >= MAX_PROBE_CACHE_ENTRIES) {\n directoryStampCache.clear();\n }\n directoryStampCache.set(directory, {\n expiresAt: now + DIRECTORY_STAMP_TTL_MS,\n value,\n });\n return value;\n}\n\nfunction stripJsonComments(source: string): string {\n let result = '';\n let quote = '';\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const current = source[index]!;\n const next = source[index + 1];\n if (quote) {\n result += current;\n if (escaped) escaped = false;\n else if (current === '\\\\') escaped = true;\n else if (current === quote) quote = '';\n continue;\n }\n if (current === '\"' || current === \"'\") {\n quote = current;\n result += current;\n continue;\n }\n if (current === '/' && next === '/') {\n while (index < source.length && source[index] !== '\\n') index += 1;\n result += '\\n';\n continue;\n }\n if (current === '/' && next === '*') {\n index += 2;\n while (\n index < source.length &&\n !(source[index] === '*' && source[index + 1] === '/')\n ) {\n if (source[index] === '\\n') result += '\\n';\n index += 1;\n }\n index += 1;\n continue;\n }\n result += current;\n }\n return result.replace(/,\\s*([}\\]])/g, '$1');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n AI_I18N_VIRTUAL_MODULE_ID,\n Analyzer,\n extractMessages,\n findInvalidDefineI18nMessagesReferences,\n findTranslationCalls,\n findUnboundCalls,\n validateRecommendedUsage,\n type Module,\n type ExtractWarningCode,\n type RecommendedUsageCode,\n type AnalysisLanguage,\n type TranslationCall,\n type TranslationHookBinding,\n} from '@ai-i18n/analyzer';\nimport { createImportResolver } from './resolve-import.js';\n\nexport interface StaticArgsWarning {\n code: ExtractWarningCode | RecommendedUsageCode;\n line: number;\n column: number;\n message: string;\n}\n\nexport interface StaticAnalysisResult {\n warnings: StaticArgsWarning[];\n translationCalls: TranslationCall[];\n}\n\nexport type AutoImportApi = 't' | 'useI18n';\nexport type AutoImportOption = boolean | readonly AutoImportApi[];\n\ninterface AutoImportBindings {\n t: boolean;\n useI18n: boolean;\n}\n\nconst POTENTIAL_TRANSLATION_RE =\n /virtual:ai-i18n|\\b(?:t|useI18n|defineI18nMessages)\\b/;\n\nexport function analyzeStaticArgs(\n code: string,\n filename: string,\n tsconfigPath?: string,\n lang?: AnalysisLanguage,\n autoImport: AutoImportOption = false,\n maxStaticCandidates = Number.POSITIVE_INFINITY,\n): StaticArgsWarning[] {\n return analyzeStaticSource(\n code,\n filename,\n tsconfigPath,\n lang,\n autoImport,\n maxStaticCandidates,\n ).warnings;\n}\n\nexport function analyzeStaticSource(\n code: string,\n filename: string,\n tsconfigPath?: string,\n lang?: AnalysisLanguage,\n autoImport: AutoImportOption = false,\n maxStaticCandidates = Number.POSITIVE_INFINITY,\n): StaticAnalysisResult {\n if (!hasPotentialTranslationCandidate(code)) {\n return { warnings: [], translationCalls: [] };\n }\n const autoImports = normalizeAutoImports(autoImport);\n const resolve = createImportResolver(tsconfigPath);\n const analyzer = new Analyzer({ resolve });\n analyzer.addFile(\n AI_I18N_VIRTUAL_MODULE_ID,\n 'export function t(source) { return source }',\n );\n const entryPath = normalizeFilename(filename);\n const entry = analyzer.addFile(entryPath, code, lang ? { lang } : undefined);\n if (!hasTranslationCandidate(entry, autoImports)) {\n return { warnings: [], translationCalls: [] };\n }\n loadDependencies(analyzer, entry, resolve);\n analyzer.link();\n const hooks = translationHooks(autoImports);\n const extraction = extractMessages(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n hooks,\n autoImports.t,\n maxStaticCandidates,\n ).warnings;\n const recommended = validateRecommendedUsage(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n hooks,\n autoImports.t,\n );\n const warnings = recommended.length\n ? [\n ...extraction.filter((warning) => warning.code === 'parse-error'),\n ...extraction.filter(\n (warning) => warning.code === 'static-candidate-limit',\n ),\n ...recommended,\n ]\n : extraction;\n return {\n warnings: warnings.map(({ code: warningCode, line, column, message }) => ({\n code: warningCode,\n line,\n column,\n message,\n })),\n translationCalls: findTranslationCalls(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n hooks,\n autoImports.t,\n ),\n };\n}\n\nexport function hasPotentialTranslationCandidate(code: string): boolean {\n return POTENTIAL_TRANSLATION_RE.test(code);\n}\n\nexport function normalizeAutoImports(\n autoImport: AutoImportOption | undefined,\n): AutoImportBindings {\n if (typeof autoImport === 'boolean') {\n return { t: autoImport, useI18n: autoImport };\n }\n return {\n t: autoImport?.includes('t') ?? false,\n useI18n: autoImport?.includes('useI18n') ?? false,\n };\n}\n\nfunction hasTranslationCandidate(\n module: Module,\n autoImports: AutoImportBindings,\n): boolean {\n const unbound = ['defineI18nMessages'];\n if (autoImports.t) unbound.push('t');\n if (autoImports.useI18n) unbound.push('useI18n');\n return (\n module.imports.some(\n (item) =>\n !item.typeOnly &&\n (item.specifier === AI_I18N_VIRTUAL_MODULE_ID ||\n item.name === 't' ||\n item.name === 'useI18n'),\n ) ||\n findInvalidDefineI18nMessagesReferences(module).length > 0 ||\n findUnboundCalls(module, new Set(unbound)).length > 0\n );\n}\n\nfunction translationHooks(\n autoImports: AutoImportBindings,\n): readonly TranslationHookBinding[] {\n return [\n {\n module: AI_I18N_VIRTUAL_MODULE_ID,\n hook: 'useI18n',\n property: 't',\n autoImport: autoImports.useI18n,\n },\n ];\n}\n\nfunction loadDependencies(\n analyzer: Analyzer,\n entry: Module,\n resolve: (specifier: string, importer: string) => string | null,\n) {\n const queue = [entry];\n const visited = new Set<string>();\n while (queue.length) {\n const module = queue.shift()!;\n if (visited.has(module.path)) continue;\n visited.add(module.path);\n for (const item of module.imports) {\n const resolved = resolve(item.specifier, module.path);\n if (!resolved || resolved === AI_I18N_VIRTUAL_MODULE_ID) continue;\n let dependency = analyzer.module(resolved);\n if (!dependency) {\n try {\n dependency = analyzer.addFile(\n resolved,\n fs.readFileSync(resolved, 'utf8'),\n );\n } catch {\n continue;\n }\n }\n queue.push(dependency);\n }\n }\n}\n\nfunction normalizeFilename(filename: string) {\n return filename.startsWith('<')\n ? path.resolve('eslint-input.ts')\n : path.resolve(filename);\n}\n","import { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { diagnosticMessage } from '@ai-i18n/analyzer';\nimport {\n analyzeVueSource,\n type VueAnalysisSource,\n type VueCompiler,\n} from '@ai-i18n/analyzer/vue';\n\ninterface VueParserServices {\n getDocumentFragment?: () => unknown;\n}\n\ntype Require = ReturnType<typeof createRequire>;\ntype NodeVueCompiler = VueCompiler & {\n registerTS?: (loadTypeScript: () => unknown) => void;\n};\n\nconst packageRequire = createRequire(import.meta.url);\n\nexport function createVueAnalysisSource(\n source: string,\n filename: string,\n parserServices: VueParserServices,\n): VueAnalysisSource {\n if (!parserServices.getDocumentFragment?.()) {\n throw new Error(\n diagnosticMessage(\n '检查 .vue 文件需要配置 vue-eslint-parser。',\n 'Configure vue-eslint-parser to lint .vue files.',\n ),\n );\n }\n\n try {\n // Vue preset 真正处理 SFC 时才加载编译器,不影响 React/Vanilla 项目。\n const compiler = loadVueCompiler(filename);\n return analyzeVueSource(source, filename, compiler);\n } catch (error) {\n if (isMissingVueCompiler(error)) {\n throw new Error(\n diagnosticMessage(\n '检查 .vue 文件需要安装 @vue/compiler-sfc。',\n 'Install @vue/compiler-sfc to lint .vue files.',\n ),\n );\n }\n throw error;\n }\n}\n\nfunction loadVueCompiler(filename: string): VueCompiler {\n const hostRequire = createRequire(path.resolve(filename));\n // 优先复用宿主 Vue 的 Node wrapper,它会为 imported types 注册 TypeScript。\n const compiler = (tryRequire(hostRequire, 'vue/compiler-sfc') ??\n tryRequire(packageRequire, 'vue/compiler-sfc') ??\n tryRequire(hostRequire, '@vue/compiler-sfc') ??\n packageRequire('@vue/compiler-sfc')) as NodeVueCompiler;\n compiler.registerTS?.(() => {\n const typescript =\n tryResolve(hostRequire, 'typescript') ??\n tryResolve(packageRequire, 'typescript');\n return packageRequire(typescript ?? 'typescript');\n });\n return compiler;\n}\n\nfunction tryRequire(require: Require, id: string): unknown {\n const resolved = tryResolve(require, id);\n return resolved ? require(resolved) : null;\n}\n\nfunction tryResolve(require: Require, id: string): string | null {\n try {\n return require.resolve(id);\n } catch (error) {\n if (isMissingVueCompiler(error)) return null;\n throw error;\n }\n}\n\nfunction isMissingVueCompiler(error: unknown): boolean {\n return (\n error instanceof Error &&\n 'code' in error &&\n (error.code === 'MODULE_NOT_FOUND' ||\n error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')\n );\n}\n","import type { Rule } from 'eslint';\nimport {\n analyzeStaticSource,\n hasPotentialTranslationCandidate,\n normalizeAutoImports,\n type AutoImportOption,\n type StaticAnalysisResult,\n type StaticArgsWarning,\n} from './analyze.js';\nimport { diagnosticMessage, type TranslationCall } from '@ai-i18n/analyzer';\nimport { createVueAnalysisSource } from './vue-sfc.js';\n\ninterface RuleAnalysisOptions {\n tsconfigPath?: string;\n autoImport?: AutoImportOption;\n}\n\ninterface CachedAnalysis {\n source: ReturnType<typeof createAnalysisSource>;\n analyses: Map<string, StaticAnalysisResult>;\n}\n\nconst DEFAULT_CANDIDATE_LIMIT = 1_000;\nconst cache = new WeakMap<object, CachedAnalysis>();\nconst reportedAnalysisFailures = new WeakSet<object>();\nconst EMPTY_ANALYSIS: StaticAnalysisResult = {\n warnings: [],\n translationCalls: [],\n};\n\nexport function analyzeRuleContext(\n context: Rule.RuleContext,\n options: RuleAnalysisOptions,\n maxStaticCandidates = Number.POSITIVE_INFINITY,\n): StaticArgsWarning[] {\n return analyzeRuleContextResult(context, options, maxStaticCandidates)\n .warnings;\n}\n\nexport function analyzeTranslationCalls(\n context: Rule.RuleContext,\n options: RuleAnalysisOptions,\n): TranslationCall[] {\n return analyzeRuleContextResult(context, options, DEFAULT_CANDIDATE_LIMIT)\n .translationCalls;\n}\n\nexport function reportAnalysisFailureOnce(\n context: Rule.RuleContext,\n node: Rule.Node,\n error: unknown,\n): void {\n if (reportedAnalysisFailures.has(context.sourceCode)) return;\n reportedAnalysisFailures.add(context.sourceCode);\n const detail = error instanceof Error ? error.message : String(error);\n context.report({\n node,\n message: diagnosticMessage(\n `静态分析失败:${detail}`,\n `Static analysis failed: ${detail}`,\n ),\n });\n}\n\nfunction analyzeRuleContextResult(\n context: Rule.RuleContext,\n options: RuleAnalysisOptions,\n maxStaticCandidates: number,\n): StaticAnalysisResult {\n if (!hasPotentialTranslationCandidate(context.sourceCode.text)) {\n return EMPTY_ANALYSIS;\n }\n const cached: CachedAnalysis = cache.get(context.sourceCode) ?? {\n source: createAnalysisSource(context),\n analyses: new Map<string, StaticAnalysisResult>(),\n };\n cache.set(context.sourceCode, cached);\n\n const analyze = (limit: number) => {\n const autoImports = normalizeAutoImports(options.autoImport);\n const key = JSON.stringify([\n options.tsconfigPath ?? null,\n autoImports.t,\n autoImports.useI18n,\n limit,\n ]);\n const existing = cached.analyses.get(key);\n if (existing) return existing;\n const result = analyzeStaticSource(\n cached.source.code,\n context.filename,\n options.tsconfigPath,\n cached.source.lang,\n options.autoImport,\n limit,\n );\n const analysis = {\n warnings: result.warnings.map((warning) => ({\n ...warning,\n ...cached.source.mapLocation(warning),\n })),\n translationCalls: result.translationCalls.map((call) => ({\n ...call,\n ...cached.source.mapLocation(call),\n })),\n };\n cached.analyses.set(key, analysis);\n return analysis;\n };\n\n if (Number.isFinite(maxStaticCandidates)) {\n return analyze(maxStaticCandidates);\n }\n const preflight = analyze(DEFAULT_CANDIDATE_LIMIT);\n return preflight.warnings.some(\n (warning) => warning.code === 'static-candidate-limit',\n )\n ? analyze(Number.POSITIVE_INFINITY)\n : preflight;\n}\n\nfunction createAnalysisSource(context: Rule.RuleContext) {\n return context.filename.endsWith('.vue')\n ? createVueAnalysisSource(\n context.sourceCode.text,\n context.filename,\n context.sourceCode.parserServices,\n )\n : {\n code: context.sourceCode.text,\n lang: undefined,\n mapLocation: (location: { line: number; column: number }) => location,\n };\n}\n","import type { TranslationCall } from '@ai-i18n/analyzer';\nimport type { Rule } from 'eslint';\nimport type { AutoImportOption } from '../analyze.js';\nimport { analyzeTranslationCalls } from '../rule-analysis.js';\n\nexport interface TranslationRuleOptions {\n tsconfigPath?: string;\n autoImport?: AutoImportOption;\n}\n\nexport interface TranslationCandidate {\n kind: TranslationCall['kind'];\n node: Rule.Node;\n}\n\nexport interface MatchedTranslationCall extends TranslationCandidate {\n call: TranslationCall;\n}\n\nexport function matchTranslationCalls(\n context: Rule.RuleContext,\n options: TranslationRuleOptions,\n candidates: readonly TranslationCandidate[],\n): MatchedTranslationCall[] {\n const calls = new Map(\n analyzeTranslationCalls(context, options).map((call) => [\n locationKey(call.line, call.column, call.kind),\n call,\n ]),\n );\n return candidates.flatMap((candidate) => {\n const location = candidate.node.loc?.start;\n if (!location) return [];\n const call = calls.get(\n locationKey(location.line, location.column, candidate.kind),\n );\n return call ? [{ ...candidate, call }] : [];\n });\n}\n\nfunction locationKey(\n line: number,\n column: number,\n kind: TranslationCall['kind'],\n): string {\n return `${kind}:${line}:${column}`;\n}\n","import { diagnosticMessage } from '@ai-i18n/analyzer';\nimport type { Rule } from 'eslint';\nimport { reportAnalysisFailureOnce } from '../rule-analysis.js';\nimport {\n matchTranslationCalls,\n type TranslationCandidate,\n type TranslationRuleOptions,\n} from './translation-call.js';\n\nconst FUNCTION_TYPES = new Set([\n 'ArrowFunctionExpression',\n 'FunctionDeclaration',\n 'FunctionExpression',\n]);\n\nexport const noEagerTranslation: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: '警告不会随语言切换更新的提前求值翻译结果',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: {\n anyOf: [\n { type: 'boolean' },\n {\n type: 'array',\n items: { enum: ['t', 'useI18n'] },\n uniqueItems: true,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n eagerTranslation: diagnosticMessage(\n '初始化期间保存的 t() 结果不会随语言切换更新。请改为函数或 Getter,在使用时调用 t();组件视图请使用 useI18n()。',\n 'A t() result stored during initialization will not update when the language changes. Evaluate it lazily in a function or getter; use useI18n() in component views.',\n ),\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as TranslationRuleOptions;\n const isVueSfc = context.filename.toLowerCase().endsWith('.vue');\n const candidates: TranslationCandidate[] = [];\n return {\n CallExpression(node) {\n candidates.push({ kind: 'call', node });\n },\n TaggedTemplateExpression(node) {\n candidates.push({ kind: 'tagged-template', node });\n },\n 'Program:exit'(program) {\n let matches;\n try {\n matches = matchTranslationCalls(context, options, candidates);\n } catch (error) {\n reportAnalysisFailureOnce(context, program as Rule.Node, error);\n return;\n }\n for (const { node } of matches) {\n if (!storesOutsideFunction(node, context, isVueSfc)) continue;\n context.report({ node, messageId: 'eagerTranslation' });\n }\n },\n };\n },\n};\n\nfunction storesOutsideFunction(\n node: Rule.Node,\n context: Rule.RuleContext,\n isVueSfc: boolean,\n): boolean {\n let current = node as ParentNode;\n let storesResult = false;\n while (current.parent) {\n const parent = current.parent;\n if (FUNCTION_TYPES.has(parent.type)) {\n // 普通 setup() 与 <script setup> 一样只执行一次;其他函数仍视为延迟求值。\n const returnsExpression =\n parent.type === 'ArrowFunctionExpression' && parent.body === current;\n return (\n isVueComponentSetup(parent, context, isVueSfc) &&\n (storesResult || returnsExpression)\n );\n }\n if (storesTranslationResult(parent, current)) storesResult = true;\n if (parent.type === 'Program') return storesResult;\n current = parent;\n }\n return storesResult;\n}\n\nfunction isVueComponentSetup(\n node: ParentNode,\n context: Rule.RuleContext,\n isVueSfc: boolean,\n): boolean {\n const directOwner = node.parent;\n if (\n directOwner?.type === 'CallExpression' &&\n directOwner.arguments?.[0] === node\n ) {\n return isImportedDefineComponent(directOwner.callee, context);\n }\n\n const property = node.parent;\n if (\n property?.type !== 'Property' ||\n property.value !== node ||\n property.computed ||\n propertyName(property.key) !== 'setup'\n ) {\n return false;\n }\n\n const options = property.parent;\n if (options?.type !== 'ObjectExpression') return false;\n const owner = options.parent;\n if (\n isVueSfc &&\n owner?.type === 'ExportDefaultDeclaration' &&\n owner.declaration === options\n ) {\n return true;\n }\n return (\n owner?.type === 'CallExpression' &&\n owner.arguments?.[0] === options &&\n isImportedDefineComponent(owner.callee, context)\n );\n}\n\nfunction propertyName(node: unknown): string | undefined {\n const key = node as { name?: string; type?: string; value?: unknown };\n if (key?.type === 'Identifier') return key.name;\n return key?.type === 'Literal' && typeof key.value === 'string'\n ? key.value\n : undefined;\n}\n\nfunction isImportedDefineComponent(\n node: unknown,\n context: Rule.RuleContext,\n): boolean {\n const callee = node as {\n computed?: boolean;\n name?: string;\n object?: unknown;\n property?: unknown;\n type?: string;\n };\n if (callee?.type === 'Identifier') {\n return isVueImport(callee, context, 'ImportSpecifier');\n }\n return (\n callee?.type === 'MemberExpression' &&\n !callee.computed &&\n propertyName(callee.property) === 'defineComponent' &&\n isVueImport(callee.object, context, 'ImportNamespaceSpecifier')\n );\n}\n\nfunction isVueImport(\n node: unknown,\n context: Rule.RuleContext,\n specifierType: 'ImportNamespaceSpecifier' | 'ImportSpecifier',\n): boolean {\n const identifier = node as Rule.Node & { name?: string };\n if (identifier?.type !== 'Identifier' || !identifier.name) return false;\n\n let scope = context.sourceCode.getScope(identifier);\n while (true) {\n const variable = scope.set.get(identifier.name);\n if (!variable) {\n if (!scope.upper) return false;\n scope = scope.upper;\n continue;\n }\n return variable.defs.some((definition) => {\n if (\n definition.type !== 'ImportBinding' ||\n definition.node.type !== specifierType ||\n definition.parent.source.value !== 'vue'\n ) {\n return false;\n }\n return (\n specifierType === 'ImportNamespaceSpecifier' ||\n propertyName((definition.node as { imported?: unknown }).imported) ===\n 'defineComponent'\n );\n });\n }\n}\n\nfunction storesTranslationResult(\n parent: ParentNode,\n child: ParentNode,\n): boolean {\n if (\n (parent as { type: string }).type === 'AccessorProperty' &&\n parent.value === child\n ) {\n return true;\n }\n switch (parent.type) {\n case 'VariableDeclarator':\n return parent.init === child;\n case 'AssignmentExpression':\n case 'AssignmentPattern':\n return parent.right === child;\n case 'PropertyDefinition':\n return parent.value === child;\n case 'ReturnStatement':\n return parent.argument === child;\n case 'ExportDefaultDeclaration':\n return parent.declaration === child;\n default:\n return false;\n }\n}\n\ntype ParentNode = Rule.Node & {\n arguments?: unknown[];\n argument?: unknown;\n body?: unknown;\n callee?: unknown;\n computed?: boolean;\n parent: ParentNode | null;\n declaration?: unknown;\n init?: unknown;\n key?: unknown;\n right?: unknown;\n value?: unknown;\n};\n","import { diagnosticMessage } from '@ai-i18n/analyzer';\nimport type { Rule } from 'eslint';\nimport { reportAnalysisFailureOnce } from '../rule-analysis.js';\nimport {\n matchTranslationCalls,\n type TranslationCandidate,\n type TranslationRuleOptions,\n} from './translation-call.js';\n\nconst FUNCTION_TYPES = new Set([\n 'ArrowFunctionExpression',\n 'FunctionDeclaration',\n 'FunctionExpression',\n]);\nconst IMMEDIATE_CONSOLE_METHODS = new Set([\n 'debug',\n 'error',\n 'info',\n 'log',\n 'warn',\n]);\n\nexport const noUnsubscribedT: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: '警告组件渲染路径中未订阅语言状态的顶层 t',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: {\n anyOf: [\n { type: 'boolean' },\n {\n type: 'array',\n items: { enum: ['t', 'useI18n'] },\n uniqueItems: true,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsubscribedT: diagnosticMessage(\n '组件渲染期间使用顶层 t 不会订阅语言状态,语言切换不会主动刷新,缓存的渲染结果也可能继续使用旧译文。请使用 useI18n() 返回的 t。',\n 'Top-level t does not subscribe the component to language updates, so language changes do not trigger a render and cached render results may remain stale. Use the t returned by useI18n().',\n ),\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as TranslationRuleOptions;\n const candidates: TranslationCandidate[] = [];\n const jsxOwners = new Set<ParentNode>();\n const templateNodes = new Set<Rule.Node>();\n const collectJsxOwner = (node: Rule.Node) => {\n const owner = nearestFunction(node);\n if (owner) jsxOwners.add(owner);\n };\n const scriptVisitor: Rule.RuleListener = {\n CallExpression(node) {\n candidates.push({ kind: 'call', node });\n },\n TaggedTemplateExpression(node) {\n candidates.push({ kind: 'tagged-template', node });\n },\n JSXElement: collectJsxOwner,\n JSXFragment: collectJsxOwner,\n 'Program:exit'(program) {\n let matches;\n try {\n matches = matchTranslationCalls(context, options, candidates);\n } catch (error) {\n reportAnalysisFailureOnce(context, program as Rule.Node, error);\n return;\n }\n for (const { call, node } of matches) {\n if (call.origin !== 'runtime') continue;\n if (templateNodes.has(node)) {\n if (!isVueTemplateEventHandler(node)) {\n context.report({ node, messageId: 'unsubscribedT' });\n }\n continue;\n }\n // ponytail: 先检查直接拥有 JSX 的最近函数;出现真实漏报后再引入调用图与框架数据流。\n const owner = nearestFunction(node);\n if (\n !owner ||\n !jsxOwners.has(owner) ||\n isImmediateConsoleEffect(node)\n ) {\n continue;\n }\n context.report({ node, messageId: 'unsubscribedT' });\n }\n },\n };\n const parserServices = context.sourceCode\n .parserServices as VueParserServices;\n if (!parserServices.defineTemplateBodyVisitor) return scriptVisitor;\n return parserServices.defineTemplateBodyVisitor(\n {\n CallExpression(node) {\n candidates.push({ kind: 'call', node });\n templateNodes.add(node);\n },\n TaggedTemplateExpression(node) {\n candidates.push({ kind: 'tagged-template', node });\n templateNodes.add(node);\n },\n },\n scriptVisitor,\n // 模板默认在 Program:exit 才遍历;提前到 Program,确保汇总前已收集候选。\n { templateBodyTriggerSelector: 'Program' },\n );\n },\n};\n\nfunction nearestFunction(node: Rule.Node): ParentNode | null {\n let current = node as ParentNode;\n while (current.parent) {\n current = current.parent;\n if (FUNCTION_TYPES.has(current.type)) return current;\n }\n return null;\n}\n\nfunction isImmediateConsoleEffect(node: Rule.Node): boolean {\n const call = (node as ParentNode).parent;\n if (\n call?.type !== 'CallExpression' ||\n call.parent?.type !== 'ExpressionStatement' ||\n !call.arguments.some((argument) => argument === node) ||\n call.callee.type !== 'MemberExpression' ||\n call.callee.object.type !== 'Identifier' ||\n call.callee.object.name !== 'console'\n ) {\n return false;\n }\n const property = call.callee.property;\n const method =\n !call.callee.computed && property.type === 'Identifier'\n ? property.name\n : call.callee.computed &&\n property.type === 'Literal' &&\n typeof property.value === 'string'\n ? property.value\n : null;\n return method !== null && IMMEDIATE_CONSOLE_METHODS.has(method);\n}\n\nfunction isVueTemplateEventHandler(node: Rule.Node): boolean {\n let current = node as unknown as TemplateParentNode;\n while (current.parent) {\n current = current.parent;\n if (current.type === 'VOnExpression') return true;\n }\n return false;\n}\n\ntype ParentNode = Rule.Node & {\n parent: ParentNode | null;\n};\n\ninterface TemplateParentNode {\n type: string;\n parent: TemplateParentNode | null;\n}\n\ninterface VueParserServices {\n defineTemplateBodyVisitor?(\n templateBodyVisitor: Record<string, (node: Rule.Node) => void>,\n scriptVisitor: Rule.RuleListener,\n options: { templateBodyTriggerSelector: 'Program' | 'Program:exit' },\n ): Rule.RuleListener;\n}\n","import type { Rule } from 'eslint';\nimport type { AutoImportOption } from '../analyze.js';\nimport {\n analyzeRuleContext,\n reportAnalysisFailureOnce,\n} from '../rule-analysis.js';\n\ninterface RuleOptions {\n tsconfigPath?: string;\n autoImport?: AutoImportOption;\n maxStaticCandidates?: number;\n}\n\nexport const staticCandidateLimit: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: '警告单个 t() 展开的静态候选数量过多',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: {\n anyOf: [\n { type: 'boolean' },\n {\n type: 'array',\n items: { enum: ['t', 'useI18n'] },\n uniqueItems: true,\n },\n ],\n },\n maxStaticCandidates: {\n type: 'integer',\n minimum: 1,\n maximum: Number.MAX_SAFE_INTEGER,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n candidateLimit: '{{reason}}',\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as RuleOptions;\n return {\n 'Program:exit'(node) {\n try {\n const warnings = analyzeRuleContext(\n context,\n options,\n options.maxStaticCandidates ?? 1_000,\n );\n for (const warning of warnings) {\n if (warning.code !== 'static-candidate-limit') continue;\n context.report({\n node,\n loc: {\n start: warning,\n end: { line: warning.line, column: warning.column + 1 },\n },\n messageId: 'candidateLimit',\n data: { reason: warning.message },\n });\n }\n } catch (error) {\n reportAnalysisFailureOnce(context, node as Rule.Node, error);\n }\n },\n };\n },\n};\n","import type { Rule } from 'eslint';\nimport { diagnosticMessage } from '@ai-i18n/analyzer';\nimport { normalizeAutoImports, type AutoImportOption } from '../analyze.js';\nimport {\n analyzeRuleContext,\n reportAnalysisFailureOnce,\n} from '../rule-analysis.js';\n\ninterface RuleOptions {\n tsconfigPath?: string;\n autoImport?: AutoImportOption;\n}\n\ninterface AstNode {\n type: string;\n name?: string;\n importKind?: string;\n range?: [number, number];\n argument?: AstNode;\n left?: AstNode;\n elements?: Array<AstNode | null>;\n properties?: AstNode[];\n value?: AstNode;\n id?: AstNode;\n declarations?: AstNode[];\n specifiers?: AstNode[];\n local?: AstNode;\n declaration?: AstNode | null;\n body?: AstNode[];\n parent?: AstNode | null;\n}\n\ninterface VueElement extends AstNode {\n startTag?: {\n attributes?: Array<{\n directive?: boolean;\n key?: { name?: string };\n }>;\n };\n}\n\ninterface VueDocument {\n children?: AstNode[];\n}\n\ninterface VueParserServices {\n defineTemplateBodyVisitor?: (\n templateVisitor: Record<string, (node: AstNode) => void>,\n scriptVisitor: Rule.RuleListener,\n ) => Rule.RuleListener;\n getDocumentFragment?: () => VueDocument | null;\n}\n\ninterface VueReference {\n id?: AstNode;\n variable?: unknown;\n}\n\nexport const tStaticArgs: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: '要求 virtual:ai-i18n 的 t() 参数可被静态提取',\n },\n schema: [\n {\n type: 'object',\n properties: {\n tsconfigPath: { type: 'string' },\n autoImport: {\n anyOf: [\n { type: 'boolean' },\n {\n type: 'array',\n items: { enum: ['t', 'useI18n'] },\n uniqueItems: true,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n analysisFailed: '{{reason}}',\n dynamicArg: '{{reason}}',\n invalidUsage: '{{reason}}',\n templateNeedsI18n: '{{reason}}',\n },\n },\n create(context) {\n const options = (context.options[0] ?? {}) as RuleOptions;\n const scriptVisitor: Rule.RuleListener = {\n 'Program:exit'(node) {\n let warnings;\n try {\n warnings = analyzeRuleContext(context, options);\n } catch (error) {\n reportAnalysisFailureOnce(context, node as Rule.Node, error);\n return;\n }\n for (const warning of warnings) {\n const analysisFailed = warning.code === 'parse-error';\n const invalidUsage =\n warning.code !== 'dynamic-argument' &&\n warning.code !== 'unresolved-argument' &&\n !analysisFailed;\n context.report({\n node,\n loc: {\n start: warning,\n end: { line: warning.line, column: warning.column + 1 },\n },\n messageId: analysisFailed\n ? 'analysisFailed'\n : invalidUsage\n ? 'invalidUsage'\n : 'dynamicArg',\n data: {\n reason: analysisFailed\n ? diagnosticMessage(\n `静态分析失败:${warning.message}`,\n `Static analysis failed: ${warning.message}`,\n )\n : invalidUsage\n ? warning.message\n : diagnosticMessage(\n 't() 的参数无法静态提取。source 请使用静态字符串,options 请使用只包含 comment 的静态对象。',\n 'The t() arguments cannot be statically extracted. Use a static string for source and a static object containing only comment for options.',\n ),\n },\n });\n }\n },\n };\n\n if (\n !context.filename.toLowerCase().endsWith('.vue') ||\n !normalizeAutoImports(options.autoImport).t\n ) {\n return scriptVisitor;\n }\n const services = context.sourceCode\n .parserServices as unknown as VueParserServices;\n const document = services.getDocumentFragment?.();\n const scripts = document?.children?.filter(isScriptElement) ?? [];\n const setupScript = scripts.find(isScriptSetup);\n // 普通 Options API 的模板可从组件实例获得 t,无法仅凭 SFC 静态确认来源。\n if ((!setupScript && scripts.length > 0) || !document) {\n return scriptVisitor;\n }\n if (\n setupScript &&\n hasTopLevelBinding(\n context.sourceCode.ast as unknown as AstNode,\n setupScript,\n 't',\n )\n ) {\n return scriptVisitor;\n }\n if (!services.defineTemplateBodyVisitor) return scriptVisitor;\n\n const reportTemplateT = (node: AstNode) => {\n const target =\n node.type === 'CallExpression'\n ? (node as AstNode & { callee?: AstNode }).callee\n : (node as AstNode & { tag?: AstNode }).tag;\n if (\n target?.type !== 'Identifier' ||\n target.name !== 't' ||\n isTemplateLocal(target)\n ) {\n return;\n }\n context.report({\n node: target as Rule.Node,\n messageId: 'templateNeedsI18n',\n data: {\n reason: diagnosticMessage(\n '模板中的裸 t 无法绑定到 ai-i18n,也不会订阅语言变化。请在 <script setup> 中声明 const { t } = useI18n()。',\n 'A bare t in the template is not bound to ai-i18n and does not subscribe to language changes. Declare const { t } = useI18n() in <script setup>.',\n ),\n },\n });\n };\n return services.defineTemplateBodyVisitor(\n {\n CallExpression: reportTemplateT,\n TaggedTemplateExpression: reportTemplateT,\n },\n scriptVisitor,\n );\n },\n};\n\nfunction isScriptElement(node: AstNode): node is VueElement {\n return node.type === 'VElement' && node.name === 'script';\n}\n\nfunction isScriptSetup(node: VueElement): boolean {\n return (\n node.startTag?.attributes?.some(\n (attribute) => !attribute.directive && attribute.key?.name === 'setup',\n ) ?? false\n );\n}\n\nfunction hasTopLevelBinding(\n program: AstNode,\n script: VueElement,\n name: string,\n): boolean {\n return (\n program.body?.some(\n (statement) =>\n isInside(statement, script) && statementDeclares(statement, name),\n ) ?? false\n );\n}\n\nfunction isInside(node: AstNode, container: AstNode): boolean {\n return Boolean(\n node.range &&\n container.range &&\n node.range[0] >= container.range[0] &&\n node.range[1] <= container.range[1],\n );\n}\n\nfunction statementDeclares(node: AstNode, name: string): boolean {\n switch (node.type) {\n case 'ImportDeclaration':\n return (\n node.importKind !== 'type' &&\n (node.specifiers?.some(\n (item) => item.importKind !== 'type' && item.local?.name === name,\n ) ??\n false)\n );\n case 'VariableDeclaration':\n return (\n node.declarations?.some((item) => patternDeclares(item.id, name)) ??\n false\n );\n case 'FunctionDeclaration':\n case 'ClassDeclaration':\n case 'TSEnumDeclaration':\n case 'TSImportEqualsDeclaration':\n return node.id?.name === name;\n case 'ExportNamedDeclaration':\n case 'ExportDefaultDeclaration':\n return node.declaration\n ? statementDeclares(node.declaration, name)\n : false;\n default:\n return false;\n }\n}\n\nfunction patternDeclares(node: AstNode | undefined, name: string): boolean {\n if (!node) return false;\n switch (node.type) {\n case 'Identifier':\n return node.name === name;\n case 'RestElement':\n return patternDeclares(node.argument, name);\n case 'AssignmentPattern':\n return patternDeclares(node.left, name);\n case 'ArrayPattern':\n return (\n node.elements?.some((item) =>\n patternDeclares(item ?? undefined, name),\n ) ?? false\n );\n case 'ObjectPattern':\n return (\n node.properties?.some((item) =>\n patternDeclares(\n item.type === 'RestElement' ? item.argument : item.value,\n name,\n ),\n ) ?? false\n );\n default:\n return false;\n }\n}\n\nfunction isTemplateLocal(node: AstNode): boolean {\n let parent = node.parent;\n while (parent && parent.type !== 'VExpressionContainer') {\n parent = parent.parent;\n }\n const references = (\n parent as (AstNode & { references?: VueReference[] }) | null | undefined\n )?.references;\n return (\n references?.some(\n (reference) =>\n reference.variable &&\n (reference.id === node ||\n (reference.id?.range?.[0] === node.range?.[0] &&\n reference.id?.range?.[1] === node.range?.[1])),\n ) ?? false\n );\n}\n","import { createRequire } from 'node:module';\nimport type { ESLint } from 'eslint';\nimport { noEagerTranslation } from './rules/no-eager-translation.js';\nimport { noUnsubscribedT } from './rules/no-unsubscribed-t.js';\nimport { staticCandidateLimit } from './rules/static-candidate-limit.js';\nimport { tStaticArgs } from './rules/t-static-args.js';\n\nconst { version } = createRequire(import.meta.url)('../package.json') as {\n version: string;\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: '@ai-i18n/eslint-plugin',\n version,\n namespace: 'ai-i18n',\n },\n rules: {\n 'no-eager-translation': noEagerTranslation,\n 'no-unsubscribed-t': noUnsubscribedT,\n 'static-candidate-limit': staticCandidateLimit,\n 't-static-args': tStaticArgs,\n },\n configs: {},\n};\n\n// 规则集必须由使用者显式引入,不会修改宿主项目的 ESLint 配置。\nplugin.configs!.recommended = [\n {\n ignores: ['**/*.vue'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: { defineI18nMessages: 'readonly' },\n },\n rules: {\n 'ai-i18n/t-static-args': 'error',\n 'ai-i18n/no-eager-translation': 'warn',\n 'ai-i18n/no-unsubscribed-t': 'warn',\n 'ai-i18n/static-candidate-limit': 'warn',\n },\n },\n];\n\nplugin.configs!.vue = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: { defineI18nMessages: 'readonly' },\n },\n rules: {\n 'ai-i18n/t-static-args': 'error',\n 'ai-i18n/no-eager-translation': 'warn',\n 'ai-i18n/no-unsubscribed-t': 'warn',\n 'ai-i18n/static-candidate-limit': 'warn',\n },\n },\n];\n\n// 自动导入 preset 必须与 Vite 各模式实际注入的 API 一一对应。\nplugin.configs!['vanilla-auto-import'] = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n t: 'readonly',\n setLang: 'readonly',\n getLang: 'readonly',\n getLangs: 'readonly',\n getLangLoadState: 'readonly',\n subscribe: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/t-static-args': ['error', { autoImport: ['t'] }],\n 'ai-i18n/no-eager-translation': ['warn', { autoImport: ['t'] }],\n 'ai-i18n/static-candidate-limit': ['warn', { autoImport: ['t'] }],\n },\n },\n];\n\nplugin.configs!['vue-auto-import'] = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n t: 'readonly',\n useI18n: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/t-static-args': ['error', { autoImport: ['t', 'useI18n'] }],\n 'ai-i18n/no-eager-translation': [\n 'warn',\n { autoImport: ['t', 'useI18n'] },\n ],\n 'ai-i18n/no-unsubscribed-t': ['warn', { autoImport: ['t', 'useI18n'] }],\n 'ai-i18n/static-candidate-limit': [\n 'warn',\n { autoImport: ['t', 'useI18n'] },\n ],\n },\n },\n];\n\nplugin.configs!['react-auto-import'] = [\n {\n files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],\n plugins: { 'ai-i18n': plugin },\n languageOptions: {\n globals: {\n t: 'readonly',\n useI18n: 'readonly',\n defineI18nMessages: 'readonly',\n },\n },\n rules: {\n 'ai-i18n/t-static-args': ['error', { autoImport: ['t', 'useI18n'] }],\n 'ai-i18n/no-eager-translation': [\n 'warn',\n { autoImport: ['t', 'useI18n'] },\n ],\n 'ai-i18n/no-unsubscribed-t': ['warn', { autoImport: ['t', 'useI18n'] }],\n 'ai-i18n/static-candidate-limit': [\n 'warn',\n { autoImport: ['t', 'useI18n'] },\n ],\n },\n },\n];\n\nexport {\n noEagerTranslation,\n noUnsubscribedT,\n staticCandidateLimit,\n tStaticArgs,\n};\nexport default plugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-i18n/eslint-plugin",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"description": "ESLint static-argument checks aligned with ai-i18n extraction semantics.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -33,17 +33,25 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ai-i18n/analyzer": "^1.0.0-alpha.
|
|
36
|
+
"@ai-i18n/analyzer": "^1.0.0-alpha.5"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@vue/compiler-sfc": ">=3.2.0",
|
|
40
40
|
"eslint": ">=9.0.0",
|
|
41
|
+
"typescript": ">=5.0.0",
|
|
42
|
+
"vue": ">=3.2.25",
|
|
41
43
|
"vue-eslint-parser": ">=9.0.0"
|
|
42
44
|
},
|
|
43
45
|
"peerDependenciesMeta": {
|
|
44
46
|
"@vue/compiler-sfc": {
|
|
45
47
|
"optional": true
|
|
46
48
|
},
|
|
49
|
+
"typescript": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"vue": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
47
55
|
"vue-eslint-parser": {
|
|
48
56
|
"optional": true
|
|
49
57
|
}
|