@taiga-ui/eslint-plugin-experience-next 0.514.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
@@ -96,6 +96,7 @@ from third-party plugins. The exact severities and file globs live in
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
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 | ✅ | 🔧 | |
99
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 | ✅ | | |
100
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 | ✅ | | |
101
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
@@ -102,6 +102,9 @@ declare const plugin: {
102
102
  'no-nested-interactive': import("eslint").Rule.RuleModule & {
103
103
  name: string;
104
104
  };
105
+ 'no-nested-ternary-in-template': import("eslint").Rule.RuleModule & {
106
+ name: string;
107
+ };
105
108
  'no-obsolete-attrs': import("eslint").Rule.RuleModule & {
106
109
  name: string;
107
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
 
@@ -1310,6 +1311,7 @@ var recommended = defineConfig([
1310
1311
  '@taiga-ui/experience-next/no-duplicate-in-head': 'error',
1311
1312
  '@taiga-ui/experience-next/no-href-with-router-link': 'error',
1312
1313
  '@taiga-ui/experience-next/no-nested-interactive': 'error',
1314
+ '@taiga-ui/experience-next/no-nested-ternary-in-template': angularVersion >= modernAngularRules.templateLet ? 'error' : 'off',
1313
1315
  '@taiga-ui/experience-next/no-obsolete-attrs': 'error',
1314
1316
  '@taiga-ui/experience-next/no-obsolete-tags': 'error',
1315
1317
  '@taiga-ui/experience-next/no-project-as-in-ng-template': 'error',
@@ -33282,6 +33284,12 @@ function sourceSpanToLoc(span) {
33282
33284
  },
33283
33285
  };
33284
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
+ }
33285
33293
 
33286
33294
  var dist$3 = {};
33287
33295
 
@@ -46269,7 +46277,7 @@ function buildMultilineStartTag(node, sourceText) {
46269
46277
  closing,
46270
46278
  ].join('\n');
46271
46279
  }
46272
- const rule$U = createRule({
46280
+ const rule$V = createRule({
46273
46281
  name: 'attrs-newline',
46274
46282
  rule: {
46275
46283
  create(context) {
@@ -46449,7 +46457,7 @@ function sameOrder(a, b) {
46449
46457
  return a.length === b.length && a.every((value, index) => value === b[index]);
46450
46458
  }
46451
46459
 
46452
- const rule$T = createRule({
46460
+ const rule$U = createRule({
46453
46461
  create(context, [order]) {
46454
46462
  const decorators = new Set(Object.keys(order));
46455
46463
  return {
@@ -46561,7 +46569,7 @@ const INLINE_HTML_ELEMENTS = new Set([
46561
46569
  'wbr',
46562
46570
  ]);
46563
46571
 
46564
- const MESSAGE_ID$h = 'expectAfter';
46572
+ const MESSAGE_ID$i = 'expectAfter';
46565
46573
  const SKIP_CHILDREN = new Set(['code', 'pre']);
46566
46574
  function isChildNode(node) {
46567
46575
  return (node instanceof dist$4.TmplAstBoundText ||
@@ -46585,7 +46593,7 @@ function getNodeLabel(node) {
46585
46593
  }
46586
46594
  return node instanceof dist$4.TmplAstBoundText ? 'binding' : 'text';
46587
46595
  }
46588
- const rule$S = createRule({
46596
+ const rule$T = createRule({
46589
46597
  name: 'element-newline',
46590
46598
  rule: {
46591
46599
  create(context) {
@@ -46611,7 +46619,7 @@ const rule$S = createRule({
46611
46619
  firstChild.sourceSpan.start.offset,
46612
46620
  ], '\n'),
46613
46621
  loc: sourceSpanToLoc(firstChild.sourceSpan),
46614
- messageId: MESSAGE_ID$h,
46622
+ messageId: MESSAGE_ID$i,
46615
46623
  });
46616
46624
  return;
46617
46625
  }
@@ -46629,7 +46637,7 @@ const rule$S = createRule({
46629
46637
  child.sourceSpan.end.offset,
46630
46638
  ], '\n'),
46631
46639
  loc: sourceSpanToLoc(next.sourceSpan),
46632
- messageId: MESSAGE_ID$h,
46640
+ messageId: MESSAGE_ID$i,
46633
46641
  });
46634
46642
  return;
46635
46643
  }
@@ -46643,7 +46651,7 @@ const rule$S = createRule({
46643
46651
  lastChild.sourceSpan.end.offset,
46644
46652
  ], '\n'),
46645
46653
  loc: sourceSpanToLoc(lastChild.sourceSpan),
46646
- messageId: MESSAGE_ID$h,
46654
+ messageId: MESSAGE_ID$i,
46647
46655
  });
46648
46656
  }
46649
46657
  },
@@ -46652,7 +46660,7 @@ const rule$S = createRule({
46652
46660
  meta: {
46653
46661
  docs: { description: 'Enforce line breaks between block-level child nodes' },
46654
46662
  fixable: 'code',
46655
- messages: { [MESSAGE_ID$h]: 'There should be a linebreak after {{name}}.' },
46663
+ messages: { [MESSAGE_ID$i]: 'There should be a linebreak after {{name}}.' },
46656
46664
  schema: [],
46657
46665
  type: 'layout',
46658
46666
  },
@@ -46724,7 +46732,7 @@ const PRESETS = {
46724
46732
  $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
46725
46733
  $VUE_ATTRIBUTE: /^v-/,
46726
46734
  };
46727
- const rule$R = createRule({
46735
+ const rule$S = createRule({
46728
46736
  create(context, [options]) {
46729
46737
  const sourceCode = context.sourceCode;
46730
46738
  const settings = {
@@ -47004,8 +47012,8 @@ const DIRECTIONAL_TO_LOGICAL = {
47004
47012
  top: 'inset-block-start',
47005
47013
  };
47006
47014
  const STYLE_PREFIX = 'style.';
47007
- const MESSAGE_ID$g = 'html-logical-properties';
47008
- const MESSAGE = `
47015
+ const MESSAGE_ID$h = 'html-logical-properties';
47016
+ const MESSAGE$1 = `
47009
47017
  Use logical CSS properties instead of directional properties. Replace:
47010
47018
  • left → inset-inline-start
47011
47019
  • right → inset-inline-end
@@ -47042,20 +47050,20 @@ const config$4 = {
47042
47050
  context.report({
47043
47051
  fix: (fixer) => fixer.replaceTextRange([propertyStart, propertyEnd], logicalProperty),
47044
47052
  loc: sourceSpanToLoc(keySpan),
47045
- messageId: MESSAGE_ID$g,
47053
+ messageId: MESSAGE_ID$h,
47046
47054
  });
47047
47055
  },
47048
47056
  };
47049
47057
  },
47050
47058
  meta: {
47051
- docs: { description: MESSAGE },
47059
+ docs: { description: MESSAGE$1 },
47052
47060
  fixable: 'code',
47053
- messages: { [MESSAGE_ID$g]: MESSAGE },
47061
+ messages: { [MESSAGE_ID$h]: MESSAGE$1 },
47054
47062
  schema: [],
47055
47063
  type: 'suggestion',
47056
47064
  },
47057
47065
  };
47058
- const rule$Q = createRule({
47066
+ const rule$R = createRule({
47059
47067
  name: 'html-logical-properties',
47060
47068
  rule: config$4,
47061
47069
  });
@@ -248297,7 +248305,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
248297
248305
  }
248298
248306
  return hasSafeRuntimeUsage;
248299
248307
  }
248300
- const rule$P = createRule({
248308
+ const rule$Q = createRule({
248301
248309
  create(context) {
248302
248310
  const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
248303
248311
  const checkCycles = context.options[0]?.checkCycles ?? true;
@@ -248959,7 +248967,7 @@ const rule$P = createRule({
248959
248967
  name: 'import-integrity',
248960
248968
  });
248961
248969
 
248962
- const MESSAGE_ID$f = 'invalid-injection-token-description';
248970
+ const MESSAGE_ID$g = 'invalid-injection-token-description';
248963
248971
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
248964
248972
  const NG_DEV_MODE = 'ngDevMode';
248965
248973
  function getVariableName(node) {
@@ -249022,7 +249030,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
249022
249030
  ? fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n')
249023
249031
  : fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
249024
249032
  }
249025
- const rule$O = createRule({
249033
+ const rule$P = createRule({
249026
249034
  create(context) {
249027
249035
  const { sourceCode } = context;
249028
249036
  const program = sourceCode.ast;
@@ -249054,7 +249062,7 @@ const rule$O = createRule({
249054
249062
  }
249055
249063
  return fixes;
249056
249064
  },
249057
- messageId: MESSAGE_ID$f,
249065
+ messageId: MESSAGE_ID$g,
249058
249066
  node: description,
249059
249067
  });
249060
249068
  }
@@ -249064,14 +249072,14 @@ const rule$O = createRule({
249064
249072
  meta: {
249065
249073
  docs: { description: ERROR_MESSAGE$3 },
249066
249074
  fixable: 'code',
249067
- messages: { [MESSAGE_ID$f]: ERROR_MESSAGE$3 },
249075
+ messages: { [MESSAGE_ID$g]: ERROR_MESSAGE$3 },
249068
249076
  schema: [],
249069
249077
  type: 'problem',
249070
249078
  },
249071
249079
  name: 'injection-token-description',
249072
249080
  });
249073
249081
 
249074
- const rule$N = createRule({
249082
+ const rule$O = createRule({
249075
249083
  create(context) {
249076
249084
  const { sourceCode } = context;
249077
249085
  const namespaceImports = new Map();
@@ -249147,7 +249155,7 @@ const rule$N = createRule({
249147
249155
  name: 'no-commonjs-import-patterns',
249148
249156
  });
249149
249157
 
249150
- const MESSAGE_ID$e = 'no-deep-imports';
249158
+ const MESSAGE_ID$f = 'no-deep-imports';
249151
249159
  const ERROR_MESSAGE$2 = 'Deep imports of Taiga UI packages are prohibited';
249152
249160
  const CODE_EXTENSIONS = new Set([
249153
249161
  '.cjs',
@@ -249166,7 +249174,7 @@ const DEFAULT_OPTIONS = {
249166
249174
  importDeclaration: '^@taiga-ui*',
249167
249175
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
249168
249176
  };
249169
- const rule$M = createRule({
249177
+ const rule$N = createRule({
249170
249178
  create(context) {
249171
249179
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
249172
249180
  const hasNonCodeExtension = (source) => {
@@ -249208,7 +249216,7 @@ const rule$M = createRule({
249208
249216
  const [start, end] = node.source.range;
249209
249217
  return fixer.replaceTextRange([start + 1, end - 1], importSource.replaceAll(new RegExp(deepImport, 'g'), ''));
249210
249218
  },
249211
- messageId: MESSAGE_ID$e,
249219
+ messageId: MESSAGE_ID$f,
249212
249220
  node: node.source,
249213
249221
  });
249214
249222
  },
@@ -249218,7 +249226,7 @@ const rule$M = createRule({
249218
249226
  defaultOptions: [DEFAULT_OPTIONS],
249219
249227
  docs: { description: ERROR_MESSAGE$2 },
249220
249228
  fixable: 'code',
249221
- messages: { [MESSAGE_ID$e]: ERROR_MESSAGE$2 },
249229
+ messages: { [MESSAGE_ID$f]: ERROR_MESSAGE$2 },
249222
249230
  schema: [
249223
249231
  {
249224
249232
  additionalProperties: false,
@@ -249258,7 +249266,7 @@ const nearestFileUpCache = new Map();
249258
249266
  const markerCache = new Map();
249259
249267
  const indexFileCache = new Map();
249260
249268
  const indexExportsCache = new Map();
249261
- const rule$L = createRule({
249269
+ const rule$M = createRule({
249262
249270
  create(context) {
249263
249271
  const parserServices = dist$3.ESLintUtils.getParserServices(context);
249264
249272
  const program = parserServices.program;
@@ -249452,13 +249460,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
249452
249460
  if (!noDuplicateAttributesRule) {
249453
249461
  throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
249454
249462
  }
249455
- const rule$K = createRule({
249463
+ const rule$L = createRule({
249456
249464
  name: 'no-duplicate-attrs',
249457
249465
  rule: noDuplicateAttributesRule,
249458
249466
  });
249459
249467
 
249460
- const MESSAGE_ID$d = 'duplicateId';
249461
- const rule$J = createRule({
249468
+ const MESSAGE_ID$e = 'duplicateId';
249469
+ const rule$K = createRule({
249462
249470
  name: 'no-duplicate-id',
249463
249471
  rule: {
249464
249472
  create(context) {
@@ -249481,7 +249489,7 @@ const rule$J = createRule({
249481
249489
  context.report({
249482
249490
  data: { id },
249483
249491
  loc: sourceSpanToLoc(attr.sourceSpan),
249484
- messageId: MESSAGE_ID$d,
249492
+ messageId: MESSAGE_ID$e,
249485
249493
  });
249486
249494
  }
249487
249495
  }
@@ -249490,14 +249498,14 @@ const rule$J = createRule({
249490
249498
  },
249491
249499
  meta: {
249492
249500
  docs: { description: 'Disallow duplicate static id attributes' },
249493
- messages: { [MESSAGE_ID$d]: "The id '{{id}}' is duplicated." },
249501
+ messages: { [MESSAGE_ID$e]: "The id '{{id}}' is duplicated." },
249494
249502
  schema: [],
249495
249503
  type: 'problem',
249496
249504
  },
249497
249505
  },
249498
249506
  });
249499
249507
 
249500
- const MESSAGE_ID$c = 'duplicateTag';
249508
+ const MESSAGE_ID$d = 'duplicateTag';
249501
249509
  function getTrackingKey(node) {
249502
249510
  if (node.name === 'title' || node.name === 'base') {
249503
249511
  return node.name;
@@ -249516,7 +249524,7 @@ function getTrackingKey(node) {
249516
249524
  ? 'link[rel=canonical]'
249517
249525
  : null;
249518
249526
  }
249519
- const rule$I = createRule({
249527
+ const rule$J = createRule({
249520
249528
  name: 'no-duplicate-in-head',
249521
249529
  rule: {
249522
249530
  create(context) {
@@ -249553,7 +249561,7 @@ const rule$I = createRule({
249553
249561
  context.report({
249554
249562
  data: { tag },
249555
249563
  loc: sourceSpanToLoc(duplicate.startSourceSpan),
249556
- messageId: MESSAGE_ID$c,
249564
+ messageId: MESSAGE_ID$d,
249557
249565
  });
249558
249566
  }
249559
249567
  }
@@ -249564,7 +249572,7 @@ const rule$I = createRule({
249564
249572
  docs: {
249565
249573
  description: 'Disallow duplicate title/base/meta/link tags inside head',
249566
249574
  },
249567
- messages: { [MESSAGE_ID$c]: 'Duplicate <{{tag}}> tag in <head>.' },
249575
+ messages: { [MESSAGE_ID$d]: 'Duplicate <{{tag}}> tag in <head>.' },
249568
249576
  schema: [],
249569
249577
  type: 'problem',
249570
249578
  },
@@ -249572,7 +249580,7 @@ const rule$I = createRule({
249572
249580
  });
249573
249581
 
249574
249582
  const COMPONENT_DECORATORS = new Set(['Component']);
249575
- const rule$H = createRule({
249583
+ const rule$I = createRule({
249576
249584
  create(context) {
249577
249585
  const { sourceCode } = context;
249578
249586
  return {
@@ -250220,7 +250228,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
250220
250228
  const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
250221
250229
  const createUntrackedRule = createRule;
250222
250230
 
250223
- const rule$G = createUntrackedRule({
250231
+ const rule$H = createUntrackedRule({
250224
250232
  create(context) {
250225
250233
  const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
250226
250234
  const signalNodeMap = esTreeNodeToTSNodeMap;
@@ -250258,7 +250266,7 @@ const rule$G = createUntrackedRule({
250258
250266
  name: 'no-fully-untracked-effect',
250259
250267
  });
250260
250268
 
250261
- const MESSAGE_ID$b = 'no-href-with-router-link';
250269
+ const MESSAGE_ID$c = 'no-href-with-router-link';
250262
250270
  const ERROR_MESSAGE$1 = 'Do not use href and routerLink attributes together on the same element';
250263
250271
  const config$3 = {
250264
250272
  create(context) {
@@ -250280,7 +250288,7 @@ const config$3 = {
250280
250288
  hrefAttr.sourceSpan.end.offset,
250281
250289
  ]),
250282
250290
  loc: sourceSpanToLoc(hrefAttr.sourceSpan),
250283
- messageId: MESSAGE_ID$b,
250291
+ messageId: MESSAGE_ID$c,
250284
250292
  });
250285
250293
  },
250286
250294
  };
@@ -250288,12 +250296,12 @@ const config$3 = {
250288
250296
  meta: {
250289
250297
  docs: { description: ERROR_MESSAGE$1 },
250290
250298
  fixable: 'code',
250291
- messages: { [MESSAGE_ID$b]: ERROR_MESSAGE$1 },
250299
+ messages: { [MESSAGE_ID$c]: ERROR_MESSAGE$1 },
250292
250300
  schema: [],
250293
250301
  type: 'problem',
250294
250302
  },
250295
250303
  };
250296
- const rule$F = createRule({
250304
+ const rule$G = createRule({
250297
250305
  name: 'no-href-with-router-link',
250298
250306
  rule: config$3,
250299
250307
  });
@@ -250354,7 +250362,7 @@ function getScopeRoot(node) {
250354
250362
  return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
250355
250363
  }
250356
250364
 
250357
- const rule$E = createRule({
250365
+ const rule$F = createRule({
250358
250366
  create(context) {
250359
250367
  const checkImplicitPublic = (node) => {
250360
250368
  const classRef = getEnclosingClass(node);
@@ -250416,7 +250424,7 @@ const rule$E = createRule({
250416
250424
  name: 'no-implicit-public',
250417
250425
  });
250418
250426
 
250419
- const rule$D = createRule({
250427
+ const rule$E = createRule({
250420
250428
  create(context) {
250421
250429
  const { sourceCode } = context;
250422
250430
  return {
@@ -250472,7 +250480,7 @@ function isInfiniteLoopLiteral(node) {
250472
250480
  function isInfiniteLoopTest(test) {
250473
250481
  return test == null || isInfiniteLoopLiteral(test);
250474
250482
  }
250475
- const rule$C = createRule({
250483
+ const rule$D = createRule({
250476
250484
  create(context) {
250477
250485
  return {
250478
250486
  DoWhileStatement(node) {
@@ -250517,7 +250525,7 @@ const rule$C = createRule({
250517
250525
  });
250518
250526
 
250519
250527
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
250520
- const rule$B = createRule({
250528
+ const rule$C = createRule({
250521
250529
  create(context) {
250522
250530
  return {
250523
250531
  Program(node) {
@@ -250594,7 +250602,7 @@ function isInteractiveElement(node) {
250594
250602
  }
250595
250603
  }
250596
250604
 
250597
- const MESSAGE_ID$a = 'noNestedInteractive';
250605
+ const MESSAGE_ID$b = 'noNestedInteractive';
250598
250606
  function getAvailableLabelParent(stack, node, labelsWithControl) {
250599
250607
  const parent = stack[stack.length - 1];
250600
250608
  return stack.length === 1 &&
@@ -250604,7 +250612,7 @@ function getAvailableLabelParent(stack, node, labelsWithControl) {
250604
250612
  ? parent
250605
250613
  : null;
250606
250614
  }
250607
- const rule$A = createRule({
250615
+ const rule$B = createRule({
250608
250616
  name: 'no-nested-interactive',
250609
250617
  rule: {
250610
250618
  create(context) {
@@ -250625,7 +250633,7 @@ const rule$A = createRule({
250625
250633
  context.report({
250626
250634
  data: { tag: parent.name },
250627
250635
  loc: sourceSpanToLoc(node.startSourceSpan),
250628
- messageId: MESSAGE_ID$a,
250636
+ messageId: MESSAGE_ID$b,
250629
250637
  });
250630
250638
  }
250631
250639
  interactiveStack.push(node);
@@ -250641,7 +250649,7 @@ const rule$A = createRule({
250641
250649
  meta: {
250642
250650
  docs: { description: 'Disallow nested interactive elements' },
250643
250651
  messages: {
250644
- [MESSAGE_ID$a]: 'Unexpected interactive element nested inside interactive <{{tag}}>.',
250652
+ [MESSAGE_ID$b]: 'Unexpected interactive element nested inside interactive <{{tag}}>.',
250645
250653
  },
250646
250654
  schema: [],
250647
250655
  type: 'problem',
@@ -250649,6 +250657,301 @@ const rule$A = createRule({
250649
250657
  },
250650
250658
  });
250651
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
+
250652
250955
  const OBSOLETE_HTML_ATTRS = {
250653
250956
  abbr: [
250654
250957
  {
@@ -254551,49 +254854,6 @@ const rule$9 = createRule({
254551
254854
  name: 'short-tui-imports',
254552
254855
  });
254553
254856
 
254554
- function isSingleLineNode(node) {
254555
- return node.loc.start.line === node.loc.end.line;
254556
- }
254557
- function hasCommentLikeText(text) {
254558
- return text.includes('//') || text.includes('/*');
254559
- }
254560
- function hasBlankLine(text) {
254561
- let lineBreaks = 0;
254562
- for (let index = 0; index < text.length; index++) {
254563
- const char = text[index];
254564
- if (char === '\n') {
254565
- lineBreaks++;
254566
- }
254567
- else if (char === '\r') {
254568
- lineBreaks++;
254569
- if (text[index + 1] === '\n') {
254570
- index++;
254571
- }
254572
- }
254573
- if (lineBreaks > 1) {
254574
- return true;
254575
- }
254576
- }
254577
- return false;
254578
- }
254579
- function getLineBreak(text) {
254580
- if (text.includes('\r\n')) {
254581
- return '\r\n';
254582
- }
254583
- return text.includes('\r') ? '\r' : '\n';
254584
- }
254585
- function getLeadingIndentation(text) {
254586
- let index = 0;
254587
- while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
254588
- index++;
254589
- }
254590
- return text.slice(0, index);
254591
- }
254592
- function getSpacingReplacement(sourceCode, betweenText, nextLine, blankLineCount) {
254593
- const indentation = getLeadingIndentation(sourceCode.lines[nextLine - 1] ?? '');
254594
- return `${getLineBreak(betweenText).repeat(blankLineCount + 1)}${indentation}`;
254595
- }
254596
-
254597
254857
  function isFieldLikeMember(member) {
254598
254858
  return (member.type === dist$3.AST_NODE_TYPES.PropertyDefinition ||
254599
254859
  member.type === dist$3.AST_NODE_TYPES.TSAbstractPropertyDefinition);
@@ -255828,29 +256088,30 @@ const plugin = {
255828
256088
  },
255829
256089
  rules: {
255830
256090
  'array-as-const': rule$5,
255831
- 'attrs-newline': rule$U,
256091
+ 'attrs-newline': rule$V,
255832
256092
  'class-property-naming': rule$4,
255833
- 'decorator-key-sort': rule$T,
255834
- 'element-newline': rule$S,
256093
+ 'decorator-key-sort': rule$U,
256094
+ 'element-newline': rule$T,
255835
256095
  'flat-exports': rule$3,
255836
- 'host-attributes-sort': rule$R,
255837
- 'html-logical-properties': rule$Q,
255838
- 'import-integrity': rule$P,
255839
- 'injection-token-description': rule$O,
255840
- 'no-commonjs-import-patterns': rule$N,
255841
- 'no-deep-imports': rule$M,
255842
- 'no-deep-imports-to-indexed-packages': rule$L,
255843
- 'no-duplicate-attrs': rule$K,
255844
- 'no-duplicate-id': rule$J,
255845
- 'no-duplicate-in-head': rule$I,
255846
- 'no-empty-style-metadata': rule$H,
255847
- 'no-fully-untracked-effect': rule$G,
255848
- 'no-href-with-router-link': rule$F,
255849
- 'no-implicit-public': rule$E,
255850
- 'no-import-assertions': rule$D,
255851
- 'no-infinite-loop': rule$C,
255852
- 'no-legacy-peer-deps': rule$B,
255853
- 'no-nested-interactive': 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,
255854
256115
  'no-obsolete-attrs': rule$z,
255855
256116
  'no-obsolete-tags': rule$y,
255856
256117
  'no-playwright-empty-fill': rule$x,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.514.0",
3
+ "version": "0.515.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "homepage": "https://github.com/taiga-family/toolkit#readme",
6
6
  "bugs": {
@@ -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,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;