@noctcore/eslint-plugin-contracts 0.3.0 → 0.5.0
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 +3 -2
- package/dist/index.cjs +714 -21
- package/dist/index.d.cts +175 -1
- package/dist/index.d.ts +175 -1
- package/dist/index.js +707 -20
- package/docs/rules/translation-key-exists.md +155 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,9 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
TRANSLATION_DEFAULTS: () => TRANSLATION_DEFAULTS,
|
|
34
|
+
catalogHasKey: () => catalogHasKey,
|
|
35
|
+
catalogHasPrefix: () => catalogHasPrefix,
|
|
36
|
+
catalogsForNamespace: () => catalogsForNamespace,
|
|
33
37
|
configs: () => configs,
|
|
38
|
+
createTranslationVisitor: () => createTranslationVisitor,
|
|
34
39
|
default: () => index_default,
|
|
35
|
-
rules: () => rules
|
|
40
|
+
rules: () => rules,
|
|
41
|
+
translationSettingsOf: () => translationSettingsOf
|
|
36
42
|
});
|
|
37
43
|
module.exports = __toCommonJS(index_exports);
|
|
38
44
|
|
|
@@ -47,13 +53,14 @@ var recommended = {
|
|
|
47
53
|
"noctcore-contracts/restrict-throw-to-taxonomy": "error",
|
|
48
54
|
"noctcore-contracts/schema-enum-field-consistency": "error",
|
|
49
55
|
"noctcore-contracts/fetch-must-check-ok": "error",
|
|
50
|
-
// Config-required / heuristic rules ship inert. `require-registered-keys
|
|
51
|
-
// `env-var-schema-parity` do nothing until their
|
|
52
|
-
// set; `require-schema-parse-at-boundary` is a conservative syntactic slice of a
|
|
56
|
+
// Config-required / heuristic rules ship inert. `require-registered-keys`,
|
|
57
|
+
// `env-var-schema-parity` and `translation-key-exists` do nothing until their
|
|
58
|
+
// `sinks` / `schema` / `catalogs` options are set; `require-schema-parse-at-boundary` is a conservative syntactic slice of a
|
|
53
59
|
// type-aware concern. Enable them explicitly once configured for your project.
|
|
54
60
|
"noctcore-contracts/require-registered-keys": "off",
|
|
55
61
|
"noctcore-contracts/env-var-schema-parity": "off",
|
|
56
|
-
"noctcore-contracts/require-schema-parse-at-boundary": "off"
|
|
62
|
+
"noctcore-contracts/require-schema-parse-at-boundary": "off",
|
|
63
|
+
"noctcore-contracts/translation-key-exists": "off"
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
// src/rules/env-var-schema-parity.ts
|
|
@@ -594,8 +601,8 @@ var fetchMustCheckOkRule = createRule({
|
|
|
594
601
|
}
|
|
595
602
|
return {
|
|
596
603
|
CallExpression(node) {
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
604
|
+
const path3 = calleePath(node.callee);
|
|
605
|
+
if (path3 === null || !fetchFunctions.has(path3)) {
|
|
599
606
|
return;
|
|
600
607
|
}
|
|
601
608
|
const parent = skipAwait(node.parent);
|
|
@@ -1094,11 +1101,11 @@ var requireRegisteredKeysRule = createRule({
|
|
|
1094
1101
|
const registryHint = registry ? ` (import it from '${registry}')` : "";
|
|
1095
1102
|
return {
|
|
1096
1103
|
CallExpression(node) {
|
|
1097
|
-
const
|
|
1098
|
-
if (
|
|
1104
|
+
const path3 = calleePath2(node.callee);
|
|
1105
|
+
if (path3 === null) {
|
|
1099
1106
|
return;
|
|
1100
1107
|
}
|
|
1101
|
-
const indexes = sinkMap.get(
|
|
1108
|
+
const indexes = sinkMap.get(path3);
|
|
1102
1109
|
if (indexes === void 0) {
|
|
1103
1110
|
return;
|
|
1104
1111
|
}
|
|
@@ -1108,7 +1115,7 @@ var requireRegisteredKeysRule = createRule({
|
|
|
1108
1115
|
context.report({
|
|
1109
1116
|
node: arg,
|
|
1110
1117
|
messageId: "unregisteredKey",
|
|
1111
|
-
data: { callee:
|
|
1118
|
+
data: { callee: path3, value: `'${arg.value}'`, registryHint }
|
|
1112
1119
|
});
|
|
1113
1120
|
}
|
|
1114
1121
|
}
|
|
@@ -1409,10 +1416,689 @@ var schemaEnumFieldConsistencyRule = createRule({
|
|
|
1409
1416
|
}
|
|
1410
1417
|
});
|
|
1411
1418
|
|
|
1419
|
+
// src/i18n/catalogs.ts
|
|
1420
|
+
var import_node_fs2 = require("fs");
|
|
1421
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1422
|
+
var NS_PLACEHOLDER = "{ns}";
|
|
1423
|
+
var SAFE_NAMESPACE = /^(?!\.{1,2}$)[^/\\\0]+$/u;
|
|
1424
|
+
var fileCache = /* @__PURE__ */ new Map();
|
|
1425
|
+
function readCatalogFile(absolute) {
|
|
1426
|
+
let mtimeMs;
|
|
1427
|
+
try {
|
|
1428
|
+
const stats = (0, import_node_fs2.statSync)(absolute);
|
|
1429
|
+
if (!stats.isFile()) return { kind: "missing" };
|
|
1430
|
+
mtimeMs = stats.mtimeMs;
|
|
1431
|
+
} catch {
|
|
1432
|
+
return { kind: "missing" };
|
|
1433
|
+
}
|
|
1434
|
+
const cached = fileCache.get(absolute);
|
|
1435
|
+
if (cached !== void 0 && cached.mtimeMs === mtimeMs) {
|
|
1436
|
+
return cached.value.ok ? { kind: "ok", entry: cached } : { kind: "invalid", reason: cached.value.reason };
|
|
1437
|
+
}
|
|
1438
|
+
let value;
|
|
1439
|
+
try {
|
|
1440
|
+
value = { ok: true, json: JSON.parse((0, import_node_fs2.readFileSync)(absolute, "utf8")) };
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
value = { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1443
|
+
}
|
|
1444
|
+
const entry = { mtimeMs, value, flattened: /* @__PURE__ */ new Map() };
|
|
1445
|
+
fileCache.set(absolute, entry);
|
|
1446
|
+
return value.ok ? { kind: "ok", entry } : { kind: "invalid", reason: value.reason };
|
|
1447
|
+
}
|
|
1448
|
+
function isRecord(value) {
|
|
1449
|
+
return value !== null && typeof value === "object";
|
|
1450
|
+
}
|
|
1451
|
+
function descend(json, keyPath) {
|
|
1452
|
+
if (keyPath === void 0 || keyPath === "") return json;
|
|
1453
|
+
let current = json;
|
|
1454
|
+
for (const segment of keyPath.split(".")) {
|
|
1455
|
+
if (!isRecord(current) || !Object.hasOwn(current, segment)) return void 0;
|
|
1456
|
+
current = current[segment];
|
|
1457
|
+
}
|
|
1458
|
+
return current;
|
|
1459
|
+
}
|
|
1460
|
+
function flatten(root, keySeparator, label) {
|
|
1461
|
+
const leaves = /* @__PURE__ */ new Set();
|
|
1462
|
+
const branches = /* @__PURE__ */ new Set();
|
|
1463
|
+
const visit = (value, prefix) => {
|
|
1464
|
+
if (!isRecord(value)) {
|
|
1465
|
+
leaves.add(prefix);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
branches.add(prefix);
|
|
1469
|
+
if (keySeparator === false) return;
|
|
1470
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1471
|
+
visit(child, `${prefix}${keySeparator}${key}`);
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
for (const [key, child] of Object.entries(root)) {
|
|
1475
|
+
if (keySeparator === false) {
|
|
1476
|
+
(isRecord(child) ? branches : leaves).add(key);
|
|
1477
|
+
} else {
|
|
1478
|
+
visit(child, key);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return { label, leaves, branches };
|
|
1482
|
+
}
|
|
1483
|
+
function loadSource(cwd, file, keyPath, keySeparator) {
|
|
1484
|
+
const absolute = import_node_path2.default.isAbsolute(file) ? file : import_node_path2.default.resolve(cwd, file);
|
|
1485
|
+
const read = readCatalogFile(absolute);
|
|
1486
|
+
if (read.kind === "missing") return { kind: "absent" };
|
|
1487
|
+
if (read.kind === "invalid") return { kind: "error", reason: `${file}: ${read.reason}` };
|
|
1488
|
+
const cacheKey = `${keyPath ?? ""}\0${keySeparator === false ? "" : keySeparator}`;
|
|
1489
|
+
const cached = read.entry.flattened.get(cacheKey);
|
|
1490
|
+
if (cached !== void 0) {
|
|
1491
|
+
return cached === null ? { kind: "absent" } : { kind: "ok", catalog: cached };
|
|
1492
|
+
}
|
|
1493
|
+
const subtree = read.entry.value.ok ? descend(read.entry.value.json, keyPath) : void 0;
|
|
1494
|
+
const label = keyPath ? `${file}#${keyPath}` : file;
|
|
1495
|
+
const catalog = isRecord(subtree) ? flatten(subtree, keySeparator, label) : null;
|
|
1496
|
+
read.entry.flattened.set(cacheKey, catalog);
|
|
1497
|
+
return catalog === null ? { kind: "absent" } : { kind: "ok", catalog };
|
|
1498
|
+
}
|
|
1499
|
+
function catalogsForNamespace(namespace, sources, settings) {
|
|
1500
|
+
const catalogs = [];
|
|
1501
|
+
const errors = [];
|
|
1502
|
+
for (const source of sources) {
|
|
1503
|
+
const templated = source.file.includes(NS_PLACEHOLDER) || (source.keyPath?.includes(NS_PLACEHOLDER) ?? false);
|
|
1504
|
+
if (templated) {
|
|
1505
|
+
if (!SAFE_NAMESPACE.test(namespace)) continue;
|
|
1506
|
+
const file = source.file.replaceAll(NS_PLACEHOLDER, namespace);
|
|
1507
|
+
const keyPath = source.keyPath?.replaceAll(NS_PLACEHOLDER, namespace);
|
|
1508
|
+
const load2 = loadSource(settings.cwd, file, keyPath, settings.keySeparator);
|
|
1509
|
+
if (load2.kind === "ok") catalogs.push(load2.catalog);
|
|
1510
|
+
else if (load2.kind === "error") errors.push(load2.reason);
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
if ((source.namespace ?? settings.defaultNamespace) !== namespace) continue;
|
|
1514
|
+
const load = loadSource(settings.cwd, source.file, source.keyPath, settings.keySeparator);
|
|
1515
|
+
if (load.kind === "ok") catalogs.push(load.catalog);
|
|
1516
|
+
else if (load.kind === "error") errors.push(load.reason);
|
|
1517
|
+
else {
|
|
1518
|
+
const where = source.keyPath ? `${source.file}#${source.keyPath}` : source.file;
|
|
1519
|
+
errors.push(`${where}: not found or not a JSON object`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return { catalogs, errors };
|
|
1523
|
+
}
|
|
1524
|
+
var PLURAL_CATEGORIES = ["zero", "one", "two", "few", "many", "other"];
|
|
1525
|
+
function catalogHasKey(catalog, key, lookup) {
|
|
1526
|
+
if (catalog.leaves.has(key)) return true;
|
|
1527
|
+
if (lookup.returnObjects && catalog.branches.has(key)) return true;
|
|
1528
|
+
if (lookup.plural) {
|
|
1529
|
+
const sep = lookup.pluralSeparator;
|
|
1530
|
+
for (const category of PLURAL_CATEGORIES) {
|
|
1531
|
+
if (catalog.leaves.has(`${key}${sep}${category}`)) return true;
|
|
1532
|
+
if (catalog.leaves.has(`${key}${sep}ordinal${sep}${category}`)) return true;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (lookup.context) {
|
|
1536
|
+
const variant = `${key}${lookup.contextSeparator}`;
|
|
1537
|
+
for (const leaf of catalog.leaves) {
|
|
1538
|
+
if (leaf.startsWith(variant)) return true;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
function catalogHasPrefix(catalog, prefix) {
|
|
1544
|
+
for (const leaf of catalog.leaves) {
|
|
1545
|
+
if (leaf.startsWith(prefix)) return true;
|
|
1546
|
+
}
|
|
1547
|
+
for (const branch of catalog.branches) {
|
|
1548
|
+
if (branch.startsWith(prefix)) return true;
|
|
1549
|
+
}
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/i18n/translationUsage.ts
|
|
1554
|
+
var import_utils11 = require("@typescript-eslint/utils");
|
|
1555
|
+
var UNRESOLVED = "unresolved";
|
|
1556
|
+
var MAX_DEPTH = 8;
|
|
1557
|
+
function unwrap(node) {
|
|
1558
|
+
let current = node;
|
|
1559
|
+
while (current.type === import_utils11.AST_NODE_TYPES.TSAsExpression || current.type === import_utils11.AST_NODE_TYPES.TSSatisfiesExpression || current.type === import_utils11.AST_NODE_TYPES.TSNonNullExpression) {
|
|
1560
|
+
current = current.expression;
|
|
1561
|
+
}
|
|
1562
|
+
return current;
|
|
1563
|
+
}
|
|
1564
|
+
function staticString(node) {
|
|
1565
|
+
const inner = unwrap(node);
|
|
1566
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Literal && typeof inner.value === "string") return inner.value;
|
|
1567
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.TemplateLiteral && inner.expressions.length === 0) {
|
|
1568
|
+
return inner.quasis[0]?.value.cooked ?? null;
|
|
1569
|
+
}
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
function propertyName2(property) {
|
|
1573
|
+
if (property.computed) return staticString(property.key);
|
|
1574
|
+
if (property.key.type === import_utils11.AST_NODE_TYPES.Identifier) return property.key.name;
|
|
1575
|
+
return staticString(property.key);
|
|
1576
|
+
}
|
|
1577
|
+
function targetIdentifier(node) {
|
|
1578
|
+
if (node.type === import_utils11.AST_NODE_TYPES.Identifier) return node;
|
|
1579
|
+
if (node.type === import_utils11.AST_NODE_TYPES.AssignmentPattern && node.left.type === import_utils11.AST_NODE_TYPES.Identifier) {
|
|
1580
|
+
return node.left;
|
|
1581
|
+
}
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
function createTranslationVisitor(context, settings, onUsage) {
|
|
1585
|
+
const sourceCode = context.sourceCode;
|
|
1586
|
+
const defaultBinding = { namespaces: [settings.defaultNamespace], keyPrefix: null };
|
|
1587
|
+
function resolveVariable(identifier) {
|
|
1588
|
+
let scope = sourceCode.getScope(identifier);
|
|
1589
|
+
while (scope !== null) {
|
|
1590
|
+
const variable = scope.set.get(identifier.name);
|
|
1591
|
+
if (variable !== void 0) return variable;
|
|
1592
|
+
scope = scope.upper;
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
function typedStringLiteral(node) {
|
|
1597
|
+
const services = sourceCode.parserServices;
|
|
1598
|
+
const program = services?.program;
|
|
1599
|
+
const map = services?.esTreeNodeToTSNodeMap;
|
|
1600
|
+
if (!program || !map) return null;
|
|
1601
|
+
const type = program.getTypeChecker().getTypeAtLocation(map.get(node));
|
|
1602
|
+
return type.isStringLiteral() ? type.value : null;
|
|
1603
|
+
}
|
|
1604
|
+
function resolveNamespaces(node, depth = 0) {
|
|
1605
|
+
if (node === void 0) return defaultBinding.namespaces;
|
|
1606
|
+
const inner = unwrap(node);
|
|
1607
|
+
const literal = staticString(inner);
|
|
1608
|
+
if (literal !== null) return [literal];
|
|
1609
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Literal && inner.value === null) return defaultBinding.namespaces;
|
|
1610
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.ArrayExpression) {
|
|
1611
|
+
const namespaces = [];
|
|
1612
|
+
for (const element of inner.elements) {
|
|
1613
|
+
if (element === null || element.type === import_utils11.AST_NODE_TYPES.SpreadElement) return UNRESOLVED;
|
|
1614
|
+
const value2 = staticString(element) ?? resolveIdentifierString(element, depth);
|
|
1615
|
+
if (value2 === null) return UNRESOLVED;
|
|
1616
|
+
namespaces.push(value2);
|
|
1617
|
+
}
|
|
1618
|
+
return namespaces.length > 0 ? namespaces : defaultBinding.namespaces;
|
|
1619
|
+
}
|
|
1620
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Identifier && inner.name === "undefined") return defaultBinding.namespaces;
|
|
1621
|
+
const value = resolveIdentifierString(inner, depth);
|
|
1622
|
+
return value === null ? UNRESOLVED : [value];
|
|
1623
|
+
}
|
|
1624
|
+
function resolveIdentifierString(node, depth) {
|
|
1625
|
+
if (node.type !== import_utils11.AST_NODE_TYPES.Identifier || depth > MAX_DEPTH) return null;
|
|
1626
|
+
if (Object.hasOwn(settings.namespaceIdentifiers, node.name)) {
|
|
1627
|
+
return settings.namespaceIdentifiers[node.name] ?? null;
|
|
1628
|
+
}
|
|
1629
|
+
const definition = resolveVariable(node)?.defs[0];
|
|
1630
|
+
if (definition?.type === "Variable" && definition.parent.kind === "const" && definition.node.id.type === import_utils11.AST_NODE_TYPES.Identifier && definition.node.init !== null) {
|
|
1631
|
+
const init = unwrap(definition.node.init);
|
|
1632
|
+
const literal = staticString(init);
|
|
1633
|
+
if (literal !== null) return literal;
|
|
1634
|
+
const chained = resolveIdentifierString(init, depth + 1);
|
|
1635
|
+
if (chained !== null) return chained;
|
|
1636
|
+
}
|
|
1637
|
+
return typedStringLiteral(node);
|
|
1638
|
+
}
|
|
1639
|
+
function isHookCall(node) {
|
|
1640
|
+
return node.type === import_utils11.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils11.AST_NODE_TYPES.Identifier && settings.hooks.has(node.callee.name);
|
|
1641
|
+
}
|
|
1642
|
+
function isInstance(node) {
|
|
1643
|
+
return node.type === import_utils11.AST_NODE_TYPES.Identifier && settings.instances.has(node.name);
|
|
1644
|
+
}
|
|
1645
|
+
function staticPrefix(node) {
|
|
1646
|
+
if (node === void 0) return null;
|
|
1647
|
+
const inner = unwrap(node);
|
|
1648
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Identifier && inner.name === "undefined") return null;
|
|
1649
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Literal && inner.value === null) return null;
|
|
1650
|
+
return staticString(inner) ?? UNRESOLVED;
|
|
1651
|
+
}
|
|
1652
|
+
function bindingFromHook(call) {
|
|
1653
|
+
const [nsArg, optionsArg] = call.arguments;
|
|
1654
|
+
const namespaces = resolveNamespaces(nsArg);
|
|
1655
|
+
if (namespaces === UNRESOLVED) return UNRESOLVED;
|
|
1656
|
+
let keyPrefix = null;
|
|
1657
|
+
if (optionsArg !== void 0) {
|
|
1658
|
+
const options = unwrap(optionsArg);
|
|
1659
|
+
if (options.type !== import_utils11.AST_NODE_TYPES.ObjectExpression) return UNRESOLVED;
|
|
1660
|
+
for (const property of options.properties) {
|
|
1661
|
+
if (property.type !== import_utils11.AST_NODE_TYPES.Property) return UNRESOLVED;
|
|
1662
|
+
if (propertyName2(property) !== "keyPrefix") continue;
|
|
1663
|
+
const prefix = staticPrefix(property.value);
|
|
1664
|
+
if (prefix === UNRESOLVED) return UNRESOLVED;
|
|
1665
|
+
keyPrefix = prefix;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return { namespaces, keyPrefix };
|
|
1669
|
+
}
|
|
1670
|
+
function bindingFromGetFixedT(call) {
|
|
1671
|
+
const [, nsArg, prefixArg] = call.arguments;
|
|
1672
|
+
const namespaces = resolveNamespaces(nsArg);
|
|
1673
|
+
if (namespaces === UNRESOLVED) return UNRESOLVED;
|
|
1674
|
+
const keyPrefix = staticPrefix(prefixArg);
|
|
1675
|
+
if (keyPrefix === UNRESOLVED) return UNRESOLVED;
|
|
1676
|
+
return { namespaces, keyPrefix };
|
|
1677
|
+
}
|
|
1678
|
+
function isGetFixedT(node) {
|
|
1679
|
+
return node.type === import_utils11.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils11.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils11.AST_NODE_TYPES.Identifier && node.callee.property.name === "getFixedT" && isInstance(node.callee.object);
|
|
1680
|
+
}
|
|
1681
|
+
function hookCallOf(identifier) {
|
|
1682
|
+
const definition = resolveVariable(identifier)?.defs[0];
|
|
1683
|
+
if (definition?.type !== "Variable" || definition.node.id.type !== import_utils11.AST_NODE_TYPES.Identifier) return null;
|
|
1684
|
+
const init = definition.node.init === null ? null : unwrap(definition.node.init);
|
|
1685
|
+
return init !== null && isHookCall(init) ? init : null;
|
|
1686
|
+
}
|
|
1687
|
+
function bindingOfTSource(object) {
|
|
1688
|
+
const inner = unwrap(object);
|
|
1689
|
+
if (isHookCall(inner)) return bindingFromHook(inner);
|
|
1690
|
+
if (inner.type === import_utils11.AST_NODE_TYPES.Identifier) {
|
|
1691
|
+
const hook = hookCallOf(inner);
|
|
1692
|
+
if (hook !== null) return bindingFromHook(hook);
|
|
1693
|
+
if (isInstance(inner)) return defaultBinding;
|
|
1694
|
+
}
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
function bindingFromType(annotation) {
|
|
1698
|
+
const type = annotation?.typeAnnotation;
|
|
1699
|
+
if (type?.type !== import_utils11.AST_NODE_TYPES.TSTypeReference) return null;
|
|
1700
|
+
const name = type.typeName.type === import_utils11.AST_NODE_TYPES.Identifier ? type.typeName.name : type.typeName.type === import_utils11.AST_NODE_TYPES.TSQualifiedName ? type.typeName.right.name : null;
|
|
1701
|
+
if (name === null || !settings.typeNames.has(name)) return null;
|
|
1702
|
+
const [nsType, prefixType] = type.typeArguments?.params ?? [];
|
|
1703
|
+
const literalOf = (node) => node.type === import_utils11.AST_NODE_TYPES.TSLiteralType ? staticString(node.literal) : null;
|
|
1704
|
+
let namespaces = defaultBinding.namespaces;
|
|
1705
|
+
if (nsType !== void 0) {
|
|
1706
|
+
if (nsType.type === import_utils11.AST_NODE_TYPES.TSTupleType) {
|
|
1707
|
+
const values = nsType.elementTypes.map(literalOf);
|
|
1708
|
+
if (values.length === 0 || values.some((value) => value === null)) return UNRESOLVED;
|
|
1709
|
+
namespaces = values;
|
|
1710
|
+
} else {
|
|
1711
|
+
const value = literalOf(nsType);
|
|
1712
|
+
if (value === null) return UNRESOLVED;
|
|
1713
|
+
namespaces = [value];
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
let keyPrefix = null;
|
|
1717
|
+
if (prefixType !== void 0) {
|
|
1718
|
+
keyPrefix = literalOf(prefixType);
|
|
1719
|
+
if (keyPrefix === null) return UNRESOLVED;
|
|
1720
|
+
}
|
|
1721
|
+
return { namespaces, keyPrefix };
|
|
1722
|
+
}
|
|
1723
|
+
function bindingFromDeclarator(declarator, name, depth) {
|
|
1724
|
+
if (declarator.init === null) return null;
|
|
1725
|
+
const init = unwrap(declarator.init);
|
|
1726
|
+
const id = declarator.id;
|
|
1727
|
+
if (id.type === import_utils11.AST_NODE_TYPES.Identifier) {
|
|
1728
|
+
if (isGetFixedT(init)) return bindingFromGetFixedT(init);
|
|
1729
|
+
if (init.type === import_utils11.AST_NODE_TYPES.MemberExpression && !init.computed && init.property.type === import_utils11.AST_NODE_TYPES.Identifier && init.property.name === "t") {
|
|
1730
|
+
return bindingOfTSource(init.object);
|
|
1731
|
+
}
|
|
1732
|
+
if (init.type === import_utils11.AST_NODE_TYPES.Identifier) return bindingOfIdentifier(init, depth + 1);
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
if (id.type === import_utils11.AST_NODE_TYPES.ObjectPattern) {
|
|
1736
|
+
for (const property of id.properties) {
|
|
1737
|
+
if (property.type !== import_utils11.AST_NODE_TYPES.Property || targetIdentifier(property.value) !== name) continue;
|
|
1738
|
+
return propertyName2(property) === "t" ? bindingOfTSource(init) : null;
|
|
1739
|
+
}
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
if (id.type === import_utils11.AST_NODE_TYPES.ArrayPattern) {
|
|
1743
|
+
const first = id.elements[0];
|
|
1744
|
+
if (first && targetIdentifier(first) === name && isHookCall(init)) return bindingFromHook(init);
|
|
1745
|
+
}
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
function bindingOfIdentifier(identifier, depth = 0) {
|
|
1749
|
+
if (depth > MAX_DEPTH) return null;
|
|
1750
|
+
const variable = resolveVariable(identifier);
|
|
1751
|
+
if (variable === null) {
|
|
1752
|
+
return settings.functions.has(identifier.name) ? defaultBinding : null;
|
|
1753
|
+
}
|
|
1754
|
+
const definition = variable.defs[0];
|
|
1755
|
+
if (definition === void 0) return null;
|
|
1756
|
+
switch (definition.type) {
|
|
1757
|
+
case "ImportBinding": {
|
|
1758
|
+
const specifier = definition.node;
|
|
1759
|
+
if (specifier.type !== import_utils11.AST_NODE_TYPES.ImportSpecifier) return null;
|
|
1760
|
+
const imported = specifier.imported.type === import_utils11.AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;
|
|
1761
|
+
return settings.functions.has(imported) ? defaultBinding : null;
|
|
1762
|
+
}
|
|
1763
|
+
case "Parameter": {
|
|
1764
|
+
const name = definition.name;
|
|
1765
|
+
if (name.type !== import_utils11.AST_NODE_TYPES.Identifier) return null;
|
|
1766
|
+
const typed = bindingFromType(name.typeAnnotation);
|
|
1767
|
+
if (typed !== null) return typed;
|
|
1768
|
+
return settings.functions.has(name.name) ? UNRESOLVED : null;
|
|
1769
|
+
}
|
|
1770
|
+
case "Variable":
|
|
1771
|
+
return definition.name.type === import_utils11.AST_NODE_TYPES.Identifier ? bindingFromDeclarator(definition.node, definition.name, depth) : null;
|
|
1772
|
+
default:
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function bindingOfCallee(callee) {
|
|
1777
|
+
if (callee.type === import_utils11.AST_NODE_TYPES.Identifier) return bindingOfIdentifier(callee);
|
|
1778
|
+
if (callee.type === import_utils11.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils11.AST_NODE_TYPES.Identifier && callee.property.name === "t") {
|
|
1779
|
+
return bindingOfTSource(callee.object);
|
|
1780
|
+
}
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
function readCallOptions(node) {
|
|
1784
|
+
const none = { namespaces: null, plural: false, context: false, returnObjects: false };
|
|
1785
|
+
if (node === void 0) return none;
|
|
1786
|
+
const inner = unwrap(node);
|
|
1787
|
+
if (inner.type !== import_utils11.AST_NODE_TYPES.ObjectExpression) return UNRESOLVED;
|
|
1788
|
+
let namespaces = null;
|
|
1789
|
+
let plural = false;
|
|
1790
|
+
let context2 = false;
|
|
1791
|
+
let returnObjects = false;
|
|
1792
|
+
for (const property of inner.properties) {
|
|
1793
|
+
if (property.type !== import_utils11.AST_NODE_TYPES.Property) return UNRESOLVED;
|
|
1794
|
+
const name = propertyName2(property);
|
|
1795
|
+
if (name === null || name === "keyPrefix") return UNRESOLVED;
|
|
1796
|
+
if (name === "ns") {
|
|
1797
|
+
const resolved = resolveNamespaces(property.value);
|
|
1798
|
+
if (resolved === UNRESOLVED) return UNRESOLVED;
|
|
1799
|
+
namespaces = resolved;
|
|
1800
|
+
} else if (name === "count") {
|
|
1801
|
+
plural = true;
|
|
1802
|
+
} else if (name === "context") {
|
|
1803
|
+
context2 = true;
|
|
1804
|
+
} else if (name === "returnObjects") {
|
|
1805
|
+
const value = unwrap(property.value);
|
|
1806
|
+
returnObjects = !(value.type === import_utils11.AST_NODE_TYPES.Literal && value.value === false);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return { namespaces, plural, context: context2, returnObjects };
|
|
1810
|
+
}
|
|
1811
|
+
function qualify(raw, binding, optionNamespaces) {
|
|
1812
|
+
const { nsSeparator, keySeparator } = settings;
|
|
1813
|
+
if (nsSeparator !== false && raw.includes(nsSeparator)) {
|
|
1814
|
+
if (binding.keyPrefix !== null) return null;
|
|
1815
|
+
const [head = "", ...rest] = raw.split(nsSeparator);
|
|
1816
|
+
if (head !== "" && rest.length > 0) {
|
|
1817
|
+
return { namespaces: [head], key: rest.join(keySeparator === false ? nsSeparator : keySeparator) };
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const namespaces = optionNamespaces ?? binding.namespaces;
|
|
1821
|
+
if (binding.keyPrefix === null || binding.keyPrefix === "") return { namespaces, key: raw };
|
|
1822
|
+
return { namespaces, key: `${binding.keyPrefix}${keySeparator === false ? "" : keySeparator}${raw}` };
|
|
1823
|
+
}
|
|
1824
|
+
function emit(node, keyNode, binding, options) {
|
|
1825
|
+
const inner = unwrap(keyNode);
|
|
1826
|
+
const raws = [];
|
|
1827
|
+
const single = staticString(inner);
|
|
1828
|
+
if (single !== null) {
|
|
1829
|
+
raws.push(single);
|
|
1830
|
+
} else if (inner.type === import_utils11.AST_NODE_TYPES.ArrayExpression && inner.elements.length > 0) {
|
|
1831
|
+
for (const element of inner.elements) {
|
|
1832
|
+
const value = element === null || element.type === import_utils11.AST_NODE_TYPES.SpreadElement ? null : staticString(element);
|
|
1833
|
+
if (value === null) {
|
|
1834
|
+
onUsage({ kind: "dynamic", node });
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
raws.push(value);
|
|
1838
|
+
}
|
|
1839
|
+
} else if (inner.type === import_utils11.AST_NODE_TYPES.TemplateLiteral) {
|
|
1840
|
+
const head = inner.quasis[0]?.value.cooked ?? "";
|
|
1841
|
+
const qualified = head === "" ? null : qualify(head, binding, options.namespaces);
|
|
1842
|
+
if (qualified === null) {
|
|
1843
|
+
onUsage({ kind: "dynamic", node });
|
|
1844
|
+
} else {
|
|
1845
|
+
onUsage({ kind: "prefix", node, namespaces: qualified.namespaces, prefix: qualified.key });
|
|
1846
|
+
}
|
|
1847
|
+
return;
|
|
1848
|
+
} else {
|
|
1849
|
+
onUsage({ kind: "dynamic", node });
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
let namespaces = null;
|
|
1853
|
+
const keys = [];
|
|
1854
|
+
for (const raw of raws) {
|
|
1855
|
+
const qualified = qualify(raw, binding, options.namespaces);
|
|
1856
|
+
if (qualified === null || raw === "") {
|
|
1857
|
+
onUsage({ kind: "unresolved", node });
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (namespaces !== null && namespaces.join("\0") !== qualified.namespaces.join("\0")) {
|
|
1861
|
+
onUsage({ kind: "unresolved", node });
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
namespaces = qualified.namespaces;
|
|
1865
|
+
keys.push(qualified.key);
|
|
1866
|
+
}
|
|
1867
|
+
onUsage({
|
|
1868
|
+
kind: "key",
|
|
1869
|
+
node,
|
|
1870
|
+
namespaces: namespaces ?? binding.namespaces,
|
|
1871
|
+
keys,
|
|
1872
|
+
plural: options.plural,
|
|
1873
|
+
context: options.context,
|
|
1874
|
+
returnObjects: options.returnObjects
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
function jsxAttributeValue(attribute) {
|
|
1878
|
+
const value = attribute.value;
|
|
1879
|
+
if (value === null) return null;
|
|
1880
|
+
if (value.type === import_utils11.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
1881
|
+
return value.expression.type === import_utils11.AST_NODE_TYPES.JSXEmptyExpression ? null : value.expression;
|
|
1882
|
+
}
|
|
1883
|
+
return value;
|
|
1884
|
+
}
|
|
1885
|
+
return {
|
|
1886
|
+
CallExpression(node) {
|
|
1887
|
+
const binding = bindingOfCallee(node.callee);
|
|
1888
|
+
if (binding === null) return;
|
|
1889
|
+
const [keyArg, secondArg, thirdArg] = node.arguments;
|
|
1890
|
+
if (keyArg === void 0) return;
|
|
1891
|
+
if (binding === UNRESOLVED) {
|
|
1892
|
+
onUsage({ kind: "unresolved", node: keyArg });
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const optionsArg = secondArg !== void 0 && staticString(secondArg) !== null ? thirdArg : secondArg;
|
|
1896
|
+
const options = readCallOptions(optionsArg);
|
|
1897
|
+
if (options === UNRESOLVED) {
|
|
1898
|
+
onUsage({ kind: "unresolved", node: keyArg });
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
emit(keyArg, keyArg, binding, options);
|
|
1902
|
+
},
|
|
1903
|
+
JSXOpeningElement(node) {
|
|
1904
|
+
if (node.name.type !== import_utils11.AST_NODE_TYPES.JSXIdentifier || !settings.transComponents.has(node.name.name)) return;
|
|
1905
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
1906
|
+
for (const attribute of node.attributes) {
|
|
1907
|
+
if (attribute.type === import_utils11.AST_NODE_TYPES.JSXSpreadAttribute) {
|
|
1908
|
+
onUsage({ kind: "unresolved", node });
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
if (attribute.name.type === import_utils11.AST_NODE_TYPES.JSXIdentifier) attributes.set(attribute.name.name, attribute);
|
|
1912
|
+
}
|
|
1913
|
+
const keyAttribute = attributes.get("i18nKey");
|
|
1914
|
+
const keyNode = keyAttribute === void 0 ? null : jsxAttributeValue(keyAttribute);
|
|
1915
|
+
if (keyNode === null) return;
|
|
1916
|
+
let binding = defaultBinding;
|
|
1917
|
+
const tAttribute = attributes.get("t");
|
|
1918
|
+
const tNode = tAttribute === void 0 ? null : jsxAttributeValue(tAttribute);
|
|
1919
|
+
if (tNode !== null) {
|
|
1920
|
+
binding = tNode.type === import_utils11.AST_NODE_TYPES.Identifier ? bindingOfIdentifier(tNode) : UNRESOLVED;
|
|
1921
|
+
}
|
|
1922
|
+
let namespaces = null;
|
|
1923
|
+
const nsAttribute = attributes.get("ns");
|
|
1924
|
+
const nsNode = nsAttribute === void 0 ? null : jsxAttributeValue(nsAttribute);
|
|
1925
|
+
if (nsNode !== null) {
|
|
1926
|
+
const resolved = resolveNamespaces(nsNode);
|
|
1927
|
+
if (resolved === UNRESOLVED) binding = UNRESOLVED;
|
|
1928
|
+
else namespaces = resolved;
|
|
1929
|
+
}
|
|
1930
|
+
if (binding === null || binding === UNRESOLVED) {
|
|
1931
|
+
onUsage({ kind: "unresolved", node: keyNode });
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
emit(keyNode, keyNode, binding, {
|
|
1935
|
+
namespaces,
|
|
1936
|
+
plural: attributes.has("count"),
|
|
1937
|
+
context: attributes.has("context"),
|
|
1938
|
+
returnObjects: false
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// src/rules/translation-key-exists.ts
|
|
1945
|
+
var RULE_NAME11 = "translation-key-exists";
|
|
1946
|
+
var stringList = { type: "array", items: { type: "string", minLength: 1 }, uniqueItems: true };
|
|
1947
|
+
var separator = { oneOf: [{ type: "string", minLength: 1 }, { type: "boolean", enum: [false] }] };
|
|
1948
|
+
var optionSchema9 = {
|
|
1949
|
+
type: "object",
|
|
1950
|
+
additionalProperties: false,
|
|
1951
|
+
properties: {
|
|
1952
|
+
catalogs: {
|
|
1953
|
+
type: "array",
|
|
1954
|
+
items: {
|
|
1955
|
+
type: "object",
|
|
1956
|
+
additionalProperties: false,
|
|
1957
|
+
required: ["file"],
|
|
1958
|
+
properties: {
|
|
1959
|
+
file: { type: "string", minLength: 1 },
|
|
1960
|
+
namespace: { type: "string", minLength: 1 },
|
|
1961
|
+
keyPath: { type: "string", minLength: 1 }
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
},
|
|
1965
|
+
defaultNamespace: { type: "string", minLength: 1 },
|
|
1966
|
+
fallbackNamespaces: stringList,
|
|
1967
|
+
hooks: stringList,
|
|
1968
|
+
instances: stringList,
|
|
1969
|
+
functions: stringList,
|
|
1970
|
+
typeNames: stringList,
|
|
1971
|
+
transComponents: stringList,
|
|
1972
|
+
namespaceIdentifiers: { type: "object", additionalProperties: { type: "string", minLength: 1 } },
|
|
1973
|
+
nsSeparator: separator,
|
|
1974
|
+
keySeparator: separator,
|
|
1975
|
+
pluralSeparator: { type: "string", minLength: 1 },
|
|
1976
|
+
contextSeparator: { type: "string", minLength: 1 },
|
|
1977
|
+
dynamicKeys: { type: "string", enum: ["ignore", "check-prefix"] }
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
var TRANSLATION_DEFAULTS = {
|
|
1981
|
+
defaultNamespace: "translation",
|
|
1982
|
+
hooks: ["useTranslation"],
|
|
1983
|
+
instances: ["i18n", "i18next"],
|
|
1984
|
+
functions: ["t"],
|
|
1985
|
+
typeNames: ["TFunction"],
|
|
1986
|
+
transComponents: ["Trans"],
|
|
1987
|
+
nsSeparator: ":",
|
|
1988
|
+
keySeparator: ".",
|
|
1989
|
+
pluralSeparator: "_",
|
|
1990
|
+
contextSeparator: "_"
|
|
1991
|
+
};
|
|
1992
|
+
function translationSettingsOf(options) {
|
|
1993
|
+
return {
|
|
1994
|
+
hooks: new Set(options.hooks ?? TRANSLATION_DEFAULTS.hooks),
|
|
1995
|
+
instances: new Set(options.instances ?? TRANSLATION_DEFAULTS.instances),
|
|
1996
|
+
functions: new Set(options.functions ?? TRANSLATION_DEFAULTS.functions),
|
|
1997
|
+
typeNames: new Set(options.typeNames ?? TRANSLATION_DEFAULTS.typeNames),
|
|
1998
|
+
transComponents: new Set(options.transComponents ?? TRANSLATION_DEFAULTS.transComponents),
|
|
1999
|
+
namespaceIdentifiers: options.namespaceIdentifiers ?? {},
|
|
2000
|
+
defaultNamespace: options.defaultNamespace ?? TRANSLATION_DEFAULTS.defaultNamespace,
|
|
2001
|
+
nsSeparator: options.nsSeparator ?? TRANSLATION_DEFAULTS.nsSeparator,
|
|
2002
|
+
keySeparator: options.keySeparator ?? TRANSLATION_DEFAULTS.keySeparator
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
var translationKeyExistsRule = createRule({
|
|
2006
|
+
name: RULE_NAME11,
|
|
2007
|
+
meta: {
|
|
2008
|
+
type: "problem",
|
|
2009
|
+
docs: {
|
|
2010
|
+
description: "Require every static i18next / react-i18next translation key (`t(...)`, `i18n.t(...)`, `<Trans i18nKey>`) to exist in the catalog of the namespace in scope."
|
|
2011
|
+
},
|
|
2012
|
+
schema: [optionSchema9],
|
|
2013
|
+
messages: {
|
|
2014
|
+
missingKey: "Translation key `{{key}}` does not exist in namespace `{{namespace}}` ({{catalogs}}). It renders as the raw key at runtime: fix the key or add it to the catalog.",
|
|
2015
|
+
missingKeyPrefix: "No key in namespace `{{namespace}}` ({{catalogs}}) starts with `{{prefix}}`, so this template key can never resolve.",
|
|
2016
|
+
unknownNamespace: "Namespace `{{namespace}}` has no catalog in the rule configuration. Fix the namespace name or add a `catalogs` entry for it.",
|
|
2017
|
+
catalogUnreadable: "Translation catalog could not be loaded: {{reason}}."
|
|
2018
|
+
}
|
|
2019
|
+
},
|
|
2020
|
+
defaultOptions: [{}],
|
|
2021
|
+
create(context, [options]) {
|
|
2022
|
+
const sources = options.catalogs ?? [];
|
|
2023
|
+
if (sources.length === 0) {
|
|
2024
|
+
return {};
|
|
2025
|
+
}
|
|
2026
|
+
const settings = translationSettingsOf(options);
|
|
2027
|
+
const fallbackNamespaces = options.fallbackNamespaces ?? [];
|
|
2028
|
+
const catalogSettings = {
|
|
2029
|
+
cwd: context.cwd,
|
|
2030
|
+
defaultNamespace: settings.defaultNamespace,
|
|
2031
|
+
keySeparator: settings.keySeparator
|
|
2032
|
+
};
|
|
2033
|
+
const lookupBase = {
|
|
2034
|
+
pluralSeparator: options.pluralSeparator ?? TRANSLATION_DEFAULTS.pluralSeparator,
|
|
2035
|
+
contextSeparator: options.contextSeparator ?? TRANSLATION_DEFAULTS.contextSeparator
|
|
2036
|
+
};
|
|
2037
|
+
const checkPrefix = options.dynamicKeys === "check-prefix";
|
|
2038
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2039
|
+
const reportedErrors = /* @__PURE__ */ new Set();
|
|
2040
|
+
function catalogsOf(namespace) {
|
|
2041
|
+
let entry = resolved.get(namespace);
|
|
2042
|
+
if (entry === void 0) {
|
|
2043
|
+
entry = catalogsForNamespace(namespace, sources, catalogSettings);
|
|
2044
|
+
resolved.set(namespace, entry);
|
|
2045
|
+
}
|
|
2046
|
+
return entry;
|
|
2047
|
+
}
|
|
2048
|
+
function searched(node, namespaces) {
|
|
2049
|
+
const catalogs = [];
|
|
2050
|
+
for (const namespace of [...namespaces, ...fallbackNamespaces]) {
|
|
2051
|
+
const entry = catalogsOf(namespace);
|
|
2052
|
+
for (const reason of entry.errors) {
|
|
2053
|
+
if (!reportedErrors.has(reason)) {
|
|
2054
|
+
reportedErrors.add(reason);
|
|
2055
|
+
context.report({ node, messageId: "catalogUnreadable", data: { reason } });
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (entry.errors.length > 0) return null;
|
|
2059
|
+
catalogs.push(...entry.catalogs);
|
|
2060
|
+
}
|
|
2061
|
+
if (catalogs.length === 0) {
|
|
2062
|
+
context.report({ node, messageId: "unknownNamespace", data: { namespace: namespaces.join("`, `") } });
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
return catalogs;
|
|
2066
|
+
}
|
|
2067
|
+
const labels = (catalogs) => catalogs.map((catalog) => catalog.label).join(", ");
|
|
2068
|
+
return createTranslationVisitor(context, settings, (usage) => {
|
|
2069
|
+
if (usage.kind === "key") {
|
|
2070
|
+
const catalogs = searched(usage.node, usage.namespaces);
|
|
2071
|
+
if (catalogs === null) return;
|
|
2072
|
+
const lookup = { ...lookupBase, plural: usage.plural, context: usage.context, returnObjects: usage.returnObjects };
|
|
2073
|
+
const found = usage.keys.some((key) => catalogs.some((catalog) => catalogHasKey(catalog, key, lookup)));
|
|
2074
|
+
if (!found) {
|
|
2075
|
+
context.report({
|
|
2076
|
+
node: usage.node,
|
|
2077
|
+
messageId: "missingKey",
|
|
2078
|
+
data: { key: usage.keys.join("` | `"), namespace: usage.namespaces.join("`, `"), catalogs: labels(catalogs) }
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (usage.kind === "prefix" && checkPrefix) {
|
|
2084
|
+
const catalogs = searched(usage.node, usage.namespaces);
|
|
2085
|
+
if (catalogs === null) return;
|
|
2086
|
+
if (!catalogs.some((catalog) => catalogHasPrefix(catalog, usage.prefix))) {
|
|
2087
|
+
context.report({
|
|
2088
|
+
node: usage.node,
|
|
2089
|
+
messageId: "missingKeyPrefix",
|
|
2090
|
+
data: { prefix: usage.prefix, namespace: usage.namespaces.join("`, `"), catalogs: labels(catalogs) }
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
});
|
|
2097
|
+
|
|
1412
2098
|
// src/rules/wire-message-naming.ts
|
|
1413
|
-
var
|
|
2099
|
+
var RULE_NAME12 = "wire-message-naming";
|
|
1414
2100
|
var DEFAULT_ROLE_SUFFIXES = ["Event", "Command", "Query"];
|
|
1415
|
-
var
|
|
2101
|
+
var optionSchema10 = {
|
|
1416
2102
|
type: "object",
|
|
1417
2103
|
additionalProperties: false,
|
|
1418
2104
|
properties: {
|
|
@@ -1453,14 +2139,14 @@ function typeLiteralNode(obj) {
|
|
|
1453
2139
|
return null;
|
|
1454
2140
|
}
|
|
1455
2141
|
var wireMessageNamingRule = createRule({
|
|
1456
|
-
name:
|
|
2142
|
+
name: RULE_NAME12,
|
|
1457
2143
|
meta: {
|
|
1458
2144
|
type: "problem",
|
|
1459
2145
|
docs: {
|
|
1460
2146
|
description: "A message-schema const ending in a role suffix (default Event/Command/Query) whose zod object declares `type: z.literal(...)` must set that literal to kebab-case(const name minus its role suffix)."
|
|
1461
2147
|
},
|
|
1462
2148
|
fixable: "code",
|
|
1463
|
-
schema: [
|
|
2149
|
+
schema: [optionSchema10],
|
|
1464
2150
|
messages: {
|
|
1465
2151
|
typeMismatch: "Wire `type` literal '{{actual}}' for `{{name}}` must be '{{expected}}' \u2014 kebab-case of the const name minus its role suffix."
|
|
1466
2152
|
}
|
|
@@ -1496,11 +2182,11 @@ var wireMessageNamingRule = createRule({
|
|
|
1496
2182
|
});
|
|
1497
2183
|
|
|
1498
2184
|
// src/rules/zod-schema-naming.ts
|
|
1499
|
-
var
|
|
2185
|
+
var RULE_NAME13 = "zod-schema-naming";
|
|
1500
2186
|
var SCHEMA_NAME = /^[A-Z][A-Za-z0-9]*Schema$/;
|
|
1501
2187
|
var SUFFIX = "Schema";
|
|
1502
2188
|
var DEFAULT_ROLE_SUFFIXES2 = [];
|
|
1503
|
-
var
|
|
2189
|
+
var optionSchema11 = {
|
|
1504
2190
|
type: "object",
|
|
1505
2191
|
additionalProperties: false,
|
|
1506
2192
|
properties: {
|
|
@@ -1533,13 +2219,13 @@ function rootIdentifierName(node) {
|
|
|
1533
2219
|
return null;
|
|
1534
2220
|
}
|
|
1535
2221
|
var zodSchemaNamingRule = createRule({
|
|
1536
|
-
name:
|
|
2222
|
+
name: RULE_NAME13,
|
|
1537
2223
|
meta: {
|
|
1538
2224
|
type: "problem",
|
|
1539
2225
|
docs: {
|
|
1540
2226
|
description: "Every exported zod schema is a PascalCase const suffixed `Schema`, paired with a same-named inferred type (`export type Foo = z.infer<typeof FooSchema>`)."
|
|
1541
2227
|
},
|
|
1542
|
-
schema: [
|
|
2228
|
+
schema: [optionSchema11],
|
|
1543
2229
|
messages: {
|
|
1544
2230
|
schemaNaming: "Exported zod schema `{{name}}` must be a PascalCase const ending in `Schema` (e.g. `FooSchema`).",
|
|
1545
2231
|
missingType: "Schema `{{name}}` has no sibling `export type {{base}} = z.infer<typeof {{name}}>`. Export the inferred type instead of hand-authoring a duplicate."
|
|
@@ -1602,7 +2288,8 @@ var rules = {
|
|
|
1602
2288
|
"env-var-schema-parity": envVarSchemaParityRule,
|
|
1603
2289
|
"require-schema-parse-at-boundary": requireSchemaParseAtBoundaryRule,
|
|
1604
2290
|
"schema-enum-field-consistency": schemaEnumFieldConsistencyRule,
|
|
1605
|
-
"fetch-must-check-ok": fetchMustCheckOkRule
|
|
2291
|
+
"fetch-must-check-ok": fetchMustCheckOkRule,
|
|
2292
|
+
"translation-key-exists": translationKeyExistsRule
|
|
1606
2293
|
};
|
|
1607
2294
|
|
|
1608
2295
|
// src/index.ts
|
|
@@ -1621,6 +2308,12 @@ var configs = plugin.configs;
|
|
|
1621
2308
|
var index_default = plugin;
|
|
1622
2309
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1623
2310
|
0 && (module.exports = {
|
|
2311
|
+
TRANSLATION_DEFAULTS,
|
|
2312
|
+
catalogHasKey,
|
|
2313
|
+
catalogHasPrefix,
|
|
2314
|
+
catalogsForNamespace,
|
|
1624
2315
|
configs,
|
|
1625
|
-
|
|
2316
|
+
createTranslationVisitor,
|
|
2317
|
+
rules,
|
|
2318
|
+
translationSettingsOf
|
|
1626
2319
|
});
|