@noctcore/eslint-plugin-contracts 0.4.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/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
 
@@ -2302,6 +2308,12 @@ var configs = plugin.configs;
2302
2308
  var index_default = plugin;
2303
2309
  // Annotate the CommonJS export names for ESM import in node:
2304
2310
  0 && (module.exports = {
2311
+ TRANSLATION_DEFAULTS,
2312
+ catalogHasKey,
2313
+ catalogHasPrefix,
2314
+ catalogsForNamespace,
2305
2315
  configs,
2306
- rules
2316
+ createTranslationVisitor,
2317
+ rules,
2318
+ translationSettingsOf
2307
2319
  });
package/dist/index.d.cts CHANGED
@@ -1,60 +1,5 @@
1
1
  import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
-
3
- /**
4
- * Translation catalog loading for the i18n rules.
5
- *
6
- * A catalog is a JSON file (or a subtree of one) that holds the keys of ONE
7
- * namespace. Projects lay catalogs out in a handful of shapes, and a
8
- * `CatalogSource` describes each of them without the rule knowing any project:
9
- *
10
- * one file per namespace `{ file: 'locales/en/{ns}.json' }`
11
- * one file, ns at the top `{ file: 'locales/en.json', keyPath: '{ns}' }`
12
- * a fixed file for one ns `{ file: 'src/i18n/en.json', namespace: 'common' }`
13
- * single-namespace app `{ file: 'src/i18n/en.json' }` (the default namespace)
14
- *
15
- * `{ns}` is substituted with the namespace being resolved. A templated source
16
- * whose file or subtree does not exist simply does not supply that namespace;
17
- * a FIXED source that cannot be read is a configuration error and is surfaced.
18
- */
19
- interface CatalogSource {
20
- /** JSON catalog path, relative to the ESLint cwd. May contain `{ns}`. */
21
- readonly file: string;
22
- /** Namespace a fixed (non-templated) source supplies. Defaults to the default namespace. */
23
- readonly namespace?: string;
24
- /** Dot-separated subtree inside the file holding the namespace's keys. May contain `{ns}`. */
25
- readonly keyPath?: string;
26
- }
27
-
28
- interface TranslationKeyExistsOptions {
29
- /** Where each namespace's catalog lives. Empty = rule is inert. */
30
- readonly catalogs?: readonly CatalogSource[];
31
- /** The namespace an unqualified `useTranslation()` / `i18n.t` resolves to (i18next `defaultNS`). */
32
- readonly defaultNamespace?: string;
33
- /** Namespaces searched after the bound ones (i18next `fallbackNS`). */
34
- readonly fallbackNamespaces?: readonly string[];
35
- /** Hooks returning a namespace-bound `t` (`useTranslation`). */
36
- readonly hooks?: readonly string[];
37
- /** i18next instance identifiers: `<instance>.t(...)`, `<instance>.getFixedT(...)`. */
38
- readonly instances?: readonly string[];
39
- /** Free translation functions bound to the default namespace when imported or global (`t`). */
40
- readonly functions?: readonly string[];
41
- /** Type names whose first type argument names a parameter's namespace (`TFunction<'ns'>`). */
42
- readonly typeNames?: readonly string[];
43
- /** JSX components taking an `i18nKey` prop (`Trans`). */
44
- readonly transComponents?: readonly string[];
45
- /** Identifiers holding a namespace name that live in another module (`{ HELP_NS: 'help' }`). */
46
- readonly namespaceIdentifiers?: Readonly<Record<string, string>>;
47
- /** i18next `nsSeparator`; `false` disables `ns:key` parsing. */
48
- readonly nsSeparator?: string | false;
49
- /** i18next `keySeparator`; `false` means flat catalogs. */
50
- readonly keySeparator?: string | false;
51
- /** i18next `pluralSeparator`. */
52
- readonly pluralSeparator?: string;
53
- /** i18next `contextSeparator`. */
54
- readonly contextSeparator?: string;
55
- /** `ignore` stays silent on template keys; `check-prefix` requires their static head to exist. */
56
- readonly dynamicKeys?: 'ignore' | 'check-prefix';
57
- }
2
+ import { TSESTree, TSESLint } from '@typescript-eslint/utils';
58
3
 
