@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 +16 -4
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +186 -81
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @ai-i18n/eslint-plugin
|
|
2
2
|
|
|
3
|
-
用于提前报告无法被 Vite/Yuku
|
|
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
|
-
|
|
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-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":";;
|
|
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)
|
|
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
|
|
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-
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|