@@ -18,24 +18,17 @@ Provide an ESLint plugin for Lingui that:
1818## 2. Environment and General Constraints
1919
20201 . ** Runtime**
21-
2221 * MUST support Node.js ≥ 18.
23222 . ** ESLint**
24-
2523 * MUST support ESLint v9 with Flat Config.
2624 * SHOULD support legacy ` .eslintrc ` configuration (optional).
27253 . ** Language and Tooling**
28-
2926 * Plugin implementation MUST be in TypeScript.
3027 * TypeScript compiler options MUST enable ` strict ` mode.
31284 . ** AST / Parser**
32-
33- * The plugin MUST support:
34-
35- * ` .js ` , ` .jsx ` , ` .ts ` , ` .tsx ` .
29+ * The plugin MUST support: ` .js ` , ` .jsx ` , ` .ts ` , ` .tsx ` .
3630 * The plugin MUST work with ` @typescript-eslint/parser ` and use ` parserServices ` when available.
37315 . ** Performance and Stability**
38-
3932 * Rules MUST NOT crash when ` parserServices.program ` or ` typeChecker ` are missing.
4033 * In absence of type information, rules MUST degrade gracefully (e.g. skip TS-dependent checks) without throwing exceptions.
4134
@@ -44,22 +37,17 @@ Provide an ESLint plugin for Lingui that:
4437## 3. Concepts and Definitions
4538
46391 . ** Lingui Macros / APIs**
47-
4840 * Tagged template macro: ` t\ ` ...``.
4941 * Message helpers: ` msg({ message: "..." }) ` , ` defineMessage({ message: "..." }) ` (exact naming configurable).
5042 * JSX component: ` <Trans>...</Trans> ` .
5143 * Function-style API examples: ` i18n._(msg\ ` ...` ) ` , ` i18n.t("...") ` (exact set configurable).
52442 . ** Message Context**
53-
54- * A “message” is any string or template used in a Lingui macro or API call, e.g. ` t\ ` ...``, ` <Trans>...</Trans> ` , ` msg({ message: "..." }) ` .
45+ * A "message" is any string or template used in a Lingui macro or API call, e.g. ` t\ ` ...``, ` <Trans>...</Trans> ` , ` msg({ message: "..." }) ` .
55463 . ** Host Expressions**
56-
5747 * Expressions inside a message, e.g.:
58-
5948 * Interpolations in template literals: ` t\ ` Hello ${expr}``.
6049 * JSX expressions: ` <Trans>Hello {expr}</Trans> ` .
61504 . ** Technical vs. UI Strings**
62-
6351 * ** UI string** : text intended for end-users, e.g. button labels, error messages.
6452 * ** Technical string** : text used only as identifiers, discriminators or internal keys (e.g. union-type literals, Redux action types).
6553
@@ -68,7 +56,6 @@ Provide an ESLint plugin for Lingui that:
6856## 4. Plugin Export Structure
6957
70581 . The plugin MUST export a default object (ES module) with:
71-
7259 * ` rules: Record<string, RuleModule> ` .
7360 * ` configs: { "flat/recommended": FlatConfig[] } ` (array or single config, depending on ESLint v9 conventions).
74612 . Flat Config usage MUST be possible like:
@@ -104,7 +91,7 @@ The plugin MUST provide at least the following rules (names can be exactly as be
10491Each rule MUST:
10592
10693* Be implemented as an ESLint rule module (with ` meta ` and ` create ` ).
107- * Provide ` meta.docs.description ` , ` meta.type ` (“ problem” or “ suggestion” ), and ` meta.schema ` .
94+ * Provide ` meta.docs.description ` , ` meta.type ` (" problem" or " suggestion" ), and ` meta.schema ` .
10895* Have test coverage as specified in Section 8.
10996
11097---
@@ -124,32 +111,23 @@ Each rule MUST:
124111** Behavior:**
125112
1261131 . For each message, inspect all embedded expressions:
127-
128114 * Template interpolation (` TemplateLiteral.expressions ` ).
129115 * JSX expressions (` JSXExpressionContainer ` inside ` <Trans> ` ).
1301162 . The following expressions MUST be allowed:
131-
132117 * Simple identifiers:
133-
134118 * ` Identifier ` : ` name ` , ` count ` .
135119 * Optionally (configurable) simple member expressions of depth 1:
136-
137120 * ` MemberExpression ` where object is ` Identifier ` and property is ` Identifier ` (e.g. ` props.name ` ).
138121 * Whitelisted Lingui helper calls, for example:
139-
140122 * ` i18n.number(value) `
141123 * ` i18n.date(value) `
142124 * Plural/select helpers (exact names configurable).
1431253 . The following expressions MUST be reported as violations:
144-
145126 * Function calls not in the allowed helper list:
146-
147127 * ` CallExpression ` where callee is not whitelisted, e.g. ` Math.random() ` , ` formatDate(date) ` .
148128 * Deep member expressions (depth > 1 or optional chains), for example:
149-
150129 * ` user.address.street ` , ` a?.b?.c ` .
151130 * Binary/arithmetical expressions:
152-
153131 * ` price * 1.2 ` , ` items.join(", ") ` .
154132 * Any other expression type not explicitly allowed.
1551334 . Each offending expression MUST produce a separate ESLint report.
@@ -158,7 +136,6 @@ Each rule MUST:
158136
159137* Default export name: ` "lingui/no-complex-expressions-in-message": "error" ` in recommended config.
160138* Options object fields (optional):
161-
162139 * ` allowedCallees: string[] `
163140 Format: dot-separated string, e.g. ` "i18n.number" ` , ` "i18n.date" ` .
164141 * ` allowMemberExpressions: boolean `
@@ -175,22 +152,18 @@ Each rule MUST:
175152** Scope:**
176153
177154* Any configured Lingui macro or JSX component:
178-
179155 * ` t\ ` ...``, ` <Trans>...</Trans> ` , ` msg ` , ` defineMessage ` , etc.
180156
181157** Behavior:**
182158
1831591 . For every usage of a Lingui macro:
184-
185160 * ` t\ ` ...``
186161 * ` <Trans>...</Trans> `
187162 * ` msg({ message: ... }) ` , ` defineMessage({ message: ... }) `
188163 * Any additional configured macro/component.
1891642 . Within the message content or children:
190-
191165 * If any * other* Lingui macro usage is found * inside* the current macro, this MUST be reported.
1921663 . The simplest form of nesting MUST be detected:
193-
194167 * ` t\ ` foo ${t` bar ` }``.
195168 * ` <Trans><Trans>Inner</Trans></Trans> ` .
196169 * ` <Trans>{t\ ` Hello` }</Trans> ` .
@@ -200,7 +173,6 @@ Each rule MUST:
200173
201174* ` macros: string[] ` (default: ` ["t", "Trans", "msg", "defineMessage"] ` ).
202175* ` allowDifferentMacros: boolean ` :
203-
204176 * If ` false ` : any macro inside any macro is forbidden.
205177 * If ` true ` : only identical macro nesting is forbidden (e.g. ` t ` inside ` t ` is error; ` t ` inside ` Trans ` is allowed).
206178
@@ -219,15 +191,12 @@ Each rule MUST:
219191** Behavior:**
220192
2211931 . A message MUST be reported if:
222-
223194 * It contains exactly one placeholder expression, and
224195 * It contains no plain text (no literal text nodes or raw text segments).
2251962 . Examples that MUST be reported:
226-
227197 * ` t\ ` ${status}``.
228198 * ` <Trans>{label}</Trans> ` .
2291993 . Messages that have additional text or structure MUST NOT be reported:
230-
231200 * ` t\ ` Status: ${status}``.
232201 * ` <Trans>Hello {name}</Trans> ` .
233202
@@ -246,14 +215,11 @@ Each rule MUST:
246215** Behavior:**
247216
2482171 . ` <Trans> ` MUST be reported if:
249-
250218 * It has exactly one child ` JSXElement ` or ` JSXFragment ` , and
251219 * There is no accompanying text.
2522202 . Examples that MUST be reported:
253-
254221 * ` <Trans><a href="/terms">Terms</a></Trans> ` .
2552223 . Non-violations:
256-
257223 * ` <Trans>Read <a href="/terms">terms</a></Trans> ` .
258224 * ` <Trans><strong>Important:</strong> {message}</Trans> ` when there is text around.
259225
@@ -275,19 +241,16 @@ Each rule MUST:
275241** Behavior:**
276242
2772431 . The rule MUST report ` t ` usage at top-level module scope:
278-
279244 * Example of violation:
280245
281246 ``` ts
282247 const msg = t ` Hello ` ;
283248 ```
2842492. The rule MUST allow ` t ` usage inside :
285-
286250 * Functions (including React components ).
287251 * Hooks .
288252 * Event handlers and callbacks .
2892533. The rule MUST be configurable to :
290-
291254 * Optionally allow ` t ` inside certain patterns if the user configures them .
292255
293256** Configuration (Options ):**
@@ -304,21 +267,17 @@ Each rule MUST:
304267**Scope:**
305268
306269* String literals and template literals in:
307-
308270 * JavaScript / TypeScript code.
309271 * JSX text and `JSXAttribute` values.
310272
311273**Core Behavior (Language -agnostic ): **
312274
3132751. The rule MUST identify candidate strings that appear to be UI strings :
314-
315276 * E .g . ` "Save changes" ` , ` "Error loading data" ` , plain JSX text ` Something went wrong ` .
3162772. The rule MUST NOT report strings that :
317-
318278 * Are already inside Lingui message contexts (` t ` , ` <Trans> ` , ` msg ` , ` defineMessage ` , etc .).
319279 * Match user -configured ignore patterns (e .g . for test IDs or technical keys ).
3202803. The rule MUST have configuration options to specify :
321-
322281 * Functions where string arguments should be ignored .
323282 * Variable names to ignore .
324283 * Property / attribute names to ignore .
@@ -328,7 +287,6 @@ Each rule MUST:
328287When TypeScript type information is available :
329288
3302894. The rule MUST NOT report string literals that are used purely as technical values based on string -literal types , such as :
331-
332290 * Union type definitions :
333291
334292 ` ` ` ts
@@ -363,27 +321,22 @@ When TypeScript type information is available:
3633215. Detection algorithm (TypeScript mode ):
364322
365323 For each string literal node ` S ` :
366-
367324 * Obtain its TypeScript type ` T ` via ` typeChecker.getTypeAtLocation(S) ` if ` parserServices.program ` exists .
368325 * If ` T ` is a string literal type or a union of string literals , and :
369-
370326 * ` S ` is used in one of the following contexts :
371-
372327 * Type alias declaration .
373328 * Typed initialization of state or discriminated union .
374329 * Property assignment for a ` type ` or ` kind ` field in a union .
375330 * THEN ` S ` MUST be treated as **technical ** and MUST NOT be reported .
376331
3773326. Fallback behavior if TypeScript info is missing :
378-
379333 * The rule MUST still run using syntactic heuristics only .
380334 * It MUST NOT throw and MUST NOT assume type information .
381335 * Implementation MAY skip TS -aware exclusions .
382336
383337**Configuration (Options ): **
384338
385339* ` tsAwareMode: "off" | "basic" | "strict" ` :
386-
387340 * ` "off" ` : ignore TypeScript types entirely (JS -only behavior ).
388341 * ` "basic" ` : use type info if available , otherwise heuristics .
389342 * ` "strict" ` (default for TS projects ): rely on TypeScript ` typeChecker ` where available .
@@ -409,10 +362,8 @@ When TypeScript type information is available:
409362**Behavior : **
410363
4113641. For each Lingui message string :
412-
413365 * Apply configured restrictions to the raw text , ignoring placeholders .
4143662. The following checks MUST be supported via configuration :
415-
416367 * Disallow specific characters or regex patterns (e .g . HTML entities , ` \n ` ).
417368 * Optionally enforce minimum length (e .g . disallow 1 -character messages ).
418369
@@ -434,7 +385,6 @@ When TypeScript type information is available:
434385**Behavior : **
435386
4363871. The rule MUST validate that whenever a plural helper is used :
437-
438388 * Required plural keys are present (e .g . at least ` other ` ).
439389 * Project -defined required keys are present (e .g . ` one ` , ` other ` , optionally ` zero ` ).
4403902. The rule MUST report missing required plural keys .
@@ -469,11 +419,9 @@ README MUST include:
469419
4704201. Tests MUST be written in TypeScript or JavaScript using a test runner (e .g . Jest or Vitest ).
4714212. Each rule MUST have its own test file (e .g . ` no-complex-expressions-in-message.test.ts ` ).
472- 3. Tests MUST use ESLint ’s ` RuleTester ` (or equivalent from ` @typescript-eslint/utils ` ) to define :
473-
422+ 3. Tests MUST use ESLint ' s `RuleTester` (or equivalent from `@typescript-eslint/utils`) to define:
474423 * ` valid ` cases where no errors are reported .
475424 * ` invalid ` cases where :
476-
477425 * The correct rule ID is reported .
478426 * Expected messages (or ` messageId ` ) are asserted .
479427 * Expected node locations are asserted (at least basic checks ).
@@ -484,12 +432,10 @@ README MUST include:
484432**For ` no-complex-expressions-in-message ` : **
485433
486434* Valid :
487-
488435 * ` t\` Hello ${name } ` ` .
489436 * ` <Trans >Hello {name }< / Trans > ` .
490437 * ` < Trans > Last login on {i18n .date (lastLogin )}< / Trans > ` (if ` i18n .date ` is whitelisted).
491438* Invalid:
492-
493439 * ` t \` Hello ${user .name } ` ` (if deep member not allowed).
494440 * ` < Trans > Hello {Math .random ()}< / Trans > ` .
495441 * ` < Trans > Price : {price * 1.2 }< / Trans > ` .
@@ -498,66 +444,54 @@ README MUST include:
498444**For ` no - nested - macros ` :**
499445
500446* Invalid:
501-
502447 * ` t \` foo ${t ` bar ` } ` ` .
503448 * ` < Trans > <Trans >Inner < / Trans > </Trans >` .
504449 * ` < Trans > {t\` Hello ` }< / Trans > ` .
505450* Valid:
506-
507451 * Distinct, non-nested uses of macros in separate nodes.
508452
509453**For ` no - single - variable - message ` :**
510454
511455* Invalid:
512-
513456 * ` t \` ${status } ` ` .
514457 * ` < Trans > {label }< / Trans > ` .
515458* Valid:
516-
517459 * ` t \` Status: ${status } ` ` .
518460 * ` < Trans > Hello {name }< / Trans > ` .
519461
520462**For ` no - single - tag - message ` :**
521463
522464* Invalid:
523-
524465 * ` < Trans > <a href =" /x" >Link < / a > </Trans >` .
525466* Valid:
526-
527467 * ` < Trans > Read < a href = " /x" > more < / a > </Trans >` .
528468
529469**For ` valid - t - call - location ` :**
530470
531471* Invalid:
532-
533- * Top-level ` const msg = t \` Hello ` ;` when ` allowedTopLevel ` is ` false ` .
472+ * Top-level ` const msg = t \` Hello ` ;` when ` allowedTopLevel ` is ` false ` .
534473* Valid:
535-
536474 * ` function Component() { const msg = t \` Hello ` ; }` .
537475
538476**For ` no - unlocalized - strings ` :**
539477
540478*JS/JSX:*
541479
542480* Invalid:
543-
544481 * ` const label = " Save changes" ;` (used as UI text).
545482 * ` < button > Save changes < / button > ` .
546483* Valid:
547-
548484 * Strings inside ` t ` , ` <Trans >` , etc.
549485 * Strings in ignored functions/variables/properties according to config.
550486
551487*TS-aware:*
552488
553489* Valid (must NOT report):
554-
555490 * ` type Status = " idle" | " loading" | " error" ;`
556491 * ` const [status, setStatus] = useState <Status >(" idle" );`
557492 * ` const action: Action = { type: " save" };` where ` Action ` is a discriminated union.
558493 * ` const ACTION_SAVE = " save" as const ;`
559494* Invalid (must report):
560-
561495 * ` const buttonLabel = " Save changes" ;` used in JSX.
562496* Tests MUST include a scenario where TypeScript ` program ` is not provided (e.g. missing ` parserOptions .project ` ) to ensure the rule does not crash.
563497
0 commit comments