@taiga-ui/eslint-plugin-experience-next 0.438.0 → 0.440.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 CHANGED
@@ -49,12 +49,267 @@ export default [
49
49
  | no-href-with-router-link | Do not use href and routerLink attributes together on the same element | | 🔧 | |
50
50
  | no-implicit-public | Require explicit `public` modifier for class members and parameter properties | ✅ | 🔧 | |
51
51
  | no-playwright-empty-fill | Enforce `clear()` over `fill('')` in Playwright tests | ✅ | 🔧 | |
52
- | standalone-imports-sort | Sort imports alphabetically | ✅ | 🔧 | |
52
+ | object-single-line | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
53
53
  | prefer-deep-imports | Allow deep imports of Taiga UI packages | | 🔧 | |
54
+ | prefer-multi-arg-push | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
54
55
  | short-tui-imports | Shorten TuiXxxComponent / TuiYyyDirective in Angular metadata | ✅ | 🔧 | |
55
56
  | standalone-imports-sort | Auto sort names inside Angular decorators | ✅ | 🔧 | |
56
57
  | strict-tui-doc-example | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |
57
58
 
59
+ ---
60
+
61
+ ## array-as-const
62
+
63
+ Exported arrays containing only class references must be marked with `as const` to preserve the tuple type and enable
64
+ proper type inference.
65
+
66
+ ```ts
67
+ // ❌ error
68
+ export const PROVIDERS = [FooService, BarService];
69
+
70
+ // ✅ after autofix
71
+ export const PROVIDERS = [FooService, BarService] as const;
72
+ ```
73
+
74
+ ---
75
+
76
+ ## class-property-naming
77
+
78
+ Enforce custom naming conventions for class properties based on their TypeScript type. Useful for enforcing project-wide
79
+ patterns (e.g. all `Subject` fields must be called `destroy$`).
80
+
81
+ Requires explicit configuration — not enabled in `recommended` by default.
82
+
83
+ ```json
84
+ {
85
+ "@taiga-ui/experience-next/class-property-naming": [
86
+ "error",
87
+ [
88
+ {
89
+ "fieldNames": ["sub", "subscription"],
90
+ "newFieldName": "destroy$",
91
+ "withTypesSpecifier": ["Subject", "Subscription"]
92
+ }
93
+ ]
94
+ ]
95
+ }
96
+ ```
97
+
98
+ ```ts
99
+ // ❌ error
100
+ class MyComponent {
101
+ sub = new Subject<void>();
102
+ }
103
+
104
+ // ✅ after autofix
105
+ class MyComponent {
106
+ destroy$ = new Subject<void>();
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## decorator-key-sort
113
+
114
+ Enforces a consistent key order inside Angular decorator objects (`@Component`, `@Directive`, `@NgModule`, `@Pipe`,
115
+ `@Injectable`). The expected order is passed as configuration.
116
+
117
+ ```json
118
+ {
119
+ "@taiga-ui/experience-next/decorator-key-sort": [
120
+ "error",
121
+ {
122
+ "Component": ["standalone", "selector", "imports", "templateUrl", "styleUrl", "changeDetection"],
123
+ "Pipe": ["standalone", "name", "pure"]
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ ```ts
130
+ // ❌ error
131
+ @Component({
132
+ templateUrl: './app.component.html',
133
+ selector: 'app-root',
134
+ standalone: true,
135
+ })
136
+
137
+ // ✅ after autofix
138
+ @Component({
139
+ standalone: true,
140
+ selector: 'app-root',
141
+ templateUrl: './app.component.html',
142
+ })
143
+ ```
144
+
145
+ ---
146
+
147
+ ## flat-exports
148
+
149
+ When an exported `as const` tuple contains another exported `as const` tuple of Angular classes, it should be spread
150
+ rather than nested. This keeps entity collections flat and avoids double-wrapping.
151
+
152
+ ```ts
153
+ // ❌ error
154
+ export const TuiTextfield = [TuiTextfieldDirective] as const;
155
+ export const TuiInput = [TuiTextfield, TuiInputDirective] as const;
156
+
157
+ // ✅ after autofix
158
+ export const TuiTextfield = [TuiTextfieldDirective] as const;
159
+ export const TuiInput = [...TuiTextfield, TuiInputDirective] as const;
160
+ ```
161
+
162
+ ---
163
+
164
+ ## injection-token-description
165
+
166
+ The description string passed to `new InjectionToken(...)` must contain the name of the variable it is assigned to. This
167
+ makes token names visible in Angular DevTools and error messages.
168
+
169
+ ```ts
170
+ // ❌ error — description does not mention TUI_MY_TOKEN
171
+ const TUI_MY_TOKEN = new InjectionToken<string>('some description');
172
+
173
+ // ✅ after autofix
174
+ const TUI_MY_TOKEN = new InjectionToken<string>('[TUI_MY_TOKEN]: some description');
175
+ ```
176
+
177
+ ---
178
+
179
+ ## no-deep-imports
180
+
181
+ Disallows deep path imports from Taiga UI packages — imports must go through the package root. Works for any
182
+ `@taiga-ui/*` package by default. Autofix strips the deep path.
183
+
184
+ ```ts
185
+ // ❌ error
186
+ import {TuiButton} from '@taiga-ui/core/components/button';
187
+
188
+ // ✅ after autofix
189
+ import {TuiButton} from '@taiga-ui/core';
190
+ ```
191
+
192
+ ```json
193
+ {
194
+ "@taiga-ui/experience-next/no-deep-imports": [
195
+ "error",
196
+ {
197
+ "currentProject": "(?<=projects/)([\\w-]+)",
198
+ "ignoreImports": ["\\?raw", "@taiga-ui/testing/cypress"]
199
+ }
200
+ ]
201
+ }
202
+ ```
203
+
204
+ | Option | Type | Description |
205
+ | ------------------- | ---------- | -------------------------------------------------------------------------- |
206
+ | `currentProject` | `string` | RegExp to extract the current project name from the file path |
207
+ | `deepImport` | `string` | RegExp to detect the deep import segment (default: `@taiga-ui/` sub-paths) |
208
+ | `importDeclaration` | `string` | RegExp to match import declarations the rule applies to |
209
+ | `ignoreImports` | `string[]` | RegExp patterns for imports to ignore |
210
+ | `projectName` | `string` | RegExp to extract the package name from the import source |
211
+
212
+ ---
213
+
214
+ ## no-deep-imports-to-indexed-packages
215
+
216
+ Disallows deep imports from any external package whose root `index.ts` (or `index.d.ts`) re-exports the same subpath and
217
+ is co-located with a `package.json` or `ng-package.json`. Does not require explicit package lists — resolves via
218
+ TypeScript.
219
+
220
+ ```ts
221
+ // ❌ error — @my-lib/index.ts already re-exports this subpath
222
+ import {Foo} from '@my-lib/internal/foo';
223
+
224
+ // ✅
225
+ import {Foo} from '@my-lib/internal';
226
+ ```
227
+
228
+ ---
229
+
230
+ ## no-href-with-router-link
231
+
232
+ Disallows using both `href` and `routerLink` on the same `<a>` element in Angular templates. Autofix removes the `href`
233
+ attribute.
234
+
235
+ ```html
236
+ <!-- ❌ error -->
237
+ <a
238
+ href="/home"
239
+ routerLink="/home"
240
+ >
241
+ Home
242
+ </a>
243
+
244
+ <!-- ✅ after autofix -->
245
+ <a routerLink="/home">Home</a>
246
+ ```
247
+
248
+ ---
249
+
250
+ ## no-implicit-public
251
+
252
+ Requires an explicit `public` modifier on all class members and constructor parameter properties that are public.
253
+ Constructors are excluded.
254
+
255
+ ```ts
256
+ // ❌ error
257
+ class MyService {
258
+ value = 42;
259
+ doSomething(): void {}
260
+ }
261
+
262
+ // ✅ after autofix
263
+ class MyService {
264
+ public value = 42;
265
+ public doSomething(): void {}
266
+ }
267
+ ```
268
+
269
+ ---
270
+
271
+ ## no-playwright-empty-fill
272
+
273
+ In Playwright tests, calling `.fill('')` on a locator should be replaced with `.clear()` — it is the idiomatic way to
274
+ empty a field and communicates intent more clearly.
275
+
276
+ ```ts
277
+ // ❌ error
278
+ await page.getByLabel('Name').fill('');
279
+
280
+ // ✅ after autofix
281
+ await page.getByLabel('Name').clear();
282
+ ```
283
+
284
+ ---
285
+
286
+ ## object-single-line
287
+
288
+ Single-property object literals that fit within `printWidth` characters on one line are collapsed to a single line.
289
+ Compatible with Prettier formatting.
290
+
291
+ ```ts
292
+ // ❌ error
293
+ const x = {
294
+ foo: bar,
295
+ };
296
+
297
+ // ✅ after autofix
298
+ const x = {foo: bar};
299
+ ```
300
+
301
+ ```json
302
+ {
303
+ "@taiga-ui/experience-next/object-single-line": ["error", {"printWidth": 90}]
304
+ }
305
+ ```
306
+
307
+ | Option | Type | Default | Description |
308
+ | ------------ | -------- | ------- | ------------------------------------- |
309
+ | `printWidth` | `number` | `90` | Maximum line length to allow inlining |
310
+
311
+ ---
312
+
58
313
  ## prefer-deep-imports
59
314
 
60
315
  Enforce imports from the deepest available entry point of Taiga UI packages.
@@ -72,3 +327,106 @@ Enforce imports from the deepest available entry point of Taiga UI packages.
72
327
  ```
73
328
 
74
329
  Use `strict` to forbid imports from intermediate entry points when deeper ones exist (recommended for CI).
330
+
331
+ ---
332
+
333
+ ## prefer-multi-arg-push
334
+
335
+ Combine consecutive `.push()` calls on the same array into a single multi-argument call.
336
+
337
+ ```ts
338
+ // ❌ error
339
+ output.push('# Getting Started');
340
+ output.push('');
341
+
342
+ // ✅ after autofix
343
+ output.push('# Getting Started', '');
344
+ ```
345
+
346
+ ---
347
+
348
+ ## short-tui-imports
349
+
350
+ In Angular decorator `imports` arrays, replaces full `TuiXxxComponent` / `TuiYyyDirective` names with their shorthand
351
+ aliases (e.g. `TuiButton`). Also updates the corresponding `import` statement.
352
+
353
+ ```ts
354
+ // ❌ error
355
+ import {TuiButtonDirective} from '@taiga-ui/core';
356
+
357
+ @Component({
358
+ imports: [TuiButtonDirective],
359
+ })
360
+
361
+ // ✅ after autofix
362
+ import {TuiButton} from '@taiga-ui/core';
363
+
364
+ @Component({
365
+ imports: [TuiButton],
366
+ })
367
+ ```
368
+
369
+ ```json
370
+ {
371
+ "@taiga-ui/experience-next/short-tui-imports": [
372
+ "error",
373
+ {
374
+ "decorators": ["Component", "Directive", "NgModule", "Pipe"],
375
+ "exceptions": [{"from": "TuiTextfieldOptionsDirective", "to": "TuiTextfield"}]
376
+ }
377
+ ]
378
+ }
379
+ ```
380
+
381
+ | Option | Type | Description |
382
+ | ------------ | ------------------------------ | ---------------------------------------------------------- |
383
+ | `decorators` | `string[]` | Decorator names to inspect (default: all Angular ones) |
384
+ | `exceptions` | `{from: string, to: string}[]` | Explicit rename mappings that override the default pattern |
385
+
386
+ ---
387
+
388
+ ## standalone-imports-sort
389
+
390
+ Sorts the `imports` array inside Angular decorators (`@Component`, `@Directive`, `@NgModule`, `@Pipe`) alphabetically.
391
+ Spread elements are placed after named identifiers.
392
+
393
+ ```ts
394
+ // ❌ error
395
+ @Component({
396
+ imports: [TuiButton, CommonModule, AsyncPipe],
397
+ })
398
+
399
+ // ✅ after autofix
400
+ @Component({
401
+ imports: [AsyncPipe, CommonModule, TuiButton],
402
+ })
403
+ ```
404
+
405
+ ```json
406
+ {
407
+ "@taiga-ui/experience-next/standalone-imports-sort": [
408
+ "error",
409
+ {"decorators": ["Component", "Directive", "NgModule", "Pipe"]}
410
+ ]
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ ## strict-tui-doc-example
417
+
418
+ Validates that properties of a `TuiDocExample`-typed object have keys matching known file-type names (`TypeScript`,
419
+ `HTML`, `CSS`, `LESS`, `JavaScript`) and that the import path extension matches the key. Autofix corrects the import
420
+ extension.
421
+
422
+ ```ts
423
+ // ❌ error — key says "TypeScript" but path has .html extension
424
+ readonly example: TuiDocExample = {
425
+ TypeScript: import('./example/index.html?raw'),
426
+ };
427
+
428
+ // ✅ after autofix
429
+ readonly example: TuiDocExample = {
430
+ TypeScript: import('./example/index.ts?raw'),
431
+ };
432
+ ```
package/index.d.ts CHANGED
@@ -51,6 +51,9 @@ declare const plugin: {
51
51
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
52
52
  name: string;
53
53
  };
54
+ 'prefer-multi-arg-push': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferMultiArgPush", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
55
+ name: string;
56
+ };
54
57
  '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> & {
55
58
  name: string;
56
59
  };
package/index.esm.js CHANGED
@@ -579,6 +579,7 @@ var recommended = defineConfig([
579
579
  ],
580
580
  '@typescript-eslint/unbound-method': 'off',
581
581
  '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error',
582
+ 'arrow-body-style': ['error', 'as-needed'],
582
583
  curly: ['error', 'all'],
583
584
  'decorator-position/decorator-position': [
584
585
  'error',
@@ -600,6 +601,7 @@ var recommended = defineConfig([
600
601
  'import/no-cycle': 'error',
601
602
  'import/no-duplicates': ['error', { 'prefer-inline': true }],
602
603
  'import/no-mutable-exports': 'error',
604
+ 'import/no-named-as-default': 'error',
603
605
  'import/no-self-import': 'error',
604
606
  'import/no-unresolved': 'off',
605
607
  'import/no-useless-path-segments': ['error', { noUselessIndex: true }],
@@ -901,6 +903,7 @@ var recommended = defineConfig([
901
903
  '@taiga-ui/experience-next/no-deep-imports-to-indexed-packages': 'error',
902
904
  '@taiga-ui/experience-next/no-implicit-public': 'error',
903
905
  '@taiga-ui/experience-next/object-single-line': ['error', { printWidth: 90 }],
906
+ '@taiga-ui/experience-next/prefer-multi-arg-push': 'error',
904
907
  '@taiga-ui/experience-next/short-tui-imports': 'error',
905
908
  '@taiga-ui/experience-next/standalone-imports-sort': [
906
909
  'error',
@@ -1312,8 +1315,8 @@ function intersect(a, b) {
1312
1315
  return a.some((type) => origin.has(type));
1313
1316
  }
1314
1317
 
1315
- const createRule$b = ESLintUtils.RuleCreator((name) => name);
1316
- var classPropertyNaming = createRule$b({
1318
+ const createRule$c = ESLintUtils.RuleCreator((name) => name);
1319
+ var classPropertyNaming = createRule$c({
1317
1320
  create(context, [configs]) {
1318
1321
  const parserServices = ESLintUtils.getParserServices(context);
1319
1322
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1482,9 +1485,9 @@ function isExternalPureTuple(typeChecker, type) {
1482
1485
  return typeArgs.every((item) => isClassType(item));
1483
1486
  }
1484
1487
 
1485
- const createRule$a = ESLintUtils.RuleCreator((name) => name);
1488
+ const createRule$b = ESLintUtils.RuleCreator((name) => name);
1486
1489
  const MESSAGE_ID$5 = 'spreadArrays';
1487
- var flatExports = createRule$a({
1490
+ var flatExports = createRule$b({
1488
1491
  create(context) {
1489
1492
  const parserServices = ESLintUtils.getParserServices(context);
1490
1493
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1611,8 +1614,8 @@ var flatExports = createRule$a({
1611
1614
 
1612
1615
  const MESSAGE_ID$4 = 'invalid-injection-token-description';
1613
1616
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
1614
- const createRule$9 = ESLintUtils.RuleCreator((name) => name);
1615
- const rule$6 = createRule$9({
1617
+ const createRule$a = ESLintUtils.RuleCreator((name) => name);
1618
+ const rule$7 = createRule$a({
1616
1619
  create(context) {
1617
1620
  return {
1618
1621
  'NewExpression[callee.name="InjectionToken"]'(node) {
@@ -1680,8 +1683,8 @@ const DEFAULT_OPTIONS = {
1680
1683
  importDeclaration: '^@taiga-ui*',
1681
1684
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
1682
1685
  };
1683
- const createRule$8 = ESLintUtils.RuleCreator((name) => name);
1684
- const rule$5 = createRule$8({
1686
+ const createRule$9 = ESLintUtils.RuleCreator((name) => name);
1687
+ const rule$6 = createRule$9({
1685
1688
  create(context) {
1686
1689
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
1687
1690
  const hasNonCodeExtension = (source) => {
@@ -1768,13 +1771,13 @@ const rule$5 = createRule$8({
1768
1771
  name: 'no-deep-imports',
1769
1772
  });
1770
1773
 
1771
- const createRule$7 = ESLintUtils.RuleCreator((name) => name);
1774
+ const createRule$8 = ESLintUtils.RuleCreator((name) => name);
1772
1775
  const resolveCacheByOptions = new WeakMap();
1773
1776
  const nearestFileUpCache = new Map();
1774
1777
  const markerCache = new Map();
1775
1778
  const indexFileCache = new Map();
1776
1779
  const indexExportsCache = new Map();
1777
- var noDeepImportsToIndexedPackages = createRule$7({
1780
+ var noDeepImportsToIndexedPackages = createRule$8({
1778
1781
  create(context) {
1779
1782
  const parserServices = ESLintUtils.getParserServices(context);
1780
1783
  const program = parserServices.program;
@@ -2017,8 +2020,8 @@ const config = {
2017
2020
  },
2018
2021
  };
2019
2022
 
2020
- const createRule$6 = ESLintUtils.RuleCreator((name) => name);
2021
- const rule$4 = createRule$6({
2023
+ const createRule$7 = ESLintUtils.RuleCreator((name) => name);
2024
+ const rule$5 = createRule$7({
2022
2025
  create(context) {
2023
2026
  const checkImplicitPublic = (node) => {
2024
2027
  const classRef = getClass(node);
@@ -2090,8 +2093,8 @@ function getClass(node) {
2090
2093
  return getClass(node.parent);
2091
2094
  }
2092
2095
 
2093
- const createRule$5 = ESLintUtils.RuleCreator((name) => name);
2094
- const rule$3 = createRule$5({
2096
+ const createRule$6 = ESLintUtils.RuleCreator((name) => name);
2097
+ const rule$4 = createRule$6({
2095
2098
  create(context) {
2096
2099
  const services = ESLintUtils.getParserServices(context);
2097
2100
  const checker = services.program.getTypeChecker();
@@ -2179,8 +2182,8 @@ function isPlaywrightLocatorType(type) {
2179
2182
  });
2180
2183
  }
2181
2184
 
2182
- const createRule$4 = ESLintUtils.RuleCreator((name) => name);
2183
- const rule$2 = createRule$4({
2185
+ const createRule$5 = ESLintUtils.RuleCreator((name) => name);
2186
+ const rule$3 = createRule$5({
2184
2187
  create(context, [{ printWidth }]) {
2185
2188
  const sourceCode = context.sourceCode;
2186
2189
  const getLineEndIndex = (lineStartIndex) => {
@@ -2460,8 +2463,8 @@ const rule$2 = createRule$4({
2460
2463
 
2461
2464
  const MESSAGE_ID$1 = 'prefer-deep-imports';
2462
2465
  const ERROR_MESSAGE = 'Import via root entry point is prohibited when nested entry points exist';
2463
- const createRule$3 = ESLintUtils.RuleCreator(() => ERROR_MESSAGE);
2464
- var preferDeepImports = createRule$3({
2466
+ const createRule$4 = ESLintUtils.RuleCreator(() => ERROR_MESSAGE);
2467
+ var preferDeepImports = createRule$4({
2465
2468
  create(context, [options]) {
2466
2469
  const allowedPackages = normalizeImportFilter(options.importFilter);
2467
2470
  const isStrictMode = options.strict ?? false;
@@ -2809,6 +2812,100 @@ function buildRewrittenImports(node, baseImportPath, symbolToEntryPoint) {
2809
2812
  return importStatements.join('\n');
2810
2813
  }
2811
2814
 
2815
+ const createRule$3 = ESLintUtils.RuleCreator((name) => name);
2816
+ function getPushCall(node) {
2817
+ if (node.expression.type !== AST_NODE_TYPES.CallExpression) {
2818
+ return null;
2819
+ }
2820
+ const call = node.expression;
2821
+ if (call.callee.type !== AST_NODE_TYPES.MemberExpression) {
2822
+ return null;
2823
+ }
2824
+ const { property } = call.callee;
2825
+ if (property.type !== AST_NODE_TYPES.Identifier || property.name !== 'push') {
2826
+ return null;
2827
+ }
2828
+ return call;
2829
+ }
2830
+ const rule$2 = createRule$3({
2831
+ create(context) {
2832
+ const { sourceCode } = context;
2833
+ function checkBody(statements) {
2834
+ let i = 0;
2835
+ while (i < statements.length) {
2836
+ const stmt = statements[i];
2837
+ if (stmt.type !== AST_NODE_TYPES.ExpressionStatement) {
2838
+ i++;
2839
+ continue;
2840
+ }
2841
+ const call = getPushCall(stmt);
2842
+ if (!call) {
2843
+ i++;
2844
+ continue;
2845
+ }
2846
+ const arrayText = sourceCode.getText(call.callee.object);
2847
+ const group = [stmt];
2848
+ let j = i + 1;
2849
+ while (j < statements.length) {
2850
+ const nextStmt = statements[j];
2851
+ if (nextStmt.type !== AST_NODE_TYPES.ExpressionStatement) {
2852
+ break;
2853
+ }
2854
+ const nextCall = getPushCall(nextStmt);
2855
+ if (!nextCall ||
2856
+ sourceCode.getText(nextCall.callee.object) !== arrayText) {
2857
+ break;
2858
+ }
2859
+ group.push(nextStmt);
2860
+ j++;
2861
+ }
2862
+ if (group.length > 1) {
2863
+ group.forEach((groupStmt, idx) => {
2864
+ context.report({
2865
+ ...(idx === 0
2866
+ ? {
2867
+ fix(fixer) {
2868
+ const allArgs = group
2869
+ .flatMap((s) => s.expression.arguments)
2870
+ .map((arg) => sourceCode.getText(arg))
2871
+ .join(', ');
2872
+ const lastStmt = group[group.length - 1];
2873
+ return fixer.replaceTextRange([groupStmt.range[0], lastStmt.range[1]], `${arrayText}.push(${allArgs});`);
2874
+ },
2875
+ }
2876
+ : {}),
2877
+ messageId: 'preferMultiArgPush',
2878
+ node: groupStmt,
2879
+ });
2880
+ });
2881
+ }
2882
+ i = j;
2883
+ }
2884
+ }
2885
+ return {
2886
+ BlockStatement(node) {
2887
+ checkBody(node.body);
2888
+ },
2889
+ Program(node) {
2890
+ checkBody(node.body);
2891
+ },
2892
+ };
2893
+ },
2894
+ defaultOptions: [],
2895
+ meta: {
2896
+ docs: {
2897
+ description: 'Enforce combining consecutive .push() calls on the same array into a single call.',
2898
+ },
2899
+ fixable: 'code',
2900
+ messages: {
2901
+ preferMultiArgPush: 'Combine consecutive .push() calls on the same array into a single multi-argument call.',
2902
+ },
2903
+ schema: [],
2904
+ type: 'suggestion',
2905
+ },
2906
+ name: 'prefer-multi-arg-push',
2907
+ });
2908
+
2812
2909
  function getImportedName(spec) {
2813
2910
  if (spec.imported.type === AST_NODE_TYPES.Identifier) {
2814
2911
  return spec.imported.name;
@@ -3284,14 +3381,15 @@ const plugin = {
3284
3381
  'class-property-naming': classPropertyNaming,
3285
3382
  'decorator-key-sort': config$1,
3286
3383
  'flat-exports': flatExports,
3287
- 'injection-token-description': rule$6,
3288
- 'no-deep-imports': rule$5,
3384
+ 'injection-token-description': rule$7,
3385
+ 'no-deep-imports': rule$6,
3289
3386
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
3290
3387
  'no-href-with-router-link': config,
3291
- 'no-implicit-public': rule$4,
3292
- 'no-playwright-empty-fill': rule$3,
3293
- 'object-single-line': rule$2,
3388
+ 'no-implicit-public': rule$5,
3389
+ 'no-playwright-empty-fill': rule$4,
3390
+ 'object-single-line': rule$3,
3294
3391
  'prefer-deep-imports': preferDeepImports,
3392
+ 'prefer-multi-arg-push': rule$2,
3295
3393
  'short-tui-imports': rule$1,
3296
3394
  'standalone-imports-sort': standaloneImportsSort,
3297
3395
  'strict-tui-doc-example': rule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.438.0",
3
+ "version": "0.440.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const rule: ESLintUtils.RuleModule<"preferMultiArgPush", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default rule;