@taiga-ui/eslint-plugin-experience-next 0.480.0 → 0.481.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +50 -1676
  2. package/index.d.ts +59 -21
  3. package/index.esm.js +3116 -3033
  4. package/package.json +1 -1
  5. package/rules/attrs-newline.d.ts +3 -2
  6. package/rules/element-newline.d.ts +3 -2
  7. package/rules/no-duplicate-attrs.d.ts +3 -2
  8. package/rules/no-duplicate-id.d.ts +3 -2
  9. package/rules/no-duplicate-in-head.d.ts +3 -2
  10. package/rules/no-obsolete-attrs.d.ts +3 -2
  11. package/rules/no-obsolete-tags.d.ts +3 -2
  12. package/rules/quotes.d.ts +3 -2
  13. package/rules/recommended/decorator-key-sort.d.ts +4 -0
  14. package/rules/{host-attributes-sort.d.ts → recommended/host-attributes-sort.d.ts} +2 -2
  15. package/rules/recommended/html-logical-properties.d.ts +4 -0
  16. package/rules/recommended/injection-token-description.d.ts +4 -0
  17. package/rules/recommended/no-commonjs-import-patterns.d.ts +6 -0
  18. package/rules/recommended/no-deep-imports-to-indexed-packages.d.ts +5 -0
  19. package/rules/recommended/no-deep-imports.d.ts +10 -0
  20. package/rules/recommended/no-href-with-router-link.d.ts +4 -0
  21. package/rules/recommended/no-implicit-public.d.ts +4 -0
  22. package/rules/recommended/no-import-assertions.d.ts +4 -0
  23. package/rules/recommended/no-infinite-loop.d.ts +5 -0
  24. package/rules/recommended/no-legacy-peer-deps.d.ts +4 -0
  25. package/rules/recommended/no-playwright-empty-fill.d.ts +4 -0
  26. package/rules/recommended/no-project-as-in-ng-template.d.ts +4 -0
  27. package/rules/recommended/no-redundant-type-annotation.d.ts +9 -0
  28. package/rules/recommended/no-side-effects-in-computed.d.ts +4 -0
  29. package/rules/recommended/object-single-line.d.ts +7 -0
  30. package/rules/recommended/prefer-combined-if-control-flow.d.ts +5 -0
  31. package/rules/recommended/prefer-multi-arg-push.d.ts +4 -0
  32. package/rules/recommended/prefer-namespace-keyword.d.ts +5 -0
  33. package/rules/{short-tui-imports.d.ts → recommended/short-tui-imports.d.ts} +1 -2
  34. package/rules/{single-line-class-property-spacing.d.ts → recommended/single-line-class-property-spacing.d.ts} +1 -2
  35. package/rules/recommended/standalone-imports-sort.d.ts +7 -0
  36. package/rules/require-doctype.d.ts +3 -2
  37. package/rules/require-img-alt.d.ts +3 -2
  38. package/rules/require-lang.d.ts +3 -2
  39. package/rules/require-li-container.d.ts +3 -2
  40. package/rules/require-title.d.ts +3 -2
  41. package/rules/taiga-specific/array-as-const.d.ts +4 -0
  42. package/rules/taiga-specific/class-property-naming.d.ts +9 -0
  43. package/rules/taiga-specific/flat-exports.d.ts +4 -0
  44. package/rules/taiga-specific/no-restricted-attr-values.d.ts +4 -0
  45. package/rules/{prefer-deep-imports.d.ts → taiga-specific/prefer-deep-imports.d.ts} +2 -2
  46. package/rules/{strict-tui-doc-example.d.ts → taiga-specific/strict-tui-doc-example.d.ts} +1 -2
  47. package/rules/utils/angular/untracked-docs.d.ts +2 -5
  48. package/rules/utils/create-rule.d.ts +13 -0
  49. package/rules/array-as-const.d.ts +0 -3
  50. package/rules/class-property-naming.d.ts +0 -10
  51. package/rules/decorator-key-sort.d.ts +0 -3
  52. package/rules/flat-exports.d.ts +0 -5
  53. package/rules/html-logical-properties.d.ts +0 -3
  54. package/rules/injection-token-description.d.ts +0 -5
  55. package/rules/no-commonjs-import-patterns.d.ts +0 -6
  56. package/rules/no-deep-imports-to-indexed-packages.d.ts +0 -5
  57. package/rules/no-deep-imports.d.ts +0 -11
  58. package/rules/no-href-with-router-link.d.ts +0 -3
  59. package/rules/no-implicit-public.d.ts +0 -5
  60. package/rules/no-import-assertions.d.ts +0 -5
  61. package/rules/no-infinite-loop.d.ts +0 -6
  62. package/rules/no-legacy-peer-deps.d.ts +0 -5
  63. package/rules/no-playwright-empty-fill.d.ts +0 -5
  64. package/rules/no-project-as-in-ng-template.d.ts +0 -3
  65. package/rules/no-redundant-type-annotation.d.ts +0 -10
  66. package/rules/no-restricted-attr-values.d.ts +0 -4
  67. package/rules/no-side-effects-in-computed.d.ts +0 -5
  68. package/rules/object-single-line.d.ts +0 -8
  69. package/rules/prefer-combined-if-control-flow.d.ts +0 -5
  70. package/rules/prefer-multi-arg-push.d.ts +0 -5
  71. package/rules/prefer-namespace-keyword.d.ts +0 -5
  72. package/rules/standalone-imports-sort.d.ts +0 -8
  73. /package/rules/{no-fully-untracked-effect.d.ts → recommended/no-fully-untracked-effect.d.ts} +0 -0
  74. /package/rules/{no-signal-reads-after-await-in-reactive-context.d.ts → recommended/no-signal-reads-after-await-in-reactive-context.d.ts} +0 -0
  75. /package/rules/{no-string-literal-concat.d.ts → recommended/no-string-literal-concat.d.ts} +0 -0
  76. /package/rules/{no-untracked-outside-reactive-context.d.ts → recommended/no-untracked-outside-reactive-context.d.ts} +0 -0
  77. /package/rules/{no-useless-untracked.d.ts → recommended/no-useless-untracked.d.ts} +0 -0
  78. /package/rules/{prefer-untracked-incidental-signal-reads.d.ts → recommended/prefer-untracked-incidental-signal-reads.d.ts} +0 -0
  79. /package/rules/{prefer-untracked-signal-getter.d.ts → recommended/prefer-untracked-signal-getter.d.ts} +0 -0