59
4
  interface FetchMustCheckOkOptions {
60
5
  /**
@@ -127,6 +72,173 @@ interface ZodSchemaNamingOptions {
127
72
  readonly roleSuffixes?: readonly string[];
128
73
  }
129
74
 
75
+ /**
76
+ * Translation catalog loading for the i18n rules.
77
+ *
78
+ * A catalog is a JSON file (or a subtree of one) that holds the keys of ONE
79
+ * namespace. Projects lay catalogs out in a handful of shapes, and a
80
+ * `CatalogSource` describes each of them without the rule knowing any project:
81
+ *
82
+ * one file per namespace `{ file: 'locales/en/{ns}.json' }`
83
+ * one file, ns at the top `{ file: 'locales/en.json', keyPath: '{ns}' }`
84
+ * a fixed file for one ns `{ file: 'src/i18n/en.json', namespace: 'common' }`
85
+ * single-namespace app `{ file: 'src/i18n/en.json' }` (the default namespace)
86
+ *
87
+ * `{ns}` is substituted with the namespace being resolved. A templated source
88
+ * whose file or subtree does not exist simply does not supply that namespace;
89
+ * a FIXED source that cannot be read is a configuration error and is surfaced.
90
+ */
91
+ interface CatalogSource {
92
+ /** JSON catalog path, relative to the ESLint cwd. May contain `{ns}`. */
93
+ readonly file: string;
94
+ /** Namespace a fixed (non-templated) source supplies. Defaults to the default namespace. */
95
+ readonly namespace?: string;
96
+ /** Dot-separated subtree inside the file holding the namespace's keys. May contain `{ns}`. */
97
+ readonly keyPath?: string;
98
+ }
99
+ /** The flattened key space of one namespace catalog. */
100
+ interface Catalog {
101
+ /** Where the keys came from, for messages: `file` or `file#keyPath`. */
102
+ readonly label: string;
103
+ /** Every leaf key (a string / number / boolean value), joined by the key separator. */
104
+ readonly leaves: ReadonlySet<string>;
105
+ /** Every non-leaf key (an object or array), joined by the key separator. */
106
+ readonly branches: ReadonlySet<string>;
107
+ }
108
+ /** Outcome of resolving one namespace against every configured source. */
109
+ interface NamespaceCatalogs {
110
+ readonly catalogs: readonly Catalog[];
111
+ /** Fixed sources that should supply this namespace but could not be loaded. */
112
+ readonly errors: readonly string[];
113
+ }
114
+ /** Every catalog the configured sources supply for `namespace`, in source order. */
115
+ declare function catalogsForNamespace(namespace: string, sources: readonly CatalogSource[], settings: {
116
+ readonly cwd: string;
117
+ readonly defaultNamespace: string;
118
+ readonly keySeparator: string | false;
119
+ }): NamespaceCatalogs;
120
+ interface KeyLookup {
121
+ /** Plural forms may answer for the key (the call passes, or may pass, `count`). */
122
+ readonly plural: boolean;
123
+ /** Context variants may answer for the key (the call passes, or may pass, `context`). */
124
+ readonly context: boolean;
125
+ /** The call asks for an object (`returnObjects`), so a branch answers too. */
126
+ readonly returnObjects: boolean;
127
+ readonly pluralSeparator: string;
128
+ readonly contextSeparator: string;
129
+ }
130
+ /** Does `key` resolve in `catalog` the way i18next would look it up? */
131
+ declare function catalogHasKey(catalog: Catalog, key: string, lookup: KeyLookup): boolean;
132
+ /** Does any key in `catalog` start with `prefix`? (the `dynamicKeys: 'check-prefix'` probe) */
133
+ declare function catalogHasPrefix(catalog: Catalog, prefix: string): boolean;
134
+
135
+ /**
136
+ * Static discovery of translation-key usages, i18next / react-i18next style.
137
+ *
138
+ * The visitor answers one question per call site: "which key, in which
139
+ * namespace(s)?" It never guesses. Every shape it cannot pin down statically
140
+ * (a variable key, a namespace held in a variable it cannot resolve, an opaque
141
+ * options bag) comes back as `dynamic` / `unresolved` so the caller can stay
142
+ * silent on it, the way sibling rules stay silent on spreads.
143
+ *
144
+ * Namespace sources it understands, all by syntax, all per file:
145
+ * `const { t } = useTranslation('ns', { keyPrefix })` (also `[t]`, `r.t`, aliases)
146
+ * `const t = i18n.getFixedT(lng, 'ns', keyPrefix)`
147
+ * `i18n.t(...)` / `i18next.t(...)` (default namespace)
148
+ * `function f(t: TFunction<'ns'>)` (typed parameter)
149
+ * `import { t } from 'i18next'` or a global `t` (default namespace)
150
+ * per call: `t('ns:key')`, `t('key', { ns: 'ns' })`
151
+ * JSX: `<Trans i18nKey="key" ns="ns" t={t} />`
152
+ * A namespace argument may be a literal, an array of literals, a same-file
153
+ * `const`, a name mapped in `namespaceIdentifiers`, or (under typed linting)
154
+ * any identifier whose type is a single string literal.
155
+ */
156
+ interface TranslationSettings {
157
+ readonly hooks: ReadonlySet<string>;
158
+ readonly instances: ReadonlySet<string>;
159
+ readonly functions: ReadonlySet<string>;
160
+ readonly typeNames: ReadonlySet<string>;
161
+ readonly transComponents: ReadonlySet<string>;
162
+ readonly namespaceIdentifiers: Readonly<Record<string, string>>;
163
+ readonly defaultNamespace: string;
164
+ readonly nsSeparator: string | false;
165
+ readonly keySeparator: string | false;
166
+ }
167
+ type TranslationUsage = {
168
+ /** Every key is static: the call is checkable. */
169
+ readonly kind: 'key';
170
+ readonly node: TSESTree.Node;
171
+ /** Namespaces i18next would search, in order. */
172
+ readonly namespaces: readonly string[];
173
+ /** Candidate keys (more than one for `t(['a', 'b'])`); any one resolving is enough. */
174
+ readonly keys: readonly string[];
175
+ readonly plural: boolean;
176
+ readonly context: boolean;
177
+ readonly returnObjects: boolean;
178
+ } | {
179
+ /** A template key with a static head: only its prefix is known. */
180
+ readonly kind: 'prefix';
181
+ readonly node: TSESTree.Node;
182
+ readonly namespaces: readonly string[];
183
+ readonly prefix: string;
184
+ } | {
185
+ /** The key itself is not static (`t(someVariable)`, `` t(`${x}`) ``). */
186
+ readonly kind: 'dynamic';
187
+ readonly node: TSESTree.Node;
188
+ } | {
189
+ /** A translation call whose namespace or options cannot be resolved statically. */
190
+ readonly kind: 'unresolved';
191
+ readonly node: TSESTree.Node;
192
+ };
193
+ type Context = Readonly<TSESLint.RuleContext<string, readonly unknown[]>>;
194
+ declare function createTranslationVisitor(context: Context, settings: TranslationSettings, onUsage: (usage: TranslationUsage) => void): TSESLint.RuleListener;
195
+
196
+ interface TranslationKeyExistsOptions {
197
+ /** Where each namespace's catalog lives. Empty = rule is inert. */
198
+ readonly catalogs?: readonly CatalogSource[];
199
+ /** The namespace an unqualified `useTranslation()` / `i18n.t` resolves to (i18next `defaultNS`). */
200
+ readonly defaultNamespace?: string;
201
+ /** Namespaces searched after the bound ones (i18next `fallbackNS`). */
202
+ readonly fallbackNamespaces?: readonly string[];
203
+ /** Hooks returning a namespace-bound `t` (`useTranslation`). */
204
+ readonly hooks?: readonly string[];
205
+ /** i18next instance identifiers: `<instance>.t(...)`, `<instance>.getFixedT(...)`. */
206
+ readonly instances?: readonly string[];
207
+ /** Free translation functions bound to the default namespace when imported or global (`t`). */
208
+ readonly functions?: readonly string[];
209
+ /** Type names whose first type argument names a parameter's namespace (`TFunction<'ns'>`). */
210
+ readonly typeNames?: readonly string[];
211
+ /** JSX components taking an `i18nKey` prop (`Trans`). */
212
+ readonly transComponents?: readonly string[];
213
+ /** Identifiers holding a namespace name that live in another module (`{ HELP_NS: 'help' }`). */
214
+ readonly namespaceIdentifiers?: Readonly<Record<string, string>>;
215
+ /** i18next `nsSeparator`; `false` disables `ns:key` parsing. */
216
+ readonly nsSeparator?: string | false;
217
+ /** i18next `keySeparator`; `false` means flat catalogs. */
218
+ readonly keySeparator?: string | false;
219
+ /** i18next `pluralSeparator`. */
220
+ readonly pluralSeparator?: string;
221
+ /** i18next `contextSeparator`. */
222
+ readonly contextSeparator?: string;
223
+ /** `ignore` stays silent on template keys; `check-prefix` requires their static head to exist. */
224
+ readonly dynamicKeys?: 'ignore' | 'check-prefix';
225
+ }
226
+ /** Defaults mirror i18next / react-i18next's own. */
227
+ declare const TRANSLATION_DEFAULTS: {
228
+ readonly defaultNamespace: "translation";
229
+ readonly hooks: readonly ["useTranslation"];
230
+ readonly instances: readonly ["i18n", "i18next"];
231
+ readonly functions: readonly ["t"];
232
+ readonly typeNames: readonly ["TFunction"];
233
+ readonly transComponents: readonly ["Trans"];
234
+ readonly nsSeparator: ":";
235
+ readonly keySeparator: ".";
236
+ readonly pluralSeparator: "_";
237
+ readonly contextSeparator: "_";
238
+ };
239
+ /** Normalise user options into the visitor's settings. */
240
+ declare function translationSettingsOf(options: TranslationKeyExistsOptions): TranslationSettings;
241
+
130
242
  /** Every rule this plugin exposes, keyed by its (unprefixed) rule id. */
131
243
  declare const rules: {
132
244
  'zod-schema-naming': _typescript_eslint_utils_ts_eslint.RuleModule<"schemaNaming" | "missingType", [ZodSchemaNamingOptions], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
@@ -221,4 +333,4 @@ declare const plugin: {
221
333
 
222
334
  declare const configs: Record<string, unknown>;
223
335
 
224
- export { configs, plugin as default, rules };
336
+ export { type Catalog, type CatalogSource, type KeyLookup, type NamespaceCatalogs, TRANSLATION_DEFAULTS, type TranslationKeyExistsOptions, type TranslationSettings, type TranslationUsage, catalogHasKey, catalogHasPrefix, catalogsForNamespace, configs, createTranslationVisitor, plugin as default, rules, translationSettingsOf };
package/dist/index.d.ts CHANGED
@@ -1,60 +1,5 @@
1
1
  import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
-
3
- /**
4
- * Translation catalog loading for the i18n rules.
5
- *
6
- * A catalog is a JSON file (or a subtree of one) that holds the keys of ONE
7
- * namespace. Projects lay catalogs out in a handful of shapes, and a
8
- * `CatalogSource` describes each of them without the rule knowing any project:
9
- *
10
- * one file per namespace `{ file: 'locales/en/{ns}.json' }`
11
- * one file, ns at the top `{ file: 'locales/en.json', keyPath: '{ns}' }`
12
- * a fixed file for one ns `{ file: 'src/i18n/en.json', namespace: 'common' }`
13
- * single-namespace app `{ file: 'src/i18n/en.json' }` (the default namespace)
14
- *
15
- * `{ns}` is substituted with the namespace being resolved. A templated source
16
- * whose file or subtree does not exist simply does not supply that namespace;
17
- * a FIXED source that cannot be read is a configuration error and is surfaced.
18
- */
19
- interface CatalogSource {
20
- /** JSON catalog path, relative to the ESLint cwd. May contain `{ns}`. */
21
- readonly file: string;
22
- /** Namespace a fixed (non-templated) source supplies. Defaults to the default namespace. */
23
- readonly namespace?: string;
24
- /** Dot-separated subtree inside the file holding the namespace's keys. May contain `{ns}`. */
25
- readonly keyPath?: string;
26
- }
27
-
28
- interface TranslationKeyExistsOptions {
29
- /** Where each namespace's catalog lives. Empty = rule is inert. */
30
- readonly catalogs?: readonly CatalogSource[];
31
- /** The namespace an unqualified `useTranslation()` / `i18n.t` resolves to (i18next `defaultNS`). */
32
- readonly defaultNamespace?: string;
33
- /** Namespaces searched after the bound ones (i18next `fallbackNS`). */
34
- readonly fallbackNamespaces?: readonly string[];
35
- /** Hooks returning a namespace-bound `t` (`useTranslation`). */
36
- readonly hooks?: readonly string[];
37
- /** i18next instance identifiers: `<instance>.t(...)`, `<instance>.getFixedT(...)`. */
38
- readonly instances?: readonly string[];
39
- /** Free translation functions bound to the default namespace when imported or global (`t`). */
40
- readonly functions?: readonly string[];
41
- /** Type names whose first type argument names a parameter's namespace (`TFunction<'ns'>`). */
42
- readonly typeNames?: readonly string[];
43
- /** JSX components taking an `i18nKey` prop (`Trans`). */
44
- readonly transComponents?: readonly string[];
45
- /** Identifiers holding a namespace name that live in another module (`{ HELP_NS: 'help' }`). */
46
- readonly namespaceIdentifiers?: Readonly<Record<string, string>>;
47
- /** i18next `nsSeparator`; `false` disables `ns:key` parsing. */
48
- readonly nsSeparator?: string | false;
49
- /** i18next `keySeparator`; `false` means flat catalogs. */
50
- readonly keySeparator?: string | false;
51
- /** i18next `pluralSeparator`. */
52
- readonly pluralSeparator?: string;
53
- /** i18next `contextSeparator`. */
54
- readonly contextSeparator?: string;
55
- /** `ignore` stays silent on template keys; `check-prefix` requires their static head to exist. */
56
- readonly dynamicKeys?: 'ignore' | 'check-prefix';
57
- }
2
+ import { TSESTree, TSESLint } from '@typescript-eslint/utils';
58
3
 
59
4
  interface FetchMustCheckOkOptions {
60
5
  /**
@@ -127,6 +72,173 @@ interface ZodSchemaNamingOptions {
127
72
  readonly roleSuffixes?: readonly string[];
128
73
  }
129
74
 
75
+ /**
76
+ * Translation catalog loading for the i18n rules.
77
+ *
78
+ * A catalog is a JSON file (or a subtree of one) that holds the keys of ONE
79
+ * namespace. Projects lay catalogs out in a handful of shapes, and a
80
+ * `CatalogSource` describes each of them without the rule knowing any project:
81
+ *
82
+ * one file per namespace `{ file: 'locales/en/{ns}.json' }`
83
+ * one file, ns at the top `{ file: 'locales/en.json', keyPath: '{ns}' }`
84
+ * a fixed file for one ns `{ file: 'src/i18n/en.json', namespace: 'common' }`
85
+ * single-namespace app `{ file: 'src/i18n/en.json' }` (the default namespace)
86
+ *
87
+ * `{ns}` is substituted with the namespace being resolved. A templated source
88
+ * whose file or subtree does not exist simply does not supply that namespace;
89
+ * a FIXED source that cannot be read is a configuration error and is surfaced.
90
+ */
91
+ interface CatalogSource {
92
+ /** JSON catalog path, relative to the ESLint cwd. May contain `{ns}`. */
93
+ readonly file: string;
94
+ /** Namespace a fixed (non-templated) source supplies. Defaults to the default namespace. */
95
+ readonly namespace?: string;
96
+ /** Dot-separated subtree inside the file holding the namespace's keys. May contain `{ns}`. */
97
+ readonly keyPath?: string;
98
+ }
99
+ /** The flattened key space of one namespace catalog. */
100
+ interface Catalog {
101
+ /** Where the keys came from, for messages: `file` or `file#keyPath`. */
102
+ readonly label: string;
103
+ /** Every leaf key (a string / number / boolean value), joined by the key separator. */
104
+ readonly leaves: ReadonlySet<string>;
105
+ /** Every non-leaf key (an object or array), joined by the key separator. */
106
+ readonly branches: ReadonlySet<string>;
107
+ }
108
+ /** Outcome of resolving one namespace against every configured source. */
109
+ interface NamespaceCatalogs {
110
+ readonly catalogs: readonly Catalog[];
111
+ /** Fixed sources that should supply this namespace but could not be loaded. */
112
+ readonly errors: readonly string[];
113
+ }
114
+ /** Every catalog the configured sources supply for `namespace`, in source order. */
115
+ declare function catalogsForNamespace(namespace: string, sources: readonly CatalogSource[], settings: {
116
+ readonly cwd: string;
117
+ readonly defaultNamespace: string;
118
+ readonly keySeparator: string | false;
119
+ }): NamespaceCatalogs;
120
+ interface KeyLookup {
121
+ /** Plural forms may answer for the key (the call passes, or may pass, `count`). */
122
+ readonly plural: boolean;
123
+ /** Context variants may answer for the key (the call passes, or may pass, `context`). */
124
+ readonly context: boolean;
125
+ /** The call asks for an object (`returnObjects`), so a branch answers too. */
126
+ readonly returnObjects: boolean;
127
+ readonly pluralSeparator: string;
128
+ readonly contextSeparator: string;
129
+ }
130
+ /** Does `key` resolve in `catalog` the way i18next would look it up? */
131
+ declare function catalogHasKey(catalog: Catalog, key: string, lookup: KeyLookup): boolean;
132
+ /** Does any key in `catalog` start with `prefix`? (the `dynamicKeys: 'check-prefix'` probe) */
133
+ declare function catalogHasPrefix(catalog: Catalog, prefix: string): boolean;
134
+
135
+ /**
136
+ * Static discovery of translation-key usages, i18next / react-i18next style.
137
+ *
138
+ * The visitor answers one question per call site: "which key, in which
139
+ * namespace(s)?" It never guesses. Every shape it cannot pin down statically
140
+ * (a variable key, a namespace held in a variable it cannot resolve, an opaque
141
+ * options bag) comes back as `dynamic` / `unresolved` so the caller can stay
142
+ * silent on it, the way sibling rules stay silent on spreads.
143
+ *
144
+ * Namespace sources it understands, all by syntax, all per file:
145
+ * `const { t } = useTranslation('ns', { keyPrefix })` (also `[t]`, `r.t`, aliases)
146
+ * `const t = i18n.getFixedT(lng, 'ns', keyPrefix)`
147
+ * `i18n.t(...)` / `i18next.t(...)` (default namespace)
148
+ * `function f(t: TFunction<'ns'>)` (typed parameter)
149
+ * `import { t } from 'i18next'` or a global `t` (default namespace)
150
+ * per call: `t('ns:key')`, `t('key', { ns: 'ns' })`
151
+ * JSX: `<Trans i18nKey="key" ns="ns" t={t} />`
152
+ * A namespace argument may be a literal, an array of literals, a same-file
153
+ * `const`, a name mapped in `namespaceIdentifiers`, or (under typed linting)
154
+ * any identifier whose type is a single string literal.
155
+ */
156
+ interface TranslationSettings {
157
+ readonly hooks: ReadonlySet<string>;
158
+ readonly instances: ReadonlySet<string>;
159
+ readonly functions: ReadonlySet<string>;
160
+ readonly typeNames: ReadonlySet<string>;
161
+ readonly transComponents: ReadonlySet<string>;
162
+ readonly namespaceIdentifiers: Readonly<Record<string, string>>;
163
+ readonly defaultNamespace: string;
164
+ readonly nsSeparator: string | false;
165
+ readonly keySeparator: string | false;
166
+ }
167
+ type TranslationUsage = {
168
+ /** Every key is static: the call is checkable. */
169
+ readonly kind: 'key';
170
+ readonly node: TSESTree.Node;
171
+ /** Namespaces i18next would search, in order. */
172
+ readonly namespaces: readonly string[];
173
+ /** Candidate keys (more than one for `t(['a', 'b'])`); any one resolving is enough. */
174
+ readonly keys: readonly string[];
175
+ readonly plural: boolean;
176
+ readonly context: boolean;
177
+ readonly returnObjects: boolean;
178
+ } | {
179
+ /** A template key with a static head: only its prefix is known. */
180
+ readonly kind: 'prefix';
181
+ readonly node: TSESTree.Node;
182
+ readonly namespaces: readonly string[];
183
+ readonly prefix: string;
184
+ } | {
185
+ /** The key itself is not static (`t(someVariable)`, `` t(`${x}`) ``). */
186
+ readonly kind: 'dynamic';
187
+ readonly node: TSESTree.Node;
188
+ } | {
189
+ /** A translation call whose namespace or options cannot be resolved statically. */
190
+ readonly kind: 'unresolved';
191
+ readonly node: TSESTree.Node;
192
+ };
193
+ type Context = Readonly<TSESLint.RuleContext<string, readonly unknown[]>>;
194
+ declare function createTranslationVisitor(context: Context, settings: TranslationSettings, onUsage: (usage: TranslationUsage) => void): TSESLint.RuleListener;
195
+
196
+ interface TranslationKeyExistsOptions {
197
+ /** Where each namespace's catalog lives. Empty = rule is inert. */
198
+ readonly catalogs?: readonly CatalogSource[];
199
+ /** The namespace an unqualified `useTranslation()` / `i18n.t` resolves to (i18next `defaultNS`). */
200
+ readonly defaultNamespace?: string;
201
+ /** Namespaces searched after the bound ones (i18next `fallbackNS`). */
202
+ readonly fallbackNamespaces?: readonly string[];
203
+ /** Hooks returning a namespace-bound `t` (`useTranslation`). */
204
+ readonly hooks?: readonly string[];
205
+ /** i18next instance identifiers: `<instance>.t(...)`, `<instance>.getFixedT(...)`. */
206
+ readonly instances?: readonly string[];
207
+ /** Free translation functions bound to the default namespace when imported or global (`t`). */
208
+ readonly functions?: readonly string[];
209
+ /** Type names whose first type argument names a parameter's namespace (`TFunction<'ns'>`). */
210
+ readonly typeNames?: readonly string[];
211
+ /** JSX components taking an `i18nKey` prop (`Trans`). */
212
+ readonly transComponents?: readonly string[];
213
+ /** Identifiers holding a namespace name that live in another module (`{ HELP_NS: 'help' }`). */
214
+ readonly namespaceIdentifiers?: Readonly<Record<string, string>>;
215
+ /** i18next `nsSeparator`; `false` disables `ns:key` parsing. */
216
+ readonly nsSeparator?: string | false;
217
+ /** i18next `keySeparator`; `false` means flat catalogs. */
218
+ readonly keySeparator?: string | false;
219
+ /** i18next `pluralSeparator`. */
220
+ readonly pluralSeparator?: string;
221
+ /** i18next `contextSeparator`. */
222
+ readonly contextSeparator?: string;
223
+ /** `ignore` stays silent on template keys; `check-prefix` requires their static head to exist. */
224
+ readonly dynamicKeys?: 'ignore' | 'check-prefix';
225
+ }
226
+ /** Defaults mirror i18next / react-i18next's own. */
227
+ declare const TRANSLATION_DEFAULTS: {
228
+ readonly defaultNamespace: "translation";
229
+ readonly hooks: readonly ["useTranslation"];
230
+ readonly instances: readonly ["i18n", "i18next"];
231
+ readonly functions: readonly ["t"];
232
+ readonly typeNames: readonly ["TFunction"];
233
+ readonly transComponents: readonly ["Trans"];
234
+ readonly nsSeparator: ":";
235
+ readonly keySeparator: ".";
236
+ readonly pluralSeparator: "_";
237
+ readonly contextSeparator: "_";
238
+ };
239
+ /** Normalise user options into the visitor's settings. */
240
+ declare function translationSettingsOf(options: TranslationKeyExistsOptions): TranslationSettings;
241
+
130
242
  /** Every rule this plugin exposes, keyed by its (unprefixed) rule id. */
131
243
  declare const rules: {
132
244
  'zod-schema-naming': _typescript_eslint_utils_ts_eslint.RuleModule<"schemaNaming" | "missingType", [ZodSchemaNamingOptions], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
@@ -221,4 +333,4 @@ declare const plugin: {
221
333
 
222
334
  declare const configs: Record<string, unknown>;
223
335
 
224
- export { configs, plugin as default, rules };
336
+ export { type Catalog, type CatalogSource, type KeyLookup, type NamespaceCatalogs, TRANSLATION_DEFAULTS, type TranslationKeyExistsOptions, type TranslationSettings, type TranslationUsage, catalogHasKey, catalogHasPrefix, catalogsForNamespace, configs, createTranslationVisitor, plugin as default, rules, translationSettingsOf };
package/dist/index.js CHANGED
@@ -2263,7 +2263,13 @@ plugin.configs.recommended = {
2263
2263
  var configs = plugin.configs;
2264
2264
  var index_default = plugin;
2265
2265
  export {
2266
+ TRANSLATION_DEFAULTS,
2267
+ catalogHasKey,
2268
+ catalogHasPrefix,
2269
+ catalogsForNamespace,
2266
2270
  configs,
2271
+ createTranslationVisitor,
2267
2272
  index_default as default,
2268
- rules
2273
+ rules,
2274
+ translationSettingsOf
2269
2275
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noctcore/eslint-plugin-contracts",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "ESLint rules for shared contract, config, error-handling, and money-precision conventions (zod schema naming, wire discriminants, no-direct-process-env, decimal money).",
5
5
  "license": "MIT",
6
6
  "type": "module",