eslint-plugin-intlayer 9.3.1 → 9.3.3

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.
Files changed (64) hide show
  1. package/README.md +103 -49
  2. package/dist/cjs/@intlayer/config/dist/esm/extract/constants.cjs +18 -0
  3. package/dist/cjs/@intlayer/config/dist/esm/extract/constants.cjs.map +1 -0
  4. package/dist/cjs/@intlayer/config/dist/esm/extract/shouldExtract.cjs +37 -0
  5. package/dist/cjs/@intlayer/config/dist/esm/extract/shouldExtract.cjs.map +1 -0
  6. package/dist/cjs/_virtual/_rolldown/runtime.cjs +28 -0
  7. package/dist/cjs/index.cjs +41 -29
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/preset.cjs +53 -0
  10. package/dist/cjs/preset.cjs.map +1 -0
  11. package/dist/cjs/rules/enforceAdapterImport.cjs +9 -2
  12. package/dist/cjs/rules/enforceAdapterImport.cjs.map +1 -1
  13. package/dist/cjs/rules/noRawText.cjs +4 -3
  14. package/dist/cjs/rules/noRawText.cjs.map +1 -1
  15. package/dist/cjs/rules/noUnusedContent.cjs +143 -0
  16. package/dist/cjs/rules/noUnusedContent.cjs.map +1 -0
  17. package/dist/cjs/rules/staticDictionaryKey.cjs +2 -2
  18. package/dist/cjs/rules/staticDictionaryKey.cjs.map +1 -1
  19. package/dist/cjs/utils/contentDeclaration.cjs +100 -0
  20. package/dist/cjs/utils/contentDeclaration.cjs.map +1 -0
  21. package/dist/cjs/utils/projectUsage.cjs +437 -0
  22. package/dist/cjs/utils/projectUsage.cjs.map +1 -0
  23. package/dist/cjs/utils/reactVersion.cjs +50 -0
  24. package/dist/cjs/utils/reactVersion.cjs.map +1 -0
  25. package/dist/esm/@intlayer/config/dist/esm/extract/constants.mjs +18 -0
  26. package/dist/esm/@intlayer/config/dist/esm/extract/constants.mjs.map +1 -0
  27. package/dist/esm/@intlayer/config/dist/esm/extract/shouldExtract.mjs +37 -0
  28. package/dist/esm/@intlayer/config/dist/esm/extract/shouldExtract.mjs.map +1 -0
  29. package/dist/esm/_virtual/_rolldown/runtime.mjs +8 -0
  30. package/dist/esm/index.mjs +36 -30
  31. package/dist/esm/index.mjs.map +1 -1
  32. package/dist/esm/preset.mjs +48 -0
  33. package/dist/esm/preset.mjs.map +1 -0
  34. package/dist/esm/rules/enforceAdapterImport.mjs +10 -3
  35. package/dist/esm/rules/enforceAdapterImport.mjs.map +1 -1
  36. package/dist/esm/rules/noRawText.mjs +2 -1
  37. package/dist/esm/rules/noRawText.mjs.map +1 -1
  38. package/dist/esm/rules/noUnusedContent.mjs +142 -0
  39. package/dist/esm/rules/noUnusedContent.mjs.map +1 -0
  40. package/dist/esm/rules/staticDictionaryKey.mjs +2 -2
  41. package/dist/esm/rules/staticDictionaryKey.mjs.map +1 -1
  42. package/dist/esm/utils/contentDeclaration.mjs +99 -0
  43. package/dist/esm/utils/contentDeclaration.mjs.map +1 -0
  44. package/dist/esm/utils/projectUsage.mjs +433 -0
  45. package/dist/esm/utils/projectUsage.mjs.map +1 -0
  46. package/dist/esm/utils/reactVersion.mjs +46 -0
  47. package/dist/esm/utils/reactVersion.mjs.map +1 -0
  48. package/dist/types/index.d.ts +19 -6
  49. package/dist/types/index.d.ts.map +1 -1
  50. package/dist/types/node_modules/.bun/@types_estree@1.0.9/node_modules/@types/estree/index.d.ts +528 -0
  51. package/dist/types/node_modules/.bun/@types_estree@1.0.9/node_modules/@types/estree/index.d.ts.map +1 -0
  52. package/dist/types/preset.d.ts +45 -0
  53. package/dist/types/preset.d.ts.map +1 -0
  54. package/dist/types/rules/enforceAdapterImport.d.ts.map +1 -1
  55. package/dist/types/rules/noUnusedContent.d.ts +33 -0
  56. package/dist/types/rules/noUnusedContent.d.ts.map +1 -0
  57. package/dist/types/utils/ast.d.ts +1 -1
  58. package/dist/types/utils/contentDeclaration.d.ts +42 -0
  59. package/dist/types/utils/contentDeclaration.d.ts.map +1 -0
  60. package/dist/types/utils/projectUsage.d.ts +102 -0
  61. package/dist/types/utils/projectUsage.d.ts.map +1 -0
  62. package/dist/types/utils/reactVersion.d.ts +18 -0
  63. package/dist/types/utils/reactVersion.d.ts.map +1 -0
  64. package/package.json +5 -3
package/README.md CHANGED
@@ -8,12 +8,12 @@ The plugin is deliberately small. Intlayer already reports most i18n mistakes
8
8
  through other layers, and a rule that duplicates one of them only produces the
9
9
  same squiggle three times:
10
10
 
11
- | Mistake | Already reported by |
12
- | --------------------------------- | ---------------------------------------------------------------- |
13
- | Unknown dictionary key | TypeScript (`__DictionaryRegistry`) **and** `@intlayer/lsp` |
14
- | Unknown field path | TypeScript (`__DictionaryRegistry`) |
15
- | Missing required locale | TypeScript (`StrictModeLocaleMap`) **and** `intlayer test` |
16
- | Unused dictionary | `intlayer test`, and the VS Code extension's inline decoration |
11
+ | Mistake | Already reported by |
12
+ | ----------------------- | -------------------------------------------------------------- |
13
+ | Unknown dictionary key | TypeScript (`__DictionaryRegistry`) **and** `@intlayer/lsp` |
14
+ | Unknown field path | TypeScript (`__DictionaryRegistry`) |
15
+ | Missing required locale | TypeScript (`StrictModeLocaleMap`) **and** `intlayer test` |
16
+ | Unused dictionary | `intlayer test`, and the VS Code extension's inline decoration |
17
17
 
18
18
  What is left are the two things neither the type system nor the language server
19
19
  can see, and that is exactly what this plugin covers:
@@ -36,24 +36,24 @@ Requires ESLint 9 or later (flat config).
36
36
 
37
37
  ```js
38
38
  // eslint.config.js
39
- import intlayer from 'eslint-plugin-intlayer';
39
+ import intlayer from "eslint-plugin-intlayer";
40
40
 
41
- export default [intlayer.configs.recommended];
41
+ export default [...intlayer.configs.recommended];
42
42
  ```
43
43
 
44
44
  Or configure rules individually:
45
45
 
