@ai-i18n/eslint-plugin 1.0.0-alpha.14 → 1.0.0-alpha.16

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,7 +1,7 @@
1
1
  # @ai-i18n/eslint-plugin
2
2
 
3
- 用于提前报告无法被 Vite/Yuku 静态提取、不符合推荐语法,或不会随语言切换刷新的 Runtime
4
- 用法。规则检查解析到 `virtual:ai-i18n` 的翻译与状态 API,以及 Vue/React 模式下
3
+ 用于提前报告无法被 Vite/Yuku 静态提取、内嵌静态 markup、不符合推荐语法,或不会随语言
4
+ 切换刷新的 Runtime 用法。规则检查解析到 `virtual:ai-i18n` 的翻译与状态 API,以及 Vue/React 模式下
5
5
  `useI18n()` 返回的订阅状态。其他库或局部同名函数不受影响。
6
6
 
7
7
  alpha 阶段请安装 `@ai-i18n/eslint-plugin@alpha`;peer 支持 ESLint 9 和 10。
@@ -91,7 +91,7 @@ export default [
91
91
  ];
92
92
  ```
93
93
 
94
- 两个 Vue preset 都复用宿主的 Vue parser,并启用五条适用规则。语义分析使用
94
+ 两个 Vue preset 都复用宿主的 Vue parser,并启用六条适用规则。语义分析使用
95
95
  Vue 项目已有的 `vue/compiler-sfc` Node 入口,与 Vite 提取器复用相同分析语义和
96
96
  source-map 映射,覆盖 `<script>`、`<script setup>`、模板插值和指令表达式。Vue、
97
97
  TypeScript 与 SFC 编译相关依赖均为可选 peer,不会安装到 React/Vanilla 项目。
@@ -220,9 +220,14 @@ API 把 `...i18nComputed()` 直接展开到根 `computed`;setup、data、metho
220
220
  template 中直接调用该工厂会提示错误位置。事件处理器、action、普通工具函数和即时 console
221
221
  调用允许按需读取。规则只分析当前文件,不追踪跨文件 store 数据流。
222
222
 
223
- 六条规则可独立启用。四条静态分析规则无法启动时,同一文件只报告一次双语错误;官方 preset 由
223
+ 七条规则可独立启用。五条静态分析规则无法启动时,同一文件只报告一次双语错误;官方 preset 由
224
224
  `t-static-args` 优先报告,避免次级规则重复提示。
225
225
 
226
+ `ai-i18n/no-embedded-markup` 是 Vanilla、Vue 和 React 共用的 warning。它检查 Analyzer
227
+ 最终提取的 source,因此覆盖直接字符串、tagged template、静态 `const`、条件候选和文案树。
228
+ 静态 HTML/SVG 应留在翻译调用外,或作为占位符传入。规则不限制文案长度、行数或占位符数量,
229
+ 也不提供自动修复。
230
+
226
231
  规则与 Vite 共用静态参数语义,包括从 `useI18n()` 获得的对象成员调用
227
232
  `i18n.t()`、`i18n['t']()`、省略式 `t('source', undefined)` 和 tagged template。
228
233
  整棵可静态求值的纯文案对象或数组可以直接传给 `t()`、Vue `tRef()` 或 `tComputed()`,不要求
@@ -334,6 +339,13 @@ export default [
334
339
  },
335
340
  plugins: { 'ai-i18n': aiI18n },
336
341
  rules: {
342
+ 'ai-i18n/no-embedded-markup': [
343
+ 'warn',
344
+ {
345
+ autoImport: ['t', 'tRef', 'tComputed', 'useI18n'],
346
+ tsconfigPath: './tsconfig.json', // 可选:覆盖自动发现入口
347
+ },
348
+ ],
337
349
  'ai-i18n/no-eager-translation': [
338
350
  'warn',
339
351
  {
package/dist/index.d.ts CHANGED
@@ -1,24 +1,27 @@
1
1
  import { ESLint, Rule } from "eslint";
2
- //#region src/rules/no-eager-translation.d.ts
2
+ //#region src/rules/common/no-eager-translation.d.ts
3
3
  declare const noEagerTranslation: Rule.RuleModule;
4
4
  //#endregion
5
- //#region src/rules/no-redundant-auto-import.d.ts
5
+ //#region src/rules/common/no-embedded-markup.d.ts
6
+ declare const noEmbeddedMarkup: Rule.RuleModule;
7
+ //#endregion
8
+ //#region src/rules/common/no-redundant-auto-import.d.ts
6
9
  declare const noRedundantAutoImport: Rule.RuleModule;
7
10
  //#endregion
8
- //#region src/rules/no-unsubscribed-runtime-state.d.ts
11
+ //#region src/rules/common/no-unsubscribed-runtime-state.d.ts
9
12
  declare const noUnsubscribedRuntimeState: Rule.RuleModule;
10
13
  //#endregion
11
- //#region src/rules/no-unsubscribed-t.d.ts
14
+ //#region src/rules/common/no-unsubscribed-t.d.ts
12
15
  declare const noUnsubscribedT: Rule.RuleModule;
13
16
  //#endregion
14
- //#region src/rules/static-candidate-limit.d.ts
17
+ //#region src/rules/common/static-candidate-limit.d.ts
15
18
  declare const staticCandidateLimit: Rule.RuleModule;
16
19
  //#endregion
17
- //#region src/rules/t-static-args.d.ts
20
+ //#region src/rules/common/t-static-args.d.ts
18
21
  declare const tStaticArgs: Rule.RuleModule;
19
22
  //#endregion
20
23
  //#region src/index.d.ts
21
24
  declare const plugin: ESLint.Plugin;
22
25
  //#endregion
23
- export { plugin as default, noEagerTranslation, noRedundantAutoImport, noUnsubscribedRuntimeState, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
26
+ export { plugin as default, noEagerTranslation, noEmbeddedMarkup, noRedundantAutoImport, noUnsubscribedRuntimeState, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
24
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/rules/no-eager-translation.ts","../src/rules/no-redundant-auto-import.ts","../src/rules/no-unsubscribed-runtime-state.ts","../src/rules/no-unsubscribed-t.ts","../src/rules/static-candidate-limit.ts","../src/rules/t-static-args.ts","../src/index.ts"],"mappings":";;cAea,oBAAoB,KAAK;;;cCMzB,uBAAuB,KAAK;;;cCU5B,4BAA4B,KAAK;;;cCVjC,iBAAiB,KAAK;;;cCRtB,sBAAsB,KAAK;;;cCA3B,aAAa,KAAK;;;cCKzB,QAAQ,OAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/rules/common/no-eager-translation.ts","../src/rules/common/no-embedded-markup.ts","../src/rules/common/no-redundant-auto-import.ts","../src/rules/common/no-unsubscribed-runtime-state.ts","../src/rules/common/no-unsubscribed-t.ts","../src/rules/common/static-candidate-limit.ts","../src/rules/common/t-static-args.ts","../src/index.ts"],"mappings":";;cAkBa,oBAAoB,KAAK;;;cCCzB,kBAAkB,KAAK;;;cCKvB,uBAAuB,KAAK;;;cCO5B,4BAA4B,KAAK;;;cCZjC,iBAAiB,KAAK;;;cCNtB,sBAAsB,KAAK;;;cCA3B,aAAa,KAAK;;;cCMzB,QAAQ,OAAO"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { createFilesMatcher, createPathsMatcher, parseTsconfig } from "get-tsconfig";
6
6
  import picomatch from "picomatch";
7
7
  import { analyzeVueSource } from "@ai-i18n/analyzer/vue";
8
+ import { defaultTreeAdapter, parse, parseFragment } from "parse5";
8
9
  //#region src/auto-imports.ts
9
10
  const RUNTIME_AUTO_IMPORTS = [
10
11
  "t",
@@ -299,7 +300,8 @@ const POTENTIAL_TRANSLATION_RE = /virtual:ai-i18n|\b(?:t|tRef|tComputed|useI18n|
299
300
  function analyzeStaticSource(code, filename, tsconfigPath, lang, autoImport = false, maxStaticCandidates = Number.POSITIVE_INFINITY, alias) {
300
301
  if (!hasPotentialTranslationCandidate(code)) return {
301
302
  warnings: [],
302
- translationCalls: []
303
+ translationCalls: [],
304
+ messages: []
303
305
  };
304
306
  const autoImports = normalizeAutoImports$1(autoImport);
305
307
  const translationAutoImports = runtimeAutoImports(autoImports);
@@ -310,25 +312,27 @@ function analyzeStaticSource(code, filename, tsconfigPath, lang, autoImport = fa
310
312
  const entry = analyzer.addFile(entryPath, code, lang ? { lang } : void 0);
311
313
  if (!hasTranslationCandidate(entry, autoImports)) return {
312
314
  warnings: [],
313
- translationCalls: []
315
+ translationCalls: [],
316
+ messages: []
314
317
  };
315
318
  loadDependencies(analyzer, entry, resolve);
316
319
  analyzer.link();
317
320
  const hooks = translationHooks(autoImports);
318
- const extraction = extractMessages(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, translationAutoImports, maxStaticCandidates).warnings;
321
+ const extraction = extractMessages(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, translationAutoImports, maxStaticCandidates);
319
322
  const recommended = validateRecommendedUsage(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, translationAutoImports);
320
323
  return {
321
324
  warnings: (recommended.length ? [
322
- ...extraction.filter((warning) => warning.code === "parse-error"),
323
- ...extraction.filter((warning) => warning.code === "static-candidate-limit"),
325
+ ...extraction.warnings.filter((warning) => warning.code === "parse-error"),
326
+ ...extraction.warnings.filter((warning) => warning.code === "static-candidate-limit"),
324
327
  ...recommended
325
- ] : extraction).map(({ code: warningCode, line, column, message }) => ({
328
+ ] : extraction.warnings).map(({ code: warningCode, line, column, message }) => ({
326
329
  code: warningCode,
327
330
  line,
328
331
  column,
329
332
  message
330
333
  })),
331
- translationCalls: findTranslationCalls(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, translationAutoImports)
334
+ translationCalls: findTranslationCalls(entry, AI_I18N_VIRTUAL_MODULE_ID, hooks, translationAutoImports),
335
+ messages: extraction.messages
332
336
  };
333
337
  }
334
338
  function hasPotentialTranslationCandidate(code) {
@@ -437,7 +441,8 @@ const cache = /* @__PURE__ */ new WeakMap();
437
441
  const reportedAnalysisFailures = /* @__PURE__ */ new WeakSet();
438
442
  const EMPTY_ANALYSIS = {
439
443
  warnings: [],
440
- translationCalls: []
444
+ translationCalls: [],
445
+ messages: []
441
446
  };
442
447
  function analyzeRuleContext(context, options, maxStaticCandidates = Number.POSITIVE_INFINITY) {
443
448
  return analyzeRuleContextResult(context, options, maxStaticCandidates).warnings;
@@ -445,6 +450,9 @@ function analyzeRuleContext(context, options, maxStaticCandidates = Number.POSIT
445
450
  function analyzeTranslationCalls(context, options) {
446
451
  return analyzeRuleContextResult(context, options, DEFAULT_CANDIDATE_LIMIT).translationCalls;
447
452
  }
453
+ function analyzeTranslationMessages(context, options) {
454
+ return analyzeRuleContextResult(context, options, Number.POSITIVE_INFINITY).messages;
455
+ }
448
456
  function reportAnalysisFailureOnce(context, node, error) {
449
457
  if (reportedAnalysisFailures.has(context.sourceCode)) return;
450
458
  reportedAnalysisFailures.add(context.sourceCode);
@@ -484,6 +492,13 @@ function analyzeRuleContextResult(context, options, maxStaticCandidates) {
484
492
  translationCalls: result.translationCalls.map((call) => ({
485
493
  ...call,
486
494
  ...cached.source.mapLocation(call)
495
+ })),
496
+ messages: result.messages.map((message) => ({
497
+ ...message,
498
+ locations: message.locations.map((location) => ({
499
+ ...location,
500
+ ...cached.source.mapLocation(location)
501
+ }))
487
502
  }))
488
503
  };
489
504
  cached.analyses.set(key, analysis);
@@ -509,7 +524,7 @@ function createAnalysisSource(context) {
509
524
  };
510
525
  }
511
526
  //#endregion
512
- //#region src/rules/translation-call.ts
527
+ //#region src/rules/common/translation-call.ts
513
528
  function matchTranslationCalls(context, options, candidates) {
514
529
  const calls = new Map(analyzeTranslationCalls(context, options).map((call) => [locationKey(call.line, call.column, call.kind), call]));
515
530
  return candidates.flatMap((candidate) => {
@@ -526,7 +541,7 @@ function locationKey(line, column, kind) {
526
541
  return `${kind}:${line}:${column}`;
527
542
  }
528
543
  //#endregion
529
- //#region src/rules/ast-context.ts
544
+ //#region src/rules/common/ast-context.ts
530
545
  const FUNCTION_TYPES = /* @__PURE__ */ new Set([
531
546
  "ArrowFunctionExpression",
532
547
  "FunctionDeclaration",
@@ -566,7 +581,7 @@ function isVueTemplateEventHandler(node) {
566
581
  return false;
567
582
  }
568
583
  //#endregion
569
- //#region src/rules/vue-options.ts
584
+ //#region src/rules/vue/options.ts
570
585
  function isOptionsComputedValue(node, context) {
571
586
  let value = node;
572
587
  while (value.parent && isTransparentExpression(value.parent) && expressionChild(value.parent) === value) value = value.parent;
@@ -681,7 +696,7 @@ function expressionChild(node) {
681
696
  return node.expression;
682
697
  }
683
698
  //#endregion
684
- //#region src/rules/no-eager-translation.ts
699
+ //#region src/rules/common/no-eager-translation.ts
685
700
  const noEagerTranslation = {
686
701
  meta: {
687
702
  type: "problem",
@@ -771,7 +786,76 @@ function storesTranslationResult(parent, child) {
771
786
  }
772
787
  }
773
788
  //#endregion
774
- //#region src/rules/no-redundant-auto-import.ts
789
+ //#region src/rules/common/no-embedded-markup.ts
790
+ const noEmbeddedMarkup = {
791
+ meta: {
792
+ type: "suggestion",
793
+ docs: { description: "警告翻译源文中内嵌的静态 HTML 或 SVG 结构" },
794
+ schema: [{
795
+ type: "object",
796
+ properties: {
797
+ tsconfigPath: { type: "string" },
798
+ autoImport: { anyOf: [{ type: "boolean" }, {
799
+ type: "array",
800
+ items: { enum: [
801
+ "t",
802
+ "tRef",
803
+ "tComputed",
804
+ "useI18n"
805
+ ] },
806
+ uniqueItems: true
807
+ }] }
808
+ },
809
+ additionalProperties: false
810
+ }],
811
+ messages: { embeddedMarkup: diagnosticMessage("翻译源文包含静态 HTML 或 SVG 结构。请保留完整的自然语言,并将 markup 移出翻译调用或作为占位符传入。", "The translation source contains static HTML or SVG structure. Keep the complete natural-language message, and move markup outside the translation call or pass it as a placeholder.") }
812
+ },
813
+ create(context) {
814
+ const options = context.options[0] ?? {};
815
+ return { "Program:exit"(program) {
816
+ let messages;
817
+ try {
818
+ messages = analyzeTranslationMessages(context, options);
819
+ } catch (error) {
820
+ reportAnalysisFailureOnce(context, program, error);
821
+ return;
822
+ }
823
+ const reported = /* @__PURE__ */ new Set();
824
+ for (const message of messages) {
825
+ if (!containsEmbeddedMarkup(message.source)) continue;
826
+ for (const location of message.locations) {
827
+ const key = `${location.line}:${location.column}`;
828
+ if (reported.has(key)) continue;
829
+ reported.add(key);
830
+ context.report({
831
+ node: program,
832
+ loc: {
833
+ start: location,
834
+ end: {
835
+ line: location.line,
836
+ column: location.column + 1
837
+ }
838
+ },
839
+ messageId: "embeddedMarkup"
840
+ });
841
+ }
842
+ }
843
+ } };
844
+ }
845
+ };
846
+ function containsEmbeddedMarkup(source) {
847
+ if (!source.includes("<")) return false;
848
+ const options = { sourceCodeLocationInfo: true };
849
+ return containsLocatedMarkup(parseFragment(source, options)) || containsLocatedMarkup(parse(source, options));
850
+ }
851
+ function containsLocatedMarkup(parent) {
852
+ return parent.childNodes.some((node) => {
853
+ if ((defaultTreeAdapter.isElementNode(node) || defaultTreeAdapter.isCommentNode(node)) && node.sourceCodeLocation) return true;
854
+ return defaultTreeAdapter.isElementNode(node) && containsLocatedMarkup(node);
855
+ });
856
+ }
857
+ //#endregion
858
+ //#region src/rules/common/no-redundant-auto-import.ts
775
859
  const RUNTIME_MODULE$2 = "virtual:ai-i18n";
776
860
  const noRedundantAutoImport = {
777
861
  meta: {
@@ -841,7 +925,7 @@ function createFix(context, node, named, redundant) {
841
925
  return (fixer) => fixer.removeRange([commaStart, closeEnd]);
842
926
  }
843
927
  //#endregion
844
- //#region src/rules/vue-runtime-state.ts
928
+ //#region src/rules/vue/runtime-state.ts
845
929
  function misplacedI18nComputed(node, context, inTemplate, isVueSfc) {
846
930
  if (inTemplate || isVueScriptSetupNode(node, context)) return true;
847
931
  if (vueOptionsSection(node, context) !== null) return true;
@@ -874,7 +958,7 @@ function storesResultBeforeOwner(node, owner) {
874
958
  return storesResult || owner.type === "ArrowFunctionExpression" && owner.body === current;
875
959
  }
876
960
  //#endregion
877
- //#region src/rules/no-unsubscribed-runtime-state.ts
961
+ //#region src/rules/common/no-unsubscribed-runtime-state.ts
878
962
  const RUNTIME_MODULE$1 = "virtual:ai-i18n";
879
963
  const STATE_APIS = ["getLang", "getLangLoadState"];
880
964
  const VUE_COMPUTED_API = "i18nComputed";
@@ -1049,7 +1133,18 @@ function reportOptionsComputedSnapshot(context, node, api) {
1049
1133
  });
1050
1134
  }
1051
1135
  //#endregion
1052
- //#region src/rules/vue-instance-member.ts
1136
+ //#region src/rules/react/no-unsubscribed-t.ts
1137
+ function reportReactUnsubscribedT(context, call, node, jsxOwners) {
1138
+ if (call.origin !== "runtime") return;
1139
+ const owner = nearestFunction(node);
1140
+ if (!owner || !jsxOwners.has(owner) || isImmediateConsoleEffect(node)) return;
1141
+ context.report({
1142
+ node,
1143
+ messageId: "unsubscribedT"
1144
+ });
1145
+ }
1146
+ //#endregion
1147
+ //#region src/rules/vue/instance-member.ts
1053
1148
  const RUNTIME_MODULE = "virtual:ai-i18n";
1054
1149
  const TRANSPARENT_EXPRESSIONS = /* @__PURE__ */ new Set([
1055
1150
  "TSAsExpression",
@@ -1242,7 +1337,63 @@ function unwrapParent(node) {
1242
1337
  return value;
1243
1338
  }
1244
1339
  //#endregion
1245
- //#region src/rules/no-unsubscribed-t.ts
1340
+ //#region src/rules/vue/no-unsubscribed-t.ts
1341
+ function reportVueTranslationLifecycle(context, call, node, templateNodes, jsxOwners) {
1342
+ if (call.origin === "vue-computed") {
1343
+ if (!isOptionsComputedValue(node, context)) context.report({
1344
+ node,
1345
+ messageId: "misplacedTComputed"
1346
+ });
1347
+ return true;
1348
+ }
1349
+ if (call.origin !== "vue-ref") return false;
1350
+ if (templateNodes.has(node)) {
1351
+ if (!isVueTemplateEventHandler(node)) context.report({
1352
+ node,
1353
+ messageId: "renderTRef"
1354
+ });
1355
+ return true;
1356
+ }
1357
+ const section = vueOptionsSection(node, context);
1358
+ if (section === "computed" || section === "data" || section === "methods") {
1359
+ context.report({
1360
+ node,
1361
+ messageId: "optionsTRef"
1362
+ });
1363
+ return true;
1364
+ }
1365
+ if (section === "render") {
1366
+ context.report({
1367
+ node,
1368
+ messageId: "renderTRef"
1369
+ });
1370
+ return true;
1371
+ }
1372
+ const owner = nearestFunction(node);
1373
+ if (owner && jsxOwners.has(owner)) context.report({
1374
+ node,
1375
+ messageId: "renderTRef"
1376
+ });
1377
+ return true;
1378
+ }
1379
+ function reportUnsupportedVueInstanceTranslation(context, node, isVueFramework, inTemplate) {
1380
+ if (!isVueFramework) return;
1381
+ const name = vueInstanceTranslationMemberName(node);
1382
+ if (!name) return;
1383
+ const origin = resolveVueInstanceMemberOrigin(context, node, name, inTemplate);
1384
+ if (origin === "local" || origin === "unknown" || origin === "not-component") return;
1385
+ context.report({
1386
+ node,
1387
+ messageId: "unsupportedInstanceTranslation"
1388
+ });
1389
+ }
1390
+ function vueInstanceTranslationMemberName(node) {
1391
+ if (node.type !== "MemberExpression" || node.object.type !== "ThisExpression") return null;
1392
+ const name = !node.computed && node.property.type === "Identifier" ? node.property.name : node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
1393
+ return name === "t" || name === "$t" ? name : null;
1394
+ }
1395
+ //#endregion
1396
+ //#region src/rules/common/no-unsubscribed-t.ts
1246
1397
  const noUnsubscribedT = {
1247
1398
  meta: {
1248
1399
  type: "problem",
@@ -1296,7 +1447,7 @@ const noUnsubscribedT = {
1296
1447
  });
1297
1448
  },
1298
1449
  MemberExpression(node) {
1299
- reportUnsupportedVueInstanceTranslation(context, node, options, false);
1450
+ reportUnsupportedVueInstanceTranslation(context, node, options.framework === "vue", false);
1300
1451
  },
1301
1452
  JSXElement: collectJsxOwner,
1302
1453
  JSXFragment: collectJsxOwner,
@@ -1309,52 +1460,11 @@ const noUnsubscribedT = {
1309
1460
  return;
1310
1461
  }
1311
1462
  for (const { call, node } of matches) {
1312
- if (call.origin === "vue-computed") {
1313
- if (!isOptionsComputedValue(node, context)) context.report({
1314
- node,
1315
- messageId: "misplacedTComputed"
1316
- });
1317
- continue;
1318
- }
1319
- if (call.origin === "vue-ref") {
1320
- if (templateNodes.has(node)) {
1321
- if (!isVueTemplateEventHandler(node)) context.report({
1322
- node,
1323
- messageId: "renderTRef"
1324
- });
1325
- continue;
1326
- }
1327
- const section = vueOptionsSection(node, context);
1328
- if (section === "computed" || section === "data" || section === "methods") {
1329
- context.report({
1330
- node,
1331
- messageId: "optionsTRef"
1332
- });
1333
- continue;
1334
- }
1335
- if (section === "render") {
1336
- context.report({
1337
- node,
1338
- messageId: "renderTRef"
1339
- });
1340
- continue;
1341
- }
1342
- const owner = nearestFunction(node);
1343
- if (owner && jsxOwners.has(owner)) context.report({
1344
- node,
1345
- messageId: "renderTRef"
1346
- });
1347
- continue;
1348
- }
1463
+ if (reportVueTranslationLifecycle(context, call, node, templateNodes, jsxOwners)) continue;
1349
1464
  if (call.origin !== "runtime") continue;
1350
1465
  if (options.framework === "vue") continue;
1351
1466
  if (templateNodes.has(node)) continue;
1352
- const owner = nearestFunction(node);
1353
- if (!owner || !jsxOwners.has(owner) || isImmediateConsoleEffect(node)) continue;
1354
- context.report({
1355
- node,
1356
- messageId: "unsubscribedT"
1357
- });
1467
+ reportReactUnsubscribedT(context, call, node, jsxOwners);
1358
1468
  }
1359
1469
  }
1360
1470
  };
@@ -1376,29 +1486,13 @@ const noUnsubscribedT = {
1376
1486
  templateNodes.add(node);
1377
1487
  },
1378
1488
  MemberExpression(node) {
1379
- reportUnsupportedVueInstanceTranslation(context, node, options, true);
1489
+ reportUnsupportedVueInstanceTranslation(context, node, options.framework === "vue", true);
1380
1490
  }
1381
1491
  }, scriptVisitor, { templateBodyTriggerSelector: "Program" });
1382
1492
  }
1383
1493
  };
1384
- function reportUnsupportedVueInstanceTranslation(context, node, options, inTemplate) {
1385
- if (options.framework !== "vue") return;
1386
- const name = vueInstanceTranslationMemberName(node);
1387
- if (!name) return;
1388
- const origin = resolveVueInstanceMemberOrigin(context, node, name, inTemplate);
1389
- if (origin === "local" || origin === "unknown" || origin === "not-component") return;
1390
- context.report({
1391
- node,
1392
- messageId: "unsupportedInstanceTranslation"
1393
- });
1394
- }
1395
- function vueInstanceTranslationMemberName(node) {
1396
- if (node.type !== "MemberExpression" || node.object.type !== "ThisExpression") return null;
1397
- const name = !node.computed && node.property.type === "Identifier" ? node.property.name : node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
1398
- return name === "t" || name === "$t" ? name : null;
1399
- }
1400
1494
  //#endregion
1401
- //#region src/rules/static-candidate-limit.ts
1495
+ //#region src/rules/common/static-candidate-limit.ts
1402
1496
  const staticCandidateLimit = {
1403
1497
  meta: {
1404
1498
  type: "suggestion",
@@ -1454,7 +1548,7 @@ const staticCandidateLimit = {
1454
1548
  }
1455
1549
  };
1456
1550
  //#endregion
1457
- //#region src/rules/t-static-args.ts
1551
+ //#region src/rules/common/t-static-args.ts
1458
1552
  const tStaticArgs = {
1459
1553
  meta: {
1460
1554
  type: "problem",
@@ -1521,6 +1615,7 @@ const plugin = {
1521
1615
  namespace: "ai-i18n"
1522
1616
  },
1523
1617
  rules: {
1618
+ "no-embedded-markup": noEmbeddedMarkup,
1524
1619
  "no-eager-translation": noEagerTranslation,
1525
1620
  "no-redundant-auto-import": noRedundantAutoImport,
1526
1621
  "no-unsubscribed-runtime-state": noUnsubscribedRuntimeState,
@@ -1536,6 +1631,7 @@ plugin.configs.recommended = [{
1536
1631
  languageOptions: { globals: { defineI18nMessages: "readonly" } },
1537
1632
  rules: {
1538
1633
  "ai-i18n/t-static-args": "error",
1634
+ "ai-i18n/no-embedded-markup": "warn",
1539
1635
  "ai-i18n/no-eager-translation": "warn",
1540
1636
  "ai-i18n/no-unsubscribed-runtime-state": "warn",
1541
1637
  "ai-i18n/no-unsubscribed-t": "warn",
@@ -1548,6 +1644,7 @@ plugin.configs.vue = [{
1548
1644
  languageOptions: { globals: { defineI18nMessages: "readonly" } },
1549
1645
  rules: {
1550
1646
  "ai-i18n/t-static-args": "error",
1647
+ "ai-i18n/no-embedded-markup": "warn",
1551
1648
  "ai-i18n/no-eager-translation": ["warn", { framework: "vue" }],
1552
1649
  "ai-i18n/no-unsubscribed-runtime-state": ["warn", { framework: "vue" }],
1553
1650
  "ai-i18n/no-unsubscribed-t": ["warn", { framework: "vue" }],
@@ -1563,6 +1660,7 @@ plugin.configs["vanilla-auto-import"] = [{
1563
1660
  } },
1564
1661
  rules: {
1565
1662
  "ai-i18n/t-static-args": ["error", { autoImport: ["t"] }],
1663
+ "ai-i18n/no-embedded-markup": ["warn", { autoImport: ["t"] }],
1566
1664
  "ai-i18n/no-eager-translation": ["warn", { autoImport: ["t"] }],
1567
1665
  "ai-i18n/no-unsubscribed-runtime-state": ["warn", { autoImport: ["getLang", "getLangLoadState"] }],
1568
1666
  "ai-i18n/static-candidate-limit": ["warn", { autoImport: ["t"] }]
@@ -1582,6 +1680,12 @@ plugin.configs["vue-auto-import"] = [{
1582
1680
  "tComputed",
1583
1681
  "useI18n"
1584
1682
  ] }],
1683
+ "ai-i18n/no-embedded-markup": ["warn", { autoImport: [
1684
+ "t",
1685
+ "tRef",
1686
+ "tComputed",
1687
+ "useI18n"
1688
+ ] }],
1585
1689
  "ai-i18n/no-eager-translation": ["warn", {
1586
1690
  autoImport: [
1587
1691
  "t",
@@ -1625,6 +1729,7 @@ plugin.configs["react-auto-import"] = [{
1625
1729
  } },
1626
1730
  rules: {
1627
1731
  "ai-i18n/t-static-args": ["error", { autoImport: ["t", "useI18n"] }],
1732
+ "ai-i18n/no-embedded-markup": ["warn", { autoImport: ["t", "useI18n"] }],
1628
1733
  "ai-i18n/no-eager-translation": ["warn", { autoImport: ["t", "useI18n"] }],
1629
1734
  "ai-i18n/no-unsubscribed-runtime-state": ["warn", { autoImport: ["getLang", "getLangLoadState"] }],
1630
1735
  "ai-i18n/no-unsubscribed-t": ["warn", { autoImport: ["t", "useI18n"] }],
@@ -1632,6 +1737,6 @@ plugin.configs["react-auto-import"] = [{
1632
1737
  }
1633
1738
  }];
1634
1739
  //#endregion
1635
- export { plugin as default, noEagerTranslation, noRedundantAutoImport, noUnsubscribedRuntimeState, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
1740
+ export { plugin as default, noEagerTranslation, noEmbeddedMarkup, noRedundantAutoImport, noUnsubscribedRuntimeState, noUnsubscribedT, staticCandidateLimit, tStaticArgs };
1636
1741
 
1637
1742
  //# sourceMappingURL=index.js.map