@taiga-ui/eslint-plugin-experience-next 0.458.0 → 0.460.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,6 +49,7 @@ 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
+ | no-project-as-in-ng-template | `ngProjectAs` has no effect inside `<ng-template>` or dynamic outlets | | | |
52
53
  | no-redundant-type-annotation | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | |
53
54
  | no-string-literal-concat | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | |
54
55
  | object-single-line | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
@@ -285,6 +286,36 @@ await page.getByLabel('Name').clear();
285
286
 
286
287
  ---
287
288
 
289
+ ## no-project-as-in-ng-template
290
+
291
+ `ngProjectAs` has no effect when the element is inside an `<ng-template>`, `*ngTemplateOutlet`, `*ngComponentOutlet`, or
292
+ `*polymorpheusOutlet`. Content instantiated through these dynamic outlets does not participate in Angular's static
293
+ content projection, so the attribute is silently ignored at runtime.
294
+
295
+ ```html
296
+ <!-- ❌ error — inside <ng-template> -->
297
+ <ng-template #tpl>
298
+ <div ngProjectAs="[someSlot]">content</div>
299
+ </ng-template>
300
+
301
+ <!-- ❌ error — on the outlet host itself -->
302
+ <ng-container
303
+ *ngTemplateOutlet="tpl"
304
+ ngProjectAs="[someSlot]"
305
+ ></ng-container>
306
+
307
+ <!-- ❌ error — polymorpheusOutlet -->
308
+ <ng-container
309
+ *polymorpheusOutlet="content"
310
+ ngProjectAs="someSlot"
311
+ ></ng-container>
312
+
313
+ <!-- ✅ ok — static content projection -->
314
+ <div ngProjectAs="[someSlot]">content</div>
315
+ ```
316
+
317
+ ---
318
+
288
319
  ## no-string-literal-concat
289
320
 
290
321
  Disallows concatenating string literals with `+`. Adjacent string literals are always mergeable into one — splitting
@@ -536,6 +567,32 @@ x: Animal = new Dog();
536
567
  x: MyService | null = inject(MyService);
