@taiga-ui/eslint-plugin-experience-next 0.466.0 → 0.467.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.
- package/README.md +385 -21
- package/index.d.ts +18 -0
- package/index.esm.js +1591 -66
- package/package.json +1 -1
- package/rules/no-fully-untracked-effect.d.ts +5 -0
- package/rules/no-signal-reads-after-await-in-reactive-context.d.ts +5 -0
- package/rules/no-untracked-outside-reactive-context.d.ts +5 -0
- package/rules/no-useless-untracked.d.ts +5 -0
- package/rules/prefer-untracked-incidental-signal-reads.d.ts +30 -0
- package/rules/prefer-untracked-signal-getter.d.ts +5 -0
- package/rules/utils/angular-imports.d.ts +14 -0
- package/rules/utils/angular-signals.d.ts +45 -0
- package/rules/utils/ast-expressions.d.ts +7 -0
- package/rules/utils/ast-walk.d.ts +30 -0
- package/rules/utils/import-fix-helpers.d.ts +14 -0
- package/rules/utils/untracked-docs.d.ts +7 -0
package/README.md
CHANGED
|
@@ -37,32 +37,41 @@ export default [
|
|
|
37
37
|
- 🔧 = fixable
|
|
38
38
|
- 💡 = has suggestions
|
|
39
39
|
|
|
40
|
-
| Rule
|
|
41
|
-
|
|
|
42
|
-
| array-as-const
|
|
43
|
-
| class-property-naming
|
|
44
|
-
| decorator-key-sort
|
|
45
|
-
| flat-exports
|
|
46
|
-
| injection-token-description
|
|
47
|
-
| no-deep-imports
|
|
48
|
-
| no-deep-imports-to-indexed-packages
|
|
49
|
-
| no-
|
|
50
|
-
| no-
|
|
51
|
-
| no-
|
|
52
|
-
| no-
|
|
53
|
-
| no-
|
|
54
|
-
| no-
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
40
|
+
| Rule | Description | ✅ | 🔧 | 💡 |
|
|
41
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------- | --- | --- | --- |
|
|
42
|
+
| array-as-const | Exported array of class references should be marked with `as const` | | 🔧 | |
|
|
43
|
+
| class-property-naming | Enforce custom naming for class properties based on their type | | 🔧 | |
|
|
44
|
+
| decorator-key-sort | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
|
|
45
|
+
| flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
|
|
46
|
+
| injection-token-description | They are required to provide a description for `InjectionToken` | ✅ | | |
|
|
47
|
+
| no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
|
|
48
|
+
| no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
|
|
49
|
+
| no-fully-untracked-effect | Disallow reactive callbacks where all signal reads are hidden inside `untracked()` | ✅ | | |
|
|
50
|
+
| no-href-with-router-link | Do not use href and routerLink attributes together on the same element | ✅ | 🔧 | |
|
|
51
|
+
| no-implicit-public | Require explicit `public` modifier for class members and parameter properties | ✅ | 🔧 | |
|
|
52
|
+
| no-legacy-peer-deps | Disallow `legacy-peer-deps=true` in `.npmrc` | ✅ | | |
|
|
53
|
+
| no-playwright-empty-fill | Enforce `clear()` over `fill('')` in Playwright tests | ✅ | 🔧 | |
|
|
54
|
+
| no-project-as-in-ng-template | `ngProjectAs` has no effect inside `<ng-template>` or dynamic outlets | ✅ | | |
|
|
55
|
+
| no-redundant-type-annotation | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | |
|
|
56
|
+
| no-signal-reads-after-await-in-reactive-context | Disallow signal reads after `await` inside reactive callbacks | ✅ | | |
|
|
57
|
+
| no-string-literal-concat | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | |
|
|
58
|
+
| no-untracked-outside-reactive-context | Disallow `untracked()` outside the synchronous body of reactive callbacks | ✅ | 🔧 | |
|
|
59
|
+
| no-useless-untracked | Disallow provably useless `untracked()` wrappers in reactive callbacks | ✅ | 🔧 | |
|
|
60
|
+
| object-single-line | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
|
|
61
|
+
| prefer-deep-imports | Allow deep imports of Taiga UI packages | | 🔧 | |
|
|
62
|
+
| prefer-multi-arg-push | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
|
|
63
|
+
| prefer-untracked-incidental-signal-reads | Wrap likely-incidental signal reads with `untracked()` in reactive callbacks | ✅ | 🔧 | |
|
|
64
|
+
| prefer-untracked-signal-getter | Prefer `untracked(signalGetter)` over `untracked(() => signalGetter())` for a single signal getter | ✅ | 🔧 | |
|
|
65
|
+
| short-tui-imports | Shorten TuiXxxComponent / TuiYyyDirective in Angular metadata | ✅ | 🔧 | |
|
|
66
|
+
| standalone-imports-sort | Auto sort names inside Angular decorators | ✅ | 🔧 | |
|
|
67
|
+
| strict-tui-doc-example | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |
|
|
61
68
|
|
|
62
69
|
---
|
|
63
70
|
|
|
64
71
|
## array-as-const
|
|
65
72
|
|
|
73
|
+
<sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
|
|
74
|
+
|
|
66
75
|
Exported arrays containing only class references must be marked with `as const` to preserve the tuple type and enable
|
|
67
76
|
proper type inference.
|
|
68
77
|
|
|
@@ -78,6 +87,8 @@ export const PROVIDERS = [FooService, BarService] as const;
|
|
|
78
87
|
|
|
79
88
|
## class-property-naming
|
|
80
89
|
|
|
90
|
+
<sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
|
|
91
|
+
|
|
81
92
|
Enforce custom naming conventions for class properties based on their TypeScript type. Useful for enforcing project-wide
|
|
82
93
|
patterns (e.g. all `Subject` fields must be called `destroy$`).
|
|
83
94
|
|
|
@@ -114,6 +125,8 @@ class MyComponent {
|
|
|
114
125
|
|
|
115
126
|
## decorator-key-sort
|
|
116
127
|
|
|
128
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
129
|
+
|
|
117
130
|
Enforces a consistent key order inside Angular decorator objects (`@Component`, `@Directive`, `@NgModule`, `@Pipe`,
|
|
118
131
|
`@Injectable`). The expected order is passed as configuration.
|
|
119
132
|
|
|
@@ -149,6 +162,8 @@ Enforces a consistent key order inside Angular decorator objects (`@Component`,
|
|
|
149
162
|
|
|
150
163
|
## flat-exports
|
|
151
164
|
|
|
165
|
+
<sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
|
|
166
|
+
|
|
152
167
|
When an exported `as const` tuple contains another exported `as const` tuple of Angular classes, it should be spread
|
|
153
168
|
rather than nested. This keeps entity collections flat and avoids double-wrapping.
|
|
154
169
|
|
|
@@ -166,6 +181,8 @@ export const TuiInput = [...TuiTextfield, TuiInputDirective] as const;
|
|
|
166
181
|
|
|
167
182
|
## injection-token-description
|
|
168
183
|
|
|
184
|
+
<sup>`✅ Recommended`</sup>
|
|
185
|
+
|
|
169
186
|
The description string passed to `new InjectionToken(...)` must contain the name of the variable it is assigned to. This
|
|
170
187
|
makes token names visible in Angular DevTools and error messages.
|
|
171
188
|
|
|
@@ -181,6 +198,8 @@ const TUI_MY_TOKEN = new InjectionToken<string>('[TUI_MY_TOKEN]: some descriptio
|
|
|
181
198
|
|
|
182
199
|
## no-deep-imports
|
|
183
200
|
|
|
201
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
202
|
+
|
|
184
203
|
Disallows deep path imports from Taiga UI packages — imports must go through the package root. Works for any
|
|
185
204
|
`@taiga-ui/*` package by default. Autofix strips the deep path.
|
|
186
205
|
|
|
@@ -216,6 +235,8 @@ import {TuiButton} from '@taiga-ui/core';
|
|
|
216
235
|
|
|
217
236
|
## no-deep-imports-to-indexed-packages
|
|
218
237
|
|
|
238
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
239
|
+
|
|
219
240
|
Disallows deep imports from any external package whose root `index.ts` (or `index.d.ts`) re-exports the same subpath and
|
|
220
241
|
is co-located with a `package.json` or `ng-package.json`. Does not require explicit package lists — resolves via
|
|
221
242
|
TypeScript.
|
|
@@ -230,8 +251,43 @@ import {Foo} from '@my-lib/internal';
|
|
|
230
251
|
|
|
231
252
|
---
|
|
232
253
|
|
|
254
|
+
## no-fully-untracked-effect
|
|
255
|
+
|
|
256
|
+
<sup>`✅ Recommended`</sup>
|
|
257
|
+
|
|
258
|
+
Reports a reactive callback whose signal reads are all wrapped in `untracked()`. That leaves the callback without
|
|
259
|
+
tracked dependencies, so Angular will not re-run it when those signals change.
|
|
260
|
+
|
|
261
|
+
Applies to `effect()`, `computed()`, `linkedSignal()`, `resource()` callbacks, and `afterRenderEffect()` phases.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// ❌ error — no tracked reads, effect never re-runs
|
|
265
|
+
effect(() => {
|
|
266
|
+
const value = untracked(() => this.count());
|
|
267
|
+
this.log(value);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ✅ ok — count() is read outside untracked, creates a reactive dependency
|
|
271
|
+
effect(() => {
|
|
272
|
+
const value = this.count();
|
|
273
|
+
untracked(() => this.log(value));
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// ❌ error — computed() also loses its dependency
|
|
279
|
+
const doubled = computed(() => untracked(() => this.count() * 2));
|
|
280
|
+
|
|
281
|
+
// ✅ ok
|
|
282
|
+
const doubled = computed(() => this.count() * 2);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
233
287
|
## no-href-with-router-link
|
|
234
288
|
|
|
289
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
290
|
+
|
|
235
291
|
> ✅ Included in `recommended` — processed by the angular-eslint template parser (`**/*.html`).
|
|
236
292
|
|
|
237
293
|
Disallows using both `href` and `routerLink` on the same `<a>` element in Angular templates. Autofix removes the `href`
|
|
@@ -254,6 +310,8 @@ attribute.
|
|
|
254
310
|
|
|
255
311
|
## no-implicit-public
|
|
256
312
|
|
|
313
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
314
|
+
|
|
257
315
|
Requires an explicit `public` modifier on all class members and constructor parameter properties that are public.
|
|
258
316
|
Constructors are excluded.
|
|
259
317
|
|
|
@@ -273,8 +331,32 @@ class MyService {
|
|
|
273
331
|
|
|
274
332
|
---
|
|
275
333
|
|
|
334
|
+
## no-legacy-peer-deps
|
|
335
|
+
|
|
336
|
+
<sup>`✅ Recommended`</sup>
|
|
337
|
+
|
|
338
|
+
> ✅ Included in `recommended` — applied to `**/.npmrc`.
|
|
339
|
+
|
|
340
|
+
Disallows `legacy-peer-deps=true` in `.npmrc`. This npm option bypasses peer dependency resolution and can hide real
|
|
341
|
+
version conflicts in the dependency graph. The preferred fix is to align incompatible package versions instead of
|
|
342
|
+
disabling the resolver.
|
|
343
|
+
|
|
344
|
+
```ini
|
|
345
|
+
# ❌ error
|
|
346
|
+
legacy-peer-deps=true
|
|
347
|
+
|
|
348
|
+
# ✅ ok
|
|
349
|
+
strict-peer-deps=true
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Comments and empty lines are ignored, so the rule only reports an active `legacy-peer-deps=true` entry.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
276
356
|
## no-playwright-empty-fill
|
|
277
357
|
|
|
358
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
359
|
+
|
|
278
360
|
In Playwright tests, calling `.fill('')` on a locator should be replaced with `.clear()` — it is the idiomatic way to
|
|
279
361
|
empty a field and communicates intent more clearly.
|
|
280
362
|
|
|
@@ -290,6 +372,8 @@ await page.getByLabel('Name').clear();
|
|
|
290
372
|
|
|
291
373
|
## no-project-as-in-ng-template
|
|
292
374
|
|
|
375
|
+
<sup>`✅ Recommended`</sup>
|
|
376
|
+
|
|
293
377
|
`ngProjectAs` has no effect when the element is inside an `<ng-template>`, `*ngTemplateOutlet`, `*ngComponentOutlet`, or
|
|
294
378
|
`*polymorpheusOutlet`. Content instantiated through these dynamic outlets does not participate in Angular's static
|
|
295
379
|
content projection, so the attribute is silently ignored at runtime.
|
|
@@ -320,6 +404,8 @@ content projection, so the attribute is silently ignored at runtime.
|
|
|
320
404
|
|
|
321
405
|
## no-string-literal-concat
|
|
322
406
|
|
|
407
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
408
|
+
|
|
323
409
|
Disallows concatenating string literals with `+`. Adjacent string literals are always mergeable into one — splitting
|
|
324
410
|
them with `+` adds noise without benefit, and multi-line splits are especially easy to miss.
|
|
325
411
|
|
|
@@ -405,8 +491,192 @@ const urlRegex =
|
|
|
405
491
|
|
|
406
492
|
---
|
|
407
493
|
|
|
494
|
+
## no-useless-untracked
|
|
495
|
+
|
|
496
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
497
|
+
|
|
498
|
+
Inside a reactive callback, `untracked()` is only meaningful when its inner function reads signals or intentionally
|
|
499
|
+
wraps opaque external code that may read signals. It can also be a valid escape hatch when a reactive callback needs to
|
|
500
|
+
create another reactive owner such as `effect()` without inheriting the ambient reactive context. Wrapping code with no
|
|
501
|
+
signal reads and no such escape-hatch purpose in `untracked()` is noise. Autofix unwraps the callback in-place when that
|
|
502
|
+
is structurally safe and removes the `untracked` import when it is no longer used elsewhere. Snapshot reads that later
|
|
503
|
+
influence branching are still valid and are not reported, because Angular allows incidental reads inside `effect()` and
|
|
504
|
+
similar reactive callbacks.
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
// ❌ error — no signal reads inside untracked, wrapper is pointless
|
|
508
|
+
effect(() => {
|
|
509
|
+
untracked(() => {
|
|
510
|
+
this.count.set(0);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// ✅ after autofix
|
|
515
|
+
effect(() => {
|
|
516
|
+
this.count.set(0);
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
// ✅ ok — snapshot reads may influence control flow without becoming dependencies
|
|
522
|
+
effect(() => {
|
|
523
|
+
const value = untracked(() => this.value());
|
|
524
|
+
|
|
525
|
+
if (this.showAdjacent() && value !== null) {
|
|
526
|
+
this.month.set(value);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
// ✅ ok — linkedSignal fallback may intentionally read a snapshot
|
|
533
|
+
const activeYear = linkedSignal(() => {
|
|
534
|
+
const year = this.year();
|
|
535
|
+
|
|
536
|
+
if (year) {
|
|
537
|
+
return year;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const value = untracked(() => this.value());
|
|
541
|
+
|
|
542
|
+
return value ?? TODAY;
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
// ✅ ok — wrapping external code is a valid Angular use-case
|
|
548
|
+
effect(() => {
|
|
549
|
+
const user = this.user();
|
|
550
|
+
untracked(() => this.logger.log(user));
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
// ✅ ok — creating a nested effect() may need to escape the current reactive context
|
|
556
|
+
const doubled = computed(() => {
|
|
557
|
+
untracked(() => {
|
|
558
|
+
effect(() => {
|
|
559
|
+
console.log(this.count());
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
return this.count() * 2;
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## no-signal-reads-after-await-in-reactive-context
|
|
570
|
+
|
|
571
|
+
<sup>`✅ Recommended`</sup>
|
|
572
|
+
|
|
573
|
+
Angular tracks signal reads only in synchronous code. If a reactive callback crosses an async boundary, any signal read
|
|
574
|
+
after `await` will not become a dependency.
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
// ❌ error
|
|
578
|
+
effect(async () => {
|
|
579
|
+
await this.fetchUser();
|
|
580
|
+
console.log(this.theme());
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// ✅ ok
|
|
584
|
+
effect(async () => {
|
|
585
|
+
const theme = this.theme();
|
|
586
|
+
await this.fetchUser();
|
|
587
|
+
console.log(theme);
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## no-untracked-outside-reactive-context
|
|
594
|
+
|
|
595
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
596
|
+
|
|
597
|
+
`untracked()` usually only affects signal reads that happen inside the synchronous body of a reactive callback. In
|
|
598
|
+
ordinary non-reactive code, nested callbacks, or code that runs after `await`, it usually does not prevent dependency
|
|
599
|
+
tracking and only adds noise. This rule reports those cases, but intentionally allows a few imperative Angular escape
|
|
600
|
+
hatches where `untracked()` can still be useful: `@Pipe().transform`, `ControlValueAccessor.writeValue`,
|
|
601
|
+
`registerOnChange`, callback-form wrappers used inside deferred scheduler / event-handler callbacks, and narrow lazy DI
|
|
602
|
+
factory wrappers like `InjectionToken({factory})` / `useFactory` when they guard creation of a reactive owner such as
|
|
603
|
+
`effect()` against an accidental ambient reactive context. For the narrow case `untracked(() => effect(...))` and
|
|
604
|
+
similar outer wrappers around a reactive call in ordinary code, autofix removes only the useless outer `untracked()`
|
|
605
|
+
wrapper.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
// ❌ error
|
|
609
|
+
const snapshot = untracked(this.user);
|
|
610
|
+
|
|
611
|
+
effect(() => {
|
|
612
|
+
button.addEventListener('click', () => {
|
|
613
|
+
console.log(untracked(this.user));
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
// ✅ ok
|
|
620
|
+
effect(() => {
|
|
621
|
+
console.log(untracked(this.user));
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const snapshot = computed(() => untracked(this.user));
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
// ❌ error
|
|
629
|
+
untracked(() => {
|
|
630
|
+
effect(() => {
|
|
631
|
+
console.log(this.user());
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// ✅ after autofix
|
|
636
|
+
effect(() => {
|
|
637
|
+
console.log(this.user());
|
|
638
|
+
});
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
// ✅ ok — imperative Angular hooks may still need untracked
|
|
643
|
+
@Pipe({name: 'demo', pure: false})
|
|
644
|
+
export class DemoPipe implements PipeTransform {
|
|
645
|
+
private readonly value = signal('');
|
|
646
|
+
|
|
647
|
+
transform(next: string): string {
|
|
648
|
+
untracked(() => this.value.set(next));
|
|
649
|
+
return this.value();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
// ✅ ok — deferred callback wrappers may execute under reactive control later
|
|
656
|
+
const update = (): void => untracked(() => value.set(input.value));
|
|
657
|
+
|
|
658
|
+
input.addEventListener('input', update, {capture: true});
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
// ✅ ok — lazy DI factories may first execute from an ambient reactive context
|
|
663
|
+
export const TOKEN = new InjectionToken<void>('TOKEN', {
|
|
664
|
+
factory: () => {
|
|
665
|
+
untracked(() => {
|
|
666
|
+
effect(() => {
|
|
667
|
+
console.log(count());
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
408
676
|
## object-single-line
|
|
409
677
|
|
|
678
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
679
|
+
|
|
410
680
|
Single-property object literals that fit within `printWidth` characters on one line are collapsed to a single line.
|
|
411
681
|
Compatible with Prettier formatting.
|
|
412
682
|
|
|
@@ -434,6 +704,8 @@ const x = {foo: bar};
|
|
|
434
704
|
|
|
435
705
|
## prefer-deep-imports
|
|
436
706
|
|
|
707
|
+
<sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
|
|
708
|
+
|
|
437
709
|
Enforce imports from the deepest available entry point of Taiga UI packages.
|
|
438
710
|
|
|
439
711
|
```json
|
|
@@ -454,6 +726,8 @@ Use `strict` to forbid imports from intermediate entry points when deeper ones e
|
|
|
454
726
|
|
|
455
727
|
## prefer-multi-arg-push
|
|
456
728
|
|
|
729
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
730
|
+
|
|
457
731
|
Combine consecutive `.push()` calls on the same array into a single multi-argument call.
|
|
458
732
|
|
|
459
733
|
```ts
|
|
@@ -467,8 +741,92 @@ output.push('# Getting Started', '');
|
|
|
467
741
|
|
|
468
742
|
---
|
|
469
743
|
|
|
744
|
+
## prefer-untracked-incidental-signal-reads
|
|
745
|
+
|
|
746
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
747
|
+
|
|
748
|
+
Inside a reactive callback, flags direct signal reads that look like snapshot-only values passed into writable-signal
|
|
749
|
+
writes such as `.set()` or into DOM side-effect calls such as `requestFullscreen(...)`. These reads are likely
|
|
750
|
+
incidental and should usually not create their own dependency. The rule only reports when the callback already has
|
|
751
|
+
another tracked dependency outside the flagged consumer call, and autofix wraps the incidental read with `untracked()`
|
|
752
|
+
while adding the import if needed. If the read is intentionally reactive, disable the rule for that line.
|
|
753
|
+
|
|
754
|
+
```ts
|
|
755
|
+
// ❌ error
|
|
756
|
+
effect(() => {
|
|
757
|
+
if (this.options().length) {
|
|
758
|
+
this.input.value.set(this.stringified());
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// ✅ after autofix
|
|
763
|
+
effect(() => {
|
|
764
|
+
if (this.options().length) {
|
|
765
|
+
this.input.value.set(untracked(() => this.stringified()));
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
// ❌ error
|
|
772
|
+
effect(() => {
|
|
773
|
+
if (this.options().length) {
|
|
774
|
+
const value = this.stringified();
|
|
775
|
+
|
|
776
|
+
this.input.value.set(value);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// ✅ after autofix
|
|
781
|
+
effect(() => {
|
|
782
|
+
if (this.options().length) {
|
|
783
|
+
const value = untracked(() => this.stringified());
|
|
784
|
+
|
|
785
|
+
this.input.value.set(value);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
// ❌ error
|
|
792
|
+
effect(async () => {
|
|
793
|
+
if (this.tuiFullscreen()) {
|
|
794
|
+
await this.root()?.requestFullscreen(this.options());
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// ✅ after autofix
|
|
799
|
+
effect(async () => {
|
|
800
|
+
if (this.tuiFullscreen()) {
|
|
801
|
+
await this.root()?.requestFullscreen(untracked(() => this.options()));
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## prefer-untracked-signal-getter
|
|
809
|
+
|
|
810
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
811
|
+
|
|
812
|
+
When `untracked()` wraps only a single signal getter, prefer passing that getter directly. This keeps the code shorter
|
|
813
|
+
while preserving the same untracked signal read semantics. The rule intentionally skips real TypeScript getters, because
|
|
814
|
+
property access would happen before `untracked()` starts.
|
|
815
|
+
|
|
816
|
+
```ts
|
|
817
|
+
// ❌ error
|
|
818
|
+
const snapshot = untracked(() => this.counter());
|
|
819
|
+
|
|
820
|
+
// ✅ after autofix
|
|
821
|
+
const snapshot = untracked(this.counter);
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
470
826
|
## short-tui-imports
|
|
471
827
|
|
|
828
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
829
|
+
|
|
472
830
|
In Angular decorator `imports` arrays, replaces full `TuiXxxComponent` / `TuiYyyDirective` names with their shorthand
|
|
473
831
|
aliases (e.g. `TuiButton`). Also updates the corresponding `import` statement.
|
|
474
832
|
|
|
@@ -509,6 +867,8 @@ import {TuiButton} from '@taiga-ui/core';
|
|
|
509
867
|
|
|
510
868
|
## standalone-imports-sort
|
|
511
869
|
|
|
870
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
871
|
+
|
|
512
872
|
Sorts the `imports` array inside Angular decorators (`@Component`, `@Directive`, `@NgModule`, `@Pipe`) alphabetically.
|
|
513
873
|
Spread elements are placed after named identifiers.
|
|
514
874
|
|
|
@@ -537,6 +897,8 @@ Spread elements are placed after named identifiers.
|
|
|
537
897
|
|
|
538
898
|
## no-redundant-type-annotation
|
|
539
899
|
|
|
900
|
+
<sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
|
|
901
|
+
|
|
540
902
|
Disallow explicit type annotations on class properties and variable declarations when TypeScript can already infer the
|
|
541
903
|
same type from the initializer. Requires type information (`parserOptions.project`).
|
|
542
904
|
|
|
@@ -599,6 +961,8 @@ const state: ElementState = flag ? {value: '', selection: [0, 0]} : existingStat
|
|
|
599
961
|
|
|
600
962
|
## strict-tui-doc-example
|
|
601
963
|
|
|
964
|
+
<sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
|
|
965
|
+
|
|
602
966
|
Validates that properties of a `TuiDocExample`-typed object have keys matching known file-type names (`TypeScript`,
|
|
603
967
|
`HTML`, `CSS`, `LESS`, `JavaScript`) and that the import path extension matches the key. Autofix corrects the import
|
|
604
968
|
extension.
|
package/index.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ declare const plugin: {
|
|
|
34
34
|
'no-deep-imports-to-indexed-packages': import("@typescript-eslint/utils/ts-eslint").RuleModule<"deepImport", readonly unknown[], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
35
35
|
name: string;
|
|
36
36
|
};
|
|
37
|
+
'no-fully-untracked-effect': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noTrackedReads", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
37
40
|
'no-href-with-router-link': import("eslint").Rule.RuleModule;
|
|
38
41
|
'no-implicit-public': import("@typescript-eslint/utils/ts-eslint").RuleModule<"implicitPublic", readonly unknown[], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
39
42
|
name: string;
|
|
@@ -50,9 +53,18 @@ declare const plugin: {
|
|
|
50
53
|
} | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
51
54
|
name: string;
|
|
52
55
|
};
|
|
56
|
+
'no-signal-reads-after-await-in-reactive-context': import("@typescript-eslint/utils/ts-eslint").RuleModule<"readAfterAwait", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
57
|
+
name: string;
|
|
58
|
+
};
|
|
53
59
|
'no-string-literal-concat': import("@typescript-eslint/utils/ts-eslint").RuleModule<"flattenTemplate" | "mergeLiterals" | "useTemplate", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
54
60
|
name: string;
|
|
55
61
|
};
|
|
62
|
+
'no-untracked-outside-reactive-context': import("@typescript-eslint/utils/ts-eslint").RuleModule<"outsideReactiveContext", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
63
|
+
name: string;
|
|
64
|
+
};
|
|
65
|
+
'no-useless-untracked': import("@typescript-eslint/utils/ts-eslint").RuleModule<"uselessUntracked", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
66
|
+
name: string;
|
|
67
|
+
};
|
|
56
68
|
'object-single-line': import("@typescript-eslint/utils/ts-eslint").RuleModule<"oneLine", [{
|
|
57
69
|
printWidth: number;
|
|
58
70
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
@@ -67,6 +79,12 @@ declare const plugin: {
|
|
|
67
79
|
'prefer-multi-arg-push': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferMultiArgPush", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
68
80
|
name: string;
|
|
69
81
|
};
|
|
82
|
+
'prefer-untracked-incidental-signal-reads': import("@typescript-eslint/utils/ts-eslint").RuleModule<"incidentalRead", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
83
|
+
name: string;
|
|
84
|
+
};
|
|
85
|
+
'prefer-untracked-signal-getter': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferGetterForm", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
86
|
+
name: string;
|
|
87
|
+
};
|
|
70
88
|
'short-tui-imports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceTuiImport", import("./rules/short-tui-imports").Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
71
89
|
name: string;
|
|
72
90
|
};
|