46
46
  ```js
47
- import intlayer from 'eslint-plugin-intlayer';
47
+ import intlayer from "eslint-plugin-intlayer";
48
48
 
49
49
  export default [
50
50
  {
51
51
  plugins: { intlayer },
52
52
  rules: {
53
- 'intlayer/no-raw-text': ['warn', { includeStringLiterals: false }],
54
- 'intlayer/static-dictionary-key': 'error',
55
- 'intlayer/no-dynamic-field-access': 'error',
56
- 'intlayer/enforce-adapter-import': 'warn',
53
+ "intlayer/no-raw-text": ["warn", { includeStringLiterals: false }],
54
+ "intlayer/static-dictionary-key": "error",
55
+ "intlayer/no-dynamic-field-access": "error",
56
+ "intlayer/enforce-adapter-import": "warn",
57
57
  },
58
58
  },
59
59
  ];
@@ -61,11 +61,11 @@ export default [
61
61
 
62
62
  ### Configs
63
63
 
64
- | Config | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` |
65
- | --------------- | ------------- | ----------------------- | ------------------------- | ------------------------ |
66
- | `recommended` | warn | error | error | off |
67
- | `strict` | error (+ non-JSX literals) | error | error | error |
68
- | `contract-only` | off | error | error | off |
64
+ | Config | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
65
+ | --------------- | -------------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
66
+ | `recommended` | warn | error | error | off | off |
67
+ | `strict` | error (+ non-JSX literals) | error | error | error | off |
68
+ | `contract-only` | off | error | error | off | off |
69
69
 
70
70
  `recommended` keeps `no-raw-text` at `warn` on purpose: pointing it at an
71
71
  existing codebase surfaces every untranslated string at once, which should not
@@ -74,21 +74,25 @@ block a build on day one.
74
74
  Use `contract-only` when translation coverage is already tracked by
75
75
  `intlayer test` in CI and you only want the rules that protect the build output.
76
76
 
77
+ `no-unused-content` is off in every config, `strict` included. It is the one
78
+ rule that reads the Intlayer configuration and walks your source files from
79
+ disk, so enabling it should be a deliberate choice.
80
+
77
81
  ### Frameworks
78
82
 
79
83
  Every rule works across all of Intlayer's integrations. The rules are
80
84
  parser-agnostic; what differs is the parser ESLint needs to read each file type,
81
85
  which you configure once:
82
86
 
83
- | Framework | Files | Parser | Text coverage | Contract rules |
84
- | ----------------------------- | ------------------------ | --------------------------------- | ------------- | -------------- |
85
- | React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` | ✅ | ✅ |
86
- | Next.js | `.jsx` `.tsx` | `typescript-eslint` | ✅ | ✅ |
87
- | Vue, Nuxt | `.vue` | `vue-eslint-parser` | ✅ template | ✅ `<script>` |
88
- | Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` | ✅ markup | ✅ `<script>` |
89
- | Angular | `.ts` | `typescript-eslint` | ✅ | ✅ |
90
- | Angular templates | `.component.html` | `@angular-eslint/template-parser` | ✅ | n/a (no JS) |
91
- | Astro | `.astro` | `astro-eslint-parser` | ✅ | ✅ frontmatter |
87
+ | Framework | Files | Parser | Text coverage | Contract rules |
88
+ | ------------------------- | ----------------- | --------------------------------- | ------------- | -------------- |
89
+ | React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` | ✅ | ✅ |
90
+ | Next.js | `.jsx` `.tsx` | `typescript-eslint` | ✅ | ✅ |
91
+ | Vue, Nuxt | `.vue` | `vue-eslint-parser` | ✅ template | ✅ `<script>` |
92
+ | Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` | ✅ markup | ✅ `<script>` |
93
+ | Angular | `.ts` | `typescript-eslint` | ✅ | ✅ |
94
+ | Angular templates | `.component.html` | `@angular-eslint/template-parser` | ✅ | n/a (no JS) |
95
+ | Astro | `.astro` | `astro-eslint-parser` | ✅ | ✅ frontmatter |
92
96
 
93
97
  `no-raw-text` understands each ecosystem's markup natively — Vue's `VText` /
94
98
  `VAttribute` (reached through `defineTemplateBodyVisitor`), Svelte's
@@ -102,24 +106,24 @@ parsers exposes as an ordinary ESTree program.
102
106
 
103
107
  ```js
104
108
  // eslint.config.js
105
- import intlayer from 'eslint-plugin-intlayer';
106
- import tseslint from 'typescript-eslint';
107
- import vueParser from 'vue-eslint-parser';
108
- import svelteParser from 'svelte-eslint-parser';
109
- import angularTemplateParser from '@angular-eslint/template-parser';
109
+ import intlayer from "eslint-plugin-intlayer";
110
+ import tseslint from "typescript-eslint";
111
+ import vueParser from "vue-eslint-parser";
112
+ import svelteParser from "svelte-eslint-parser";
113
+ import angularTemplateParser from "@angular-eslint/template-parser";
110
114
 
111
115
  export default [
112
- intlayer.configs.recommended,
116
+ ...intlayer.configs.recommended,
113
117
 
114
118
  // React / Angular components / plain TS
115
119
  {
116
- files: ['**/*.{ts,tsx,jsx}'],
120
+ files: ["**/*.{ts,tsx,jsx}"],
117
121
  languageOptions: { parser: tseslint.parser },
118
122
  },
119
123
 
120
124
  // Vue — the TS parser handles <script lang="ts">
121
125
  {
122
- files: ['**/*.vue'],
126
+ files: ["**/*.vue"],
123
127
  languageOptions: {
124
128
  parser: vueParser,
125
129
  parserOptions: { parser: tseslint.parser },
@@ -128,7 +132,7 @@ export default [
128
132
 
129
133
  // Svelte
130
134
  {
131
- files: ['**/*.svelte'],
135
+ files: ["**/*.svelte"],
132
136
  languageOptions: {
133
137
  parser: svelteParser,
134
138
  parserOptions: { parser: tseslint.parser },
@@ -137,7 +141,7 @@ export default [
137
141
 
138
142
  // Angular templates
139
143
  {
140
- files: ['**/*.component.html'],
144
+ files: ["**/*.component.html"],
141
145
  languageOptions: { parser: angularTemplateParser },
142
146
  },
143
147
  ];
@@ -229,13 +233,13 @@ useIntlayer(`home-${suffix}`);
229
233
  getTranslations({ namespace: page });
230
234
 
231
235
  // ✗ — a const still is not a literal at the call site
232
- const key = 'home';
236
+ const key = "home";
233
237
  useIntlayer(key);
234
238
 
235
239
  // ✓
236
- useIntlayer('home');
237
- useIntlayer('home' as const);
238
- getTranslations({ namespace: 'home' });
240
+ useIntlayer("home");
241
+ useIntlayer("home" as const);
242
+ getTranslations({ namespace: "home" });
239
243
  ```
240
244
 
241
245
  The rule covers every caller in the shared registry — `useIntlayer`,
@@ -256,17 +260,17 @@ a missed optimization.
256
260
 
257
261
  ```ts
258
262
  // ✗
259
- const content = useIntlayer('home');
263
+ const content = useIntlayer("home");
260
264
  content[fieldName];
261
265
 
262
- const t = useTranslations('home');
266
+ const t = useTranslations("home");
263
267
  t(messageKey);
264
268
 
265
269
  // ✓
266
270
  content.title;
267
- content['title'];
271
+ content["title"];
268
272
  content.items[0];
269
- t('hero.title');
273
+ t("hero.title");
270
274
  ```
