Skip to content

Commit cb853e7

Browse files
committed
fix: optimized formatting
1 parent 8625b95 commit cb853e7

File tree

1 file changed

+5
-71
lines changed

1 file changed

+5
-71
lines changed

docs/spec-sheet.md

Lines changed: 5 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,17 @@ Provide an ESLint plugin for Lingui that:
1818
## 2. Environment and General Constraints
1919

2020
1. **Runtime**
21-
2221
* MUST support Node.js ≥ 18.
2322
2. **ESLint**
24-
2523
* MUST support ESLint v9 with Flat Config.
2624
* SHOULD support legacy `.eslintrc` configuration (optional).
2725
3. **Language and Tooling**
28-
2926
* Plugin implementation MUST be in TypeScript.
3027
* TypeScript compiler options MUST enable `strict` mode.
3128
4. **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.
3731
5. **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

4639
1. **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).
5244
2. **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: "..." })`.
5546
3. **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>`.
6150
4. **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

7058
1. 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).
7461
2. 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
10491
Each 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

126113
1. For each message, inspect all embedded expressions:
127-
128114
* Template interpolation (`TemplateLiteral.expressions`).
129115
* JSX expressions (`JSXExpressionContainer` inside `<Trans>`).
130116
2. 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).
143125
3. 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.
155133
4. 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

183159
1. For every usage of a Lingui macro:
184-
185160
* `t\`...``
186161
* `<Trans>...</Trans>`
187162
* `msg({ message: ... })`, `defineMessage({ message: ... })`
188163
* Any additional configured macro/component.
189164
2. Within the message content or children:
190-
191165
* If any *other* Lingui macro usage is found *inside* the current macro, this MUST be reported.
192166
3. 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

221193
1. 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).
225196
2. Examples that MUST be reported:
226-
227197
* `t\`${status}``.
228198
* `<Trans>{label}</Trans>`.
229199
3. 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

248217
1. `<Trans>` MUST be reported if:
249-
250218
* It has exactly one child `JSXElement` or `JSXFragment`, and
251219
* There is no accompanying text.
252220
2. Examples that MUST be reported:
253-
254221
* `<Trans><a href="/terms">Terms</a></Trans>`.
255222
3. 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

277243
1. 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
```
284249
2. The rule MUST allow `t` usage inside:
285-
286250
* Functions (including React components).
287251
* Hooks.
288252
* Event handlers and callbacks.
289253
3. 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

313275
1. 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`.
316277
2. 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).
320280
3. 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:
328287
When TypeScript type information is available:
329288

330289
4. 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:
363321
5. 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

377332
6. 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

411364
1. For each Lingui message string:
412-
413365
* Apply configured restrictions to the raw text, ignoring placeholders.
414366
2. 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

436387
1. 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`).
440390
2. The rule MUST report missing required plural keys.
@@ -469,11 +419,9 @@ README MUST include:
469419

470420
1. Tests MUST be written in TypeScript or JavaScript using a test runner (e.g. Jest or Vitest).
471421
2. Each rule MUST have its own test file (e.g. `no-complex-expressions-in-message.test.ts`).
472-
3. Tests MUST use ESLints `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

Comments
 (0)