537
568
  ```
538
569
 
570
+ The rule does **not** report when the annotation provides contextual typing that narrows an array literal to a tuple.
571
+ Without the annotation TypeScript would infer `number[]` instead of the required tuple type, widening the type and
572
+ breaking compilation:
573
+
574
+ ```ts
575
+ type SelectionRange = readonly [from: number, to: number];
576
+ interface ElementState {
577
+ readonly value: string;
578
+ readonly selection: SelectionRange;
579
+ }
580
+
581
+ // ✅ ok — [0, 0] is inferred as SelectionRange only because of the annotation;
582
+ // removing it would widen the type to ElementState | {value: string; selection: number[]}
583
+ const state: ElementState = flag ? {value: '', selection: [0, 0]} : existingState;
584
+ ```
585
+
586
+ ```json
587
+ {
588
+ "@taiga-ui/experience-next/no-redundant-type-annotation": ["error", {"ignoreTupleContextualTyping": true}]
589
+ }
590
+ ```
591
+
592
+ | Option | Type | Default | Description |
593
+ | ----------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------ |
594
+ | `ignoreTupleContextualTyping` | `boolean` | `true` | Preserve annotations when they provide contextual typing that narrows an array literal to a tuple type |
595
+
539
596
  ---
540
597
 
541
598
  ## strict-tui-doc-example
package/index.d.ts CHANGED
@@ -40,7 +40,10 @@ declare const plugin: {
40
40
  'no-playwright-empty-fill': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useClear", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
41
41
  name: string;
42
42
  };
43
- 'no-redundant-type-annotation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"redundantTypeAnnotation", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
43
+ 'no-project-as-in-ng-template': import("eslint").Rule.RuleModule;
44
+ 'no-redundant-type-annotation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"redundantTypeAnnotation", [({
45
+ ignoreTupleContextualTyping?: boolean;
46
+ } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
44
47
  name: string;
45
48
  };
46
49
  'no-string-literal-concat': import("@typescript-eslint/utils/ts-eslint").RuleModule<"flattenTemplate" | "mergeLiterals" | "useTemplate", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
package/index.esm.js CHANGED
@@ -32,6 +32,7 @@ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
32
32
  import ts, { isCallExpression } from 'typescript';
33
33
  import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from '@typescript-eslint/types';
34
34
  import path from 'node:path';
35
+ import { TmplAstTemplate } from '@angular-eslint/bundled-angular-compiler';
35
36
 
36
37
  var htmlEslint = defineConfig([
37
38
  {
@@ -630,7 +631,7 @@ var recommended = defineConfig([
630
631
  'max-depth': 'error',
631
632
  'max-nested-callbacks': ['error', 4],
632
633
  'max-params': ['error', 5],
633
- 'no-bitwise': 'error',
634
+ 'no-bitwise': 'off',
634
635
  'no-case-declarations': 'error',
635
636
  'no-console': ['error', { allow: ['info', 'assert', 'warn', 'error'] }],
636
637
  'no-constant-condition': 'error',
@@ -989,6 +990,7 @@ var recommended = defineConfig([
989
990
  '@angular-eslint/template/prefer-control-flow': angularVersion >= modernAngularRules.preferControlFlow ? 'error' : 'off',
990
991
  '@angular-eslint/template/prefer-self-closing-tags': 'error',
991
992
  '@angular-eslint/template/prefer-template-literal': angularVersion >= modernAngularRules.templateLiteral ? 'error' : 'off',
993
+ '@taiga-ui/experience-next/no-project-as-in-ng-template': 'error',
992
994
  },
993
995
  },
994
996
  {
@@ -1238,7 +1240,7 @@ function readJSON(path) {
1238
1240
  }
1239
1241
  }
1240
1242
 
1241
- const config$2 = {
1243
+ const config$3 = {
1242
1244
  create(context) {
1243
1245
  const classesInFile = new Map();
1244
1246
  return {
@@ -1292,7 +1294,6 @@ const config$2 = {
1292
1294
  },
1293
1295
  };
1294
1296
 
1295
- /* eslint-disable no-bitwise */
1296
1297
  function getFieldTypes(type, checker) {
1297
1298
  const typeNames = [];
1298
1299
  if (type.isUnionOrIntersection()) {
@@ -1398,7 +1399,7 @@ var classPropertyNaming = createRule$e({
1398
1399
  name: 'class-property-naming',
1399
1400
  });
1400
1401
 
1401
- const config$1 = {
1402
+ const config$2 = {
1402
1403
  create(context) {
1403
1404
  const order = context.options[0] || {};
1404
1405
  return {
@@ -1501,7 +1502,7 @@ function isExternalPureTuple(typeChecker, type) {
1501
1502
  }
1502
1503
 
1503
1504
  const createRule$d = ESLintUtils.RuleCreator((name) => name);
1504
- const MESSAGE_ID$5 = 'spreadArrays';
1505
+ const MESSAGE_ID$6 = 'spreadArrays';
1505
1506
  var flatExports = createRule$d({
1506
1507
  create(context) {
1507
1508
  const parserServices = ESLintUtils.getParserServices(context);
@@ -1567,7 +1568,7 @@ var flatExports = createRule$d({
1567
1568
  fix(fixer) {
1568
1569
  return fixer.replaceText(elementNode, `...${meta.name}`);
1569
1570
  },
1570
- messageId: MESSAGE_ID$5,
1571
+ messageId: MESSAGE_ID$6,
1571
1572
  node: elementNode,
1572
1573
  });
1573
1574
  }
@@ -1618,7 +1619,7 @@ var flatExports = createRule$d({
1618
1619
  },
1619
1620
  fixable: 'code',
1620
1621
  messages: {
1621
- [MESSAGE_ID$5]: 'Spread "{{ name }}" to avoid nested arrays in exported entities.',
1622
+ [MESSAGE_ID$6]: 'Spread "{{ name }}" to avoid nested arrays in exported entities.',
1622
1623
  },
1623
1624
  schema: [],
1624
1625
  type: 'suggestion',
@@ -1626,7 +1627,7 @@ var flatExports = createRule$d({
1626
1627
  name: 'flat-exports',
1627
1628
  });
1628
1629
 
1629
- const MESSAGE_ID$4 = 'invalid-injection-token-description';
1630
+ const MESSAGE_ID$5 = 'invalid-injection-token-description';
1630
1631
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
1631
1632
  const createRule$c = ESLintUtils.RuleCreator((name) => name);
1632
1633
  const rule$9 = createRule$c({
@@ -1660,7 +1661,7 @@ const rule$9 = createRule$c({
1660
1661
  const [start, end] = description.range;
1661
1662
  return fixer.insertTextBeforeRange([start + 1, end], `[${name}]: `);
1662
1663
  },
1663
- messageId: MESSAGE_ID$4,
1664
+ messageId: MESSAGE_ID$5,
1664
1665
  node: description,
1665
1666
  });
1666
1667
  }
@@ -1670,14 +1671,14 @@ const rule$9 = createRule$c({
1670
1671
  meta: {
1671
1672
  docs: { description: ERROR_MESSAGE$3 },
1672
1673
  fixable: 'code',
1673
- messages: { [MESSAGE_ID$4]: ERROR_MESSAGE$3 },
1674
+ messages: { [MESSAGE_ID$5]: ERROR_MESSAGE$3 },
1674
1675
  schema: [],
1675
1676
  type: 'problem',
1676
1677
  },
1677
1678
  name: 'injection-token-description',
1678
1679
  });
1679
1680
 
1680
- const MESSAGE_ID$3 = 'no-deep-imports';
1681
+ const MESSAGE_ID$4 = 'no-deep-imports';
1681
1682
  const ERROR_MESSAGE$2 = 'Deep imports of Taiga UI packages are prohibited';
1682
1683
  const CODE_EXTENSIONS = new Set([
1683
1684
  '.cjs',
@@ -1739,7 +1740,7 @@ const rule$8 = createRule$b({
1739
1740
  const [start, end] = node.source.range;
1740
1741
  return fixer.replaceTextRange([start + 1, end - 1], importSource.replaceAll(new RegExp(deepImport, 'g'), ''));
1741
1742
  },
1742
- messageId: MESSAGE_ID$3,
1743
+ messageId: MESSAGE_ID$4,
1743
1744
  node: node.source,
1744
1745
  });
1745
1746
  },
@@ -1749,7 +1750,7 @@ const rule$8 = createRule$b({
1749
1750
  defaultOptions: [DEFAULT_OPTIONS],
1750
1751
  docs: { description: ERROR_MESSAGE$2 },
1751
1752
  fixable: 'code',
1752
- messages: { [MESSAGE_ID$3]: ERROR_MESSAGE$2 },
1753
+ messages: { [MESSAGE_ID$4]: ERROR_MESSAGE$2 },
1753
1754
  schema: [
1754
1755
  {
1755
1756
  additionalProperties: false,
@@ -1985,9 +1986,9 @@ function stripKnownExtensions(filePathOrSpecifier) {
1985
1986
  return filePathOrSpecifier.replace(/\.(?:d\.ts|ts|tsx|js|jsx|mjs|cjs)$/, '');
1986
1987
  }
1987
1988
 
1988
- const MESSAGE_ID$2 = 'no-href-with-router-link';
1989
+ const MESSAGE_ID$3 = 'no-href-with-router-link';
1989
1990
  const ERROR_MESSAGE$1 = 'Do not use href and routerLink attributes together on the same element';
1990
- const config = {
1991
+ const config$1 = {
1991
1992
  create(context) {
1992
1993
  return {
1993
1994
  Tag(node) {
@@ -2015,7 +2016,7 @@ const config = {
2015
2016
  fix: (fixer) => hrefAttribute?.range
2016
2017
  ? fixer.removeRange(hrefAttribute.range)
2017
2018
  : null,
2018
- messageId: MESSAGE_ID$2,
2019
+ messageId: MESSAGE_ID$3,
2019
2020
  node: (routerLinkAttribute ??
2020
2021
  hrefAttribute ??
2021
2022
  htmlNode),
@@ -2027,7 +2028,7 @@ const config = {
2027
2028
  meta: {
2028
2029
  docs: { description: ERROR_MESSAGE$1 },
2029
2030
  fixable: 'code',
2030
- messages: { [MESSAGE_ID$2]: ERROR_MESSAGE$1 },
2031
+ messages: { [MESSAGE_ID$3]: ERROR_MESSAGE$1 },
2031
2032
  type: 'problem',
2032
2033
  },
2033
2034
  };
@@ -2192,11 +2193,115 @@ function isPlaywrightLocatorType(type) {
2192
2193
  });
2193
2194
  }
2194
2195
 
2196
+ const MESSAGE_ID$2 = 'no-project-as-in-ng-template';
2197
+ const NESTED_TEMPLATE_MESSAGE_ID = 'no-nested-template-in-dynamic-outlet';
2198
+ const PROJECT_AS_ERROR_MESSAGE = 'ngProjectAs on a dynamic outlet breaks SSR hydration. Use static content projection instead.';
2199
+ const NESTED_TEMPLATE_ERROR_MESSAGE = 'Avoid nesting ng-template inside dynamic outlet containers. Move the template outside the ng-container.';
2200
+ const DYNAMIC_OUTLET_DIRECTIVES = new Set([
2201
+ 'ngComponentOutlet',
2202
+ 'ngTemplateOutlet',
2203
+ 'polymorpheusOutlet',
2204
+ ]);
2205
+ function toLoc(span) {
2206
+ return {
2207
+ end: {
2208
+ column: span.end.col,
2209
+ line: span.end.line + 1,
2210
+ },
2211
+ start: {
2212
+ column: span.start.col,
2213
+ line: span.start.line + 1,
2214
+ },
2215
+ };
2216
+ }
2217
+ function isInsideDynamicOutlet(node) {
2218
+ let parent = node.parent;
2219
+ while (parent) {
2220
+ const hasDynamicOutletDirective = parent instanceof TmplAstTemplate &&
2221
+ parent.templateAttrs.some((attr) => DYNAMIC_OUTLET_DIRECTIVES.has(attr.name));
2222
+ if (hasDynamicOutletDirective) {
2223
+ return true;
2224
+ }
2225
+ parent = parent.parent;
2226
+ }
2227
+ return false;
2228
+ }
2229
+ function checkProjectAsOnDynamicOutlet(context, node) {
2230
+ const ngProjectAsAttr = node.attributes.find((attr) => attr.name === 'ngProjectAs') ??
2231
+ node.inputs.find((input) => input.name === 'ngProjectAs');
2232
+ if (ngProjectAsAttr && isInsideDynamicOutlet(node)) {
2233
+ context.report({
2234
+ loc: toLoc(ngProjectAsAttr.sourceSpan),
2235
+ messageId: MESSAGE_ID$2,
2236
+ });
2237
+ }
2238
+ }
2239
+ function checkNestedTemplateInDynamicOutlet(context, node) {
2240
+ const hasDynamicOutletInput = node.inputs.some((input) => DYNAMIC_OUTLET_DIRECTIVES.has(input.name));
2241
+ if (!hasDynamicOutletInput) {
2242
+ return;
2243
+ }
2244
+ for (const child of node.children) {
2245
+ if (child instanceof TmplAstTemplate && child.tagName === 'ng-template') {
2246
+ context.report({
2247
+ loc: toLoc(child.sourceSpan),
2248
+ messageId: NESTED_TEMPLATE_MESSAGE_ID,
2249
+ });
2250
+ }
2251
+ }
2252
+ }
2253
+ const config = {
2254
+ create(context) {
2255
+ return {
2256
+ Element(rawNode) {
2257
+ const node = rawNode;
2258
+ checkProjectAsOnDynamicOutlet(context, node);
2259
+ checkNestedTemplateInDynamicOutlet(context, node);
2260
+ },
2261
+ };
2262
+ },
2263
+ meta: {
2264
+ docs: { description: PROJECT_AS_ERROR_MESSAGE },
2265
+ messages: {
2266
+ [MESSAGE_ID$2]: PROJECT_AS_ERROR_MESSAGE,
2267
+ [NESTED_TEMPLATE_MESSAGE_ID]: NESTED_TEMPLATE_ERROR_MESSAGE,
2268
+ },
2269
+ schema: [],
2270
+ type: 'problem',
2271
+ },
2272
+ };
2273
+
2195
2274
  const createRule$7 = ESLintUtils.RuleCreator((name) => name);
2275
+ function collectArrayExpressions(node) {
2276
+ const result = [];
2277
+ if (node.type === AST_NODE_TYPES.ArrayExpression) {
2278
+ result.push(node);
2279
+ }
2280
+ switch (node.type) {
2281
+ case AST_NODE_TYPES.BinaryExpression:
2282
+ case AST_NODE_TYPES.LogicalExpression:
2283
+ result.push(...collectArrayExpressions(node.left));
2284
+ result.push(...collectArrayExpressions(node.right));
2285
+ break;
2286
+ case AST_NODE_TYPES.ConditionalExpression:
2287
+ result.push(...collectArrayExpressions(node.consequent));
2288
+ result.push(...collectArrayExpressions(node.alternate));
2289
+ break;
2290
+ case AST_NODE_TYPES.ObjectExpression:
2291
+ for (const property of node.properties) {
2292
+ if (property.type === AST_NODE_TYPES.Property) {
2293
+ result.push(...collectArrayExpressions(property.value));
2294
+ }
2295
+ }
2296
+ break;
2297
+ }
2298
+ return result;
2299
+ }
2196
2300
  const rule$5 = createRule$7({
2197
2301
  create(context) {
2198
2302
  const parserServices = ESLintUtils.getParserServices(context);
2199
2303
  const typeChecker = parserServices.program.getTypeChecker();
2304
+ const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
2200
2305
  function check(node, typeAnnotation, value) {
2201
2306
  if (!typeAnnotation || !value) {
2202
2307
  return;
@@ -2219,6 +2324,19 @@ const rule$5 = createRule$7({
2219
2324
  !value.returnType) {
2220
2325
  return;
2221
2326
  }
2327
+ // If the annotation provides contextual typing that narrows an array
2328
+ // literal to a tuple (e.g. [0, 0] → readonly [number, number]), removing
2329
+ // it would widen the inferred type. This covers both direct array literals
2330
+ // and arrays nested inside object literals or conditional expressions.
2331
+ if (ignoreTupleContextualTyping) {
2332
+ const arrayExpressions = collectArrayExpressions(value);
2333
+ for (const arrayExpression of arrayExpressions) {
2334
+ const tsArrayNode = parserServices.esTreeNodeToTSNodeMap.get(arrayExpression);
2335
+ if (typeChecker.isTupleType(typeChecker.getTypeAtLocation(tsArrayNode))) {
2336
+ return;
2337
+ }
2338
+ }
2339
+ }
2222
2340
  // If the initializer is a call to a generic function with no explicit
2223
2341
  // type arguments, the type parameters may be inferred from the
2224
2342
  // contextual return type provided by this annotation. Removing the
@@ -2258,7 +2376,18 @@ const rule$5 = createRule$7({
2258
2376
  messages: {
2259
2377
  redundantTypeAnnotation: 'Type annotation is redundant — the type is already inferred from the initializer',
2260
2378
  },
2261
- schema: [],
2379
+ schema: [
2380
+ {
2381
+ additionalProperties: false,
2382
+ properties: {
2383
+ ignoreTupleContextualTyping: {
2384
+ description: 'Preserve annotations when they provide contextual typing that narrows an array literal to a tuple type. Defaults to true.',
2385
+ type: 'boolean',
2386
+ },
2387
+ },
2388
+ type: 'object',
2389
+ },
2390
+ ],
2262
2391
  type: 'suggestion',
2263
2392
  },
2264
2393
  name: 'no-redundant-type-annotation',
@@ -3621,16 +3750,17 @@ const plugin = {
3621
3750
  version: pkg.version,
3622
3751
  },
3623
3752
  rules: {
3624
- 'array-as-const': config$2,
3753
+ 'array-as-const': config$3,
3625
3754
  'class-property-naming': classPropertyNaming,
3626
- 'decorator-key-sort': config$1,
3755
+ 'decorator-key-sort': config$2,
3627
3756
  'flat-exports': flatExports,
3628
3757
  'injection-token-description': rule$9,
3629
3758
  'no-deep-imports': rule$8,
3630
3759
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
3631
- 'no-href-with-router-link': config,
3760
+ 'no-href-with-router-link': config$1,
3632
3761
  'no-implicit-public': rule$7,
3633
3762
  'no-playwright-empty-fill': rule$6,
3763
+ 'no-project-as-in-ng-template': config,
3634
3764
  'no-redundant-type-annotation': rule$5,
3635
3765
  'no-string-literal-concat': rule$4,
3636
3766
  'object-single-line': rule$3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.458.0",
3
+ "version": "0.460.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,23 +27,23 @@
27
27
  ],
28
28
  "type": "module",
29
29
  "devDependencies": {
30
- "@typescript-eslint/rule-tester": "8.58.0",
30
+ "@typescript-eslint/rule-tester": "8.58.1",
31
31
  "glob": "13.0.6"
32
32
  },
33
33
  "peerDependencies": {
34
- "@eslint/compat": "^2.0.4",
34
+ "@eslint/compat": "^2.0.5",
35
35
  "@eslint/markdown": "^8.0.1",
36
36
  "@html-eslint/eslint-plugin": "^0.59.0",
37
37
  "@html-eslint/parser": "^0.59.0",
38
38
  "@smarttools/eslint-plugin-rxjs": "^1.0.22",
39
39
  "@stylistic/eslint-plugin": "^5.10.0",
40
40
  "@types/glob": "*",
41
- "@typescript-eslint/eslint-plugin": "^8.58.0",
41
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
42
42
  "angular-eslint": "^20.7.0",
43
43
  "eslint": "^9.39.2",
44
44
  "eslint-config-prettier": "^10.1.7",
45
45
  "eslint-plugin-compat": "^7.0.1",
46
- "eslint-plugin-cypress": "^6.2.3",
46
+ "eslint-plugin-cypress": "^6.3.0",
47
47
  "eslint-plugin-de-morgan": "^2.1.1",
48
48
  "eslint-plugin-decorator-position": "^6.0.0",
49
49
  "eslint-plugin-file-progress": "^3.0.2",
@@ -61,7 +61,7 @@
61
61
  "eslint-plugin-unused-imports": "^4.4.1",
62
62
  "glob": "*",
63
63
  "globals": "^17.4.0",
64
- "typescript-eslint": "^8.58.0"
64
+ "typescript-eslint": "^8.58.1"
65
65
  },
66
66
  "publishConfig": {
67
67
  "access": "public"
@@ -0,0 +1,3 @@
1
+ import { type Rule } from 'eslint';
2
+ declare const config: Rule.RuleModule;
3
+ export default config;
@@ -1,5 +1,10 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
- export declare const rule: ESLintUtils.RuleModule<"redundantTypeAnnotation", [], unknown, ESLintUtils.RuleListener> & {
2
+ type Options = [
3
+ {
4
+ ignoreTupleContextualTyping?: boolean;
5
+ }?
6
+ ];
7
+ export declare const rule: ESLintUtils.RuleModule<"redundantTypeAnnotation", Options, unknown, ESLintUtils.RuleListener> & {
3
8
  name: string;
4
9
  };
5
10
  export default rule;