@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 CHANGED
@@ -1,13 +1,16 @@
1
1
  # @ai-i18n/eslint-plugin
2
2
 
3
- 用于提前报告无法被 Vite/Yuku 静态提取的 `t()` 参数。规则检查解析到
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`。宿主 Auto Import 插件管理的其他 API 仍由
21
- 它自己的 ESLint 集成负责。
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,只启用同一条 `t-static-args` 规则。语义分析使用
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)`。未绑定到 ai-i18n 的 template-only `t()` 不参与检查。
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/rules/t-static-args.ts","../src/index.ts"],"mappings":";;cASa,aAAa,KAAK;;;cCDzB,QAAQ,OAAO"}
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
- return extractMessages(entry, AI_I18N_VIRTUAL_MODULE_ID, translationHooks(autoImport), autoImport).warnings.map(({ code: warningCode, line, column, message }) => ({
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")) || autoImport && findUnboundCalls(module, /* @__PURE__ */ new Set(["t", "useI18n"])).length > 0;
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 文件需要使用 vue-eslint-parser");
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: "ai-i18n 静态分析失败:{{reason}}",
194
- dynamicArg: "t() 参数无法静态提取,请使用字符串字面量、静态模板、条件表达式或可解析的 const 字符串。"
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
- const source = context.filename.endsWith(".vue") ? createVueAnalysisSource(context.sourceCode.text, context.filename, context.sourceCode.parserServices) : {
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
- ...analysisFailed ? { data: { reason: warning.message } } : {}
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: { "t-static-args": tStaticArgs },
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
- rules: { "ai-i18n/t-static-args": "error" }
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: { useI18n: "readonly" } },
259
- rules: { "ai-i18n/t-static-args": ["error", { autoImport: true }] }
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: { useI18n: "readonly" } },
265
- rules: { "ai-i18n/t-static-args": ["error", { autoImport: true }] }
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: { "ai-i18n/t-static-args": ["error", { autoImport: true }] }
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":"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"}
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.2",
4
- "description": "ESLint 9 static-argument checks aligned with ai-i18n extraction semantics.",
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.2"
36
+ "@ai-i18n/analyzer": "^1.0.0-alpha.4"
37
37
  },
38
38
  "peerDependencies": {
39
- "@vue/compiler-sfc": "^3.5.0",
40
- "eslint": "^9.0.0",
41
- "vue-eslint-parser": "^10.0.0"
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": {