package/README.md CHANGED
@@ -37,1679 +37,53 @@ export default [
37
37
  - 🔧 = fixable
38
38
  - 💡 = has suggestions
39
39
 
40
- | Rule | Description | ✅ | 🔧 | 💡 |
41
- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------- | --- | --- | --- |
42
- | array-as-const | Exported array of class references should be marked with `as const` | | 🔧 | |
43
- | attrs-newline | Enforce one attribute per line when a start tag spans multiple lines | | 🔧 | |
44
- | class-property-naming | Enforce custom naming for class properties based on their type | | 🔧 | |
45
- | decorator-key-sort | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
46
- | element-newline | Require line breaks around block-level child nodes in HTML templates | | 🔧 | |
47
- | flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
48
- | host-attributes-sort | Sort Angular host metadata attributes using configurable attribute groups | ✅ | 🔧 | |
49
- | injection-token-description | Require `InjectionToken` descriptions to include the token name | ✅ | 🔧 | |
50
- | no-commonjs-import-patterns | Disallow legacy CommonJS interop import patterns | ✅ | | |
51
- | no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
52
- | no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
53
- | no-duplicate-attrs | Disallow duplicate attributes on the same HTML element | | | |
54
- | no-duplicate-id | Disallow duplicate static `id` values in HTML templates | | | |
55
- | no-duplicate-in-head | Disallow duplicate `title`, `base`, and key metadata tags inside `<head>` | | | |
56
- | no-fully-untracked-effect | Disallow reactive callbacks where all signal reads are hidden inside `untracked()` | ✅ | | |
57
- | no-href-with-router-link | Do not use href and routerLink attributes together on the same element | ✅ | 🔧 | |
58
- | no-import-assertions | Replace legacy `assert { ... }` import assertions with `with { ... }` | ✅ | 🔧 | |
59
- | no-implicit-public | Require explicit `public` modifier for class members and parameter properties | ✅ | 🔧 | |
60
- | no-infinite-loop | Disallow `while (true)` and `for` loops without an explicit condition | ✅ | | |
61
- | no-legacy-peer-deps | Disallow `legacy-peer-deps=true` in `.npmrc` | ✅ | | |
62
- | no-obsolete-attrs | Disallow obsolete HTML attributes | | | |
63
- | no-obsolete-tags | Disallow obsolete HTML tags | | | |
64
- | no-playwright-empty-fill | Enforce `clear()` over `fill('')` in Playwright tests | ✅ | 🔧 | |
65
- | no-project-as-in-ng-template | `ngProjectAs` has no effect inside `<ng-template>` or dynamic outlets | ✅ | | |
66
- | no-restricted-attr-values | Disallow configured attribute values in Angular templates | | | |
67
- | no-redundant-type-annotation | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | |
68
- | no-side-effects-in-computed | Disallow side effects and effectful helper calls inside Angular `computed()` callbacks | ✅ | | |
69
- | no-signal-reads-after-await-in-reactive-context | Disallow bare signal reads after `await` inside reactive callbacks | ✅ | | |
70
- | no-string-literal-concat | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | |
71
- | no-untracked-outside-reactive-context | Disallow `untracked()` outside reactive callbacks, except explicit post-`await` snapshots | ✅ | 🔧 | |
72
- | no-useless-untracked | Disallow provably useless `untracked()` wrappers in reactive callbacks | ✅ | 🔧 | |
73
- | object-single-line | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
74
- | prefer-combined-if-control-flow | Combine consecutive `if` statements that use the same `return`, `break`, `continue`, or `throw` | ✅ | 🔧 | |
75
- | prefer-deep-imports | Allow deep imports of Taiga UI packages | | 🔧 | |
76
- | prefer-multi-arg-push | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
77
- | prefer-namespace-keyword | Replace `module Foo {}` with `namespace Foo {}` for TypeScript namespace declarations | ✅ | 🔧 | |
78
- | prefer-untracked-incidental-signal-reads | Wrap likely-incidental signal reads with `untracked()` in reactive callbacks | ✅ | 🔧 | |
79
- | prefer-untracked-signal-getter | Prefer `untracked(signalGetter)` over `untracked(() => signalGetter())` for a single signal getter | ✅ | 🔧 | |
80
- | quotes | Enforce double quotes around HTML attribute values | | 🔧 | |
81
- | require-doctype | Require `<!DOCTYPE html>` at the top of HTML documents | | 🔧 | |
82
- | require-img-alt | Require `alt` on `<img>` elements, including Angular attribute bindings | | | |
83
- | require-lang | Require a non-empty `lang` attribute on `<html>` | | | |
84
- | require-li-container | Require `<li>` to be nested inside `<ul>`, `<ol>`, or `<menu>` | | | |
85
- | require-title | Require a non-empty `<title>` inside `<head>` | | | |
86
- | short-tui-imports | Shorten TuiXxxComponent / TuiYyyDirective in Angular metadata | ✅ | 🔧 | |
87
- | single-line-class-property-spacing | Group consecutive single-line class properties and separate multiline ones with a blank line | ✅ | 🔧 | |
88
- | standalone-imports-sort | Auto sort names inside Angular decorators | ✅ | 🔧 | |
89
- | strict-tui-doc-example | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |
90
-
91
- ---
92
-
93
- ## array-as-const
94
-
95
- <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
96
-
97
- Exported arrays containing only class references must be marked with `as const` to preserve the tuple type and enable
98
- proper type inference.
99
-
100
- ```ts
101
- // ❌ error
102
- export const PROVIDERS = [FooService, BarService];
103
-
104
- // ✅ after autofix
105
- export const PROVIDERS = [FooService, BarService] as const;
106
- ```
107
-
108
- ---
109
-
110
- ## attrs-newline
111
-
112
- <sup>`Fixable`</sup>
113
-
114
- Requires line breaks between attributes when a start tag has more than two attributes. This keeps larger HTML tags
115
- readable and makes attribute diffs much easier to scan in Angular templates.
116
-
117
- ```html
118
- <!-- ❌ error -->
119
- <div
120
- class="b"
121
- id="a"
122
- title="c"
123
- ></div>
124
-
125
- <!-- ✅ after autofix -->
126
- <div
127
- class="b"
128
- id="a"
129
- title="c"
130
- ></div>
131
- ```
132
-
133
- ---
134
-
135
- ## class-property-naming
136
-
137
- <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
138
-
139
- Enforce custom naming conventions for class properties based on their TypeScript type. Useful for enforcing project-wide
140
- patterns (e.g. all `Subject` fields must be called `destroy$`).
141
-
142
- Requires explicit configuration — not enabled in `recommended` by default.
143
-
144
- ```json
145
- {
146
- "@taiga-ui/experience-next/class-property-naming": [
147
- "error",
148
- [
149
- {
150
- "fieldNames": ["sub", "subscription"],
151
- "newFieldName": "destroy$",
152
- "withTypesSpecifier": ["Subject", "Subscription"]
153
- }
154
- ]
155
- ]
156
- }
157
- ```
158
-
159
- ```ts
160
- // ❌ error
161
- class MyComponent {
162
- sub = new Subject<void>();
163
- }
164
-
165
- // ✅ after autofix
166
- class MyComponent {
167
- destroy$ = new Subject<void>();
168
- }
169
- ```
170
-
171
- ---
172
-
173
- ## decorator-key-sort
174
-
175
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
176
-
177
- Enforces a consistent key order inside Angular decorator objects (`@Component`, `@Directive`, `@NgModule`, `@Pipe`,
178
- `@Injectable`). The expected order is passed as configuration.
179
-
180
- ```json
181
- {
182
- "@taiga-ui/experience-next/decorator-key-sort": [
183
- "error",
184
- {
185
- "Component": ["standalone", "selector", "imports", "templateUrl", "styleUrl", "changeDetection"],
186
- "Pipe": ["standalone", "name", "pure"]
187
- }
188
- ]
189
- }
190
- ```
191
-
192
- ```ts
193
- // ❌ error
194
- @Component({
195
- templateUrl: './app.component.html',
196
- selector: 'app-root',
197
- standalone: true,
198
- })
199
-
200
- // ✅ after autofix
201
- @Component({
202
- standalone: true,
203
- selector: 'app-root',
204
- templateUrl: './app.component.html',
205
- })
206
- ```
207
-
208
- ---
209
-
210
- ## element-newline
211
-
212
- <sup>`Fixable`</sup>
213
-
214
- Requires line breaks around block-level child nodes. Inline text and inline elements can stay on one line, but block
215
- content should be visually separated from its container.
216
-
217
- ```html
218
- <!-- ❌ error -->
219
- <div><section>Content</section></div>
220
-
221
- <!-- ✅ after autofix -->
222
- <div>
223
- <section>Content</section>
224
- </div>
225
- ```
226
-
227
- ---
228
-
229
- ## flat-exports
230
-
231
- <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
232
-
233
- When an exported `as const` tuple contains another exported `as const` tuple of Angular classes, it should be spread
234
- rather than nested. This keeps entity collections flat and avoids double-wrapping.
235
-
236
- ```ts
237
- // ❌ error
238
- export const TuiTextfield = [TuiTextfieldDirective] as const;
239
- export const TuiInput = [TuiTextfield, TuiInputDirective] as const;
240
-
241
- // ✅ after autofix
242
- export const TuiTextfield = [TuiTextfieldDirective] as const;
243
- export const TuiInput = [...TuiTextfield, TuiInputDirective] as const;
244
- ```
245
-
246
- ---
247
-
248
- ## host-attributes-sort
249
-
250
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
251
-
252
- Sorts Angular `host` metadata entries in `@Component` and `@Directive` using configurable attribute groups, matching the
253
- same grouping model used for template attributes in Prettier. The recommended config enables the rule with a default
254
- group order that places `id` before plain attributes, `class`, animation bindings, inputs, two-way bindings, and
255
- outputs.
256
-
257
- ```ts
258
- // ❌ error
259
- @Component({
260
- host: {
261
- '(click)': 'handleClick()',
262
- '[value]': 'value()',
263
- class: 'cmp',
264
- id: 'cmp-id',
265
- },
266
- })
267
-
268
- // ✅ after autofix
269
- @Component({
270
- host: {
271
- id: 'cmp-id',
272
- class: 'cmp',
273
- '[value]': 'value()',
274
- '(click)': 'handleClick()',
275
- },
276
- })
277
- ```
278
-
279
- The rule understands the same preset names as `prettier-plugin-organize-attributes`. You can use aggregate presets such
280
- as `$ANGULAR`, `$HTML`, and `$CODE_GUIDE`, or compose atomic presets such as `$CLASS`, `$ID`, `$ARIA`, `$ANGULAR_INPUT`,
281
- `$ANGULAR_TWO_WAY_BINDING`, and `$ANGULAR_OUTPUT`.
282
-
283
- ```json
284
- {
285
- "@taiga-ui/experience-next/host-attributes-sort": [
286
- "error",
287
- {
288
- "attributeGroups": ["$ANGULAR"]
289
- }
290
- ]
291
- }
292
- ```
293
-
294
- Use `$ANGULAR` when `host` should follow the familiar Angular template-style order:
295
- `class -> id -> #ref -> *directive -> @animation -> [@animation] -> [(model)] -> [input] -> (output)`.
296
-
297
- ```json
298
- {
299
- "@taiga-ui/experience-next/host-attributes-sort": [
300
- "error",
301
- {
302
- "attributeGroups": ["$HTML"]
303
- }
304
- ]
305
- }
306
- ```
307
-
308
- Use `$HTML` when only `class` and `id` should be pulled to the front, and everything else can stay in the trailing
309
- default group.
310
-
311
- ```json
312
- {
313
- "@taiga-ui/experience-next/host-attributes-sort": [
314
- "error",
315
- {
316
- "attributeGroups": ["$CODE_GUIDE"]
317
- }
318
- ]
319
- }
320
- ```
321
-
322
- Use `$CODE_GUIDE` for a wider HTML-oriented order: `class`, `id`, `name`, `data-*`, `src`, `for`, `type`, `href`,
323
- `value`, `title`, `alt`, `role`, `aria-*`.
324
-
325
- ```json
326
- {
327
- "@taiga-ui/experience-next/host-attributes-sort": [
328
- "error",
329
- {
330
- "attributeGroups": ["$ID", "$DEFAULT", "$ARIA", "$ANGULAR_OUTPUT"]
331
- }
332
- ]
333
- }
334
- ```
335
-
336
- Use atomic presets when you want a custom order instead of one of the bundled aliases.
337
-
338
- | Option | Type | Description |
339
- | --------------------- | --------------------------- | ----------------------------------------------------------------- |
340
- | `attributeGroups` | `string[]` | Group order. Supports the same preset tokens as Prettier plugins. |
341
- | `attributeIgnoreCase` | `boolean` | Ignore case when matching custom regexp groups. |
342
- | `attributeSort` | `'ASC' \| 'DESC' \| 'NONE'` | Sort order inside each matched group. |
343
- | `decorators` | `string[]` | Decorator names whose `host` metadata should be checked. |
344
-
345
- ---
346
-
347
- ## injection-token-description
348
-
349
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
350
-
351
- The description passed to `new InjectionToken(...)` must contain the name of the variable it is assigned to. The rule
352
- accepts both direct string descriptions and Angular's `ngDevMode ? '...' : ''` pattern, and the autofix rewrites invalid
353
- descriptions to the dev-only form. If `ngDevMode` is not declared in the file, the autofix inserts
354
- `declare const ngDevMode: boolean;` after imports.
355
-
356
- ```ts
357
- // ❌ error
358
- import {InjectionToken} from '@angular/core';
359
-
360
- export const TUI_MY_TOKEN = new InjectionToken<string>('some description');
361
-
362
- // ✅ after autofix
363
- import {InjectionToken} from '@angular/core';
364
-
365
- declare const ngDevMode: boolean;
366
-
367
- export const TUI_MY_TOKEN = new InjectionToken<string>(ngDevMode ? '[TUI_MY_TOKEN]: some description' : '');
368
- ```
369
-
370
- ---
371
-
372
- ## no-commonjs-import-patterns
373
-
374
- <sup>`✅ Recommended`</sup>
375
-
376
- Disallows legacy CommonJS interop import patterns that are brittle under modern ESM-oriented toolchains. It reports
377
- `import foo = require('foo')` and namespace imports that are used as callable values, constructors, or tag functions.
378
-
379
- ```ts
380
- // ❌ error
381
- import toolkit = require('@taiga-ui/cdk');
382
-
383
- import * as createClient from 'legacy-client';
384
- createClient();
385
-
386
- // ✅ ok
387
- import toolkit from '@taiga-ui/cdk';
388
-
389
- import createClient from 'legacy-client';
390
- createClient();
391
- ```
392
-
393
- ---
394
-
395
- ## no-deep-imports
396
-
397
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
398
-
399
- Disallows deep path imports from Taiga UI packages — imports must go through the package root. Works for any
400
- `@taiga-ui/*` package by default. Autofix strips the deep path.
401
-
402
- ```ts
403
- // ❌ error
404
- import {TuiButton} from '@taiga-ui/core/components/button';
405
-
406
- // ✅ after autofix
407
- import {TuiButton} from '@taiga-ui/core';
408
- ```
409
-
410
- ```json
411
- {
412
- "@taiga-ui/experience-next/no-deep-imports": [
413
- "error",
414
- {
415
- "currentProject": "(?<=projects/)([\\w-]+)",
416
- "ignoreImports": ["\\?raw", "@taiga-ui/testing/cypress"]
417
- }
418
- ]
419
- }
420
- ```
421
-
422
- | Option | Type | Description |
423
- | ------------------- | ---------- | -------------------------------------------------------------------------- |
424
- | `currentProject` | `string` | RegExp to extract the current project name from the file path |
425
- | `deepImport` | `string` | RegExp to detect the deep import segment (default: `@taiga-ui/` sub-paths) |
426
- | `importDeclaration` | `string` | RegExp to match import declarations the rule applies to |
427
- | `ignoreImports` | `string[]` | RegExp patterns for imports to ignore |
428
- | `projectName` | `string` | RegExp to extract the package name from the import source |
429
-
430
- ---
431
-
432
- ## no-deep-imports-to-indexed-packages
433
-
434
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
435
-
436
- Disallows deep imports from any external package whose root `index.ts` (or `index.d.ts`) re-exports the same subpath and
437
- is co-located with a `package.json` or `ng-package.json`. Does not require explicit package lists — resolves via
438
- TypeScript.
439
-
440
- ```ts
441
- // ❌ error — @my-lib/index.ts already re-exports this subpath
442
- import {Foo} from '@my-lib/internal/foo';
443
-
444
- // ✅
445
- import {Foo} from '@my-lib/internal';
446
- ```
447
-
448
- ---
449
-
450
- ## no-duplicate-attrs
451
-
452
- Disallows repeated attributes on the same element. The rule works on Angular template AST and catches duplicate plain
453
- HTML attributes before they become ambiguous or silently override one another.
454
-
455
- ```html
456
- <!-- ❌ error -->
457
- <div
458
- class="a"
459
- class="b"
460
- ></div>
461
-
462
- <!-- ✅ ok -->
463
- <div
464
- class="a"
465
- id="b"
466
- ></div>
467
- ```
468
-
469
- ---
470
-
471
- ## no-duplicate-id
472
-
473
- Disallows duplicate static `id` values within the same HTML template. This helps keep selectors, label associations, and
474
- accessibility relationships deterministic.
475
-
476
- ```html
477
- <!-- ❌ error -->
478
- <label for="name"></label>
479
- <input id="name" />
480
- <div id="name"></div>
481
-
482
- <!-- ✅ ok -->
483
- <label for="name"></label>
484
- <input id="name" />
485
- <div id="description"></div>
486
- ```
487
-
488
- ---
489
-
490
- ## no-duplicate-in-head
491
-
492
- Disallows duplicate singleton tags inside `<head>`, including `<title>`, `<base>`, `meta[charset]`,
493
- `meta[name="viewport"]`, and `link[rel="canonical"]`. Multiple copies of these tags make document metadata unreliable.
494
-
495
- ```html
496
- <!-- ❌ error -->
497
- <head>
498
- <title>One</title>
499
- <title>Two</title>
500
- </head>
501
-
502
- <!-- ✅ ok -->
503
- <head>
504
- <title>One</title>
505
- </head>
506
- ```
507
-
508
- ---
509
-
510
- ## no-fully-untracked-effect
511
-
512
- <sup>`✅ Recommended`</sup>
513
-
514
- Reports a reactive callback whose signal reads are all wrapped in `untracked()`. That leaves the callback without
515
- tracked dependencies, so Angular will not re-run it when those signals change.
516
-
517
- Applies to `effect()`, `computed()`, `linkedSignal()`, `resource()` callbacks, and `afterRenderEffect()` phases.
518
-
519
- ```ts
520
- // ❌ error — no tracked reads, effect never re-runs
521
- effect(() => {
522
- const value = untracked(() => this.count());
523
- this.log(value);
524
- });
525
-
526
- // ✅ ok — count() is read outside untracked, creates a reactive dependency
527
- effect(() => {
528
- const value = this.count();
529
- untracked(() => this.log(value));
530
- });
531
- ```
532
-
533
- ```ts
534
- // ❌ error — computed() also loses its dependency
535
- const doubled = computed(() => untracked(() => this.count() * 2));
536
-
537
- // ✅ ok
538
- const doubled = computed(() => this.count() * 2);
539
- ```
540
-
541
- ---
542
-
543
- ## no-href-with-router-link
544
-
545
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
546
-
547
- > ✅ Included in `recommended` — processed by the angular-eslint template parser (`**/*.html`).
548
-
549
- Disallows using both `href` and `routerLink` on the same `<a>` element in Angular templates. Autofix removes the `href`
550
- attribute.
551
-
552
- ```html
553
- <!-- ❌ error -->
554
- <a
555
- href="/home"
556
- routerLink="/home"
557
- >
558
- Home
559
- </a>
560
-
561
- <!-- ✅ after autofix -->
562
- <a routerLink="/home">Home</a>
563
- ```
564
-
565
- ---
566
-
567
- ## no-import-assertions
568
-
569
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
570
-
571
- Disallows legacy `assert { ... }` import assertions and rewrites them to `with { ... }` import attributes.
572
-
573
- ```ts
574
- // ❌ error
575
- import data from './file.json' assert {type: 'json'};
576
-
577
- // ✅ after autofix
578
- import data from './file.json' with {type: 'json'};
579
- ```
580
-
581
- ---
582
-
583
- ## no-implicit-public
584
-
585
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
586
-
587
- Requires an explicit `public` modifier on all class members and constructor parameter properties that are public.
588
- Constructors are excluded.
589
-
590
- ```ts
591
- // ❌ error
592
- class MyService {
593
- value = 42;
594
- doSomething(): void {}
595
- }
596
-
597
- // ✅ after autofix
598
- class MyService {
599
- public value = 42;
600
- public doSomething(): void {}
601
- }
602
- ```
603
-
604
- ---
605
-
606
- ## no-infinite-loop
607
-
608
- <sup>`✅ Recommended`</sup>
609
-
610
- Disallows the two loop forms banned by this project: `while (true)` and `for` loops without a condition, including the
611
- canonical `for (;;)` form. These loops hide the real exit condition inside the body, which makes control flow harder to
612
- scan and review.
613
-
614
- ```ts
615
- // ❌ error
616
- while (true) {
617
- if (isDone) {
618
- break;
619
- }
620
-
621
- process();
622
- }
623
-
624
- // ✅ ok
625
- while (!isDone) {
626
- process();
627
- }
628
- ```
629
-
630
- ```ts
631
- // ❌ error
632
- for (;;) {
633
- if (queue.length === 0) {
634
- break;
635
- }
636
-
637
- flush(queue.shift());
638
- }
639
-
640
- // ✅ ok
641
- for (; queue.length > 0; ) {
642
- flush(queue.shift());
643
- }
644
- ```
645
-
646
- ---
647
-
648
- ## no-legacy-peer-deps
649
-
650
- <sup>`✅ Recommended`</sup>
651
-
652
- > ✅ Included in `recommended` — applied to `**/.npmrc`.
653
-
654
- Disallows `legacy-peer-deps=true` in `.npmrc`. This npm option bypasses peer dependency resolution and can hide real
655
- version conflicts in the dependency graph. The preferred fix is to align incompatible package versions instead of
656
- disabling the resolver.
657
-
658
- ```ini
659
- # ❌ error
660
- legacy-peer-deps=true
661
-
662
- # ✅ ok
663
- strict-peer-deps=true
664
- ```
665
-
666
- Comments and empty lines are ignored, so the rule only reports an active `legacy-peer-deps=true` entry.
667
-
668
- ---
669
-
670
- ## no-obsolete-attrs
671
-
672
- Disallows obsolete HTML attributes such as presentational or deprecated legacy attributes that should be replaced with
673
- modern HTML or CSS.
674
-
675
- ```html
676
- <!-- ❌ error -->
677
- <table border="1"></table>
678
-
679
- <!-- ✅ ok -->
680
- <table class="with-border"></table>
681
- ```
682
-
683
- ---
684
-
685
- ## no-obsolete-tags
686
-
687
- Disallows obsolete HTML tags that should no longer appear in modern markup. This keeps templates aligned with current
688
- HTML standards and avoids legacy presentational elements.
689
-
690
- ```html
691
- <!-- ❌ error -->
692
- <center>Title</center>
693
-
694
- <!-- ✅ ok -->
695
- <div class="centered">Title</div>
696
- ```
697
-
698
- ---
699
-
700
- ## no-playwright-empty-fill
701
-
702
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
703
-
704
- In Playwright tests, calling `.fill('')` on a locator should be replaced with `.clear()` — it is the idiomatic way to
705
- empty a field and communicates intent more clearly.
706
-
707
- ```ts
708
- // ❌ error
709
- await page.getByLabel('Name').fill('');
710
-
711
- // ✅ after autofix
712
- await page.getByLabel('Name').clear();
713
- ```
714
-
715
- ---
716
-
717
- ## no-project-as-in-ng-template
718
-
719
- <sup>`✅ Recommended`</sup>
720
-
721
- `ngProjectAs` has no effect when the element is inside an `<ng-template>`, `*ngTemplateOutlet`, `*ngComponentOutlet`, or
722
- `*polymorpheusOutlet`. Content instantiated through these dynamic outlets does not participate in Angular's static
723
- content projection, so the attribute is silently ignored at runtime.
724
-
725
- ```html
726
- <!-- ❌ error — inside <ng-template> -->
727
- <ng-template #tpl>
728
- <div ngProjectAs="[someSlot]">content</div>
729
- </ng-template>
730
-
731
- <!-- ❌ error — on the outlet host itself -->
732
- <ng-container
733
- *ngTemplateOutlet="tpl"
734
- ngProjectAs="[someSlot]"
735
- ></ng-container>
736
-
737
- <!-- ❌ error — polymorpheusOutlet -->
738
- <ng-container
739
- *polymorpheusOutlet="content"
740
- ngProjectAs="someSlot"
741
- ></ng-container>
742
-
743
- <!-- ✅ ok — static content projection -->
744
- <div ngProjectAs="[someSlot]">content</div>
745
- ```
746
-
747
- ---
748
-
749
- ## no-restricted-attr-values
750
-
751
- <sup>`Taiga-specific`</sup>
752
-
753
- Disallows configured string values for Angular template attributes, including both plain HTML attributes and literal
754
- string bindings like `[icon]="'@tui.x'"`. This is useful when a project wants to ban hardcoded design-system tokens in
755
- markup and require them to come from component inputs or options objects instead.
756
-
757
- ```json
758
- {
759
- "@taiga-ui/experience-next/no-restricted-attr-values": [
760
- "error",
761
- {
762
- "attrPatterns": ["iconStart", "iconEnd", "icon"],
763
- "attrValuePatterns": ["@tui"],
764
- "message": "Icons must be configured"
765
- }
766
- ]
767
- }
768
- ```
769
-
770
- ```html
771
- <!-- ❌ error -->
772
- <button iconStart="@tui.x"></button>
773
- <tui-icon [icon]="'@tui.chevron-down'"></tui-icon>
774
-
775
- <!-- ✅ ok -->
776
- <button [iconStart]="options.iconStart"></button>
777
- <tui-icon [icon]="options.icon"></tui-icon>
778
- ```
779
-
780
- ---
781
-
782
- ## no-string-literal-concat
783
-
784
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
785
-
786
- Disallows concatenating string literals with `+`. Adjacent string literals are always mergeable into one — splitting
787
- them with `+` adds noise without benefit, and multi-line splits are especially easy to miss.
788
-
789
- Replaces the built-in `no-useless-concat` rule, which only catches same-line concatenation.
790
-
791
- ```ts
792
- // ❌ error
793
- const msg = 'Hello, ' + 'world!';
794
-
795
- // ✅ after autofix
796
- const msg = 'Hello, world!';
797
- ```
798
-
799
- ```ts
800
- // ❌ error — also caught across lines
801
- it(
802
- 'returns the last day of month when' +
803
- ' the result month has fewer days',
804
- () => { ... },
805
- );
806
-
807
- // ✅ after autofix
808
- it('returns the last day of month when the result month has fewer days', () => {
809
- ...
810
- });
811
- ```
812
-
813
- ```ts
814
- // ❌ error — string variables concatenated with +
815
- const a = 'hello';
816
- const b = 'world';
817
- const c = a + b;
818
-
819
- // ✅ after autofix
820
- const c = `${a}${b}`;
821
- ```
822
-
823
- When the concatenation is a **direct expression inside a template literal**, the parts are inlined into the outer
824
- template instead of producing a nested template literal:
825
-
826
- ```ts
827
- // ❌ error
828
- const url = `${base}${path + query}`;
829
-
830
- // ✅ after autofix — inlined, no nesting
831
- const url = `${base}${path}${query}`;
832
- ```
833
-
834
- ```ts
835
- // ❌ error — literal concat inside template
836
- const mask = `${'HH' + ':MM'}`;
837
-
838
- // ✅ after autofix
839
- const mask = `HH:MM`;
840
- ```
841
-
842
- When the concatenation appears **inside a method call or other expression** within a template literal, the rule skips it
843
- to avoid creating unreadable nested template literals like `` `${`${a}${b}`.method()}` ``.
844
-
845
- The rule also **flattens already-nested template literals** produced by earlier autofixes or written by hand:
846
-
847
- ```ts
848
- // ❌ error
849
- const s = `${`${dateMode}${dateTimeSeparator}`}HH:MM`;
850
-
851
- // ✅ after autofix
852
- const s = `${dateMode}${dateTimeSeparator}HH:MM`;
853
- ```
854
-
855
- Concatenation that uses **inline comments between parts** is intentionally left untouched, as the comments serve as
856
- documentation:
857
-
858
- ```ts
859
- // ✅ not flagged — comments are preserved
860
- const urlRegex =
861
- String.raw`^([a-zA-Z]+:\/\/)?` + // protocol
862
- String.raw`([\w-]+\.)+[\w]{2,}` + // domain
863
- String.raw`(\/\S*)?$`; // path
864
- ```
865
-
866
- > For mixed concatenation (`'prefix' + variable`) use the standard `prefer-template` rule, which is already enabled in
867
- > `recommended`. Template literals (`` `foo` + `bar` ``) and tagged templates are not flagged by this rule.
868
-
869
- ---
870
-
871
- ## no-useless-untracked
872
-
873
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
874
-
875
- Inside a reactive callback, `untracked()` is only meaningful when its inner function reads signals or intentionally
876
- wraps opaque external code that may read signals. It can also be a valid escape hatch when a reactive callback needs to
877
- create another reactive owner such as `effect()` without inheriting the ambient reactive context. Wrapping code with no
878
- signal reads and no such escape-hatch purpose in `untracked()` is noise. Autofix unwraps the callback in-place when that
879
- is structurally safe and removes the `untracked` import when it is no longer used elsewhere. Snapshot reads that later
880
- influence branching are still valid and are not reported, because Angular allows incidental reads inside `effect()` and
881
- similar reactive callbacks.
882
-
883
- ```ts
884
- // ❌ error — no signal reads inside untracked, wrapper is pointless
885
- effect(() => {
886
- untracked(() => {
887
- this.count.set(0);
888
- });
889
- });
890
-
891
- // ✅ after autofix
892
- effect(() => {
893
- this.count.set(0);
894
- });
895
- ```
896
-
897
- ```ts
898
- // ✅ ok — snapshot reads may influence control flow without becoming dependencies
899
- effect(() => {
900
- const value = untracked(() => this.value());
901
-
902
- if (this.showAdjacent() && value !== null) {
903
- this.month.set(value);
904
- }
905
- });
906
- ```
907
-
908
- ```ts
909
- // ✅ ok — linkedSignal fallback may intentionally read a snapshot
910
- const activeYear = linkedSignal(() => {
911
- const year = this.year();
912
-
913
- if (year) {
914
- return year;
915
- }
916
-
917
- const value = untracked(() => this.value());
918
-
919
- return value ?? TODAY;
920
- });
921
- ```
922
-
923
- ```ts
924
- // ✅ ok — wrapping external code is a valid Angular use-case
925
- effect(() => {
926
- const user = this.user();
927
- untracked(() => this.logger.log(user));
928
- });
929
- ```
930
-
931
- ```ts
932
- // ✅ ok — creating a nested effect() may need to escape the current reactive context
933
- const doubled = computed(() => {
934
- untracked(() => {
935
- effect(() => {
936
- console.log(this.count());
937
- });
938
- });
939
-
940
- return this.count() * 2;
941
- });
942
- ```
943
-
944
- ---
945
-
946
- ## no-side-effects-in-computed
947
-
948
- <sup>`✅ Recommended`</sup>
949
-
950
- `computed()` should only derive a value from its inputs. This rule reports observable side effects inside Angular
951
- `computed()` callbacks, including signal writes (`.set()`, `.update()`, `.mutate()`), `effect()`, `inject()`,
952
- assignments to captured state, `++/--`, `delete`, property mutations on objects that were not created inside the
953
- computation itself, and calls to local helper functions or methods when their bodies perform those operations.
954
-
955
- ```ts
956
- // ❌ error
957
- import {computed, signal} from '@angular/core';
958
-
959
- const source = signal(0);
960
- const target = signal(0);
961
-
962
- function syncTarget(): void {
963
- target.set(source() + 1);
964
- }
965
-
966
- const derived = computed(() => {
967
- syncTarget();
968
- return target();
969
- });
970
- ```
971
-
972
- ```ts
973
- // ✅ ok
974
- import {computed, signal} from '@angular/core';
975
-
976
- const source = signal(0);
977
- const derived = computed(() => source() + 1);
978
- ```
979
-
980
- ---
981
-
982
- ## no-signal-reads-after-await-in-reactive-context
983
-
984
- <sup>`✅ Recommended`</sup>
985
-
986
- Angular tracks signal reads only in synchronous code. If a reactive callback crosses an async boundary, any bare signal
987
- read after `await` will not become a dependency. Snapshot before `await` when you need the earlier value, or make an
988
- intentional post-`await` current-value read explicit with `untracked(...)`.
989
-
990
- ```ts
991
- // ❌ error
992
- effect(async () => {
993
- await this.fetchUser();
994
- console.log(this.theme());
995
- });
996
-
997
- // ✅ ok
998
- effect(async () => {
999
- const theme = this.theme();
1000
- await this.fetchUser();
1001
- console.log(theme);
1002
- });
1003
- ```
1004
-
1005
- ```ts
1006
- // ✅ ok — explicit current-value read after await
1007
- effect(async () => {
1008
- await this.fetchUser();
1009
- console.log(untracked(this.theme));
1010
- });
1011
- ```
1012
-
1013
- ---
1014
-
1015
- ## no-untracked-outside-reactive-context
1016
-
1017
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1018
-
1019
- `untracked()` usually only affects signal reads that happen inside the synchronous body of a reactive callback. In
1020
- ordinary non-reactive code or nested callbacks it usually does not prevent dependency tracking and only adds noise. This
1021
- rule reports those cases, but intentionally allows a few explicit escape hatches: post-`await` reads inside a reactive
1022
- callback when `untracked()` is used to document an intentional current-value snapshot, imperative Angular hooks such as
1023
- `@Pipe().transform`, `ControlValueAccessor.writeValue`, `registerOnChange` including patched accessors such as
1024
- `accessor.writeValue = (...) => {}`, callback-form wrappers used inside deferred scheduler / event-handler callbacks,
1025
- and narrow lazy DI factory wrappers like `InjectionToken({factory})` / `useFactory` when they guard creation of a
1026
- reactive owner such as `effect()` against an accidental ambient reactive context. For the narrow case
1027
- `untracked(() => effect(...))` and similar outer wrappers around a reactive call in ordinary code, autofix removes only
1028
- the useless outer `untracked()` wrapper.
1029
-
1030
- ```ts
1031
- // ❌ error
1032
- const snapshot = untracked(this.user);
1033
-
1034
- effect(() => {
1035
- button.addEventListener('click', () => {
1036
- console.log(untracked(this.user));
1037
- });
1038
- });
1039
- ```
1040
-
1041
- ```ts
1042
- // ✅ ok
1043
- effect(() => {
1044
- console.log(untracked(this.user));
1045
- });
1046
-
1047
- const snapshot = computed(() => untracked(this.user));
1048
- ```
1049
-
1050
- ```ts
1051
- // ✅ ok — after await, untracked can mark an intentional current snapshot
1052
- effect(async () => {
1053
- await this.refresh();
1054
-
1055
- if (untracked(this.user) !== previousUser) {
1056
- console.log('changed');
1057
- }
1058
- });
1059
- ```
1060
-
1061
- ```ts
1062
- // ❌ error
1063
- untracked(() => {
1064
- effect(() => {
1065
- console.log(this.user());
1066
- });
1067
- });
1068
-
1069
- // ✅ after autofix
1070
- effect(() => {
1071
- console.log(this.user());
1072
- });
1073
- ```
1074
-
1075
- ```ts
1076
- // ✅ ok — imperative Angular hooks may still need untracked
1077
- @Pipe({name: 'demo', pure: false})
1078
- export class DemoPipe implements PipeTransform {
1079
- private readonly value = signal('');
1080
-
1081
- transform(next: string): string {
1082
- untracked(() => this.value.set(next));
1083
- return this.value();
1084
- }
1085
- }
1086
- ```
1087
-
1088
- ```ts
1089
- // ✅ ok — deferred callback wrappers may execute under reactive control later
1090
- const update = (): void => untracked(() => value.set(input.value));
1091
-
1092
- input.addEventListener('input', update, {capture: true});
1093
- ```
1094
-
1095
- ```ts
1096
- // ✅ ok — lazy DI factories may first execute from an ambient reactive context
1097
- export const TOKEN = new InjectionToken<void>('TOKEN', {
1098
- factory: () => {
1099
- untracked(() => {
1100
- effect(() => {
1101
- console.log(count());
1102
- });
1103
- });
1104
- },
1105
- });
1106
- ```
1107
-
1108
- ---
1109
-
1110
- ## object-single-line
1111
-
1112
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1113
-
1114
- Single-property object literals that fit within `printWidth` characters on one line are collapsed to a single line.
1115
- Compatible with Prettier formatting.
1116
-
1117
- ```ts
1118
- // ❌ error
1119
- const x = {
1120
- foo: bar,
1121
- };
1122
-
1123
- // ✅ after autofix
1124
- const x = {foo: bar};
1125
- ```
1126
-
1127
- ```json
1128
- {
1129
- "@taiga-ui/experience-next/object-single-line": ["error", {"printWidth": 90}]
1130
- }
1131
- ```
1132
-
1133
- | Option | Type | Default | Description |
1134
- | ------------ | -------- | ------- | ------------------------------------- |
1135
- | `printWidth` | `number` | `90` | Maximum line length to allow inlining |
1136
-
1137
- ---
1138
-
1139
- ## prefer-combined-if-control-flow
1140
-
1141
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1142
-
1143
- Combine consecutive `if` statements when they have no `else` branch and use the same `return`, `break`, `continue`, or
1144
- `throw` statement. The autofix merges their conditions with `||`, while intentionally skipping cases with intervening
1145
- code or comments that should remain a separate control-flow boundary.
1146
-
1147
- ```ts
1148
- // ❌ error
1149
- while (true) {
1150
- if (a) continue;
1151
- if (b && c) continue;
1152
- }
1153
-
1154
- // ✅ after autofix
1155
- while (true) {
1156
- if (a || (b && c)) continue;
1157
- }
1158
- ```
1159
-
1160
- ```ts
1161
- // ❌ error
1162
- if (a || b) {
1163
- return;
1164
- }
1165
-
1166
- if (c) {
1167
- return;
1168
- }
1169
-
1170
- // ✅ after autofix
1171
- if (a || b || c) {
1172
- return;
1173
- }
1174
- ```
1175
-
1176
- ```ts
1177
- // ❌ error
1178
- if (isInvalid) return result;
1179
-
1180
- if (isLegacy && shouldStop) return result;
1181
-
1182
- // ✅ after autofix
1183
- if (isInvalid || (isLegacy && shouldStop)) return result;
1184
- ```
1185
-
1186
- ```ts
1187
- // ❌ error
1188
- while (true) {
1189
- if (isDone) break;
1190
- if (hasError) break;
1191
- }
1192
-
1193
- // ✅ after autofix
1194
- while (true) {
1195
- if (isDone || hasError) break;
1196
- }
1197
- ```
1198
-
1199
- ```ts
1200
- // ❌ error
1201
- if (isFatal) throw error;
1202
-
1203
- if (isExpired && shouldAbort) throw error;
1204
-
1205
- // ✅ after autofix
1206
- if (isFatal || (isExpired && shouldAbort)) throw error;
1207
- ```
1208
-
1209
- ```ts
1210
- // not changed — different control flow
1211
- while (true) {
1212
- if (isDone) continue;
1213
- if (hasError) break;
1214
- }
1215
- ```
1216
-
1217
- ```ts
1218
- // not changed — comment keeps branches separate
1219
- if (a) {
1220
- return value;
1221
- }
1222
-
1223
- // explain why this branch exists
1224
- if (b) {
1225
- return value;
1226
- }
1227
- ```
1228
-
1229
- ---
1230
-
1231
- ## prefer-deep-imports
1232
-
1233
- <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
1234
-
1235
- Enforce imports from the deepest available entry point of Taiga UI packages.
1236
-
1237
- ```json
1238
- {
1239
- "@taiga-ui/experience-next/prefer-deep-imports": [
1240
- "error",
1241
- {
1242
- "importFilter": ["@taiga-ui/core", "@taiga-ui/kit"],
1243
- "strict": true
1244
- }
1245
- ]
1246
- }
1247
- ```
1248
-
1249
- Use `strict` to forbid imports from intermediate entry points when deeper ones exist (recommended for CI).
1250
-
1251
- ---
1252
-
1253
- ## prefer-multi-arg-push
1254
-
1255
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1256
-
1257
- Combine consecutive `.push()` calls on the same array into a single multi-argument call.
1258
-
1259
- ```ts
1260
- // ❌ error
1261
- output.push('# Getting Started');
1262
- output.push('');
1263
-
1264
- // ✅ after autofix
1265
- output.push('# Getting Started', '');
1266
- ```
1267
-
1268
- ---
1269
-
1270
- ## prefer-namespace-keyword
1271
-
1272
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1273
-
1274
- Prefers `namespace Foo {}` over the older `module Foo {}` syntax for TypeScript namespace declarations. External module
1275
- augmentations such as `declare module 'pkg' {}` are ignored.
1276
-
1277
- ```ts
1278
- // ❌ error
1279
- module Foo.Bar {
1280
- export type Value = string;
1281
- }
1282
-
1283
- // ✅ after autofix
1284
- namespace Foo.Bar {
1285
- export type Value = string;
1286
- }
1287
- ```
1288
-
1289
- ---
1290
-
1291
- ## prefer-untracked-incidental-signal-reads
1292
-
1293
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1294
-
1295
- Inside a reactive callback, flags direct signal reads that look like snapshot-only values passed into writable-signal
1296
- writes such as `.set()` or into DOM side-effect calls such as `requestFullscreen(...)`. These reads are likely
1297
- incidental and should usually not create their own dependency. The rule only reports when the callback already has
1298
- another tracked dependency outside the flagged consumer call, and autofix wraps the incidental read with `untracked()`
1299
- while adding the import if needed. If the read is intentionally reactive, disable the rule for that line.
1300
-
1301
- ```ts
1302
- // ❌ error
1303
- effect(() => {
1304
- if (this.options().length) {
1305
- this.input.value.set(this.stringified());
1306
- }
1307
- });
1308
-
1309
- // ✅ after autofix
1310
- effect(() => {
1311
- if (this.options().length) {
1312
- this.input.value.set(untracked(() => this.stringified()));
1313
- }
1314
- });
1315
- ```
1316
-
1317
- ```ts
1318
- // ❌ error
1319
- effect(() => {
1320
- if (this.options().length) {
1321
- const value = this.stringified();
1322
-
1323
- this.input.value.set(value);
1324
- }
1325
- });
1326
-
1327
- // ✅ after autofix
1328
- effect(() => {
1329
- if (this.options().length) {
1330
- const value = untracked(() => this.stringified());
1331
-
1332
- this.input.value.set(value);
1333
- }
1334
- });
1335
- ```
1336
-
1337
- ```ts
1338
- // ❌ error
1339
- effect(async () => {
1340
- if (this.tuiFullscreen()) {
1341
- await this.root()?.requestFullscreen(this.options());
1342
- }
1343
- });
1344
-
1345
- // ✅ after autofix
1346
- effect(async () => {
1347
- if (this.tuiFullscreen()) {
1348
- await this.root()?.requestFullscreen(untracked(() => this.options()));
1349
- }
1350
- });
1351
- ```
1352
-
1353
- ---
1354
-
1355
- ## prefer-untracked-signal-getter
1356
-
1357
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1358
-
1359
- When `untracked()` wraps only a single signal getter, prefer passing that getter directly. This keeps the code shorter
1360
- while preserving the same untracked signal read semantics. The rule intentionally skips real TypeScript getters, because
1361
- property access would happen before `untracked()` starts.
1362
-
1363
- ```ts
1364
- // ❌ error
1365
- const snapshot = untracked(() => this.counter());
1366
-
1367
- // ✅ after autofix
1368
- const snapshot = untracked(this.counter);
1369
- ```
1370
-
1371
- ---
1372
-
1373
- ## quotes
1374
-
1375
- <sup>`Fixable`</sup>
1376
-
1377
- Enforces double quotes around HTML attribute values, including Angular template bindings written in markup. It also adds
1378
- missing quotes when the attribute value is currently unquoted.
1379
-
1380
- ```html
1381
- <!-- ❌ error -->
1382
- <div
1383
- class="foo"
1384
- title="bar"
1385
- ></div>
1386
-
1387
- <!-- ✅ after autofix -->
1388
- <div
1389
- class="foo"
1390
- title="bar"
1391
- ></div>
1392
- ```
1393
-
1394
- ---
1395
-
1396
- ## require-doctype
1397
-
1398
- <sup>`Fixable`</sup>
1399
-
1400
- Requires `<!DOCTYPE html>` at the beginning of HTML documents. Even though Angular's template parser does not expose
1401
- doctype nodes directly, the rule still validates and autofixes the source text.
1402
-
1403
- ```html
1404
- <!-- ❌ error -->
1405
- <html lang="en"></html>
1406
-
1407
- <!-- ✅ after autofix -->
1408
- <!DOCTYPE html>
1409
- <html lang="en"></html>
1410
- ```
1411
-
1412
- ---
1413
-
1414
- ## require-img-alt
1415
-
1416
- Requires an accessible text alternative on `<img>` elements. The rule accepts plain `alt`, `[alt]`, and `[attr.alt]` so
1417
- it works naturally with Angular bindings.
1418
-
1419
- ```html
1420
- <!-- ❌ error -->
1421
- <img src="avatar.png" />
1422
-
1423
- <!-- ✅ ok -->
1424
- <img
1425
- src="avatar.png"
1426
- alt="Profile"
1427
- />
1428
- <img
1429
- [src]="avatar"
1430
- [attr.alt]="description"
1431
- />
1432
- ```
1433
-
1434
- ---
1435
-
1436
- ## require-lang
1437
-
1438
- Requires a non-empty `lang` attribute on the root `<html>` element. Bound Angular forms such as `[attr.lang]` are also
1439
- accepted.
1440
-
1441
- ```html
1442
- <!-- ❌ error -->
1443
- <html>
1444
- <body></body>
1445
- </html>
1446
-
1447
- <!-- ✅ ok -->
1448
- <html lang="en">
1449
- <body></body>
1450
- </html>
1451
- <html [attr.lang]="locale()">
1452
- <body></body>
1453
- </html>
1454
- ```
1455
-
1456
- ---
1457
-
1458
- ## require-li-container
1459
-
1460
- Requires `<li>` elements to appear inside `<ul>`, `<ol>`, or `<menu>`. This prevents structurally invalid list markup in
1461
- Angular templates.
1462
-
1463
- ```html
1464
- <!-- ❌ error -->
1465
- <div><li>Item</li></div>
1466
-
1467
- <!-- ✅ ok -->
1468
- <ul>
1469
- <li>Item</li>
1470
- </ul>
1471
- ```
1472
-
1473
- ---
1474
-
1475
- ## require-title
1476
-
1477
- Requires a non-empty `<title>` inside `<head>`. Plain text and Angular interpolation are both treated as valid title
1478
- content.
1479
-
1480
- ```html
1481
- <!-- ❌ error -->
1482
- <html lang="en">
1483
- <head></head>
1484
- <body></body>
1485
- </html>
1486
-
1487
- <!-- ✅ ok -->
1488
- <html lang="en">
1489
- <head>
1490
- <title>Page</title>
1491
- </head>
1492
- <body></body>
1493
- </html>
1494
- <html lang="en">
1495
- <head>
1496
- <title>{{ pageTitle }}</title>
1497
- </head>
1498
- <body></body>
1499
- </html>
1500
- ```
1501
-
1502
- ---
1503
-
1504
- ## short-tui-imports
1505
-
1506
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1507
-
1508
- In Angular decorator `imports` arrays, replaces full `TuiXxxComponent` / `TuiYyyDirective` names with their shorthand
1509
- aliases (e.g. `TuiButton`). Also updates the corresponding `import` statement.
1510
-
1511
- ```ts
1512
- // ❌ error
1513
- import {TuiButtonDirective} from '@taiga-ui/core';
1514
-
1515
- @Component({
1516
- imports: [TuiButtonDirective],
1517
- })
1518
-
1519
- // ✅ after autofix
1520
- import {TuiButton} from '@taiga-ui/core';
1521
-
1522
- @Component({
1523
- imports: [TuiButton],
1524
- })
1525
- ```
1526
-
1527
- ```json
1528
- {
1529
- "@taiga-ui/experience-next/short-tui-imports": [
1530
- "error",
1531
- {
1532
- "decorators": ["Component", "Directive", "NgModule", "Pipe"],
1533
- "exceptions": [{"from": "TuiTextfieldOptionsDirective", "to": "TuiTextfield"}]
1534
- }
1535
- ]
1536
- }
1537
- ```
1538
-
1539
- | Option | Type | Description |
1540
- | ------------ | ------------------------------ | ---------------------------------------------------------- |
1541
- | `decorators` | `string[]` | Decorator names to inspect (default: all Angular ones) |
1542
- | `exceptions` | `{from: string, to: string}[]` | Explicit rename mappings that override the default pattern |
1543
-
1544
- ---
1545
-
1546
- ## single-line-class-property-spacing
1547
-
1548
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1549
-
1550
- Keeps consecutive single-line field-like class members visually grouped, including `abstract` fields. A multiline field
1551
- must be separated from neighboring field-like members with a blank line before it and, when another field follows, a
1552
- blank line after it. `get` and `set` accessors always act as visual boundaries and must be separated from surrounding
1553
- fields by a blank line. This removes noisy empty lines inside simple field groups without flattening longer, wrapped
1554
- initializers or blending fields into accessors.
1555
-
1556
- ```ts
1557
- // ❌ error
1558
- class TuiEditorStarter {
1559
- protected readonly template = import('./import/template.md?raw');
1560
-
1561
- protected readonly component = import('./import/component.md?raw');
1562
- protected readonly exampleIcons =
1563
- import('./import/angular-to-long-long-long-long-long-long-long-text-for-prettier.json.md?raw');
1564
- protected readonly isE2E = inject(TUI_IS_E2E);
1565
- }
1566
-
1567
- // ✅ after autofix
1568
- class TuiEditorStarter {
1569
- protected readonly template = import('./import/template.md?raw');
1570
- protected readonly component = import('./import/component.md?raw');
1571
-
1572
- protected readonly exampleIcons =
1573
- import('./import/angular-to-long-long-long-long-long-long-long-text-for-prettier.json.md?raw');
1574
-
1575
- protected readonly isE2E = inject(TUI_IS_E2E);
1576
- }
1577
- ```
1578
-
1579
- ```ts
1580
- // ❌ error
1581
- abstract class Example {
1582
- protected readonly template = import('./template.md?raw');
1583
- get component() {
1584
- return this.template;
1585
- }
1586
- public abstract markdown: string;
1587
- }
1588
-
1589
- // ✅ after autofix
1590
- abstract class Example {
1591
- protected readonly template = import('./template.md?raw');
1592
-
1593
- get component() {
1594
- return this.template;
1595
- }
1596
-
1597
- public abstract markdown: string;
1598
- }
1599
- ```
1600
-
1601
- ---
1602
-
1603
- ## standalone-imports-sort
1604
-
1605
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1606
-
1607
- Sorts the `imports` array inside Angular decorators (`@Component`, `@Directive`, `@NgModule`, `@Pipe`) alphabetically.
1608
- Spread elements are placed after named identifiers.
1609
-
1610
- ```ts
1611
- // ❌ error
1612
- @Component({
1613
- imports: [TuiButton, CommonModule, AsyncPipe],
1614
- })
1615
-
1616
- // ✅ after autofix
1617
- @Component({
1618
- imports: [AsyncPipe, CommonModule, TuiButton],
1619
- })
1620
- ```
1621
-
1622
- ```json
1623
- {
1624
- "@taiga-ui/experience-next/standalone-imports-sort": [
1625
- "error",
1626
- {"decorators": ["Component", "Directive", "NgModule", "Pipe"]}
1627
- ]
1628
- }
1629
- ```
1630
-
1631
- ---
1632
-
1633
- ## no-redundant-type-annotation
1634
-
1635
- <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
1636
-
1637
- Disallow explicit type annotations on class properties and variable declarations when TypeScript can already infer the
1638
- same type from the initializer. Requires type information (`parserOptions.project`).
1639
-
1640
- Works well in combination with `unused-imports/no-unused-imports` or `@typescript-eslint/no-unused-vars`, which will
1641
- then clean up any import that is no longer referenced after the annotation is removed.
1642
-
1643
- ```ts
1644
- // ❌ error — type is already inferred from inject()
1645
- private readonly options: TuiInputNumberOptions = inject(TUI_INPUT_NUMBER_OPTIONS);
1646
-
1647
- // ✅ after autofix
1648
- private readonly options = inject(TUI_INPUT_NUMBER_OPTIONS);
1649
- ```
1650
-
1651
- ```ts
1652
- // ❌ error — variable declaration
1653
- const service: MyService = inject(MyService);
1654
-
1655
- // ✅ after autofix
1656
- const service = inject(MyService);
1657
- ```
1658
-
1659
- The rule does **not** report when the annotation intentionally widens or changes the type:
1660
-
1661
- ```ts
1662
- // ✅ ok — annotation widens Dog to Animal
1663
- x: Animal = new Dog();
1664
-
1665
- // ✅ ok — annotation adds null to the union
1666
- x: MyService | null = inject(MyService);
1667
- ```
1668
-
1669
- The rule does **not** report when the annotation provides contextual typing that narrows an array literal to a tuple.
1670
- Without the annotation TypeScript would infer `number[]` instead of the required tuple type, widening the type and
1671
- breaking compilation:
1672
-
1673
- ```ts
1674
- type SelectionRange = readonly [from: number, to: number];
1675
- interface ElementState {
1676
- readonly value: string;
1677
- readonly selection: SelectionRange;
1678
- }
1679
-
1680
- // ✅ ok — [0, 0] is inferred as SelectionRange only because of the annotation;
1681
- // removing it would widen the type to ElementState | {value: string; selection: number[]}
1682
- const state: ElementState = flag ? {value: '', selection: [0, 0]} : existingState;
1683
- ```
1684
-
1685
- ```json
1686
- {
1687
- "@taiga-ui/experience-next/no-redundant-type-annotation": ["error", {"ignoreTupleContextualTyping": true}]
1688
- }
1689
- ```
1690
-
1691
- | Option | Type | Default | Description |
1692
- | ----------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------ |
1693
- | `ignoreTupleContextualTyping` | `boolean` | `true` | Preserve annotations when they provide contextual typing that narrows an array literal to a tuple type |
1694
-
1695
- ---
1696
-
1697
- ## strict-tui-doc-example
1698
-
1699
- <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
1700
-
1701
- Validates that properties of a `TuiDocExample`-typed object have keys matching known file-type names (`TypeScript`,
1702
- `HTML`, `CSS`, `LESS`, `JavaScript`) and that the import path extension matches the key. Autofix corrects the import
1703
- extension.
1704
-
1705
- ```ts
1706
- // ❌ error — key says "TypeScript" but path has .html extension
1707
- readonly example: TuiDocExample = {
1708
- TypeScript: import('./example/index.html?raw'),
1709
- };
1710
-
1711
- // ✅ after autofix
1712
- readonly example: TuiDocExample = {
1713
- TypeScript: import('./example/index.ts?raw'),
1714
- };
1715
- ```
40
+ | Rule | Description | ✅ | 🔧 | 💡 |
41
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --- | --- | --- |
42
+ | [array-as-const](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/array-as-const.md) | Exported array of class references should be marked with `as const` | | 🔧 | |
43
+ | [attrs-newline](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/attrs-newline.md) | Enforce one attribute per line when a start tag spans multiple lines | | 🔧 | |
44
+ | [class-property-naming](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/class-property-naming.md) | Enforce custom naming for class properties based on their type | | 🔧 | |
45
+ | [decorator-key-sort](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/decorator-key-sort.md) | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
46
+ | [element-newline](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/element-newline.md) | Require line breaks around block-level child nodes in HTML templates | | 🔧 | |
47
+ | [flat-exports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/flat-exports.md) | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
48
+ | [host-attributes-sort](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/host-attributes-sort.md) | Sort Angular host metadata attributes using configurable attribute groups | ✅ | 🔧 | |
49
+ | [injection-token-description](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/injection-token-description.md) | Require `InjectionToken` descriptions to include the token name | ✅ | 🔧 | |
50
+ | [no-commonjs-import-patterns](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-commonjs-import-patterns.md) | Disallow legacy CommonJS interop import patterns | ✅ | | |
51
+ | [no-deep-imports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-deep-imports.md) | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
52
+ | [no-deep-imports-to-indexed-packages](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-deep-imports-to-indexed-packages.md) | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
53
+ | [no-duplicate-attrs](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-duplicate-attrs.md) | Disallow duplicate attributes on the same HTML element | | | |
54
+ | [no-duplicate-id](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-duplicate-id.md) | Disallow duplicate static `id` values in HTML templates | | | |
55
+ | [no-duplicate-in-head](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-duplicate-in-head.md) | Disallow duplicate `title`, `base`, and key metadata tags inside `<head>` | | | |
56
+ | [no-fully-untracked-effect](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-fully-untracked-effect.md) | Disallow reactive callbacks where all signal reads are hidden inside `untracked()` | ✅ | | |
57
+ | [no-href-with-router-link](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-href-with-router-link.md) | Do not use href and routerLink attributes together on the same element | ✅ | 🔧 | |
58
+ | [no-import-assertions](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-import-assertions.md) | Replace legacy `assert { ... }` import assertions with `with { ... }` | ✅ | 🔧 | |
59
+ | [no-implicit-public](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-implicit-public.md) | Require explicit `public` modifier for class members and parameter properties | ✅ | 🔧 | |
60
+ | [no-infinite-loop](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-infinite-loop.md) | Disallow `while (true)` and `for` loops without an explicit condition | ✅ | | |
61
+ | [no-legacy-peer-deps](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-legacy-peer-deps.md) | Disallow `legacy-peer-deps=true` in `.npmrc` | ✅ | | |
62
+ | [no-obsolete-attrs](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-obsolete-attrs.md) | Disallow obsolete HTML attributes | | | |
63
+ | [no-obsolete-tags](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-obsolete-tags.md) | Disallow obsolete HTML tags | | | |
64
+ | [no-playwright-empty-fill](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-playwright-empty-fill.md) | Enforce `clear()` over `fill('')` in Playwright tests | ✅ | 🔧 | |
65
+ | [no-project-as-in-ng-template](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-project-as-in-ng-template.md) | `ngProjectAs` has no effect inside `<ng-template>` or dynamic outlets | ✅ | | |
66
+ | [no-restricted-attr-values](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-restricted-attr-values.md) | Disallow configured attribute values in Angular templates | | | |
67
+ | [no-redundant-type-annotation](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-redundant-type-annotation.md) | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | |
68
+ | [no-side-effects-in-computed](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-side-effects-in-computed.md) | Disallow side effects and effectful helper calls inside Angular `computed()` callbacks | ✅ | | |
69
+ | [no-signal-reads-after-await-in-reactive-context](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-signal-reads-after-await-in-reactive-context.md) | Disallow bare signal reads after `await` inside reactive callbacks | ✅ | | |
70
+ | [no-string-literal-concat](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-string-literal-concat.md) | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | |
71
+ | [no-untracked-outside-reactive-context](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-untracked-outside-reactive-context.md) | Disallow `untracked()` outside reactive callbacks, except explicit post-`await` snapshots | ✅ | 🔧 | |
72
+ | [no-useless-untracked](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-useless-untracked.md) | Disallow provably useless `untracked()` wrappers in reactive callbacks | ✅ | 🔧 | |
73
+ | [object-single-line](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/object-single-line.md) | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
74
+ | [prefer-combined-if-control-flow](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-combined-if-control-flow.md) | Combine consecutive `if` statements that use the same `return`, `break`, `continue`, or `throw` | ✅ | 🔧 | |
75
+ | [prefer-deep-imports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-deep-imports.md) | Allow deep imports of Taiga UI packages | | 🔧 | |
76
+ | [prefer-multi-arg-push](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-multi-arg-push.md) | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
77
+ | [prefer-namespace-keyword](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-namespace-keyword.md) | Replace `module Foo {}` with `namespace Foo {}` for TypeScript namespace declarations | ✅ | 🔧 | |
78
+ | [prefer-untracked-incidental-signal-reads](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-untracked-incidental-signal-reads.md) | Wrap likely-incidental signal reads with `untracked()` in reactive callbacks | ✅ | 🔧 | |
79
+ | [prefer-untracked-signal-getter](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/prefer-untracked-signal-getter.md) | Prefer `untracked(signalGetter)` over `untracked(() => signalGetter())` for a single signal getter | ✅ | 🔧 | |
80
+ | [quotes](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/quotes.md) | Enforce double quotes around HTML attribute values | | 🔧 | |
81
+ | [require-doctype](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/require-doctype.md) | Require `<!DOCTYPE html>` at the top of HTML documents | | 🔧 | |
82
+ | [require-img-alt](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/require-img-alt.md) | Require `alt` on `<img>` elements, including Angular attribute bindings | | | |
83
+ | [require-lang](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/require-lang.md) | Require a non-empty `lang` attribute on `<html>` | | | |
84
+ | [require-li-container](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/require-li-container.md) | Require `<li>` to be nested inside `<ul>`, `<ol>`, or `<menu>` | | | |
85
+ | [require-title](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/require-title.md) | Require a non-empty `<title>` inside `<head>` | | | |
86
+ | [short-tui-imports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/short-tui-imports.md) | Shorten TuiXxxComponent / TuiYyyDirective in Angular metadata | ✅ | 🔧 | |
87
+ | [single-line-class-property-spacing](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/single-line-class-property-spacing.md) | Group consecutive single-line class properties and separate multiline ones with a blank line | ✅ | 🔧 | |
88
+ | [standalone-imports-sort](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/standalone-imports-sort.md) | Auto sort names inside Angular decorators | ✅ | 🔧 | |
89
+ | [strict-tui-doc-example](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/strict-tui-doc-example.md) | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |