@taiga-ui/eslint-plugin-experience-next 0.513.0 → 0.515.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
@@ -95,6 +95,8 @@ from third-party plugins. The exact severities and file globs live in
95
95
  | [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 | ✅ | 🔧 | |
96
96
  | [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 | ✅ | | |
97
97
  | [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` | ✅ | | |
98
+ | [no-nested-interactive](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-nested-interactive.md) | Disallow interactive HTML elements nested inside other interactive elements | ✅ | | |
99
+ | [no-nested-ternary-in-template](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-nested-ternary-in-template.md) | Disallow nested ternary expressions in Angular 19+ templates | ✅ | 🔧 | |
98
100
  | [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 | ✅ | | |
99
101
  | [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 | ✅ | | |
100
102
  | [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 | ✅ | 🔧 | |
@@ -4,5 +4,6 @@ export declare const modernAngularRules: {
4
4
  readonly modernStyles: 17;
5
5
  readonly preferControlFlow: 17;
6
6
  readonly preferSignals: 17;
7
+ readonly templateLet: 19;
7
8
  readonly templateLiteral: 19;
8
9
  };
package/index.d.ts CHANGED
@@ -99,6 +99,12 @@ declare const plugin: {
99
99
  'no-legacy-peer-deps': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noLegacyPeerDeps", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
100
100
  name: string;
101
101
  };
102
+ 'no-nested-interactive': import("eslint").Rule.RuleModule & {
103
+ name: string;
104
+ };
105
+ 'no-nested-ternary-in-template': import("eslint").Rule.RuleModule & {
106
+ name: string;
107
+ };
102
108
  'no-obsolete-attrs': import("eslint").Rule.RuleModule & {
103
109
  name: string;
104
110
  };
package/index.esm.js CHANGED
@@ -514,6 +514,7 @@ const modernAngularRules = {
514
514
  modernStyles: 17,
515
515
  preferControlFlow: 17,
516
516
  preferSignals: 17,
517
+ templateLet: 19,
517
518
  templateLiteral: 19,
518
519
  };
519
520
 
@@ -1309,6 +1310,8 @@ var recommended = defineConfig([
1309
1310
  '@taiga-ui/experience-next/no-duplicate-id': 'error',
1310
1311
  '@taiga-ui/experience-next/no-duplicate-in-head': 'error',
1311
1312
  '@taiga-ui/experience-next/no-href-with-router-link': 'error',
1313
+ '@taiga-ui/experience-next/no-nested-interactive': 'error',
1314
+ '@taiga-ui/experience-next/no-nested-ternary-in-template': angularVersion >= modernAngularRules.templateLet ? 'error' : 'off',
1312
1315
  '@taiga-ui/experience-next/no-obsolete-attrs': 'error',
1313
1316
  '@taiga-ui/experience-next/no-obsolete-tags': 'error',
1314
1317
  '@taiga-ui/experience-next/no-project-as-in-ng-template': 'error',
@@ -33212,9 +33215,44 @@ __name(compileHmrUpdateCallback, "compileHmrUpdateCallback");
33212
33215
  var VERSION = new Version("20.3.12");
33213
33216
  publishFacade(_global);
33214
33217
 
33218
+ function getAttributeNames(name) {
33219
+ return typeof name === 'string'
33220
+ ? [name.toLowerCase()]
33221
+ : name.map((item) => item.toLowerCase());
33222
+ }
33223
+ function isMatchingAttributeName(name, names) {
33224
+ return names.includes(name.toLowerCase());
33225
+ }
33215
33226
  function getAttributeValueSpan(attr) {
33216
33227
  return attr instanceof dist$4.TmplAstBoundEvent ? attr.handlerSpan : attr.valueSpan;
33217
33228
  }
33229
+ function getStaticAttribute(element, name) {
33230
+ const attrName = name.toLowerCase();
33231
+ return element.attributes.find((attr) => attr.name.toLowerCase() === attrName);
33232
+ }
33233
+ function getStaticAttributeValue(element, name) {
33234
+ return getStaticAttribute(element, name)?.value;
33235
+ }
33236
+ function hasInputBinding(element, name) {
33237
+ const names = getAttributeNames(name);
33238
+ return element.inputs.some((input) => isMatchingAttributeName(input.name, names));
33239
+ }
33240
+ function hasElementAttribute(element, name) {
33241
+ const names = getAttributeNames(name);
33242
+ return (element.attributes.some((attr) => isMatchingAttributeName(attr.name, names)) ||
33243
+ element.inputs.some((input) => {
33244
+ if (isMatchingAttributeName(input.name, names)) {
33245
+ return true;
33246
+ }
33247
+ const details = input.keySpan.details?.toLowerCase();
33248
+ return (details?.startsWith('attr.') === true && names.includes(details.slice(5)));
33249
+ }));
33250
+ }
33251
+ function hasOutputBinding(element, name) {
33252
+ {
33253
+ return element.outputs.length > 0;
33254
+ }
33255
+ }
33218
33256
  function getElementAttributeLikes(element) {
33219
33257
  const seen = new Set();
33220
33258
  return [
@@ -33246,6 +33284,12 @@ function sourceSpanToLoc(span) {
33246
33284
  },
33247
33285
  };
33248
33286
  }
33287
+ function containsAbsoluteSourceSpan(container, child) {
33288
+ return container.start <= child.start && child.end <= container.end;
33289
+ }
33290
+ function getAbsoluteSourceSpanText(text, span) {
33291
+ return text.slice(span.start, span.end);
33292
+ }
33249
33293
 
33250
33294
  var dist$3 = {};
33251
33295
 
@@ -46233,7 +46277,7 @@ function buildMultilineStartTag(node, sourceText) {
46233
46277
  closing,
46234
46278
  ].join('\n');
46235
46279
  }
46236
- const rule$T = createRule({
46280
+ const rule$V = createRule({
46237
46281
  name: 'attrs-newline',
46238
46282
  rule: {
46239
46283
  create(context) {
@@ -46413,7 +46457,7 @@ function sameOrder(a, b) {
46413
46457
  return a.length === b.length && a.every((value, index) => value === b[index]);
46414
46458
  }
46415
46459
 
46416
- const rule$S = createRule({
46460
+ const rule$U = createRule({
46417
46461
  create(context, [order]) {
46418
46462
  const decorators = new Set(Object.keys(order));
46419
46463
  return {
@@ -46525,7 +46569,7 @@ const INLINE_HTML_ELEMENTS = new Set([
46525
46569
  'wbr',
46526
46570
  ]);
46527
46571
 
46528
- const MESSAGE_ID$g = 'expectAfter';
46572
+ const MESSAGE_ID$i = 'expectAfter';
46529
46573
  const SKIP_CHILDREN = new Set(['code', 'pre']);
46530
46574
  function isChildNode(node) {
46531
46575
  return (node instanceof dist$4.TmplAstBoundText ||
@@ -46549,7 +46593,7 @@ function getNodeLabel(node) {
46549
46593
  }
46550
46594
  return node instanceof dist$4.TmplAstBoundText ? 'binding' : 'text';
46551
46595
  }
46552
- const rule$R = createRule({
46596
+ const rule$T = createRule({
46553
46597
  name: 'element-newline',
46554
46598
  rule: {
46555
46599
  create(context) {
@@ -46575,7 +46619,7 @@ const rule$R = createRule({
46575
46619
  firstChild.sourceSpan.start.offset,
46576
46620
  ], '\n'),
46577
46621
  loc: sourceSpanToLoc(firstChild.sourceSpan),
46578
- messageId: MESSAGE_ID$g,
46622
+ messageId: MESSAGE_ID$i,
46579
46623
  });
46580
46624
  return;
46581
46625
  }
@@ -46593,7 +46637,7 @@ const rule$R = createRule({
46593
46637
  child.sourceSpan.end.offset,
46594
46638
  ], '\n'),
46595
46639
  loc: sourceSpanToLoc(next.sourceSpan),
46596
- messageId: MESSAGE_ID$g,
46640
+ messageId: MESSAGE_ID$i,
46597
46641
  });
46598
46642
  return;
46599
46643
  }
@@ -46607,7 +46651,7 @@ const rule$R = createRule({
46607
46651
  lastChild.sourceSpan.end.offset,
46608
46652
  ], '\n'),
46609
46653
  loc: sourceSpanToLoc(lastChild.sourceSpan),
46610
- messageId: MESSAGE_ID$g,
46654
+ messageId: MESSAGE_ID$i,
46611
46655
  });
46612
46656
  }
46613
46657
  },
@@ -46616,7 +46660,7 @@ const rule$R = createRule({
46616
46660
  meta: {
46617
46661
  docs: { description: 'Enforce line breaks between block-level child nodes' },
46618
46662
  fixable: 'code',
46619
- messages: { [MESSAGE_ID$g]: 'There should be a linebreak after {{name}}.' },
46663
+ messages: { [MESSAGE_ID$i]: 'There should be a linebreak after {{name}}.' },
46620
46664
  schema: [],
46621
46665
  type: 'layout',
46622
46666
  },
@@ -46688,7 +46732,7 @@ const PRESETS = {
46688
46732
  $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
46689
46733
  $VUE_ATTRIBUTE: /^v-/,
46690
46734
  };
46691
- const rule$Q = createRule({
46735
+ const rule$S = createRule({
46692
46736
  create(context, [options]) {
46693
46737
  const sourceCode = context.sourceCode;
46694
46738
  const settings = {
@@ -46968,8 +47012,8 @@ const DIRECTIONAL_TO_LOGICAL = {
46968
47012
  top: 'inset-block-start',
46969
47013
  };
46970
47014
  const STYLE_PREFIX = 'style.';
46971
- const MESSAGE_ID$f = 'html-logical-properties';
46972
- const MESSAGE = `
47015
+ const MESSAGE_ID$h = 'html-logical-properties';
47016
+ const MESSAGE$1 = `
46973
47017
  Use logical CSS properties instead of directional properties. Replace:
46974
47018
  • left → inset-inline-start
46975
47019
  • right → inset-inline-end
@@ -47006,20 +47050,20 @@ const config$4 = {
47006
47050
  context.report({
47007
47051
  fix: (fixer) => fixer.replaceTextRange([propertyStart, propertyEnd], logicalProperty),
47008
47052
  loc: sourceSpanToLoc(keySpan),
47009
- messageId: MESSAGE_ID$f,
47053
+ messageId: MESSAGE_ID$h,
47010
47054
  });
47011
47055
  },
47012
47056
  };
47013
47057
  },
47014
47058
  meta: {
47015
- docs: { description: MESSAGE },
47059
+ docs: { description: MESSAGE$1 },
47016
47060
  fixable: 'code',
47017
- messages: { [MESSAGE_ID$f]: MESSAGE },
47061
+ messages: { [MESSAGE_ID$h]: MESSAGE$1 },
47018
47062
  schema: [],
47019
47063
  type: 'suggestion',
47020
47064
  },
47021
47065
  };
47022
- const rule$P = createRule({
47066
+ const rule$R = createRule({
47023
47067
  name: 'html-logical-properties',
47024
47068
  rule: config$4,
47025
47069
  });
@@ -248261,7 +248305,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
248261
248305
  }
248262
248306
  return hasSafeRuntimeUsage;
248263
248307
  }
248264
- const rule$O = createRule({
248308
+ const rule$Q = createRule({
248265
248309
  create(context) {
248266
248310
  const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
248267
248311
  const checkCycles = context.options[0]?.checkCycles ?? true;
@@ -248923,7 +248967,7 @@ const rule$O = createRule({
248923
248967
  name: 'import-integrity',
248924
248968
  });
248925
248969
 
248926
- const MESSAGE_ID$e = 'invalid-injection-token-description';
248970
+ const MESSAGE_ID$g = 'invalid-injection-token-description';
248927
248971
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
248928
248972
  const NG_DEV_MODE = 'ngDevMode';
248929
248973
  function getVariableName(node) {
@@ -248986,7 +249030,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
248986
249030
  ? fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n')
248987
249031
  : fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
248988
249032
  }
248989
- const rule$N = createRule({
249033
+ const rule$P = createRule({
248990
249034
  create(context) {
248991
249035
  const { sourceCode } = context;
248992
249036
  const program = sourceCode.ast;
@@ -249018,7 +249062,7 @@ const rule$N = createRule({
249018
249062
  }
249019
249063
  return fixes;
249020
249064
  },
249021
- messageId: MESSAGE_ID$e,
249065
+ messageId: MESSAGE_ID$g,
249022
249066
  node: description,
249023
249067
  });
249024
249068
  }
@@ -249028,14 +249072,14 @@ const rule$N = createRule({
249028
249072
  meta: {
249029
249073
  docs: { description: ERROR_MESSAGE$3 },
249030
249074
  fixable: 'code',
249031
- messages: { [MESSAGE_ID$e]: ERROR_MESSAGE$3 },
249075
+ messages: { [MESSAGE_ID$g]: ERROR_MESSAGE$3 },
249032
249076
  schema: [],
249033
249077
  type: 'problem',
249034
249078
  },
249035
249079
  name: 'injection-token-description',
249036
249080
  });
249037
249081
 
249038
- const rule$M = createRule({
249082
+ const rule$O = createRule({
249039
249083
  create(context) {
249040
249084
  const { sourceCode } = context;
249041
249085
  const namespaceImports = new Map();
@@ -249111,7 +249155,7 @@ const rule$M = createRule({
249111
249155
  name: 'no-commonjs-import-patterns',
249112
249156
  });
249113
249157
 
249114
- const MESSAGE_ID$d = 'no-deep-imports';
249158
+ const MESSAGE_ID$f = 'no-deep-imports';
249115
249159
  const ERROR_MESSAGE$2 = 'Deep imports of Taiga UI packages are prohibited';
249116
249160
  const CODE_EXTENSIONS = new Set([
249117
249161
  '.cjs',
@@ -249130,7 +249174,7 @@ const DEFAULT_OPTIONS = {
249130
249174
  importDeclaration: '^@taiga-ui*',
249131
249175
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
249132
249176
  };
249133
- const rule$L = createRule({
249177
+ const rule$N = createRule({
249134
249178
  create(context) {
249135
249179
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
249136
249180
  const hasNonCodeExtension = (source) => {
@@ -249172,7 +249216,7 @@ const rule$L = createRule({
249172
249216
  const [start, end] = node.source.range;
249173
249217
  return fixer.replaceTextRange([start + 1, end - 1], importSource.replaceAll(new RegExp(deepImport, 'g'), ''));
249174
249218
  },
249175
- messageId: MESSAGE_ID$d,
249219
+ messageId: MESSAGE_ID$f,
249176
249220
  node: node.source,
249177
249221
  });
249178
249222
  },
@@ -249182,7 +249226,7 @@ const rule$L = createRule({
249182
249226
  defaultOptions: [DEFAULT_OPTIONS],
249183
249227
  docs: { description: ERROR_MESSAGE$2 },
249184
249228
  fixable: 'code',
249185
- messages: { [MESSAGE_ID$d]: ERROR_MESSAGE$2 },
249229
+ messages: { [MESSAGE_ID$f]: ERROR_MESSAGE$2 },
249186
249230
  schema: [
249187
249231
  {
249188
249232
  additionalProperties: false,
@@ -249222,7 +249266,7 @@ const nearestFileUpCache = new Map();
249222
249266
  const markerCache = new Map();
249223
249267
  const indexFileCache = new Map();
249224
249268
  const indexExportsCache = new Map();
249225
- const rule$K = createRule({
249269
+ const rule$M = createRule({
249226
249270
  create(context) {
249227
249271
  const parserServices = dist$3.ESLintUtils.getParserServices(context);
249228
249272
  const program = parserServices.program;
@@ -249416,13 +249460,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
249416
249460
  if (!noDuplicateAttributesRule) {
249417
249461
  throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
249418
249462
  }
249419
- const rule$J = createRule({
249463
+ const rule$L = createRule({
249420
249464
  name: 'no-duplicate-attrs',
249421
249465
  rule: noDuplicateAttributesRule,
249422
249466
  });
249423
249467
 
249424
- const MESSAGE_ID$c = 'duplicateId';
249425
- const rule$I = createRule({
249468
+ const MESSAGE_ID$e = 'duplicateId';
249469
+ const rule$K = createRule({
249426
249470
  name: 'no-duplicate-id',
249427
249471
  rule: {
249428
249472
  create(context) {
@@ -249445,7 +249489,7 @@ const rule$I = createRule({
249445
249489
  context.report({
249446
249490
  data: { id },
249447
249491
  loc: sourceSpanToLoc(attr.sourceSpan),
249448
- messageId: MESSAGE_ID$c,
249492
+ messageId: MESSAGE_ID$e,
249449
249493
  });
249450
249494
  }
249451
249495
  }
@@ -249454,36 +249498,33 @@ const rule$I = createRule({
249454
249498
  },
249455
249499
  meta: {
249456
249500
  docs: { description: 'Disallow duplicate static id attributes' },
249457
- messages: { [MESSAGE_ID$c]: "The id '{{id}}' is duplicated." },
249501
+ messages: { [MESSAGE_ID$e]: "The id '{{id}}' is duplicated." },
249458
249502
  schema: [],
249459
249503
  type: 'problem',
249460
249504
  },
249461
249505
  },
249462
249506
  });
249463
249507
 
249464
- const MESSAGE_ID$b = 'duplicateTag';
249465
- function findAttr(node, attrName) {
249466
- return node.attributes.find((attr) => attr.name === attrName);
249467
- }
249508
+ const MESSAGE_ID$d = 'duplicateTag';
249468
249509
  function getTrackingKey(node) {
249469
249510
  if (node.name === 'title' || node.name === 'base') {
249470
249511
  return node.name;
249471
249512
  }
249472
249513
  if (node.name === 'meta') {
249473
- if (findAttr(node, 'charset')) {
249514
+ if (getStaticAttribute(node, 'charset')) {
249474
249515
  return 'meta[charset]';
249475
249516
  }
249476
- if (findAttr(node, 'name')?.value === 'viewport') {
249517
+ if (getStaticAttribute(node, 'name')?.value === 'viewport') {
249477
249518
  return 'meta[name=viewport]';
249478
249519
  }
249479
249520
  }
249480
249521
  return node.name === 'link' &&
249481
- findAttr(node, 'rel')?.value === 'canonical' &&
249482
- findAttr(node, 'href')
249522
+ getStaticAttribute(node, 'rel')?.value === 'canonical' &&
249523
+ getStaticAttribute(node, 'href')
249483
249524
  ? 'link[rel=canonical]'
249484
249525
  : null;
249485
249526
  }
249486
- const rule$H = createRule({
249527
+ const rule$J = createRule({
249487
249528
  name: 'no-duplicate-in-head',
249488
249529
  rule: {
249489
249530
  create(context) {
@@ -249520,7 +249561,7 @@ const rule$H = createRule({
249520
249561
  context.report({
249521
249562
  data: { tag },
249522
249563
  loc: sourceSpanToLoc(duplicate.startSourceSpan),
249523
- messageId: MESSAGE_ID$b,
249564
+ messageId: MESSAGE_ID$d,
249524
249565
  });
249525
249566
  }
249526
249567
  }
@@ -249531,7 +249572,7 @@ const rule$H = createRule({
249531
249572
  docs: {
249532
249573
  description: 'Disallow duplicate title/base/meta/link tags inside head',
249533
249574
  },
249534
- messages: { [MESSAGE_ID$b]: 'Duplicate <{{tag}}> tag in <head>.' },
249575
+ messages: { [MESSAGE_ID$d]: 'Duplicate <{{tag}}> tag in <head>.' },
249535
249576
  schema: [],
249536
249577
  type: 'problem',
249537
249578
  },
@@ -249539,7 +249580,7 @@ const rule$H = createRule({
249539
249580
  });
249540
249581
 
249541
249582
  const COMPONENT_DECORATORS = new Set(['Component']);
249542
- const rule$G = createRule({
249583
+ const rule$I = createRule({
249543
249584
  create(context) {
249544
249585
  const { sourceCode } = context;
249545
249586
  return {
@@ -250187,7 +250228,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
250187
250228
  const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
250188
250229
  const createUntrackedRule = createRule;
250189
250230
 
250190
- const rule$F = createUntrackedRule({
250231
+ const rule$H = createUntrackedRule({
250191
250232
  create(context) {
250192
250233
  const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
250193
250234
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -250225,7 +250266,7 @@ const rule$F = createUntrackedRule({
250225
250266
  name: 'no-fully-untracked-effect',
250226
250267
  });
250227
250268
 
250228
- const MESSAGE_ID$a = 'no-href-with-router-link';
250269
+ const MESSAGE_ID$c = 'no-href-with-router-link';
250229
250270
  const ERROR_MESSAGE$1 = 'Do not use href and routerLink attributes together on the same element';
250230
250271
  const config$3 = {
250231
250272
  create(context) {
@@ -250235,9 +250276,9 @@ const config$3 = {
250235
250276
  if (node.name !== 'a') {
250236
250277
  return;
250237
250278
  }
250238
- const hrefAttr = node.attributes.find((attr) => attr.name === 'href');
250239
- const hasRouterLink = node.attributes.some((attr) => attr.name.toLowerCase() === 'routerlink') ||
250240
- node.inputs.some((input) => input.name.toLowerCase() === 'routerlink');
250279
+ const hrefAttr = getStaticAttribute(node, 'href');
250280
+ const hasRouterLink = Boolean(getStaticAttribute(node, 'routerlink')) ||
250281
+ hasInputBinding(node, 'routerlink');
250241
250282
  if (!hrefAttr || !hasRouterLink) {
250242
250283
  return;
250243
250284
  }
@@ -250247,7 +250288,7 @@ const config$3 = {
250247
250288
  hrefAttr.sourceSpan.end.offset,
250248
250289
  ]),
250249
250290
  loc: sourceSpanToLoc(hrefAttr.sourceSpan),
250250
- messageId: MESSAGE_ID$a,
250291
+ messageId: MESSAGE_ID$c,
250251
250292
  });
250252
250293
  },
250253
250294
  };
@@ -250255,12 +250296,12 @@ const config$3 = {
250255
250296
  meta: {
250256
250297
  docs: { description: ERROR_MESSAGE$1 },
250257
250298
  fixable: 'code',
250258
- messages: { [MESSAGE_ID$a]: ERROR_MESSAGE$1 },
250299
+ messages: { [MESSAGE_ID$c]: ERROR_MESSAGE$1 },
250259
250300
  schema: [],
250260
250301
  type: 'problem',
250261
250302
  },
250262
250303
  };
250263
- const rule$E = createRule({
250304
+ const rule$G = createRule({
250264
250305
  name: 'no-href-with-router-link',
250265
250306
  rule: config$3,
250266
250307
  });
@@ -250321,7 +250362,7 @@ function getScopeRoot(node) {
250321
250362
  return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
250322
250363
  }
250323
250364
 
250324
- const rule$D = createRule({
250365
+ const rule$F = createRule({
250325
250366
  create(context) {
250326
250367
  const checkImplicitPublic = (node) => {
250327
250368
  const classRef = getEnclosingClass(node);
@@ -250383,7 +250424,7 @@ const rule$D = createRule({
250383
250424
  name: 'no-implicit-public',
250384
250425
  });
250385
250426
 
250386
- const rule$C = createRule({
250427
+ const rule$E = createRule({
250387
250428
  create(context) {
250388
250429
  const { sourceCode } = context;
250389
250430
  return {
@@ -250439,7 +250480,7 @@ function isInfiniteLoopLiteral(node) {
250439
250480
  function isInfiniteLoopTest(test) {
250440
250481
  return test == null || isInfiniteLoopLiteral(test);
250441
250482
  }
250442
- const rule$B = createRule({
250483
+ const rule$D = createRule({
250443
250484
  create(context) {
250444
250485
  return {
250445
250486
  DoWhileStatement(node) {
@@ -250484,7 +250525,7 @@ const rule$B = createRule({
250484
250525
  });
250485
250526
 
250486
250527
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
250487
- const rule$A = createRule({
250528
+ const rule$C = createRule({
250488
250529
  create(context) {
250489
250530
  return {
250490
250531
  Program(node) {
@@ -250522,6 +250563,395 @@ const rule$A = createRule({
250522
250563
  name: 'no-legacy-peer-deps',
250523
250564
  });
250524
250565
 
250566
+ function isInteractiveElement(node) {
250567
+ const tagName = node.name.toLowerCase();
250568
+ if (hasElementAttribute(node, 'tabindex') || hasOutputBinding(node)) {
250569
+ return true;
250570
+ }
250571
+ switch (tagName) {
250572
+ case 'a':
250573
+ return hasElementAttribute(node, ['href', 'routerLink']);
250574
+ case 'area':
250575
+ return hasElementAttribute(node, 'href');
250576
+ case 'audio':
250577
+ return hasElementAttribute(node, 'controls');
250578
+ case 'button':
250579
+ return true;
250580
+ case 'details':
250581
+ return true;
250582
+ case 'embed':
250583
+ return true;
250584
+ case 'iframe':
250585
+ return true;
250586
+ case 'img':
250587
+ return hasElementAttribute(node, 'usemap');
250588
+ case 'input':
250589
+ return (getStaticAttributeValue(node, 'type')?.trim().toLowerCase() !== 'hidden');
250590
+ case 'label':
250591
+ return true;
250592
+ case 'select':
250593
+ return true;
250594
+ case 'summary':
250595
+ return true;
250596
+ case 'textarea':
250597
+ return true;
250598
+ case 'video':
250599
+ return hasElementAttribute(node, 'controls');
250600
+ default:
250601
+ return false;
250602
+ }
250603
+ }
250604
+
250605
+ const MESSAGE_ID$b = 'noNestedInteractive';
250606
+ function getAvailableLabelParent(stack, node, labelsWithControl) {
250607
+ const parent = stack[stack.length - 1];
250608
+ return stack.length === 1 &&
250609
+ parent?.name.toLowerCase() === 'label' &&
250610
+ node.name.toLowerCase() !== 'label' &&
250611
+ !labelsWithControl.has(parent)
250612
+ ? parent
250613
+ : null;
250614
+ }
250615
+ const rule$B = createRule({
250616
+ name: 'no-nested-interactive',
250617
+ rule: {
250618
+ create(context) {
250619
+ const interactiveStack = [];
250620
+ const labelsWithControl = new WeakSet();
250621
+ return {
250622
+ Element(rawNode) {
250623
+ const node = rawNode;
250624
+ if (!isInteractiveElement(node)) {
250625
+ return;
250626
+ }
250627
+ const parent = interactiveStack[interactiveStack.length - 1];
250628
+ const availableLabelParent = getAvailableLabelParent(interactiveStack, node, labelsWithControl);
250629
+ if (availableLabelParent) {
250630
+ labelsWithControl.add(availableLabelParent);
250631
+ }
250632
+ else if (parent) {
250633
+ context.report({
250634
+ data: { tag: parent.name },
250635
+ loc: sourceSpanToLoc(node.startSourceSpan),
250636
+ messageId: MESSAGE_ID$b,
250637
+ });
250638
+ }
250639
+ interactiveStack.push(node);
250640
+ },
250641
+ 'Element:exit'(rawNode) {
250642
+ const node = rawNode;
250643
+ if (interactiveStack[interactiveStack.length - 1] === node) {
250644
+ interactiveStack.pop();
250645
+ }
250646
+ },
250647
+ };
250648
+ },
250649
+ meta: {
250650
+ docs: { description: 'Disallow nested interactive elements' },
250651
+ messages: {
250652
+ [MESSAGE_ID$b]: 'Unexpected interactive element nested inside interactive <{{tag}}>.',
250653
+ },
250654
+ schema: [],
250655
+ type: 'problem',
250656
+ },
250657
+ },
250658
+ });
250659
+
250660
+ function isBoundAttribute(node) {
250661
+ return node instanceof dist$4.TmplAstBoundAttribute;
250662
+ }
250663
+ function getBoundAttributes(container) {
250664
+ return container instanceof dist$4.TmplAstTemplate
250665
+ ? [...container.inputs, ...container.templateAttrs.filter(isBoundAttribute)]
250666
+ : container.inputs;
250667
+ }
250668
+ function getContainingBoundAttribute(container, node) {
250669
+ return container
250670
+ ? (getBoundAttributes(container).find((attribute) => containsAbsoluteSourceSpan(attribute.value.sourceSpan, node.sourceSpan)) ?? null)
250671
+ : null;
250672
+ }
250673
+
250674
+ function isConditional(node) {
250675
+ return 'condition' in node && 'trueExp' in node && 'falseExp' in node;
250676
+ }
250677
+
250678
+ class TemplateIdentifierCollector extends dist$4.TmplAstRecursiveVisitor {
250679
+ constructor(names) {
250680
+ super();
250681
+ this.names = names;
250682
+ }
250683
+ visitLetDeclaration(node) {
250684
+ this.names.add(node.name);
250685
+ }
250686
+ visitReference(node) {
250687
+ this.names.add(node.name);
250688
+ }
250689
+ visitVariable(node) {
250690
+ this.names.add(node.name);
250691
+ }
250692
+ }
250693
+ function getTemplateNodes(ast) {
250694
+ if (!ast || typeof ast !== 'object' || !('templateNodes' in ast)) {
250695
+ return [];
250696
+ }
250697
+ const { templateNodes } = ast;
250698
+ return Array.isArray(templateNodes) ? templateNodes : [];
250699
+ }
250700
+ function collectTemplateIdentifiers(ast) {
250701
+ const names = new Set();
250702
+ dist$4.tmplAstVisitAll(new TemplateIdentifierCollector(names), [...getTemplateNodes(ast)]);
250703
+ return names;
250704
+ }
250705
+
250706
+ const IDENTIFIER = /^[A-Z_$][\w$]*$/i;
250707
+ const RESERVED_IDENTIFIERS = new Set([
250708
+ 'arguments',
250709
+ 'await',
250710
+ 'break',
250711
+ 'case',
250712
+ 'catch',
250713
+ 'class',
250714
+ 'const',
250715
+ 'continue',
250716
+ 'debugger',
250717
+ 'default',
250718
+ 'delete',
250719
+ 'do',
250720
+ 'else',
250721
+ 'enum',
250722
+ 'eval',
250723
+ 'export',
250724
+ 'extends',
250725
+ 'false',
250726
+ 'finally',
250727
+ 'for',
250728
+ 'function',
250729
+ 'if',
250730
+ 'implements',
250731
+ 'import',
250732
+ 'in',
250733
+ 'instanceof',
250734
+ 'interface',
250735
+ 'let',
250736
+ 'new',
250737
+ 'null',
250738
+ 'package',
250739
+ 'private',
250740
+ 'protected',
250741
+ 'public',
250742
+ 'return',
250743
+ 'static',
250744
+ 'super',
250745
+ 'switch',
250746
+ 'this',
250747
+ 'throw',
250748
+ 'true',
250749
+ 'try',
250750
+ 'typeof',
250751
+ 'undefined',
250752
+ 'var',
250753
+ 'void',
250754
+ 'while',
250755
+ 'with',
250756
+ 'yield',
250757
+ ]);
250758
+ function isIdentifier(name) {
250759
+ return IDENTIFIER.test(name) && !RESERVED_IDENTIFIERS.has(name);
250760
+ }
250761
+ function getAvailableIdentifier(baseName, unavailableNames) {
250762
+ if (!isIdentifier(baseName)) {
250763
+ throw new Error(`Expected a valid identifier, got "${baseName}"`);
250764
+ }
250765
+ if (!unavailableNames.has(baseName)) {
250766
+ return baseName;
250767
+ }
250768
+ let suffix = 2;
250769
+ let name = `${baseName}${suffix}`;
250770
+ while (unavailableNames.has(name)) {
250771
+ suffix++;
250772
+ name = `${baseName}${suffix}`;
250773
+ }
250774
+ return name;
250775
+ }
250776
+
250777
+ function isSingleLineNode(node) {
250778
+ return node.loc.start.line === node.loc.end.line;
250779
+ }
250780
+ function hasCommentLikeText(text) {
250781
+ return text.includes('//') || text.includes('/*');
250782
+ }
250783
+ function hasBlankLine(text) {
250784
+ let lineBreaks = 0;
250785
+ for (let index = 0; index < text.length; index++) {
250786
+ const char = text[index];
250787
+ if (char === '\n') {
250788
+ lineBreaks++;
250789
+ }
250790
+ else if (char === '\r') {
250791
+ lineBreaks++;
250792
+ if (text[index + 1] === '\n') {
250793
+ index++;
250794
+ }
250795
+ }
250796
+ if (lineBreaks > 1) {
250797
+ return true;
250798
+ }
250799
+ }
250800
+ return false;
250801
+ }
250802
+ function getLineBreak(text) {
250803
+ if (text.includes('\r\n')) {
250804
+ return '\r\n';
250805
+ }
250806
+ return text.includes('\r') ? '\r' : '\n';
250807
+ }
250808
+ function getLeadingIndentation(text) {
250809
+ let index = 0;
250810
+ while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
250811
+ index++;
250812
+ }
250813
+ return text.slice(0, index);
250814
+ }
250815
+ function getIndentAtOffset(text, offset) {
250816
+ const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
250817
+ const indent = text.slice(lineStart, offset);
250818
+ return indent.trim() === '' ? indent : '';
250819
+ }
250820
+ function getSpacingReplacement(sourceCode, betweenText, nextLine, blankLineCount) {
250821
+ const indentation = getLeadingIndentation(sourceCode.lines[nextLine - 1] ?? '');
250822
+ return `${getLineBreak(betweenText).repeat(blankLineCount + 1)}${indentation}`;
250823
+ }
250824
+
250825
+ const MESSAGE_ID$a = 'noNestedTernaryInTemplate';
250826
+ const MESSAGE = 'Avoid nested ternary expressions in Angular templates. Move the nested branch into an @let declaration.';
250827
+ function createLetFixes(node, baseName, unavailableNames, text) {
250828
+ const name = getAvailableIdentifier(baseName, unavailableNames);
250829
+ unavailableNames.add(name);
250830
+ const nestedLets = [];
250831
+ const render = (child) => {
250832
+ if (isConditional(child)) {
250833
+ const result = createLetFixes(child, baseName, unavailableNames, text);
250834
+ nestedLets.push(...result.lets);
250835
+ return result.reference;
250836
+ }
250837
+ return getAbsoluteSourceSpanText(text, child.sourceSpan);
250838
+ };
250839
+ const condition = render(node.condition);
250840
+ const trueExp = render(node.trueExp);
250841
+ const falseExp = render(node.falseExp);
250842
+ return {
250843
+ lets: [
250844
+ ...nestedLets,
250845
+ {
250846
+ expression: `${condition} ? ${trueExp} : ${falseExp}`,
250847
+ name,
250848
+ },
250849
+ ],
250850
+ reference: name,
250851
+ };
250852
+ }
250853
+ const rule$A = createRule({
250854
+ name: 'no-nested-ternary-in-template',
250855
+ rule: {
250856
+ create(context) {
250857
+ const { sourceCode } = context;
250858
+ const conditionalStack = [];
250859
+ const containerStack = [];
250860
+ const letNames = collectTemplateIdentifiers(sourceCode.ast);
250861
+ let boundEventDepth = 0;
250862
+ let letDeclarationDepth = 0;
250863
+ function reportNestedConditional(node, options) {
250864
+ const container = containerStack[containerStack.length - 1];
250865
+ const attribute = getContainingBoundAttribute(container, node);
250866
+ const baseName = attribute?.name;
250867
+ const fixable = options.allowFix &&
250868
+ boundEventDepth === 0 &&
250869
+ letDeclarationDepth === 0 &&
250870
+ baseName &&
250871
+ isIdentifier(baseName);
250872
+ context.report({
250873
+ ...(fixable
250874
+ ? {
250875
+ fix(fixer) {
250876
+ const result = createLetFixes(node, baseName, letNames, sourceCode.text);
250877
+ const insertOffset = container?.startSourceSpan.start.offset;
250878
+ if (insertOffset === undefined) {
250879
+ return null;
250880
+ }
250881
+ const indent = getIndentAtOffset(sourceCode.text, insertOffset);
250882
+ const declarations = result.lets
250883
+ .map(({ expression, name }, index) => `${index === 0 ? '' : indent}@let ${name} = ${expression};`)
250884
+ .join('\n');
250885
+ return [
250886
+ fixer.insertTextBeforeRange([insertOffset, insertOffset], `${declarations}\n${indent}`),
250887
+ fixer.replaceTextRange([node.sourceSpan.start, node.sourceSpan.end], result.reference),
250888
+ ];
250889
+ },
250890
+ }
250891
+ : {}),
250892
+ loc: {
250893
+ end: sourceCode.getLocFromIndex(node.sourceSpan.end),
250894
+ start: sourceCode.getLocFromIndex(node.sourceSpan.start),
250895
+ },
250896
+ messageId: MESSAGE_ID$a,
250897
+ });
250898
+ }
250899
+ return {
250900
+ BoundEvent() {
250901
+ boundEventDepth++;
250902
+ },
250903
+ 'BoundEvent:exit'() {
250904
+ boundEventDepth--;
250905
+ },
250906
+ Conditional(rawNode) {
250907
+ const node = rawNode;
250908
+ if (conditionalStack.length > 0) {
250909
+ reportNestedConditional(node, {
250910
+ allowFix: conditionalStack.length === 1,
250911
+ });
250912
+ }
250913
+ conditionalStack.push(node);
250914
+ },
250915
+ 'Conditional:exit'(rawNode) {
250916
+ const node = rawNode;
250917
+ if (conditionalStack[conditionalStack.length - 1] === node) {
250918
+ conditionalStack.pop();
250919
+ }
250920
+ },
250921
+ Element(rawNode) {
250922
+ containerStack.push(rawNode);
250923
+ },
250924
+ 'Element:exit'(rawNode) {
250925
+ if (containerStack[containerStack.length - 1] === rawNode) {
250926
+ containerStack.pop();
250927
+ }
250928
+ },
250929
+ LetDeclaration() {
250930
+ letDeclarationDepth++;
250931
+ },
250932
+ 'LetDeclaration:exit'() {
250933
+ letDeclarationDepth--;
250934
+ },
250935
+ Template(rawNode) {
250936
+ containerStack.push(rawNode);
250937
+ },
250938
+ 'Template:exit'(rawNode) {
250939
+ if (containerStack[containerStack.length - 1] === rawNode) {
250940
+ containerStack.pop();
250941
+ }
250942
+ },
250943
+ };
250944
+ },
250945
+ meta: {
250946
+ docs: { description: MESSAGE },
250947
+ fixable: 'code',
250948
+ messages: { [MESSAGE_ID$a]: MESSAGE },
250949
+ schema: [],
250950
+ type: 'suggestion',
250951
+ },
250952
+ },
250953
+ });
250954
+
250525
250955
  const OBSOLETE_HTML_ATTRS = {
250526
250956
  abbr: [
250527
250957
  {
@@ -254048,10 +254478,6 @@ const rule$e = createRule({
254048
254478
  });
254049
254479
 
254050
254480
  const MESSAGE_ID$5 = 'missingAlt';
254051
- function hasAlt(node) {
254052
- return (node.attributes.some((attr) => attr.name === 'alt') ||
254053
- node.inputs.some((input) => input.name === 'alt' || input.keySpan.details === 'attr.alt'));
254054
- }
254055
254481
  const rule$d = createRule({
254056
254482
  name: 'require-img-alt',
254057
254483
  rule: {
@@ -254059,7 +254485,7 @@ const rule$d = createRule({
254059
254485
  return {
254060
254486
  Element(rawNode) {
254061
254487
  const node = rawNode;
254062
- if (node.name !== 'img' || hasAlt(node)) {
254488
+ if (node.name !== 'img' || hasElementAttribute(node, 'alt')) {
254063
254489
  return;
254064
254490
  }
254065
254491
  context.report({
@@ -254092,16 +254518,14 @@ const rule$c = createRule({
254092
254518
  if (node.name !== 'html') {
254093
254519
  return;
254094
254520
  }
254095
- const langAttr = node.attributes.find((attr) => attr.name === 'lang');
254096
- const hasBoundLang = node.inputs.some((input) => input.name === 'lang' ||
254097
- input.keySpan.details === 'attr.lang');
254098
- if (!langAttr && !hasBoundLang) {
254521
+ if (!hasElementAttribute(node, 'lang')) {
254099
254522
  context.report({
254100
254523
  loc: sourceSpanToLoc(node.startSourceSpan),
254101
254524
  messageId: MESSAGE_IDS$1.MISSING,
254102
254525
  });
254103
254526
  return;
254104
254527
  }
254528
+ const langAttr = getStaticAttribute(node, 'lang');
254105
254529
  if (langAttr?.value.trim().length === 0) {
254106
254530
  context.report({
254107
254531
  loc: sourceSpanToLoc(langAttr.sourceSpan),
@@ -254430,49 +254854,6 @@ const rule$9 = createRule({
254430
254854
  name: 'short-tui-imports',
254431
254855
  });
254432
254856
 
254433
- function isSingleLineNode(node) {
254434
- return node.loc.start.line === node.loc.end.line;
254435
- }
254436
- function hasCommentLikeText(text) {
254437
- return text.includes('//') || text.includes('/*');
254438
- }
254439
- function hasBlankLine(text) {
254440
- let lineBreaks = 0;
254441
- for (let index = 0; index < text.length; index++) {
254442
- const char = text[index];
254443
- if (char === '\n') {
254444
- lineBreaks++;
254445
- }
254446
- else if (char === '\r') {
254447
- lineBreaks++;
254448
- if (text[index + 1] === '\n') {
254449
- index++;
254450
- }
254451
- }
254452
- if (lineBreaks > 1) {
254453
- return true;
254454
- }
254455
- }
254456
- return false;
254457
- }
254458
- function getLineBreak(text) {
254459
- if (text.includes('\r\n')) {
254460
- return '\r\n';
254461
- }
254462
- return text.includes('\r') ? '\r' : '\n';
254463
- }
254464
- function getLeadingIndentation(text) {
254465
- let index = 0;
254466
- while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
254467
- index++;
254468
- }
254469
- return text.slice(0, index);
254470
- }
254471
- function getSpacingReplacement(sourceCode, betweenText, nextLine, blankLineCount) {
254472
- const indentation = getLeadingIndentation(sourceCode.lines[nextLine - 1] ?? '');
254473
- return `${getLineBreak(betweenText).repeat(blankLineCount + 1)}${indentation}`;
254474
- }
254475
-
254476
254857
  function isFieldLikeMember(member) {
254477
254858
  return (member.type === dist$3.AST_NODE_TYPES.PropertyDefinition ||
254478
254859
  member.type === dist$3.AST_NODE_TYPES.TSAbstractPropertyDefinition);
@@ -255707,28 +256088,30 @@ const plugin = {
255707
256088
  },
255708
256089
  rules: {
255709
256090
  'array-as-const': rule$5,
255710
- 'attrs-newline': rule$T,
256091
+ 'attrs-newline': rule$V,
255711
256092
  'class-property-naming': rule$4,
255712
- 'decorator-key-sort': rule$S,
255713
- 'element-newline': rule$R,
256093
+ 'decorator-key-sort': rule$U,
256094
+ 'element-newline': rule$T,
255714
256095
  'flat-exports': rule$3,
255715
- 'host-attributes-sort': rule$Q,
255716
- 'html-logical-properties': rule$P,
255717
- 'import-integrity': rule$O,
255718
- 'injection-token-description': rule$N,
255719
- 'no-commonjs-import-patterns': rule$M,
255720
- 'no-deep-imports': rule$L,
255721
- 'no-deep-imports-to-indexed-packages': rule$K,
255722
- 'no-duplicate-attrs': rule$J,
255723
- 'no-duplicate-id': rule$I,
255724
- 'no-duplicate-in-head': rule$H,
255725
- 'no-empty-style-metadata': rule$G,
255726
- 'no-fully-untracked-effect': rule$F,
255727
- 'no-href-with-router-link': rule$E,
255728
- 'no-implicit-public': rule$D,
255729
- 'no-import-assertions': rule$C,
255730
- 'no-infinite-loop': rule$B,
255731
- 'no-legacy-peer-deps': rule$A,
256096
+ 'host-attributes-sort': rule$S,
256097
+ 'html-logical-properties': rule$R,
256098
+ 'import-integrity': rule$Q,
256099
+ 'injection-token-description': rule$P,
256100
+ 'no-commonjs-import-patterns': rule$O,
256101
+ 'no-deep-imports': rule$N,
256102
+ 'no-deep-imports-to-indexed-packages': rule$M,
256103
+ 'no-duplicate-attrs': rule$L,
256104
+ 'no-duplicate-id': rule$K,
256105
+ 'no-duplicate-in-head': rule$J,
256106
+ 'no-empty-style-metadata': rule$I,
256107
+ 'no-fully-untracked-effect': rule$H,
256108
+ 'no-href-with-router-link': rule$G,
256109
+ 'no-implicit-public': rule$F,
256110
+ 'no-import-assertions': rule$E,
256111
+ 'no-infinite-loop': rule$D,
256112
+ 'no-legacy-peer-deps': rule$C,
256113
+ 'no-nested-interactive': rule$B,
256114
+ 'no-nested-ternary-in-template': rule$A,
255732
256115
  'no-obsolete-attrs': rule$z,
255733
256116
  'no-obsolete-tags': rule$y,
255734
256117
  'no-playwright-empty-fill': rule$x,
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.513.0",
3
+ "version": "0.515.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
+ "homepage": "https://github.com/taiga-family/toolkit#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/taiga-family/toolkit/issues"
8
+ },
5
9
  "repository": {
6
10
  "type": "git",
7
11
  "url": "https://github.com/taiga-family/toolkit.git"
@@ -0,0 +1,5 @@
1
+ import { type Rule } from 'eslint';
2
+ export declare const rule: Rule.RuleModule & {
3
+ name: string;
4
+ };
5
+ export default rule;
@@ -0,0 +1,5 @@
1
+ import { type Rule } from 'eslint';
2
+ export declare const rule: Rule.RuleModule & {
3
+ name: string;
4
+ };
5
+ export default rule;
@@ -1,4 +1,11 @@
1
1
  import { type ParseSourceSpan, type TmplAstBoundAttribute, TmplAstBoundEvent, type TmplAstElement, type TmplAstReference, type TmplAstTextAttribute } from '@angular-eslint/bundled-angular-compiler';
2
2
  export type ElementAttributeLike = TmplAstBoundAttribute | TmplAstBoundEvent | TmplAstReference | TmplAstTextAttribute;
3
+ export type ElementAttributeName = string | readonly string[];
3
4
  export declare function getAttributeValueSpan(attr: ElementAttributeLike): ParseSourceSpan | undefined;
5
+ export declare function getStaticAttribute(element: TmplAstElement, name: string): TmplAstTextAttribute | undefined;
6
+ export declare function getStaticAttributeValue(element: TmplAstElement, name: string): string | undefined;
7
+ export declare function hasInputBinding(element: TmplAstElement, name: ElementAttributeName): boolean;
8
+ export declare function hasAttributeBinding(element: TmplAstElement, name: ElementAttributeName): boolean;
9
+ export declare function hasElementAttribute(element: TmplAstElement, name: ElementAttributeName): boolean;
10
+ export declare function hasOutputBinding(element: TmplAstElement, name?: ElementAttributeName): boolean;
4
11
  export declare function getElementAttributeLikes(element: TmplAstElement): ElementAttributeLike[];
@@ -0,0 +1,2 @@
1
+ import { type TmplAstElement } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function isInteractiveElement(node: TmplAstElement): boolean;
@@ -1,3 +1,5 @@
1
- import { type ParseSourceSpan } from '@angular-eslint/bundled-angular-compiler';
1
+ import { type AbsoluteSourceSpan, type ParseSourceSpan } from '@angular-eslint/bundled-angular-compiler';
2
2
  import { type SourceLocation } from 'estree';
3
3
  export declare function sourceSpanToLoc(span: ParseSourceSpan): SourceLocation;
4
+ export declare function containsAbsoluteSourceSpan(container: AbsoluteSourceSpan, child: AbsoluteSourceSpan): boolean;
5
+ export declare function getAbsoluteSourceSpanText(text: string, span: AbsoluteSourceSpan): string;
@@ -0,0 +1,5 @@
1
+ import { type AST, TmplAstBoundAttribute, type TmplAstElement, TmplAstTemplate } from '@angular-eslint/bundled-angular-compiler';
2
+ export type TemplateAttributeContainer = TmplAstElement | TmplAstTemplate;
3
+ export declare function isBoundAttribute(node: unknown): node is TmplAstBoundAttribute;
4
+ export declare function getBoundAttributes(container: TemplateAttributeContainer): readonly TmplAstBoundAttribute[];
5
+ export declare function getContainingBoundAttribute(container: TemplateAttributeContainer | undefined, node: AST): TmplAstBoundAttribute | null;
@@ -0,0 +1,4 @@
1
+ import { type AST, type ASTWithSource, type Conditional } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function isAstWithSource(node: AST): node is ASTWithSource;
3
+ export declare function unwrapAstWithSource(node: AST): AST;
4
+ export declare function isConditional(node: AST): node is Conditional;
@@ -0,0 +1,10 @@
1
+ import { type TmplAstLetDeclaration, type TmplAstNode, TmplAstRecursiveVisitor, type TmplAstReference, type TmplAstVariable } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare class TemplateIdentifierCollector extends TmplAstRecursiveVisitor {
3
+ private readonly names;
4
+ constructor(names: Set<string>);
5
+ visitLetDeclaration(node: TmplAstLetDeclaration): void;
6
+ visitReference(node: TmplAstReference): void;
7
+ visitVariable(node: TmplAstVariable): void;
8
+ }
9
+ export declare function getTemplateNodes(ast: unknown): readonly TmplAstNode[];
10
+ export declare function collectTemplateIdentifiers(ast: unknown): Set<string>;
@@ -0,0 +1,2 @@
1
+ export declare function isIdentifier(name: string): boolean;
2
+ export declare function getAvailableIdentifier(baseName: string, unavailableNames: ReadonlySet<string>): string;
@@ -4,4 +4,5 @@ export declare function hasCommentLikeText(text: string): boolean;
4
4
  export declare function hasBlankLine(text: string): boolean;
5
5
  export declare function getLineBreak(text: string): string;
6
6
  export declare function getLeadingIndentation(text: string): string;
7
+ export declare function getIndentAtOffset(text: string, offset: number): string;
7
8
  export declare function getSpacingReplacement(sourceCode: Readonly<TSESLint.SourceCode>, betweenText: string, nextLine: number, blankLineCount: number): string;