271
275
 
272
276
  Bindings are resolved through ESLint's scope analysis, so two components in the
@@ -282,17 +286,67 @@ configured; the adapter always does. Autofixable.
282
286
 
283
287
  ```ts
284
288
  // ✗
285
- import { useTranslation } from 'react-i18next';
286
- import { getTranslations } from 'next-intl/server';
289
+ import { useTranslation } from "react-i18next";
290
+ import { getTranslations } from "next-intl/server";
287
291
 
288
292
  // ✓
289
- import { useTranslation } from '@intlayer/react-i18next';
290
- import { getTranslations } from '@intlayer/next-intl/server';
293
+ import { useTranslation } from "@intlayer/react-i18next";
294
+ import { getTranslations } from "@intlayer/next-intl/server";
291
295
  ```
292
296
 
293
297
  The mapping is derived from `@intlayer/config/callers`, so a library added to
294
298
  the shared registry is covered here automatically.
295
299
 
300
+ ### no-unused-content
301
+
302
+ Off by default. Reports dictionaries and content fields that nothing in the
303
+ project reads, plus keys declared in more than one place.
304
+
305
+ ```ts
306
+ // home.content.ts
307
+ export default {
308
+ key: "home", // ✗ unusedDictionary — no caller anywhere asks for "home"
309
+ content: {
310
+ title: t({ en: "Title" }),
311
+ hero: {
312
+ // ✗ unusedField — nothing reads `hero`
313
+ subtitle: t({ en: "Subtitle" }),
314
+ },
315
+ },
316
+ };
317
+ ```
318
+
319
+ Unlike the other rules, this one cannot answer from the file in front of it: a
320
+ field is unused only relative to the whole project. On the first content file of
321
+ a lint run it loads your Intlayer configuration, globs the source files it
322
+ declares (`build.traversePattern`, `compiler.transformPattern`) and runs the
323
+ same analyser `@intlayer/lsp` and the VS Code extension use. The result is cached
324
+ for `cacheTtl` milliseconds.
325
+
326
+ ```js
327
+ {
328
+ "intlayer/no-unused-content": ["warn", {
329
+ reportUnusedDictionaries: true, // dictionary keys nothing references
330
+ reportUnusedFields: true, // content fields nothing reads
331
+ reportDuplicateKeys: true, // keys declared in more than one place
332
+ ignoreFields: ["^meta"], // regexes for field paths to never report
333
+ baseDir: process.cwd(), // project root the scan starts from
334
+ cacheTtl: 30000, // how long one scan is reused, in ms
335
+ }],
336
+ }
337
+ ```
338
+
339
+ It errs towards silence, because a false positive here deletes a translation.
340
+ Nothing is reported when the dictionary is consumed in a way the analysis cannot
341
+ follow — the content object passed on as a whole, a translator function bound
342
+ from it, a declaration reached through a direct import, a `nest()` from another
343
+ dictionary, or a field list made non-exhaustive by a spread. Single-file
344
+ components (`.vue`, `.svelte`, `.astro`) count as using every field of the
345
+ dictionaries they mention, since their script blocks are not parsed here.
346
+
347
+ `reportDuplicateKeys` reads the unmerged dictionaries the build writes to
348
+ `.intlayer/`, so it stays quiet until the project has been built at least once.
349
+
296
350
  ## How it stays in sync
297
351
 
298
352
  The plugin reads the same two shared sources as the compiler and the language
