@ai-i18n/eslint-plugin 1.0.0-alpha.2 → 1.0.0-alpha.4
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 +36 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +133 -30
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# @ai-i18n/eslint-plugin
|
|
2
2
|
|
|
3
|
-
用于提前报告无法被 Vite/Yuku
|
|
3
|
+
用于提前报告无法被 Vite/Yuku 静态提取或不符合推荐语法的 `t()` 参数。规则检查解析到
|
|
4
4
|
`virtual:ai-i18n` 的 `t` binding,以及 Vue/React 模式下 `useI18n()` 解构或对象成员得到的
|
|
5
5
|
`t`。其他库或局部同名函数不受影响。
|
|
6
6
|
|
|
7
|
+
alpha 阶段请安装 `@ai-i18n/eslint-plugin@alpha`;peer 支持 ESLint 9 和 10。
|
|
8
|
+
|
|
7
9
|
## 按模式配置
|
|
8
10
|
|
|
9
11
|
显式 import 的 Vanilla 项目可以使用 `recommended`。启用 ai-i18n 按需导入时,选择与
|
|
10
|
-
Vite `framework` 一致的 preset
|
|
12
|
+
Vite `framework` 一致的 preset;它会声明对应只读全局,并启用静态参数报错与候选数量
|
|
13
|
+
警告:
|
|
11
14
|
|
|
12
15
|
```js
|
|
13
16
|
import aiI18n from '@ai-i18n/eslint-plugin';
|
|
@@ -17,8 +20,9 @@ export default [
|
|
|
17
20
|
];
|
|
18
21
|
```
|
|
19
22
|
|
|
20
|
-
这些 preset 只负责 ai-i18n 的 `t`/`useI18n
|
|
21
|
-
|
|
23
|
+
这些 preset 只负责 ai-i18n 的 `t`/`useI18n`,并在所有模式下声明只读的
|
|
24
|
+
`defineI18nMessages` 编译宏。宿主 Auto Import 插件管理的其他 API 仍由它自己的 ESLint
|
|
25
|
+
集成负责。
|
|
22
26
|
|
|
23
27
|
## Vue SFC
|
|
24
28
|
|
|
@@ -42,7 +46,8 @@ export default [
|
|
|
42
46
|
];
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
`configs.vue` 复用宿主的 Vue parser
|
|
49
|
+
`configs.vue` 复用宿主的 Vue parser,并启用 `t-static-args` 与
|
|
50
|
+
`static-candidate-limit`。语义分析使用
|
|
46
51
|
Vue 项目已有的 `@vue/compiler-sfc`,与 Vite 提取器共享编译结果和 source map,覆盖
|
|
47
52
|
`<script>`、`<script setup>`、模板插值和指令表达式。两个 Vue 依赖均为可选 peer,
|
|
48
53
|
不会安装到 React/Vanilla 项目。
|
|
@@ -50,8 +55,13 @@ Vue 项目已有的 `@vue/compiler-sfc`,与 Vite 提取器共享编译结果
|
|
|
50
55
|
Vue preset 同时覆盖 Vue JSX/TSX,但宿主仍需用 `@vitejs/plugin-vue-jsx` 编译这些文件。
|
|
51
56
|
React 项目使用 React preset;同一个 Vite build 不支持两种框架模式混用。
|
|
52
57
|
|
|
53
|
-
规则与 Vite 共用静态参数语义,包括 `i18n.t()`、`i18n['t']()
|
|
54
|
-
`t('source', undefined)
|
|
58
|
+
规则与 Vite 共用静态参数语义,包括 `i18n.t()`、`i18n['t']()`、省略式
|
|
59
|
+
`t('source', undefined)` 和 tagged template。未绑定到 ai-i18n 的 template-only `t()`
|
|
60
|
+
不参与检查。
|
|
61
|
+
|
|
62
|
+
对象或数组成员只有在根集合由 `defineI18nMessages()` 标记后才属于推荐写法。字符串拼接、
|
|
63
|
+
逻辑表达式、`let` 文案、普通集合成员、`const tr = t`、命名空间调用、二次 Hook 解构、
|
|
64
|
+
`useI18n().t()` 与 `require()` 都会报错。
|
|
55
65
|
|
|
56
66
|
需要解析 `tsconfig` 路径别名时,可以显式配置规则:
|
|
57
67
|
|
|
@@ -60,8 +70,19 @@ import aiI18n from '@ai-i18n/eslint-plugin';
|
|
|
60
70
|
|
|
61
71
|
export default [
|
|
62
72
|
{
|
|
73
|
+
languageOptions: {
|
|
74
|
+
globals: { defineI18nMessages: 'readonly' },
|
|
75
|
+
},
|
|
63
76
|
plugins: { 'ai-i18n': aiI18n },
|
|
64
77
|
rules: {
|
|
78
|
+
'ai-i18n/static-candidate-limit': [
|
|
79
|
+
'warn',
|
|
80
|
+
{
|
|
81
|
+
autoImport: true,
|
|
82
|
+
tsconfigPath: './tsconfig.json',
|
|
83
|
+
maxStaticCandidates: 2_000,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
65
86
|
'ai-i18n/t-static-args': [
|
|
66
87
|
'error',
|
|
67
88
|
{
|
|
@@ -73,4 +94,12 @@ export default [
|
|
|
73
94
|
];
|
|
74
95
|
```
|
|
75
96
|
|
|
97
|
+
`ai-i18n/static-candidate-limit` 默认在单个 `t()` 的 source 与 options 组合超过 1000 个
|
|
98
|
+
时警告。`maxStaticCandidates` 必须是正整数,只改变 ESLint 的提示阈值;Vite 提取不设
|
|
99
|
+
上限,也没有对应插件选项。
|
|
100
|
+
|
|
76
101
|
插件不会自动修改宿主 ESLint 配置。
|
|
102
|
+
|
|
103
|
+
诊断默认按 Node 时区选择语言:`Asia/Shanghai` 与 `Asia/Urumqi` 使用中文,其他时区使用
|
|
104
|
+
英文。设置 `AI_I18N_DIAGNOSTIC_LOCALE=zh-CN` 或 `en-US` 可以固定语言,`auto` 恢复自动
|
|
105
|
+
检测。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { ESLint, Rule } from "eslint";
|
|
2
|
+
//#region src/rules/static-candidate-limit.d.ts
|
|
3
|
+
declare const staticCandidateLimit: Rule.RuleModule;
|
|
4
|
+
//#endregion
|
|
2
5
|
//#region src/rules/t-static-args.d.ts
|
|
3
6
|
declare const tStaticArgs: Rule.RuleModule;
|
|
4
7
|
//#endregion
|
|
5
8
|
//#region src/index.d.ts
|
|
6
9
|
declare const plugin: ESLint.Plugin;
|
|
7
10
|
//#endregion
|
|
8
|
-
export { plugin as default, tStaticArgs };
|
|
11
|
+
export { plugin as default, staticCandidateLimit, tStaticArgs };
|
|
9
12
|
//# 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/t-static-args.ts","../src/index.ts"],"mappings":";;cASa,aAAa,KAAK;;;
|
|
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":";;cASa,sBAAsB,KAAK;;;cCA3B,aAAa,KAAK;;;cCAzB,QAAQ,OAAO"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { AI_I18N_VIRTUAL_MODULE_ID, Analyzer, extractMessages, findUnboundCalls } from "@ai-i18n/analyzer";
|
|
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 = [
|
|
@@ -110,7 +110,7 @@ function stripJsonComments(source) {
|
|
|
110
110
|
}
|
|
111
111
|
//#endregion
|
|
112
112
|
//#region src/analyze.ts
|
|
113
|
-
function analyzeStaticArgs(code, filename, tsconfigPath, lang, autoImport = false) {
|
|
113
|
+
function analyzeStaticArgs(code, filename, tsconfigPath, lang, autoImport = false, maxStaticCandidates = Number.POSITIVE_INFINITY) {
|
|
114
114
|
const resolve = createImportResolver(tsconfigPath);
|
|
115
115
|
const analyzer = new Analyzer({ resolve });
|
|
116
116
|
analyzer.addFile(AI_I18N_VIRTUAL_MODULE_ID, "export function t(source) { return source }");
|
|
@@ -119,7 +119,13 @@ function analyzeStaticArgs(code, filename, tsconfigPath, lang, autoImport = fals
|
|
|
119
119
|
if (!hasTranslationCandidate(entry, autoImport)) return [];
|
|
120
120
|
loadDependencies(analyzer, entry, resolve);
|
|
121
121
|
analyzer.link();
|
|
122
|
-
|
|
122
|
+
const extraction = extractMessages(entry, AI_I18N_VIRTUAL_MODULE_ID, translationHooks(autoImport), autoImport, maxStaticCandidates).warnings;
|
|
123
|
+
const recommended = validateRecommendedUsage(entry, AI_I18N_VIRTUAL_MODULE_ID, translationHooks(autoImport), autoImport);
|
|
124
|
+
return (recommended.length ? [
|
|
125
|
+
...extraction.filter((warning) => warning.code === "parse-error"),
|
|
126
|
+
...extraction.filter((warning) => warning.code === "static-candidate-limit"),
|
|
127
|
+
...recommended
|
|
128
|
+
] : extraction).map(({ code: warningCode, line, column, message }) => ({
|
|
123
129
|
code: warningCode,
|
|
124
130
|
line,
|
|
125
131
|
column,
|
|
@@ -127,7 +133,7 @@ function analyzeStaticArgs(code, filename, tsconfigPath, lang, autoImport = fals
|
|
|
127
133
|
}));
|
|
128
134
|
}
|
|
129
135
|
function hasTranslationCandidate(module, autoImport) {
|
|
130
|
-
return module.imports.some((item) => !item.typeOnly && (item.name === "t" || item.name === "useI18n")) ||
|
|
136
|
+
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, /* @__PURE__ */ new Set(["defineI18nMessages", ...autoImport ? ["t", "useI18n"] : []])).length > 0;
|
|
131
137
|
}
|
|
132
138
|
function translationHooks(autoImport) {
|
|
133
139
|
return [{
|
|
@@ -164,11 +170,11 @@ function normalizeFilename(filename) {
|
|
|
164
170
|
//#region src/vue-sfc.ts
|
|
165
171
|
const require = createRequire(import.meta.url);
|
|
166
172
|
function createVueAnalysisSource(source, filename, parserServices) {
|
|
167
|
-
if (!parserServices.getDocumentFragment?.()) throw new Error("检查 .vue
|
|
173
|
+
if (!parserServices.getDocumentFragment?.()) throw new Error(diagnosticMessage("检查 .vue 文件需要配置 vue-eslint-parser。", "Configure vue-eslint-parser to lint .vue files."));
|
|
168
174
|
try {
|
|
169
175
|
return analyzeVueSource(source, filename, require("@vue/compiler-sfc"));
|
|
170
176
|
} catch (error) {
|
|
171
|
-
if (isMissingVueCompiler(error)) throw new Error("检查 .vue 文件需要安装 @vue/compiler-sfc");
|
|
177
|
+
if (isMissingVueCompiler(error)) throw new Error(diagnosticMessage("检查 .vue 文件需要安装 @vue/compiler-sfc。", "Install @vue/compiler-sfc to lint .vue files."));
|
|
172
178
|
throw error;
|
|
173
179
|
}
|
|
174
180
|
}
|
|
@@ -176,6 +182,87 @@ function isMissingVueCompiler(error) {
|
|
|
176
182
|
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
177
183
|
}
|
|
178
184
|
//#endregion
|
|
185
|
+
//#region src/rule-analysis.ts
|
|
186
|
+
const DEFAULT_CANDIDATE_LIMIT = 1e3;
|
|
187
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
188
|
+
function analyzeRuleContext(context, options, maxStaticCandidates = Number.POSITIVE_INFINITY) {
|
|
189
|
+
const cached = cache.get(context.sourceCode) ?? {
|
|
190
|
+
source: createAnalysisSource(context),
|
|
191
|
+
warnings: /* @__PURE__ */ new Map()
|
|
192
|
+
};
|
|
193
|
+
cache.set(context.sourceCode, cached);
|
|
194
|
+
const analyze = (limit) => {
|
|
195
|
+
const key = JSON.stringify([
|
|
196
|
+
options.tsconfigPath ?? null,
|
|
197
|
+
options.autoImport ?? false,
|
|
198
|
+
limit
|
|
199
|
+
]);
|
|
200
|
+
const existing = cached.warnings.get(key);
|
|
201
|
+
if (existing) return existing;
|
|
202
|
+
const warnings = analyzeStaticArgs(cached.source.code, context.filename, options.tsconfigPath, cached.source.lang, options.autoImport, limit).map((warning) => ({
|
|
203
|
+
...warning,
|
|
204
|
+
...cached.source.mapLocation(warning)
|
|
205
|
+
}));
|
|
206
|
+
cached.warnings.set(key, warnings);
|
|
207
|
+
return warnings;
|
|
208
|
+
};
|
|
209
|
+
if (Number.isFinite(maxStaticCandidates)) return analyze(maxStaticCandidates);
|
|
210
|
+
const preflight = analyze(DEFAULT_CANDIDATE_LIMIT);
|
|
211
|
+
return preflight.some((warning) => warning.code === "static-candidate-limit") ? analyze(Number.POSITIVE_INFINITY) : preflight;
|
|
212
|
+
}
|
|
213
|
+
function createAnalysisSource(context) {
|
|
214
|
+
return context.filename.endsWith(".vue") ? createVueAnalysisSource(context.sourceCode.text, context.filename, context.sourceCode.parserServices) : {
|
|
215
|
+
code: context.sourceCode.text,
|
|
216
|
+
lang: void 0,
|
|
217
|
+
mapLocation: (location) => location
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/rules/static-candidate-limit.ts
|
|
222
|
+
const staticCandidateLimit = {
|
|
223
|
+
meta: {
|
|
224
|
+
type: "problem",
|
|
225
|
+
docs: { description: "警告单个 t() 展开的静态候选数量过多" },
|
|
226
|
+
schema: [{
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
tsconfigPath: { type: "string" },
|
|
230
|
+
autoImport: { type: "boolean" },
|
|
231
|
+
maxStaticCandidates: {
|
|
232
|
+
type: "integer",
|
|
233
|
+
minimum: 1,
|
|
234
|
+
maximum: Number.MAX_SAFE_INTEGER
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
additionalProperties: false
|
|
238
|
+
}],
|
|
239
|
+
messages: { candidateLimit: "{{reason}}" }
|
|
240
|
+
},
|
|
241
|
+
create(context) {
|
|
242
|
+
const options = context.options[0] ?? {};
|
|
243
|
+
return { "Program:exit"(node) {
|
|
244
|
+
try {
|
|
245
|
+
const warnings = analyzeRuleContext(context, options, options.maxStaticCandidates ?? 1e3);
|
|
246
|
+
for (const warning of warnings) {
|
|
247
|
+
if (warning.code !== "static-candidate-limit") continue;
|
|
248
|
+
context.report({
|
|
249
|
+
node,
|
|
250
|
+
loc: {
|
|
251
|
+
start: warning,
|
|
252
|
+
end: {
|
|
253
|
+
line: warning.line,
|
|
254
|
+
column: warning.column + 1
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
messageId: "candidateLimit",
|
|
258
|
+
data: { reason: warning.message }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
} catch {}
|
|
262
|
+
} };
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
//#endregion
|
|
179
266
|
//#region src/rules/t-static-args.ts
|
|
180
267
|
const tStaticArgs = {
|
|
181
268
|
meta: {
|
|
@@ -190,8 +277,9 @@ const tStaticArgs = {
|
|
|
190
277
|
additionalProperties: false
|
|
191
278
|
}],
|
|
192
279
|
messages: {
|
|
193
|
-
analysisFailed: "
|
|
194
|
-
dynamicArg: "
|
|
280
|
+
analysisFailed: "{{reason}}",
|
|
281
|
+
dynamicArg: "{{reason}}",
|
|
282
|
+
invalidUsage: "{{reason}}"
|
|
195
283
|
}
|
|
196
284
|
},
|
|
197
285
|
create(context) {
|
|
@@ -199,26 +287,18 @@ const tStaticArgs = {
|
|
|
199
287
|
return { "Program:exit"(node) {
|
|
200
288
|
let warnings;
|
|
201
289
|
try {
|
|
202
|
-
|
|
203
|
-
code: context.sourceCode.text,
|
|
204
|
-
lang: void 0,
|
|
205
|
-
mapLocation: (location) => location
|
|
206
|
-
};
|
|
207
|
-
warnings = analyzeStaticArgs(source.code, context.filename, options.tsconfigPath, source.lang, options.autoImport);
|
|
208
|
-
warnings = warnings.map((warning) => ({
|
|
209
|
-
...warning,
|
|
210
|
-
...source.mapLocation(warning)
|
|
211
|
-
}));
|
|
290
|
+
warnings = analyzeRuleContext(context, options);
|
|
212
291
|
} catch (error) {
|
|
213
292
|
context.report({
|
|
214
293
|
node,
|
|
215
294
|
messageId: "analysisFailed",
|
|
216
|
-
data: { reason: error instanceof Error ? error.message : String(error) }
|
|
295
|
+
data: { reason: diagnosticMessage(`静态分析失败:${error instanceof Error ? error.message : String(error)}`, `Static analysis failed: ${error instanceof Error ? error.message : String(error)}`) }
|
|
217
296
|
});
|
|
218
297
|
return;
|
|
219
298
|
}
|
|
220
299
|
for (const warning of warnings) {
|
|
221
300
|
const analysisFailed = warning.code === "parse-error";
|
|
301
|
+
const invalidUsage = warning.code !== "dynamic-argument" && warning.code !== "unresolved-argument" && !analysisFailed;
|
|
222
302
|
context.report({
|
|
223
303
|
node,
|
|
224
304
|
loc: {
|
|
@@ -228,8 +308,8 @@ const tStaticArgs = {
|
|
|
228
308
|
column: warning.column + 1
|
|
229
309
|
}
|
|
230
310
|
},
|
|
231
|
-
messageId: analysisFailed ? "analysisFailed" : "dynamicArg",
|
|
232
|
-
|
|
311
|
+
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 请使用只包含 id、comment 的静态对象。", "The t() arguments cannot be statically extracted. Use a static string for source and a static object containing only id and comment for options.") }
|
|
233
313
|
});
|
|
234
314
|
}
|
|
235
315
|
} };
|
|
@@ -244,25 +324,44 @@ const plugin = {
|
|
|
244
324
|
version,
|
|
245
325
|
namespace: "ai-i18n"
|
|
246
326
|
},
|
|
247
|
-
rules: {
|
|
327
|
+
rules: {
|
|
328
|
+
"static-candidate-limit": staticCandidateLimit,
|
|
329
|
+
"t-static-args": tStaticArgs
|
|
330
|
+
},
|
|
248
331
|
configs: {}
|
|
249
332
|
};
|
|
250
333
|
plugin.configs.recommended = [{
|
|
251
334
|
ignores: ["**/*.vue"],
|
|
252
335
|
plugins: { "ai-i18n": plugin },
|
|
253
|
-
|
|
336
|
+
languageOptions: { globals: { defineI18nMessages: "readonly" } },
|
|
337
|
+
rules: {
|
|
338
|
+
"ai-i18n/static-candidate-limit": "warn",
|
|
339
|
+
"ai-i18n/t-static-args": "error"
|
|
340
|
+
}
|
|
254
341
|
}];
|
|
255
342
|
plugin.configs.vue = [{
|
|
256
343
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}"],
|
|
257
344
|
plugins: { "ai-i18n": plugin },
|
|
258
|
-
languageOptions: { globals: {
|
|
259
|
-
|
|
345
|
+
languageOptions: { globals: {
|
|
346
|
+
useI18n: "readonly",
|
|
347
|
+
defineI18nMessages: "readonly"
|
|
348
|
+
} },
|
|
349
|
+
rules: {
|
|
350
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: true }],
|
|
351
|
+
"ai-i18n/t-static-args": ["error", { autoImport: true }]
|
|
352
|
+
}
|
|
260
353
|
}];
|
|
261
354
|
plugin.configs.react = [{
|
|
262
355
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
263
356
|
plugins: { "ai-i18n": plugin },
|
|
264
|
-
languageOptions: { globals: {
|
|
265
|
-
|
|
357
|
+
languageOptions: { globals: {
|
|
358
|
+
useI18n: "readonly",
|
|
359
|
+
defineI18nMessages: "readonly"
|
|
360
|
+
} },
|
|
361
|
+
rules: {
|
|
362
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: true }],
|
|
363
|
+
"ai-i18n/t-static-args": ["error", { autoImport: true }]
|
|
364
|
+
}
|
|
266
365
|
}];
|
|
267
366
|
plugin.configs.vanilla = [{
|
|
268
367
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
@@ -272,11 +371,15 @@ plugin.configs.vanilla = [{
|
|
|
272
371
|
setLang: "readonly",
|
|
273
372
|
getLang: "readonly",
|
|
274
373
|
getLangs: "readonly",
|
|
275
|
-
subscribe: "readonly"
|
|
374
|
+
subscribe: "readonly",
|
|
375
|
+
defineI18nMessages: "readonly"
|
|
276
376
|
} },
|
|
277
|
-
rules: {
|
|
377
|
+
rules: {
|
|
378
|
+
"ai-i18n/static-candidate-limit": ["warn", { autoImport: true }],
|
|
379
|
+
"ai-i18n/t-static-args": ["error", { autoImport: true }]
|
|
380
|
+
}
|
|
278
381
|
}];
|
|
279
382
|
//#endregion
|
|
280
|
-
export { plugin as default, tStaticArgs };
|
|
383
|
+
export { plugin as default, staticCandidateLimit, tStaticArgs };
|
|
281
384
|
|
|
282
385
|
//# 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/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 findUnboundCalls,\n type Module,\n type ExtractWarningCode,\n type AnalysisLanguage,\n type TranslationHookBinding,\n} from '@ai-i18n/analyzer';\nimport { createImportResolver } from './resolve-import.js';\n\nexport interface StaticArgsWarning {\n code: ExtractWarningCode;\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): 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 return extractMessages(\n entry,\n AI_I18N_VIRTUAL_MODULE_ID,\n translationHooks(autoImport),\n autoImport,\n ).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 && (item.name === 't' || item.name === 'useI18n'),\n ) ||\n (autoImport &&\n findUnboundCalls(module, new Set(['t', 'useI18n'])).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 {\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('检查 .vue 文件需要使用 vue-eslint-parser');\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('检查 .vue 文件需要安装 @vue/compiler-sfc');\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 } from '../analyze.js';\nimport { createVueAnalysisSource } from '../vue-sfc.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: 'ai-i18n 静态分析失败:{{reason}}',\n dynamicArg:\n 't() 参数无法静态提取,请使用字符串字面量、静态模板、条件表达式或可解析的 const 字符串。',\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 const source = 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 }) =>\n location,\n };\n warnings = analyzeStaticArgs(\n source.code,\n context.filename,\n options.tsconfigPath,\n source.lang,\n options.autoImport,\n );\n warnings = warnings.map((warning) => ({\n ...warning,\n ...source.mapLocation(warning),\n }));\n } catch (error) {\n context.report({\n node,\n messageId: 'analysisFailed',\n data: {\n reason: error instanceof Error ? error.message : String(error),\n },\n });\n return;\n }\n for (const warning of warnings) {\n const analysisFailed = warning.code === 'parse-error';\n context.report({\n node,\n loc: {\n start: warning,\n end: { line: warning.line, column: warning.column + 1 },\n },\n messageId: analysisFailed ? 'analysisFailed' : 'dynamicArg',\n ...(analysisFailed ? { data: { reason: warning.message } } : {}),\n });\n }\n },\n };\n },\n};\n","import { createRequire } from 'node:module';\nimport type { ESLint } from 'eslint';\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 't-static-args': tStaticArgs,\n },\n configs: {},\n};\n\n// 规则集必须由使用者显式引入,不会修改宿主项目的 ESLint 配置。\nplugin.configs!.recommended = [\n {\n ignores: ['**/*.vue'],\n plugins: { 'ai-i18n': plugin },\n rules: { 'ai-i18n/t-static-args': 'error' },\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: { globals: { useI18n: 'readonly' } },\n rules: {\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: { globals: { useI18n: 'readonly' } },\n rules: {\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 },\n },\n rules: {\n 'ai-i18n/t-static-args': ['error', { autoImport: true }],\n },\n },\n];\n\nexport { tStaticArgs };\nexport default plugin;\n"],"mappings":";;;;;;AAGA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAeA,SAAgB,qBAAqB,cAAuB;CAC1D,MAAM,UAAmB,CAAC;CAC1B,IAAI,UAAU,QAAQ,IAAI;CAC1B,IAAI,cAAc;EAChB,MAAM,iBAAiB,KAAK,QAAQ,YAAY;EAChD,MAAM,SAAS,aAAa,cAAc;EAC1C,UAAU,KAAK,QACb,KAAK,QAAQ,cAAc,GAC3B,OAAO,iBAAiB,WAAW,GACrC;EACA,KAAK,MAAM,CAAC,SAAS,YAAY,OAAO,QACtC,OAAO,iBAAiB,SAAS,CAAC,CACpC,GAAG;GACD,MAAM,OAAO,QAAQ,QAAQ,GAAG;GAChC,QAAQ,KAAK;IACX,QAAQ,OAAO,IAAI,UAAU,QAAQ,MAAM,GAAG,IAAI;IAClD,QAAQ,OAAO,IAAI,KAAK,QAAQ,MAAM,OAAO,CAAC;IAC9C;GACF,CAAC;EACH;CACF;CAEA,QAAQ,WAAmB,aAAoC;EAC7D,IAAI,cAAc,mBAAmB,OAAO;EAC5C,IAAI,UAAU,WAAW,GAAG,GAC1B,OAAO,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,GAAG,SAAS,CAAC;EAEpE,KAAK,MAAM,SAAS,SAAS;GAC3B,IACE,CAAC,UAAU,WAAW,MAAM,MAAM,KAClC,CAAC,UAAU,SAAS,MAAM,MAAM,GAEhC;GAEF,MAAM,QAAQ,UAAU,MACtB,MAAM,OAAO,QACb,UAAU,SAAS,MAAM,OAAO,UAAU,KAAA,CAC5C;GACA,KAAK,MAAM,UAAU,MAAM,SAAS;IAClC,MAAM,YAAY,OAAO,SAAS,GAAG,IACjC,OAAO,QAAQ,KAAK,KAAK,IACzB;IACJ,MAAM,WAAW,YAAY,KAAK,QAAQ,SAAS,SAAS,CAAC;IAC7D,IAAI,UAAU,OAAO;GACvB;EACF;EACA,OAAO;CACT;AACF;AAEA,SAAS,YAAY,WAAkC;CACrD,KAAK,MAAM,aAAa,mBAAmB;EACzC,MAAM,OAAO,GAAG,YAAY;EAC5B,IAAI,OAAO,IAAI,GAAG,OAAO,KAAK,UAAU,IAAI;CAC9C;CACA,KAAK,MAAM,aAAa,kBAAkB,MAAM,CAAC,GAAG;EAClD,MAAM,OAAO,KAAK,KAAK,WAAW,QAAQ,WAAW;EACrD,IAAI,OAAO,IAAI,GAAG,OAAO,KAAK,UAAU,IAAI;CAC9C;CACA,OAAO;AACT;AAEA,SAAS,OAAO,MAAc;CAC5B,IAAI;EACF,OAAO,GAAG,SAAS,IAAI,CAAC,CAAC,OAAO;CAClC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,aAAa,UAA4B;CAChD,IAAI;EACF,OAAO,KAAK,MAAM,kBAAkB,GAAG,aAAa,UAAU,MAAM,CAAC,CAAC;CACxE,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,kBAAkB,QAAwB;CACjD,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;EACrD,MAAM,UAAU,OAAO;EACvB,MAAM,OAAO,OAAO,QAAQ;EAC5B,IAAI,OAAO;GACT,UAAU;GACV,IAAI,SAAS,UAAU;QAClB,IAAI,YAAY,MAAM,UAAU;QAChC,IAAI,YAAY,OAAO,QAAQ;GACpC;EACF;EACA,IAAI,YAAY,QAAO,YAAY,KAAK;GACtC,QAAQ;GACR,UAAU;GACV;EACF;EACA,IAAI,YAAY,OAAO,SAAS,KAAK;GACnC,OAAO,QAAQ,OAAO,UAAU,OAAO,WAAW,MAAM,SAAS;GACjE,UAAU;GACV;EACF;EACA,IAAI,YAAY,OAAO,SAAS,KAAK;GACnC,SAAS;GACT,OACE,QAAQ,OAAO,UACf,EAAE,OAAO,WAAW,OAAO,OAAO,QAAQ,OAAO,MACjD;IACA,IAAI,OAAO,WAAW,MAAM,UAAU;IACtC,SAAS;GACX;GACA,SAAS;GACT;EACF;EACA,UAAU;CACZ;CACA,OAAO,OAAO,QAAQ,gBAAgB,IAAI;AAC5C;;;AC5HA,SAAgB,kBACd,MACA,UACA,cACA,MACA,aAAa,OACQ;CACrB,MAAM,UAAU,qBAAqB,YAAY;CACjD,MAAM,WAAW,IAAI,SAAS,EAAE,QAAQ,CAAC;CACzC,SAAS,QACP,2BACA,6CACF;CACA,MAAM,YAAY,kBAAkB,QAAQ;CAC5C,MAAM,QAAQ,SAAS,QAAQ,WAAW,MAAM,OAAO,EAAE,KAAK,IAAI,KAAA,CAAS;CAC3E,IAAI,CAAC,wBAAwB,OAAO,UAAU,GAAG,OAAO,CAAC;CACzD,iBAAiB,UAAU,OAAO,OAAO;CACzC,SAAS,KAAK;CACd,OAAO,gBACL,OACA,2BACA,iBAAiB,UAAU,GAC3B,UACF,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ,eAAe;EAChE,MAAM;EACN;EACA;EACA;CACF,EAAE;AACJ;AAEA,SAAS,wBAAwB,QAAgB,YAA8B;CAC7E,OACE,OAAO,QAAQ,MACZ,SACC,CAAC,KAAK,aAAa,KAAK,SAAS,OAAO,KAAK,SAAS,UAC1D,KACC,cACC,iBAAiB,wBAAQ,IAAI,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;AAEnE;AAEA,SAAS,iBACP,YACmC;CACnC,OAAO,CACL;EACE,QAAQ;EACR,MAAM;EACN,UAAU;EACV;CACF,CACF;AACF;AAEA,SAAS,iBACP,UACA,OACA,SACA;CACA,MAAM,QAAQ,CAAC,KAAK;CACpB,MAAM,0BAAU,IAAI,IAAY;CAChC,OAAO,MAAM,QAAQ;EACnB,MAAM,SAAS,MAAM,MAAM;EAC3B,IAAI,QAAQ,IAAI,OAAO,IAAI,GAAG;EAC9B,QAAQ,IAAI,OAAO,IAAI;EACvB,KAAK,MAAM,QAAQ,OAAO,SAAS;GACjC,MAAM,WAAW,QAAQ,KAAK,WAAW,OAAO,IAAI;GACpD,IAAI,CAAC,YAAY,aAAa,2BAA2B;GACzD,IAAI,aAAa,SAAS,OAAO,QAAQ;GACzC,IAAI,CAAC,YACH,IAAI;IACF,aAAa,SAAS,QACpB,UACA,GAAG,aAAa,UAAU,MAAM,CAClC;GACF,QAAQ;IACN;GACF;GAEF,MAAM,KAAK,UAAU;EACvB;CACF;AACF;AAEA,SAAS,kBAAkB,UAAkB;CAC3C,OAAO,SAAS,WAAW,GAAG,IAC1B,KAAK,QAAQ,iBAAiB,IAC9B,KAAK,QAAQ,QAAQ;AAC3B;;;ACnGA,MAAM,UAAU,cAAc,OAAO,KAAK,GAAG;AAE7C,SAAgB,wBACd,QACA,UACA,gBACmB;CACnB,IAAI,CAAC,eAAe,sBAAsB,GACxC,MAAM,IAAI,MAAM,kCAAkC;CAGpD,IAAI;EAGF,OAAO,iBAAiB,QAAQ,UADf,QAAQ,mBACwB,CAAC;CACpD,SAAS,OAAO;EACd,IAAI,qBAAqB,KAAK,GAC5B,MAAM,IAAI,MAAM,kCAAkC;EAEpD,MAAM;CACR;AACF;AAEA,SAAS,qBAAqB,OAAyB;CACrD,OACE,iBAAiB,SACjB,UAAU,SACV,MAAM,SAAS;AAEnB;;;AC/BA,MAAa,cAA+B;CAC1C,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,oCACf;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY;IACV,cAAc,EAAE,MAAM,SAAS;IAC/B,YAAY,EAAE,MAAM,UAAU;GAChC;GACA,sBAAsB;EACxB,CACF;EACA,UAAU;GACR,gBAAgB;GAChB,YACE;EACJ;CACF;CACA,OAAO,SAAS;EACd,MAAM,UAAW,QAAQ,QAAQ,MAAM,CAAC;EACxC,OAAO,EACL,eAAe,MAAM;GACnB,IAAI;GACJ,IAAI;IACF,MAAM,SAAS,QAAQ,SAAS,SAAS,MAAM,IAC3C,wBACE,QAAQ,WAAW,MACnB,QAAQ,UACR,QAAQ,WAAW,cACrB,IACA;KACE,MAAM,QAAQ,WAAW;KACzB,MAAM,KAAA;KACN,cAAc,aACZ;IACJ;IACJ,WAAW,kBACT,OAAO,MACP,QAAQ,UACR,QAAQ,cACR,OAAO,MACP,QAAQ,UACV;IACA,WAAW,SAAS,KAAK,aAAa;KACpC,GAAG;KACH,GAAG,OAAO,YAAY,OAAO;IAC/B,EAAE;GACJ,SAAS,OAAO;IACd,QAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EACJ,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC/D;IACF,CAAC;IACD;GACF;GACA,KAAK,MAAM,WAAW,UAAU;IAC9B,MAAM,iBAAiB,QAAQ,SAAS;IACxC,QAAQ,OAAO;KACb;KACA,KAAK;MACH,OAAO;MACP,KAAK;OAAE,MAAM,QAAQ;OAAM,QAAQ,QAAQ,SAAS;MAAE;KACxD;KACA,WAAW,iBAAiB,mBAAmB;KAC/C,GAAI,iBAAiB,EAAE,MAAM,EAAE,QAAQ,QAAQ,QAAQ,EAAE,IAAI,CAAC;IAChE,CAAC;GACH;EACF,EACF;CACF;AACF;;;ACjFA,MAAM,EAAE,YAAY,cAAc,OAAO,KAAK,GAAG,CAAC,CAAC,iBAAiB;AAIpE,MAAM,SAAwB;CAC5B,MAAM;EACJ,MAAM;EACN;EACA,WAAW;CACb;CACA,OAAO,EACL,iBAAiB,YACnB;CACA,SAAS,CAAC;AACZ;AAGA,OAAO,QAAS,cAAc,CAC5B;CACE,SAAS,CAAC,UAAU;CACpB,SAAS,EAAE,WAAW,OAAO;CAC7B,OAAO,EAAE,yBAAyB,QAAQ;AAC5C,CACF;AAEA,OAAO,QAAS,MAAM,CACpB;CACE,OAAO,CAAC,0CAA0C;CAClD,SAAS,EAAE,WAAW,OAAO;CAC7B,iBAAiB,EAAE,SAAS,EAAE,SAAS,WAAW,EAAE;CACpD,OAAO,EACL,yBAAyB,CAAC,SAAS,EAAE,YAAY,KAAK,CAAC,EACzD;AACF,CACF;AAEA,OAAO,QAAS,QAAQ,CACtB;CACE,OAAO,CAAC,sCAAsC;CAC9C,SAAS,EAAE,WAAW,OAAO;CAC7B,iBAAiB,EAAE,SAAS,EAAE,SAAS,WAAW,EAAE;CACpD,OAAO,EACL,yBAAyB,CAAC,SAAS,EAAE,YAAY,KAAK,CAAC,EACzD;AACF,CACF;AAEA,OAAO,QAAS,UAAU,CACxB;CACE,OAAO,CAAC,8BAA8B;CACtC,SAAS,EAAE,WAAW,OAAO;CAC7B,iBAAiB,EACf,SAAS;EACP,GAAG;EACH,SAAS;EACT,SAAS;EACT,UAAU;EACV,WAAW;CACb,EACF;CACA,OAAO,EACL,yBAAyB,CAAC,SAAS,EAAE,YAAY,KAAK,CAAC,EACzD;AACF,CACF"}
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-i18n/eslint-plugin",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"description": "ESLint
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
|
+
"description": "ESLint static-argument checks aligned with ai-i18n extraction semantics.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
7
7
|
"eslintplugin",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ai-i18n/analyzer": "^1.0.0-alpha.
|
|
36
|
+
"@ai-i18n/analyzer": "^1.0.0-alpha.4"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@vue/compiler-sfc": "
|
|
40
|
-
"eslint": "
|
|
41
|
-
"vue-eslint-parser": "
|
|
39
|
+
"@vue/compiler-sfc": ">=3.2.0",
|
|
40
|
+
"eslint": ">=9.0.0",
|
|
41
|
+
"vue-eslint-parser": ">=9.0.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@vue/compiler-sfc": {
|