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.
- package/README.md +103 -49
- package/dist/cjs/@intlayer/config/dist/esm/extract/constants.cjs +18 -0
- package/dist/cjs/@intlayer/config/dist/esm/extract/constants.cjs.map +1 -0
- package/dist/cjs/@intlayer/config/dist/esm/extract/shouldExtract.cjs +37 -0
- package/dist/cjs/@intlayer/config/dist/esm/extract/shouldExtract.cjs.map +1 -0
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +28 -0
- package/dist/cjs/index.cjs +41 -29
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/preset.cjs +53 -0
- package/dist/cjs/preset.cjs.map +1 -0
- package/dist/cjs/rules/enforceAdapterImport.cjs +9 -2
- package/dist/cjs/rules/enforceAdapterImport.cjs.map +1 -1
- package/dist/cjs/rules/noRawText.cjs +4 -3
- package/dist/cjs/rules/noRawText.cjs.map +1 -1
- package/dist/cjs/rules/noUnusedContent.cjs +143 -0
- package/dist/cjs/rules/noUnusedContent.cjs.map +1 -0
- package/dist/cjs/rules/staticDictionaryKey.cjs +2 -2
- package/dist/cjs/rules/staticDictionaryKey.cjs.map +1 -1
- package/dist/cjs/utils/contentDeclaration.cjs +100 -0
- package/dist/cjs/utils/contentDeclaration.cjs.map +1 -0
- package/dist/cjs/utils/projectUsage.cjs +437 -0
- package/dist/cjs/utils/projectUsage.cjs.map +1 -0
- package/dist/cjs/utils/reactVersion.cjs +50 -0
- package/dist/cjs/utils/reactVersion.cjs.map +1 -0
- package/dist/esm/@intlayer/config/dist/esm/extract/constants.mjs +18 -0
- package/dist/esm/@intlayer/config/dist/esm/extract/constants.mjs.map +1 -0
- package/dist/esm/@intlayer/config/dist/esm/extract/shouldExtract.mjs +37 -0
- package/dist/esm/@intlayer/config/dist/esm/extract/shouldExtract.mjs.map +1 -0
- package/dist/esm/_virtual/_rolldown/runtime.mjs +8 -0
- package/dist/esm/index.mjs +36 -30
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/preset.mjs +48 -0
- package/dist/esm/preset.mjs.map +1 -0
- package/dist/esm/rules/enforceAdapterImport.mjs +10 -3
- package/dist/esm/rules/enforceAdapterImport.mjs.map +1 -1
- package/dist/esm/rules/noRawText.mjs +2 -1
- package/dist/esm/rules/noRawText.mjs.map +1 -1
- package/dist/esm/rules/noUnusedContent.mjs +142 -0
- package/dist/esm/rules/noUnusedContent.mjs.map +1 -0
- package/dist/esm/rules/staticDictionaryKey.mjs +2 -2
- package/dist/esm/rules/staticDictionaryKey.mjs.map +1 -1
- package/dist/esm/utils/contentDeclaration.mjs +99 -0
- package/dist/esm/utils/contentDeclaration.mjs.map +1 -0
- package/dist/esm/utils/projectUsage.mjs +433 -0
- package/dist/esm/utils/projectUsage.mjs.map +1 -0
- package/dist/esm/utils/reactVersion.mjs +46 -0
- package/dist/esm/utils/reactVersion.mjs.map +1 -0
- package/dist/types/index.d.ts +19 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/node_modules/.bun/@types_estree@1.0.9/node_modules/@types/estree/index.d.ts +528 -0
- package/dist/types/node_modules/.bun/@types_estree@1.0.9/node_modules/@types/estree/index.d.ts.map +1 -0
- package/dist/types/preset.d.ts +45 -0
- package/dist/types/preset.d.ts.map +1 -0
- package/dist/types/rules/enforceAdapterImport.d.ts.map +1 -1
- package/dist/types/rules/noUnusedContent.d.ts +33 -0
- package/dist/types/rules/noUnusedContent.d.ts.map +1 -0
- package/dist/types/utils/ast.d.ts +1 -1
- package/dist/types/utils/contentDeclaration.d.ts +42 -0
- package/dist/types/utils/contentDeclaration.d.ts.map +1 -0
- package/dist/types/utils/projectUsage.d.ts +102 -0
- package/dist/types/utils/projectUsage.d.ts.map +1 -0
- package/dist/types/utils/reactVersion.d.ts +18 -0
- package/dist/types/utils/reactVersion.d.ts.map +1 -0
- 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
|
|
12
|
-
|
|
|
13
|
-
| Unknown dictionary key
|
|
14
|
-
| Unknown field path
|
|
15
|
-
| Missing required locale
|
|
16
|
-
| Unused dictionary
|
|
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
|
|
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
|
|
47
|
+
import intlayer from "eslint-plugin-intlayer";
|
|
48
48
|
|
|
49
49
|
export default [
|
|
50
50
|
{
|
|
51
51
|
plugins: { intlayer },
|
|
52
52
|
rules: {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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`
|
|
65
|
-
| --------------- |
|
|
66
|
-
| `recommended` | warn
|
|
67
|
-
| `strict` | error (+ non-JSX literals) | error
|
|
68
|
-
| `contract-only` | 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
|
|
84
|
-
|
|
|
85
|
-
| React, Preact, Solid, Lit
|
|
86
|
-
| Next.js
|
|
87
|
-
| Vue, Nuxt
|
|
88
|
-
| Svelte, SvelteKit
|
|
89
|
-
| Angular
|
|
90
|
-
| Angular templates
|
|
91
|
-
| Astro
|
|
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
|
|
106
|
-
import tseslint from
|
|
107
|
-
import vueParser from
|
|
108
|
-
import svelteParser from
|
|
109
|
-
import angularTemplateParser from
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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 =
|
|
236
|
+
const key = "home";
|
|
233
237
|
useIntlayer(key);
|
|
234
238
|
|
|
235
239
|
// ✓
|
|
236
|
-
useIntlayer(
|
|
237
|
-
useIntlayer(
|
|
238
|
-
getTranslations({ namespace:
|
|
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(
|
|
263
|
+
const content = useIntlayer("home");
|
|
260
264
|
content[fieldName];
|
|
261
265
|
|
|
262
|
-
const t = useTranslations(
|
|
266
|
+
const t = useTranslations("home");
|
|
263
267
|
t(messageKey);
|
|
264
268
|
|
|
265
269
|
// ✓
|
|
266
270
|
content.title;
|
|
267
|
-
content[
|
|
271
|
+
content["title"];
|
|
268
272
|
content.items[0];
|
|
269
|
-
t(
|
|
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
|
|
286
|
-
import { getTranslations } from
|
|
289
|
+
import { useTranslation } from "react-i18next";
|
|
290
|
+
import { getTranslations } from "next-intl/server";
|
|
287
291
|
|
|
288
292
|
// ✓
|
|
289
|
-
import { useTranslation } from
|
|
290
|
-
import { getTranslations } from
|
|
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;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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,
|
|
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: { [
|
|
48
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["noRawText","staticDictionaryKey","noDynamicFieldAccess","enforceAdapterImport","
|
|
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
|
|
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.
|
|
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));
|