@@ -0,0 +1,18 @@
1
+ //#region ../@intlayer/config/dist/esm/extract/constants.mjs
2
+ /**
3
+ * Attributes that should be extracted as translatable strings from JSX/HTML elements.
4
+ * This is the single source of truth shared across all Intlayer compiler packages
5
+ * (@intlayer/babel, @intlayer/vue-compiler, @intlayer/svelte-compiler, @intlayer/engine)
6
+ * and the `eslint-plugin-intlayer` `no-raw-text` rule.
7
+ */
8
+ const ATTRIBUTES_TO_EXTRACT = [
9
+ "title",
10
+ "placeholder",
11
+ "alt",
12
+ "aria-label",
13
+ "label"
14
+ ];
15
+
16
+ //#endregion
17
+ exports.ATTRIBUTES_TO_EXTRACT = ATTRIBUTES_TO_EXTRACT;
18
+ //# sourceMappingURL=constants.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.cjs","names":[],"sources":["../../../../../../../../@intlayer/config/dist/esm/extract/constants.mjs"],"sourcesContent":["//#region src/extract/constants.ts\n/**\n* Attributes that should be extracted as translatable strings from JSX/HTML elements.\n* This is the single source of truth shared across all Intlayer compiler packages\n* (@intlayer/babel, @intlayer/vue-compiler, @intlayer/svelte-compiler, @intlayer/engine)\n* and the `eslint-plugin-intlayer` `no-raw-text` rule.\n*/\nconst ATTRIBUTES_TO_EXTRACT = [\n\t\"title\",\n\t\"placeholder\",\n\t\"alt\",\n\t\"aria-label\",\n\t\"label\"\n];\n\n//#endregion\nexport { ATTRIBUTES_TO_EXTRACT };\n//# sourceMappingURL=constants.mjs.map"],"mappings":";;;;;;;AAOA,MAAM,wBAAwB;CAC7B;CACA;CACA;CACA;CACA;AACD"}
@@ -0,0 +1,37 @@
1
+ //#region ../@intlayer/config/dist/esm/extract/shouldExtract.mjs
2
+ /**
3
+ * Checks whether the given text should be extracted as a translatable string.
4
+ *
5
+ * Filters out:
6
+ * - Empty strings
7
+ * - Emails
8
+ * - Single capitalized words (likely brand/proper nouns, e.g. "Intlayer")
9
+ * - Uncapitalized strings of 2 words or fewer (likely technical terms)
10
+ * - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)
11
+ */
12
+ const shouldExtract = (text) => {
13
+ const trimmed = text.trim();
14
+ if (!trimmed) return false;
15
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return false;
16
+ if (trimmed.startsWith("{") && !trimmed.startsWith("{{") || trimmed.startsWith("v-")) return false;
17
+ if (trimmed.includes("=>") || trimmed.includes(");") || trimmed.includes("(){") || trimmed.includes("==") || trimmed.includes("window.") || trimmed.startsWith("(function") || trimmed.startsWith("function(")) return false;
18
+ if ((trimmed.match(/[^\p{L}\p{N}\s.,!?;:'"()[\]{}–—/«»„“\p{Sc}%&*+#@^_+=<>/~]/gu) || []).length > 5) return false;
19
+ const wordCount = trimmed.split(/\s+/).length;
20
+ const cssClassTokenRegex = /^!?([a-z][a-z0-9-]*:)*[a-z][a-z0-9]*(-[a-z0-9[\].,%#/]+)*(\/[a-z0-9]+)?$/;
21
+ if (wordCount > 1) {
22
+ const tokens = trimmed.split(/\s+/);
23
+ if (tokens.every((token) => cssClassTokenRegex.test(token)) && tokens.some((token) => token.includes("-"))) return false;
24
+ }
25
+ const isCapitalized = /^['"([]*\p{Lu}/u.test(trimmed);
26
+ if (wordCount === 1 && isCapitalized) return false;
27
+ if (wordCount === 1) {
28
+ if (/[a-z]\p{Lu}/u.test(trimmed)) return false;
29
+ if (trimmed.includes("-") || trimmed.includes("_")) return false;
30
+ }
31
+ if (!isCapitalized && wordCount <= 2) return false;
32
+ return true;
33
+ };
34
+
35
+ //#endregion
36
+ exports.shouldExtract = shouldExtract;
37
+ //# sourceMappingURL=shouldExtract.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldExtract.cjs","names":[],"sources":["../../../../../../../../@intlayer/config/dist/esm/extract/shouldExtract.mjs"],"sourcesContent":["//#region src/extract/shouldExtract.ts\n/**\n* Checks whether the given text should be extracted as a translatable string.\n*\n* Filters out:\n* - Empty strings\n* - Emails\n* - Single capitalized words (likely brand/proper nouns, e.g. \"Intlayer\")\n* - Uncapitalized strings of 2 words or fewer (likely technical terms)\n* - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)\n*/\nconst shouldExtract = (text) => {\n\tconst trimmed = text.trim();\n\tif (!trimmed) return false;\n\tif (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(trimmed)) return false;\n\tif (trimmed.startsWith(\"{\") && !trimmed.startsWith(\"{{\") || trimmed.startsWith(\"v-\")) return false;\n\tif (trimmed.includes(\"=>\") || trimmed.includes(\");\") || trimmed.includes(\"(){\") || trimmed.includes(\"==\") || trimmed.includes(\"window.\") || trimmed.startsWith(\"(function\") || trimmed.startsWith(\"function(\")) return false;\n\tif ((trimmed.match(/[^\\p{L}\\p{N}\\s.,!?;:'\"()[\\]{}–—/«»„“\\p{Sc}%&*+#@^_+=<>/~]/gu) || []).length > 5) return false;\n\tconst wordCount = trimmed.split(/\\s+/).length;\n\tconst cssClassTokenRegex = /^!?([a-z][a-z0-9-]*:)*[a-z][a-z0-9]*(-[a-z0-9[\\].,%#/]+)*(\\/[a-z0-9]+)?$/;\n\tif (wordCount > 1) {\n\t\tconst tokens = trimmed.split(/\\s+/);\n\t\tif (tokens.every((token) => cssClassTokenRegex.test(token)) && tokens.some((token) => token.includes(\"-\"))) return false;\n\t}\n\tconst isCapitalized = /^['\"([]*\\p{Lu}/u.test(trimmed);\n\tif (wordCount === 1 && isCapitalized) return false;\n\tif (wordCount === 1) {\n\t\tif (/[a-z]\\p{Lu}/u.test(trimmed)) return false;\n\t\tif (trimmed.includes(\"-\") || trimmed.includes(\"_\")) return false;\n\t}\n\tif (!isCapitalized && wordCount <= 2) return false;\n\treturn true;\n};\n\n//#endregion\nexport { shouldExtract };\n//# sourceMappingURL=shouldExtract.mjs.map"],"mappings":";;;;;;;;;;;AAWA,MAAM,iBAAiB,SAAS;CAC/B,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,6BAA6B,KAAK,OAAO,GAAG,OAAO;CACvD,IAAI,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,IAAI,GAAG,OAAO;CAC7F,IAAI,QAAQ,SAAS,IAAI,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SAAS,SAAS,KAAK,QAAQ,WAAW,WAAW,KAAK,QAAQ,WAAW,WAAW,GAAG,OAAO;CACvN,KAAK,QAAQ,MAAM,6DAA6D,KAAK,CAAC,EAAC,CAAE,SAAS,GAAG,OAAO;CAC5G,MAAM,YAAY,QAAQ,MAAM,KAAK,CAAC,CAAC;CACvC,MAAM,qBAAqB;CAC3B,IAAI,YAAY,GAAG;EAClB,MAAM,SAAS,QAAQ,MAAM,KAAK;EAClC,IAAI,OAAO,OAAO,UAAU,mBAAmB,KAAK,KAAK,CAAC,KAAK,OAAO,MAAM,UAAU,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO;CACpH;CACA,MAAM,gBAAgB,kBAAkB,KAAK,OAAO;CACpD,IAAI,cAAc,KAAK,eAAe,OAAO;CAC7C,IAAI,cAAc,GAAG;EACpB,IAAI,eAAe,KAAK,OAAO,GAAG,OAAO;EACzC,IAAI,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAG,OAAO;CAC5D;CACA,IAAI,CAAC,iBAAiB,aAAa,GAAG,OAAO;CAC7C,OAAO;AACR"}
@@ -0,0 +1,28 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ exports.__toESM = __toESM;
@@ -1,21 +1,25 @@
1
1
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_preset = require('./preset.cjs');
2
3
  const require_rules_enforceAdapterImport = require('./rules/enforceAdapterImport.cjs');
3
4
  const require_rules_noDynamicFieldAccess = require('./rules/noDynamicFieldAccess.cjs');
4
5
  const require_rules_noRawText = require('./rules/noRawText.cjs');
6
+ const require_rules_noUnusedContent = require('./rules/noUnusedContent.cjs');
5
7
  const require_rules_staticDictionaryKey = require('./rules/staticDictionaryKey.cjs');
6
- let _intlayer_config_lint = require("@intlayer/config/lint");
8
+ const require_utils_reactVersion = require('./utils/reactVersion.cjs');
7
9
 
8
10
  //#region src/index.ts
9
- const PLUGIN_NAME = "intlayer";
10
11
  /**
11
12
  * Every rule this plugin ships, keyed by the name used in configuration.
12
13
  *
13
- * The rules split into two families:
14
+ * The rules split into three families:
14
15
  * - **Coverage** (`no-raw-text`) — user-facing copy that never reached a
15
16
  * dictionary. Nothing else in the toolchain reports it.
16
17
  * - **Compiler contract** (`static-dictionary-key`, `no-dynamic-field-access`)
17
18
  * — call shapes that type-check and run, but that the Babel/SWC passes cannot
18
19
  * analyse, so they silently lose the optimize pass or read a purged field.
20
+ * - **Dead content** (`no-unused-content`) — dictionaries and fields nothing
21
+ * reads. Off in every preset: it is the only rule that scans the whole
22
+ * project from disk, so enabling it is a cost the user should opt into.
19
23
  *
20
24
  * Deliberately absent: unknown dictionary keys, unknown field paths and missing
21
25
  * required locales. The generated `__DictionaryRegistry` module augmentation
@@ -26,7 +30,8 @@ const rules = {
26
30
  "no-raw-text": require_rules_noRawText.noRawText,
27
31
  "static-dictionary-key": require_rules_staticDictionaryKey.staticDictionaryKey,
28
32
  "no-dynamic-field-access": require_rules_noDynamicFieldAccess.noDynamicFieldAccess,
29
- "enforce-adapter-import": require_rules_enforceAdapterImport.enforceAdapterImport
33
+ "enforce-adapter-import": require_rules_enforceAdapterImport.enforceAdapterImport,
34
+ "no-unused-content": require_rules_noUnusedContent.noUnusedContent
30
35
  };
31
36
  const meta = {
32
37
  name: "eslint-plugin-intlayer",
@@ -42,18 +47,22 @@ const plugin = {
42
47
  * @param name - Config name, surfaced by `--print-config` and the config inspector.
43
48
  * @param ruleSeverities - Severity per rule name.
44
49
  */
45
- const createConfig = (name, ruleSeverities) => ({
50
+ const createConfig = (name, ruleEntries) => [{
51
+ name: `intlayer/${name}/ignores`,
52
+ ignores: require_preset.GENERATED_OUTPUT_GLOBS
53
+ }, {
46
54
  name: `intlayer/${name}`,
47
- plugins: { [PLUGIN_NAME]: plugin },
48
- rules: Object.fromEntries(Object.entries(ruleSeverities).map(([ruleName, severity]) => [`${PLUGIN_NAME}/${ruleName}`, severity]))
49
- });
55
+ plugins: { [require_preset.PLUGIN_NAMESPACE]: plugin },
56
+ settings: { react: { version: require_utils_reactVersion.getReactVersion() } },
57
+ rules: Object.fromEntries(Object.entries(ruleEntries).map(([ruleName, entry]) => [`${require_preset.PLUGIN_NAMESPACE}/${ruleName}`, entry]))
58
+ }];
50
59
  /**
51
60
  * Flat configs, for `eslint.config.js`:
52
61
  *
53
62
  * ```js
54
63
  * import intlayer from 'eslint-plugin-intlayer';
55
64
  *
56
- * export default [intlayer.configs.recommended];
65
+ * export default [...intlayer]; // or [...intlayer.configs.recommended]
57
66
  * ```
58
67
  *
59
68
  * - `recommended` — the compiler-contract rules as errors, plus `no-raw-text`
@@ -65,27 +74,21 @@ const createConfig = (name, ruleSeverities) => ({
65
74
  * `enforce-adapter-import` is opt-in: importing from the original package is
66
75
  * perfectly valid once the bundler alias is set up, so a project that has done
67
76
  * that should not be nagged. Only `strict` turns it on.
77
+ *
78
+ * `no-unused-content` is in no preset at all, `strict` included. It reads the
79
+ * Intlayer configuration and walks the project's source files from disk, which
80
+ * is a different order of cost from the AST-only rules and depends on build
81
+ * output for its duplicate report — spreading a preset should never quietly
82
+ * turn that on.
68
83
  */
69
84
  const configs = {
70
- recommended: {
71
- name: `${PLUGIN_NAME}/recommended`,
72
- plugins: { [PLUGIN_NAME]: plugin },
73
- rules: (0, _intlayer_config_lint.getRecommendedLintRules)()
74
- },
75
- strict: {
76
- ...createConfig("strict", {
77
- "no-raw-text": "error",
78
- "static-dictionary-key": "error",
79
- "no-dynamic-field-access": "error",
80
- "enforce-adapter-import": "error"
81
- }),
82
- rules: {
83
- "intlayer/no-raw-text": ["error", { includeStringLiterals: true }],
84
- "intlayer/static-dictionary-key": "error",
85
- "intlayer/no-dynamic-field-access": "error",
86
- "intlayer/enforce-adapter-import": "error"
87
- }
88
- },
85
+ recommended: createConfig("recommended", require_preset.RECOMMENDED_RULES),
86
+ strict: createConfig("strict", {
87
+ "no-raw-text": ["error", { includeStringLiterals: true }],
88
+ "static-dictionary-key": "error",
89
+ "no-dynamic-field-access": "error",
90
+ "enforce-adapter-import": "error"
91
+ }),
89
92
  "contract-only": createConfig("contract-only", {
90
93
  "static-dictionary-key": "error",
91
94
  "no-dynamic-field-access": "error"
@@ -94,12 +97,21 @@ const configs = {
94
97
  const intlayerPlugin = {
95
98
  meta,
96
99
  rules,
97
- configs
100
+ configs,
101
+ *[Symbol.iterator]() {
102
+ yield* configs.recommended;
103
+ }
98
104
  };
99
105
 
100
106
  //#endregion
107
+ exports.GENERATED_OUTPUT_GLOBS = require_preset.GENERATED_OUTPUT_GLOBS;
108
+ exports.PLUGIN_NAMESPACE = require_preset.PLUGIN_NAMESPACE;
109
+ exports.PLUGIN_PACKAGE_NAME = require_preset.PLUGIN_PACKAGE_NAME;
110
+ exports.RECOMMENDED_RULES = require_preset.RECOMMENDED_RULES;
101
111
  exports.configs = configs;
102
112
  exports.default = intlayerPlugin;
113
+ exports.getReactVersion = require_utils_reactVersion.getReactVersion;
114
+ exports.getRecommendedRules = require_preset.getRecommendedRules;
103
115
  exports.meta = meta;
104
116
  exports.rules = rules;
105
117
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["noRawText","staticDictionaryKey","noDynamicFieldAccess","enforceAdapterImport","getRecommendedLintRules"],"sources":["../../src/index.ts"],"sourcesContent":["import { getRecommendedLintRules } from '@intlayer/config/lint';\nimport type { ESLint, Linter } from 'eslint';\nimport { enforceAdapterImport } from './rules/enforceAdapterImport';\nimport { noDynamicFieldAccess } from './rules/noDynamicFieldAccess';\nimport { noRawText } from './rules/noRawText';\nimport { staticDictionaryKey } from './rules/staticDictionaryKey';\n\nconst PLUGIN_NAME = 'intlayer';\n\n/**\n * Every rule this plugin ships, keyed by the name used in configuration.\n *\n * The rules split into two families:\n * - **Coverage** (`no-raw-text`) — user-facing copy that never reached a\n * dictionary. Nothing else in the toolchain reports it.\n * - **Compiler contract** (`static-dictionary-key`, `no-dynamic-field-access`)\n * — call shapes that type-check and run, but that the Babel/SWC passes cannot\n * analyse, so they silently lose the optimize pass or read a purged field.\n *\n * Deliberately absent: unknown dictionary keys, unknown field paths and missing\n * required locales. The generated `__DictionaryRegistry` module augmentation\n * already makes all three compile errors, and `@intlayer/lsp` reports the first\n * as an editor diagnostic — a third report would only add noise.\n */\nexport const rules = {\n 'no-raw-text': noRawText,\n 'static-dictionary-key': staticDictionaryKey,\n 'no-dynamic-field-access': noDynamicFieldAccess,\n 'enforce-adapter-import': enforceAdapterImport,\n} satisfies ESLint.Plugin['rules'];\n\n/** Rule names this plugin exposes. */\nexport type IntlayerRuleName = keyof typeof rules;\n\nconst meta: { name: string; version: string } = {\n name: 'eslint-plugin-intlayer',\n version: '9.3.1',\n};\n\nconst plugin: ESLint.Plugin = { meta, rules };\n\n/** The flat configs this plugin ships. */\nexport type IntlayerConfigName = 'recommended' | 'strict' | 'contract-only';\n\n/**\n * Build a flat config enabling the given rules under the `intlayer/` prefix.\n *\n * @param name - Config name, surfaced by `--print-config` and the config inspector.\n * @param ruleSeverities - Severity per rule name.\n */\nconst createConfig = (\n name: string,\n ruleSeverities: Partial<Record<IntlayerRuleName, Linter.RuleSeverity>>\n): Linter.Config => ({\n name: `intlayer/${name}`,\n plugins: { [PLUGIN_NAME]: plugin },\n rules: Object.fromEntries(\n Object.entries(ruleSeverities).map(([ruleName, severity]) => [\n `${PLUGIN_NAME}/${ruleName}`,\n severity,\n ])\n ),\n});\n\n/**\n * Flat configs, for `eslint.config.js`:\n *\n * ```js\n * import intlayer from 'eslint-plugin-intlayer';\n *\n * export default [intlayer.configs.recommended];\n * ```\n *\n * - `recommended` — the compiler-contract rules as errors, plus `no-raw-text`\n * as a warning so an existing codebase is not blocked on day one.\n * - `strict` — everything as an error, including string literals outside JSX.\n * - `contract-only` — just the rules that protect the build output; use this\n * when translation coverage is tracked by `intlayer test` instead.\n *\n * `enforce-adapter-import` is opt-in: importing from the original package is\n * perfectly valid once the bundler alias is set up, so a project that has done\n * that should not be nagged. Only `strict` turns it on.\n */\nexport const configs: Record<IntlayerConfigName, Linter.Config> = {\n // Built from the shared preset in `@intlayer/config/lint`, which `intlayer\n // init` also writes into `.oxlintrc.json` — oxlint resolves a plugin's rules\n // but not its configs, so the two must not drift.\n recommended: {\n name: `${PLUGIN_NAME}/recommended`,\n plugins: { [PLUGIN_NAME]: plugin },\n rules: getRecommendedLintRules() as Linter.Config['rules'],\n },\n strict: {\n ...createConfig('strict', {\n 'no-raw-text': 'error',\n 'static-dictionary-key': 'error',\n 'no-dynamic-field-access': 'error',\n 'enforce-adapter-import': 'error',\n }),\n rules: {\n 'intlayer/no-raw-text': ['error', { includeStringLiterals: true }],\n 'intlayer/static-dictionary-key': 'error',\n 'intlayer/no-dynamic-field-access': 'error',\n 'intlayer/enforce-adapter-import': 'error',\n },\n },\n 'contract-only': createConfig('contract-only', {\n 'static-dictionary-key': 'error',\n 'no-dynamic-field-access': 'error',\n }),\n};\n\nexport type { NoRawTextOptions } from './rules/noRawText';\nexport { meta };\n\n/** The plugin object, as consumed by `eslint.config.js`. */\nexport type IntlayerPlugin = ESLint.Plugin & {\n meta: { name: string; version: string };\n rules: typeof rules;\n configs: Record<IntlayerConfigName, Linter.Config>;\n};\n\nconst intlayerPlugin: IntlayerPlugin = { meta, rules, configs };\n\nexport default intlayerPlugin;\n"],"mappings":";;;;;;;;AAOA,MAAM,cAAc;;;;;;;;;;;;;;;;AAiBpB,MAAa,QAAQ;CACnB,eAAeA;CACf,yBAAyBC;CACzB,2BAA2BC;CAC3B,0BAA0BC;AAC5B;AAKA,MAAM,OAA0C;CAC9C,MAAM;CACN,SAAS;AACX;AAEA,MAAM,SAAwB;CAAE;CAAM;AAAM;;;;;;;AAW5C,MAAM,gBACJ,MACA,oBACmB;CACnB,MAAM,YAAY;CAClB,SAAS,GAAG,cAAc,OAAO;CACjC,OAAO,OAAO,YACZ,OAAO,QAAQ,cAAc,CAAC,CAAC,KAAK,CAAC,UAAU,cAAc,CAC3D,GAAG,YAAY,GAAG,YAClB,QACF,CAAC,CACH;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,UAAqD;CAIhE,aAAa;EACX,MAAM,GAAG,YAAY;EACrB,SAAS,GAAG,cAAc,OAAO;EACjC,WAAOC,+CAAwB;CACjC;CACA,QAAQ;EACN,GAAG,aAAa,UAAU;GACxB,eAAe;GACf,yBAAyB;GACzB,2BAA2B;GAC3B,0BAA0B;EAC5B,CAAC;EACD,OAAO;GACL,wBAAwB,CAAC,SAAS,EAAE,uBAAuB,KAAK,CAAC;GACjE,kCAAkC;GAClC,oCAAoC;GACpC,mCAAmC;EACrC;CACF;CACA,iBAAiB,aAAa,iBAAiB;EAC7C,yBAAyB;EACzB,2BAA2B;CAC7B,CAAC;AACH;AAYA,MAAM,iBAAiC;CAAE;CAAM;CAAO;AAAQ"}
1
+ {"version":3,"file":"index.cjs","names":["noRawText","staticDictionaryKey","noDynamicFieldAccess","enforceAdapterImport","noUnusedContent","GENERATED_OUTPUT_GLOBS","PLUGIN_NAMESPACE","getReactVersion","RECOMMENDED_RULES"],"sources":["../../src/index.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport {\n GENERATED_OUTPUT_GLOBS,\n PLUGIN_NAMESPACE,\n RECOMMENDED_RULES,\n} from './preset';\nimport { enforceAdapterImport } from './rules/enforceAdapterImport';\nimport { noDynamicFieldAccess } from './rules/noDynamicFieldAccess';\nimport { noRawText } from './rules/noRawText';\nimport { noUnusedContent } from './rules/noUnusedContent';\nimport { staticDictionaryKey } from './rules/staticDictionaryKey';\nimport { getReactVersion } from './utils/reactVersion';\n\n/**\n * Every rule this plugin ships, keyed by the name used in configuration.\n *\n * The rules split into three families:\n * - **Coverage** (`no-raw-text`) — user-facing copy that never reached a\n * dictionary. Nothing else in the toolchain reports it.\n * - **Compiler contract** (`static-dictionary-key`, `no-dynamic-field-access`)\n * — call shapes that type-check and run, but that the Babel/SWC passes cannot\n * analyse, so they silently lose the optimize pass or read a purged field.\n * - **Dead content** (`no-unused-content`) — dictionaries and fields nothing\n * reads. Off in every preset: it is the only rule that scans the whole\n * project from disk, so enabling it is a cost the user should opt into.\n *\n * Deliberately absent: unknown dictionary keys, unknown field paths and missing\n * required locales. The generated `__DictionaryRegistry` module augmentation\n * already makes all three compile errors, and `@intlayer/lsp` reports the first\n * as an editor diagnostic — a third report would only add noise.\n */\nexport const rules = {\n 'no-raw-text': noRawText,\n 'static-dictionary-key': staticDictionaryKey,\n 'no-dynamic-field-access': noDynamicFieldAccess,\n 'enforce-adapter-import': enforceAdapterImport,\n 'no-unused-content': noUnusedContent,\n} satisfies ESLint.Plugin['rules'];\n\n/** Rule names this plugin exposes. */\nexport type IntlayerRuleName = keyof typeof rules;\n\nconst meta: { name: string; version: string } = {\n name: 'eslint-plugin-intlayer',\n version: '9.3.1',\n};\n\nconst plugin: ESLint.Plugin = { meta, rules };\n\n/** The flat configs this plugin ships. */\nexport type IntlayerConfigName = 'recommended' | 'strict' | 'contract-only';\n\n/**\n * Build a flat config enabling the given rules under the `intlayer/` prefix.\n *\n * @param name - Config name, surfaced by `--print-config` and the config inspector.\n * @param ruleSeverities - Severity per rule name.\n */\nconst createConfig = (\n name: string,\n ruleEntries: Partial<Record<IntlayerRuleName, Linter.RuleEntry>>\n): Linter.Config[] => [\n // A config carrying only `ignores` is a *global* ignore in flat config, so\n // this keeps ESLint away from generated output wherever the preset is spread.\n {\n name: `intlayer/${name}/ignores`,\n ignores: GENERATED_OUTPUT_GLOBS,\n },\n {\n name: `intlayer/${name}`,\n plugins: { [PLUGIN_NAMESPACE]: plugin },\n settings: {\n react: {\n version: getReactVersion(),\n },\n },\n rules: Object.fromEntries(\n Object.entries(ruleEntries).map(([ruleName, entry]) => [\n `${PLUGIN_NAMESPACE}/${ruleName}`,\n entry,\n ])\n ),\n },\n];\n\n/**\n * Flat configs, for `eslint.config.js`:\n *\n * ```js\n * import intlayer from 'eslint-plugin-intlayer';\n *\n * export default [...intlayer]; // or [...intlayer.configs.recommended]\n * ```\n *\n * - `recommended` — the compiler-contract rules as errors, plus `no-raw-text`\n * as a warning so an existing codebase is not blocked on day one.\n * - `strict` — everything as an error, including string literals outside JSX.\n * - `contract-only` — just the rules that protect the build output; use this\n * when translation coverage is tracked by `intlayer test` instead.\n *\n * `enforce-adapter-import` is opt-in: importing from the original package is\n * perfectly valid once the bundler alias is set up, so a project that has done\n * that should not be nagged. Only `strict` turns it on.\n *\n * `no-unused-content` is in no preset at all, `strict` included. It reads the\n * Intlayer configuration and walks the project's source files from disk, which\n * is a different order of cost from the AST-only rules and depends on build\n * output for its duplicate report — spreading a preset should never quietly\n * turn that on.\n */\nexport const configs: Record<IntlayerConfigName, Linter.Config[]> = {\n recommended: createConfig('recommended', RECOMMENDED_RULES),\n strict: createConfig('strict', {\n 'no-raw-text': ['error', { includeStringLiterals: true }],\n 'static-dictionary-key': 'error',\n 'no-dynamic-field-access': 'error',\n 'enforce-adapter-import': 'error',\n }),\n 'contract-only': createConfig('contract-only', {\n 'static-dictionary-key': 'error',\n 'no-dynamic-field-access': 'error',\n }),\n};\n\nexport {\n GENERATED_OUTPUT_GLOBS,\n getRecommendedRules,\n PLUGIN_NAMESPACE,\n PLUGIN_PACKAGE_NAME,\n RECOMMENDED_RULES,\n} from './preset';\nexport type { NoRawTextOptions } from './rules/noRawText';\nexport type { NoUnusedContentOptions } from './rules/noUnusedContent';\nexport { getReactVersion } from './utils/reactVersion';\nexport { meta };\n\n/** The plugin object, as consumed by `eslint.config.js`. */\nexport type IntlayerPlugin = ESLint.Plugin &\n Iterable<Linter.Config> & {\n meta: { name: string; version: string };\n rules: typeof rules;\n configs: Record<IntlayerConfigName, Linter.Config[]>;\n };\n\nconst intlayerPlugin: IntlayerPlugin = {\n meta,\n rules,\n configs,\n *[Symbol.iterator]() {\n yield* configs.recommended;\n },\n};\n\nexport default intlayerPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,QAAQ;CACnB,eAAeA;CACf,yBAAyBC;CACzB,2BAA2BC;CAC3B,0BAA0BC;CAC1B,qBAAqBC;AACvB;AAKA,MAAM,OAA0C;CAC9C,MAAM;CACN,SAAS;AACX;AAEA,MAAM,SAAwB;CAAE;CAAM;AAAM;;;;;;;AAW5C,MAAM,gBACJ,MACA,gBACoB,CAGpB;CACE,MAAM,YAAY,KAAK;CACvB,SAASC;AACX,GACA;CACE,MAAM,YAAY;CAClB,SAAS,GAAGC,kCAAmB,OAAO;CACtC,UAAU,EACR,OAAO,EACL,SAASC,2CAAgB,EAC3B,EACF;CACA,OAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,CAAC,KAAK,CAAC,UAAU,WAAW,CACrD,GAAGD,gCAAiB,GAAG,YACvB,KACF,CAAC,CACH;AACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,UAAuD;CAClE,aAAa,aAAa,eAAeE,gCAAiB;CAC1D,QAAQ,aAAa,UAAU;EAC7B,eAAe,CAAC,SAAS,EAAE,uBAAuB,KAAK,CAAC;EACxD,yBAAyB;EACzB,2BAA2B;EAC3B,0BAA0B;CAC5B,CAAC;CACD,iBAAiB,aAAa,iBAAiB;EAC7C,yBAAyB;EACzB,2BAA2B;CAC7B,CAAC;AACH;AAsBA,MAAM,iBAAiC;CACrC;CACA;CACA;CACA,EAAE,OAAO,YAAY;EACnB,OAAO,QAAQ;CACjB;AACF"}
@@ -0,0 +1,53 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region src/preset.ts
3
+ /** npm package name of this plugin. */
4
+ const PLUGIN_PACKAGE_NAME = "eslint-plugin-intlayer";
5
+ /** Prefix the rules are registered under, e.g. `intlayer/no-raw-text`. */
6
+ const PLUGIN_NAMESPACE = "intlayer";
7
+ /**
8
+ * Intlayer's generated output, which every preset ignores.
9
+ *
10
+ * `.intlayer/` holds compiled dictionaries and generated types — machine-written
11
+ * files that are never source. Linting them is at best wasted work and at worst
12
+ * a hard failure, since third-party rules can crash on generated shapes they
13
+ * were never meant to see. The rest of the toolchain already excludes this
14
+ * directory (`content.watchExclude`, the build globs), so the lint preset
15
+ * matches.
16
+ */
17
+ const GENERATED_OUTPUT_GLOBS = ["**/.intlayer/**"];
18
+ /**
19
+ * Rules enabled by the `recommended` preset, keyed by unprefixed rule name.
20
+ *
21
+ * Exported because oxlint resolves a plugin's *rules* but not its shipped
22
+ * *configs*: `intlayer init` reads this to spell the preset out explicitly in
23
+ * `.oxlintrc.json`. Keeping the definition next to the rules it names means
24
+ * enabling one by default is a single-line change.
25
+ *
26
+ * `no-raw-text` stays a warning on purpose — pointed at an existing codebase it
27
+ * surfaces every untranslated string at once, which should not break a build on
28
+ * day one. `enforce-adapter-import` is deliberately absent: importing from the
29
+ * original package is valid once the bundler alias is configured, so it is
30
+ * opt-in through the `strict` preset. `no-unused-content` is absent from every
31
+ * preset — it scans the project from disk, which no preset should impose.
32
+ */
33
+ const RECOMMENDED_RULES = {
34
+ "no-raw-text": "warn",
35
+ "static-dictionary-key": "error",
36
+ "no-dynamic-field-access": "error"
37
+ };
38
+ /**
39
+ * The `recommended` preset with every rule name prefixed by the plugin
40
+ * namespace — the shape both ESLint flat config and `.oxlintrc.json` expect.
41
+ *
42
+ * `intlayer init` uses this to write the preset into `.oxlintrc.json`, since
43
+ * oxlint resolves a plugin's rules but not its configs.
44
+ */
45
+ const getRecommendedRules = () => Object.fromEntries(Object.entries(RECOMMENDED_RULES).map(([ruleName, entry]) => [`${PLUGIN_NAMESPACE}/${ruleName}`, entry]));
46
+
47
+ //#endregion
48
+ exports.GENERATED_OUTPUT_GLOBS = GENERATED_OUTPUT_GLOBS;
49
+ exports.PLUGIN_NAMESPACE = PLUGIN_NAMESPACE;
50
+ exports.PLUGIN_PACKAGE_NAME = PLUGIN_PACKAGE_NAME;
51
+ exports.RECOMMENDED_RULES = RECOMMENDED_RULES;
52
+ exports.getRecommendedRules = getRecommendedRules;
53
+ //# sourceMappingURL=preset.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset.cjs","names":[],"sources":["../../src/preset.ts"],"sourcesContent":["import type { Linter } from 'eslint';\nimport type { IntlayerRuleName } from './index';\n\n/** npm package name of this plugin. */\nexport const PLUGIN_PACKAGE_NAME = 'eslint-plugin-intlayer';\n\n/** Prefix the rules are registered under, e.g. `intlayer/no-raw-text`. */\nexport const PLUGIN_NAMESPACE = 'intlayer';\n\n/**\n * Intlayer's generated output, which every preset ignores.\n *\n * `.intlayer/` holds compiled dictionaries and generated types — machine-written\n * files that are never source. Linting them is at best wasted work and at worst\n * a hard failure, since third-party rules can crash on generated shapes they\n * were never meant to see. The rest of the toolchain already excludes this\n * directory (`content.watchExclude`, the build globs), so the lint preset\n * matches.\n */\nexport const GENERATED_OUTPUT_GLOBS = ['**/.intlayer/**'];\n\n/**\n * Rules enabled by the `recommended` preset, keyed by unprefixed rule name.\n *\n * Exported because oxlint resolves a plugin's *rules* but not its shipped\n * *configs*: `intlayer init` reads this to spell the preset out explicitly in\n * `.oxlintrc.json`. Keeping the definition next to the rules it names means\n * enabling one by default is a single-line change.\n *\n * `no-raw-text` stays a warning on purpose — pointed at an existing codebase it\n * surfaces every untranslated string at once, which should not break a build on\n * day one. `enforce-adapter-import` is deliberately absent: importing from the\n * original package is valid once the bundler alias is configured, so it is\n * opt-in through the `strict` preset. `no-unused-content` is absent from every\n * preset — it scans the project from disk, which no preset should impose.\n */\nexport const RECOMMENDED_RULES: Partial<\n Record<IntlayerRuleName, Linter.RuleEntry>\n> = {\n 'no-raw-text': 'warn',\n 'static-dictionary-key': 'error',\n 'no-dynamic-field-access': 'error',\n};\n\n/**\n * The `recommended` preset with every rule name prefixed by the plugin\n * namespace — the shape both ESLint flat config and `.oxlintrc.json` expect.\n *\n * `intlayer init` uses this to write the preset into `.oxlintrc.json`, since\n * oxlint resolves a plugin's rules but not its configs.\n */\nexport const getRecommendedRules = (): Record<string, Linter.RuleEntry> =>\n Object.fromEntries(\n Object.entries(RECOMMENDED_RULES).map(([ruleName, entry]) => [\n `${PLUGIN_NAMESPACE}/${ruleName}`,\n entry,\n ])\n );\n"],"mappings":";;;AAIA,MAAa,sBAAsB;;AAGnC,MAAa,mBAAmB;;;;;;;;;;;AAYhC,MAAa,yBAAyB,CAAC,iBAAiB;;;;;;;;;;;;;;;;AAiBxD,MAAa,oBAET;CACF,eAAe;CACf,yBAAyB;CACzB,2BAA2B;AAC7B;;;;;;;;AASA,MAAa,4BACX,OAAO,YACL,OAAO,QAAQ,iBAAiB,CAAC,CAAC,KAAK,CAAC,UAAU,WAAW,CAC3D,GAAG,iBAAiB,GAAG,YACvB,KACF,CAAC,CACH"}
@@ -6,20 +6,27 @@ let _intlayer_config_callers = require("@intlayer/config/callers");
6
6
  //#region src/rules/enforceAdapterImport.ts
7
7
  const ADAPTER_SCOPE = "@intlayer/";
8
8
  /**
9
- * Original library specifier → the `@intlayer/*` adapter that replaces it.
9
+ * Original third-party library specifier → the `@intlayer/*` adapter that
10
+ * replaces it, e.g. `next-intl` → `@intlayer/next-intl`.
10
11
  *
11
12
  * Derived from the shared registry rather than hard-coded: every compat
12
13
  * descriptor lists both its original specifiers and their adapter equivalents in
13
14
  * `importSources`, so a library added to `@intlayer/config/callers` is covered
14
15
  * here for free.
15
16
  *
17
+ * Built from `COMPAT_CALLERS` only, never `ALL_CALLERS`. Intlayer's own packages
18
+ * are not a migration pair: `BASE_CALLERS` lists `intlayer` alongside
19
+ * `@intlayer/core` because `getIntlayer` is importable from both, and treating
20
+ * the scoped one as an "adapter" would tell users to rewrite ordinary
21
+ * `from 'intlayer'` imports.
22
+ *
16
23
  * Sub-paths line up by position within a descriptor — `next-intl/server` and
17
24
  * `@intlayer/next-intl/server` are listed together — so the mapping is built by
18
25
  * matching each original against the adapter that ends with the same tail.
19
26
  */
20
27
  const buildAdapterMap = () => {
21
28
  const adapterBySpecifier = /* @__PURE__ */ new Map();
22
- for (const descriptor of _intlayer_config_callers.ALL_CALLERS) {
29
+ for (const descriptor of _intlayer_config_callers.COMPAT_CALLERS) {
23
30
  const adapters = descriptor.importSources.filter((source) => source.startsWith(ADAPTER_SCOPE));
24
31
  if (adapters.length === 0) continue;
25
32
  const originals = descriptor.importSources.filter((source) => !source.startsWith(ADAPTER_SCOPE));