@taiga-ui/eslint-plugin-experience-next 0.472.0 → 0.473.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
@@ -60,6 +60,7 @@ export default [
60
60
  | no-untracked-outside-reactive-context | Disallow `untracked()` outside reactive callbacks, except explicit post-`await` snapshots | ✅ | 🔧 | |
61
61
  | no-useless-untracked | Disallow provably useless `untracked()` wrappers in reactive callbacks | ✅ | 🔧 | |
62
62
  | object-single-line | Enforce single-line formatting for single-property objects when it fits `printWidth` | ✅ | 🔧 | |
63
+ | prefer-combined-if-control-flow | Combine consecutive `if` statements that use the same `return`, `break`, `continue`, or `throw` | ✅ | 🔧 | |
63
64
  | prefer-deep-imports | Allow deep imports of Taiga UI packages | | 🔧 | |
64
65
  | prefer-multi-arg-push | Combine consecutive `.push()` calls on the same array into a single multi-argument call | ✅ | 🔧 | |
65
66
  | prefer-untracked-incidental-signal-reads | Wrap likely-incidental signal reads with `untracked()` in reactive callbacks | ✅ | 🔧 | |
@@ -863,6 +864,98 @@ const x = {foo: bar};
863
864
 
864
865
  ---
865
866
 
867
+ ## prefer-combined-if-control-flow
868
+
869
+ <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
870
+
871
+ Combine consecutive `if` statements when they have no `else` branch and use the same `return`, `break`, `continue`, or
872
+ `throw` statement. The autofix merges their conditions with `||`, while intentionally skipping cases with intervening
873
+ code or comments that should remain a separate control-flow boundary.
874
+
875
+ ```ts
876
+ // ❌ error
877
+ while (true) {
878
+ if (a) continue;
879
+ if (b && c) continue;
880
+ }
881
+
882
+ // ✅ after autofix
883
+ while (true) {
884
+ if (a || (b && c)) continue;
885
+ }
886
+ ```
887
+
888
+ ```ts
889
+ // ❌ error
890
+ if (a || b) {
891
+ return;
892
+ }
893
+
894
+ if (c) {
895
+ return;
896
+ }
897
+
898
+ // ✅ after autofix
899
+ if (a || b || c) {
900
+ return;
901
+ }
902
+ ```
903
+
904
+ ```ts
905
+ // ❌ error
906
+ if (isInvalid) return result;
907
+
908
+ if (isLegacy && shouldStop) return result;
909
+
910
+ // ✅ after autofix
911
+ if (isInvalid || (isLegacy && shouldStop)) return result;
912
+ ```
913
+
914
+ ```ts
915
+ // ❌ error
916
+ while (true) {
917
+ if (isDone) break;
918
+ if (hasError) break;
919
+ }
920
+
921
+ // ✅ after autofix
922
+ while (true) {
923
+ if (isDone || hasError) break;
924
+ }
925
+ ```
926
+
927
+ ```ts
928
+ // ❌ error
929
+ if (isFatal) throw error;
930
+
931
+ if (isExpired && shouldAbort) throw error;
932
+
933
+ // ✅ after autofix
934
+ if (isFatal || (isExpired && shouldAbort)) throw error;
935
+ ```
936
+
937
+ ```ts
938
+ // not changed — different control flow
939
+ while (true) {
940
+ if (isDone) continue;
941
+ if (hasError) break;
942
+ }
943
+ ```
944
+
945
+ ```ts
946
+ // not changed — comment keeps branches separate
947
+ if (a) {
948
+ return value;
949
+ }
950
+
951
+ // explain why this branch exists
952
+ if (b) {
953
+ return value;
954
+ }
955
+ ```
956
+
957
+ ---
958
+
866
959
  ## prefer-deep-imports
867
960
 
868
961
  <sup>`Taiga-specific`</sup> <sup>`Fixable`</sup>
package/index.d.ts CHANGED
@@ -81,6 +81,9 @@ declare const plugin: {
81
81
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
82
82
  name: string;
83
83
  };
84
+ 'prefer-combined-if-control-flow': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferCombinedIfControlFlow", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
85
+ name: string;
86
+ };
84
87
  'prefer-deep-imports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"prefer-deep-imports", [{
85
88
  importFilter: string[] | string;
86
89
  strict?: boolean;
package/index.esm.js CHANGED
@@ -914,6 +914,7 @@ var recommended = defineConfig([
914
914
  '@taiga-ui/experience-next/no-untracked-outside-reactive-context': 'error',
915
915
  '@taiga-ui/experience-next/no-useless-untracked': 'error',
916
916
  '@taiga-ui/experience-next/object-single-line': ['error', { printWidth: 90 }],
917
+ '@taiga-ui/experience-next/prefer-combined-if-control-flow': 'error',
917
918
  '@taiga-ui/experience-next/prefer-multi-arg-push': 'error',
918
919
  '@taiga-ui/experience-next/prefer-untracked-incidental-signal-reads': 'error',
919
920
  '@taiga-ui/experience-next/prefer-untracked-signal-getter': 'error',
@@ -1335,8 +1336,8 @@ function intersect(a, b) {
1335
1336
  return a.some((type) => origin.has(type));
1336
1337
  }
1337
1338
 
1338
- const createRule$h = ESLintUtils.RuleCreator((name) => name);
1339
- var classPropertyNaming = createRule$h({
1339
+ const createRule$i = ESLintUtils.RuleCreator((name) => name);
1340
+ var classPropertyNaming = createRule$i({
1340
1341
  create(context, [configs]) {
1341
1342
  const parserServices = ESLintUtils.getParserServices(context);
1342
1343
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1505,9 +1506,9 @@ function isExternalPureTuple(typeChecker, type) {
1505
1506
  return typeArgs.every((item) => isClassType(item));
1506
1507
  }
1507
1508
 
1508
- const createRule$g = ESLintUtils.RuleCreator((name) => name);
1509
+ const createRule$h = ESLintUtils.RuleCreator((name) => name);
1509
1510
  const MESSAGE_ID$7 = 'spreadArrays';
1510
- var flatExports = createRule$g({
1511
+ var flatExports = createRule$h({
1511
1512
  create(context) {
1512
1513
  const parserServices = ESLintUtils.getParserServices(context);
1513
1514
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1667,10 +1668,7 @@ function getDecoratorMetadata(decorator, allowedNames) {
1667
1668
  return null;
1668
1669
  }
1669
1670
  const callee = expr.callee;
1670
- if (callee.type !== AST_NODE_TYPES$1.Identifier) {
1671
- return null;
1672
- }
1673
- if (!allowedNames.has(callee.name)) {
1671
+ if (callee.type !== AST_NODE_TYPES$1.Identifier || !allowedNames.has(callee.name)) {
1674
1672
  return null;
1675
1673
  }
1676
1674
  const arg = expr.arguments[0];
@@ -1746,8 +1744,8 @@ const PRESETS = {
1746
1744
  $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
1747
1745
  $VUE_ATTRIBUTE: /^v-/,
1748
1746
  };
1749
- const createRule$f = ESLintUtils.RuleCreator((name) => name);
1750
- const rule$i = createRule$f({
1747
+ const createRule$g = ESLintUtils.RuleCreator((name) => name);
1748
+ const rule$j = createRule$g({
1751
1749
  create(context, [options]) {
1752
1750
  const sourceCode = context.sourceCode;
1753
1751
  const settings = {
@@ -1842,10 +1840,8 @@ function getHostObject(metadata) {
1842
1840
  if (property.type !== AST_NODE_TYPES$1.Property ||
1843
1841
  property.kind !== 'init' ||
1844
1842
  property.computed ||
1845
- property.method) {
1846
- continue;
1847
- }
1848
- if (getStaticPropertyName(property.key) !== 'host') {
1843
+ property.method ||
1844
+ getStaticPropertyName(property.key) !== 'host') {
1849
1845
  continue;
1850
1846
  }
1851
1847
  return property.value.type === AST_NODE_TYPES$1.ObjectExpression
@@ -2025,7 +2021,7 @@ const config$2 = {
2025
2021
  const MESSAGE_ID$5 = 'invalid-injection-token-description';
2026
2022
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
2027
2023
  const NG_DEV_MODE = 'ngDevMode';
2028
- const createRule$e = ESLintUtils.RuleCreator((name) => name);
2024
+ const createRule$f = ESLintUtils.RuleCreator((name) => name);
2029
2025
  function getVariableName(node) {
2030
2026
  if (node.parent.type !== AST_NODE_TYPES$1.VariableDeclarator) {
2031
2027
  return undefined;
@@ -2096,7 +2092,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
2096
2092
  }
2097
2093
  return fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
2098
2094
  }
2099
- const rule$h = createRule$e({
2095
+ const rule$i = createRule$f({
2100
2096
  create(context) {
2101
2097
  const { sourceCode } = context;
2102
2098
  const program = sourceCode.ast;
@@ -2164,8 +2160,8 @@ const DEFAULT_OPTIONS = {
2164
2160
  importDeclaration: '^@taiga-ui*',
2165
2161
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
2166
2162
  };
2167
- const createRule$d = ESLintUtils.RuleCreator((name) => name);
2168
- const rule$g = createRule$d({
2163
+ const createRule$e = ESLintUtils.RuleCreator((name) => name);
2164
+ const rule$h = createRule$e({
2169
2165
  create(context) {
2170
2166
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
2171
2167
  const hasNonCodeExtension = (source) => {
@@ -2252,13 +2248,13 @@ const rule$g = createRule$d({
2252
2248
  name: 'no-deep-imports',
2253
2249
  });
2254
2250
 
2255
- const createRule$c = ESLintUtils.RuleCreator((name) => name);
2251
+ const createRule$d = ESLintUtils.RuleCreator((name) => name);
2256
2252
  const resolveCacheByOptions = new WeakMap();
2257
2253
  const nearestFileUpCache = new Map();
2258
2254
  const markerCache = new Map();
2259
2255
  const indexFileCache = new Map();
2260
2256
  const indexExportsCache = new Map();
2261
- var noDeepImportsToIndexedPackages = createRule$c({
2257
+ var noDeepImportsToIndexedPackages = createRule$d({
2262
2258
  create(context) {
2263
2259
  const parserServices = ESLintUtils.getParserServices(context);
2264
2260
  const program = parserServices.program;
@@ -2364,13 +2360,9 @@ var noDeepImportsToIndexedPackages = createRule$c({
2364
2360
  return {
2365
2361
  ImportDeclaration(node) {
2366
2362
  const importSpecifier = node.source.value;
2367
- if (typeof importSpecifier !== 'string') {
2368
- return;
2369
- }
2370
- if (!importSpecifier.includes('/')) {
2371
- return;
2372
- }
2373
- if (!isExternalModuleSpecifier(importSpecifier)) {
2363
+ if (typeof importSpecifier !== 'string' ||
2364
+ !importSpecifier.includes('/') ||
2365
+ !isExternalModuleSpecifier(importSpecifier)) {
2374
2366
  return;
2375
2367
  }
2376
2368
  const packageRootSpecifier = getPackageRootSpecifier(importSpecifier);
@@ -2438,10 +2430,8 @@ function getPackageRootSpecifier(importSpecifier) {
2438
2430
  return pathParts[0] ?? importSpecifier;
2439
2431
  }
2440
2432
  function getSubpath(importSpecifier, packageRootSpecifier) {
2441
- if (importSpecifier === packageRootSpecifier) {
2442
- return null;
2443
- }
2444
- if (!importSpecifier.startsWith(`${packageRootSpecifier}/`)) {
2433
+ if (importSpecifier === packageRootSpecifier ||
2434
+ !importSpecifier.startsWith(`${packageRootSpecifier}/`)) {
2445
2435
  return null;
2446
2436
  }
2447
2437
  return importSpecifier.slice(packageRootSpecifier.length + 1);
@@ -2516,10 +2506,8 @@ function getOrderedChildren(node) {
2516
2506
  ];
2517
2507
  return children.filter((child) => child !== undefined && child !== null);
2518
2508
  }
2519
- if (node.type === AST_NODE_TYPES.BlockStatement) {
2520
- return node.body;
2521
- }
2522
- if (node.type === AST_NODE_TYPES.Program) {
2509
+ if (node.type === AST_NODE_TYPES.BlockStatement ||
2510
+ node.type === AST_NODE_TYPES.Program) {
2523
2511
  return node.body;
2524
2512
  }
2525
2513
  if (node.type === AST_NODE_TYPES.IfStatement) {
@@ -2580,10 +2568,7 @@ function getOrderedChildren(node) {
2580
2568
  function walkSynchronousAst(root, visitor) {
2581
2569
  traverse(root, true);
2582
2570
  function traverse(node, isRoot = false) {
2583
- if (visitor(node) === false) {
2584
- return false;
2585
- }
2586
- if (!isRoot && isFunctionLike$1(node)) {
2571
+ if (visitor(node) === false || (!isRoot && isFunctionLike$1(node))) {
2587
2572
  return false;
2588
2573
  }
2589
2574
  if (node.type === AST_NODE_TYPES.AwaitExpression) {
@@ -2864,10 +2849,8 @@ function isWritableSignalWrite(node, checker, esTreeNodeToTSNodeMap) {
2864
2849
  return false;
2865
2850
  }
2866
2851
  const { object, property } = node.callee;
2867
- if (property.type !== AST_NODE_TYPES.Identifier) {
2868
- return false;
2869
- }
2870
- if (!SIGNAL_WRITE_METHODS.has(property.name)) {
2852
+ if (property.type !== AST_NODE_TYPES.Identifier ||
2853
+ !SIGNAL_WRITE_METHODS.has(property.name)) {
2871
2854
  return false;
2872
2855
  }
2873
2856
  return isSignalType(object, checker, esTreeNodeToTSNodeMap);
@@ -2941,10 +2924,8 @@ const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES
2941
2924
  function collectReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
2942
2925
  const reads = [];
2943
2926
  walkSynchronousAst(root, (node) => {
2944
- if (node.type !== AST_NODE_TYPES.CallExpression) {
2945
- return;
2946
- }
2947
- if (!isAngularUntrackedCall(node, program)) {
2927
+ if (node.type !== AST_NODE_TYPES.CallExpression ||
2928
+ !isAngularUntrackedCall(node, program)) {
2948
2929
  return;
2949
2930
  }
2950
2931
  const [arg] = node.arguments;
@@ -2963,7 +2944,7 @@ function collectReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, progr
2963
2944
  });
2964
2945
  return reads;
2965
2946
  }
2966
- const rule$f = createUntrackedRule({
2947
+ const rule$g = createUntrackedRule({
2967
2948
  create(context) {
2968
2949
  const parserServices = ESLintUtils.getParserServices(context);
2969
2950
  const checker = parserServices.program.getTypeChecker();
@@ -3049,15 +3030,15 @@ const config$1 = {
3049
3030
  },
3050
3031
  };
3051
3032
 
3052
- const createRule$b = ESLintUtils.RuleCreator((name) => name);
3053
- const rule$e = createRule$b({
3033
+ const createRule$c = ESLintUtils.RuleCreator((name) => name);
3034
+ const rule$f = createRule$c({
3054
3035
  create(context) {
3055
3036
  const checkImplicitPublic = (node) => {
3056
3037
  const classRef = getClass(node);
3057
- if (!classRef || node.kind === 'constructor' || !!node?.accessibility) {
3058
- return;
3059
- }
3060
- if (node.key?.type === AST_NODE_TYPES.PrivateIdentifier) {
3038
+ if (!classRef ||
3039
+ node.kind === 'constructor' ||
3040
+ !!node?.accessibility ||
3041
+ node.key?.type === AST_NODE_TYPES.PrivateIdentifier) {
3061
3042
  return;
3062
3043
  }
3063
3044
  const name = node?.key?.name ||
@@ -3121,9 +3102,9 @@ function getClass(node) {
3121
3102
  return getClass(node.parent);
3122
3103
  }
3123
3104
 
3124
- const createRule$a = ESLintUtils.RuleCreator((name) => name);
3105
+ const createRule$b = ESLintUtils.RuleCreator((name) => name);
3125
3106
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
3126
- const rule$d = createRule$a({
3107
+ const rule$e = createRule$b({
3127
3108
  create(context) {
3128
3109
  return {
3129
3110
  Program(node) {
@@ -3161,8 +3142,8 @@ const rule$d = createRule$a({
3161
3142
  name: 'no-legacy-peer-deps',
3162
3143
  });
3163
3144
 
3164
- const createRule$9 = ESLintUtils.RuleCreator((name) => name);
3165
- const rule$c = createRule$9({
3145
+ const createRule$a = ESLintUtils.RuleCreator((name) => name);
3146
+ const rule$d = createRule$a({
3166
3147
  create(context) {
3167
3148
  const services = ESLintUtils.getParserServices(context);
3168
3149
  const checker = services.program.getTypeChecker();
@@ -3327,7 +3308,7 @@ const config = {
3327
3308
  },
3328
3309
  };
3329
3310
 
3330
- const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3311
+ const createRule$9 = ESLintUtils.RuleCreator((name) => name);
3331
3312
  function collectArrayExpressions(node) {
3332
3313
  const result = [];
3333
3314
  if (node.type === AST_NODE_TYPES.ArrayExpression) {
@@ -3353,7 +3334,7 @@ function collectArrayExpressions(node) {
3353
3334
  }
3354
3335
  return result;
3355
3336
  }
3356
- const rule$b = createRule$8({
3337
+ const rule$c = createRule$9({
3357
3338
  create(context) {
3358
3339
  const parserServices = ESLintUtils.getParserServices(context);
3359
3340
  const typeChecker = parserServices.program.getTypeChecker();
@@ -3481,7 +3462,7 @@ function unwrapExpression(expression) {
3481
3462
  return current;
3482
3463
  }
3483
3464
 
3484
- const createRule$7 = ESLintUtils.RuleCreator((name) => name);
3465
+ const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3485
3466
  function isReactiveCallback(node) {
3486
3467
  return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3487
3468
  node?.type === AST_NODE_TYPES.FunctionExpression);
@@ -3603,7 +3584,7 @@ function inspectComputedBody(root, inspectionContext, report) {
3603
3584
  return;
3604
3585
  });
3605
3586
  }
3606
- const rule$a = createRule$7({
3587
+ const rule$b = createRule$8({
3607
3588
  create(context) {
3608
3589
  const parserServices = ESLintUtils.getParserServices(context);
3609
3590
  const checker = parserServices.program.getTypeChecker();
@@ -3650,7 +3631,7 @@ const rule$a = createRule$7({
3650
3631
  name: 'no-side-effects-in-computed',
3651
3632
  });
3652
3633
 
3653
- const rule$9 = createUntrackedRule({
3634
+ const rule$a = createUntrackedRule({
3654
3635
  create(context) {
3655
3636
  const parserServices = ESLintUtils.getParserServices(context);
3656
3637
  const checker = parserServices.program.getTypeChecker();
@@ -3699,7 +3680,7 @@ const rule$9 = createUntrackedRule({
3699
3680
  name: 'no-signal-reads-after-await-in-reactive-context',
3700
3681
  });
3701
3682
 
3702
- const createRule$6 = ESLintUtils.RuleCreator((name) => name);
3683
+ const createRule$7 = ESLintUtils.RuleCreator((name) => name);
3703
3684
  function isStringLiteral(node) {
3704
3685
  return (node.type === AST_NODE_TYPES.Literal &&
3705
3686
  typeof node.value === 'string');
@@ -3763,7 +3744,7 @@ function hasTemplateLiteralAncestor(node) {
3763
3744
  }
3764
3745
  return false;
3765
3746
  }
3766
- const rule$8 = createRule$6({
3747
+ const rule$9 = createRule$7({
3767
3748
  create(context) {
3768
3749
  const { sourceCode } = context;
3769
3750
  let parserServices = null;
@@ -4183,7 +4164,7 @@ function buildReactiveCallReplacement(outerUntrackedCall, reactiveCall, sourceCo
4183
4164
  }
4184
4165
  return dedent$1(text, reactiveCall.loc.start.column - outerUntrackedCall.parent.loc.start.column);
4185
4166
  }
4186
- const rule$7 = createUntrackedRule({
4167
+ const rule$8 = createUntrackedRule({
4187
4168
  create(context) {
4188
4169
  const parserServices = ESLintUtils.getParserServices(context);
4189
4170
  const checker = parserServices.program.getTypeChecker();
@@ -4207,20 +4188,12 @@ const rule$7 = createUntrackedRule({
4207
4188
  }
4208
4189
  return {
4209
4190
  CallExpression(node) {
4210
- if (!isAngularUntrackedCall(node, program)) {
4211
- return;
4212
- }
4213
- if (findEnclosingReactiveScope(node, program) ||
4214
- findEnclosingReactiveScopeAfterAsyncBoundary(node, program)) {
4215
- return;
4216
- }
4217
- if (isAllowedImperativeAngularContext(node)) {
4218
- return;
4219
- }
4220
- if (isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap)) {
4221
- return;
4222
- }
4223
- if (isAllowedLazyAngularFactoryContext(node, program)) {
4191
+ if (!isAngularUntrackedCall(node, program) ||
4192
+ findEnclosingReactiveScope(node, program) ||
4193
+ findEnclosingReactiveScopeAfterAsyncBoundary(node, program) ||
4194
+ isAllowedImperativeAngularContext(node) ||
4195
+ isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) ||
4196
+ isAllowedLazyAngularFactoryContext(node, program)) {
4224
4197
  return;
4225
4198
  }
4226
4199
  const reactiveCall = getFixableReactiveCall(node, program);
@@ -4327,10 +4300,8 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
4327
4300
  }
4328
4301
  return;
4329
4302
  }
4330
- if (node.type !== AST_NODE_TYPES.CallExpression) {
4331
- return;
4332
- }
4333
- if (isAngularUntrackedCall(node, program) ||
4303
+ if (node.type !== AST_NODE_TYPES.CallExpression ||
4304
+ isAngularUntrackedCall(node, program) ||
4334
4305
  isSignalReadCall(node, checker, esTreeNodeToTSNodeMap) ||
4335
4306
  isWritableSignalWrite(node, checker, esTreeNodeToTSNodeMap)) {
4336
4307
  return;
@@ -4340,7 +4311,7 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
4340
4311
  });
4341
4312
  return found;
4342
4313
  }
4343
- const rule$6 = createUntrackedRule({
4314
+ const rule$7 = createUntrackedRule({
4344
4315
  create(context) {
4345
4316
  const parserServices = ESLintUtils.getParserServices(context);
4346
4317
  const checker = parserServices.program.getTypeChecker();
@@ -4361,14 +4332,12 @@ const rule$6 = createUntrackedRule({
4361
4332
  return;
4362
4333
  }
4363
4334
  const { reads } = collectSignalUsages(arg, checker, esTreeNodeToTSNodeMap, program);
4364
- if (reads.length > 0) {
4335
+ if (reads.length > 0 ||
4336
+ hasOpaqueSynchronousCalls(arg, checker, esTreeNodeToTSNodeMap, program)) {
4365
4337
  // Snapshot reads inside reactive callbacks are a valid Angular
4366
4338
  // pattern even when the snapshot later influences branching.
4367
4339
  return;
4368
4340
  }
4369
- if (hasOpaqueSynchronousCalls(arg, checker, esTreeNodeToTSNodeMap, program)) {
4370
- return;
4371
- }
4372
4341
  // Only fix when the parent is a plain ExpressionStatement so we can
4373
4342
  // replace statement-for-statement without breaking surrounding structure.
4374
4343
  const parent = untrackedCall.parent;
@@ -4430,8 +4399,8 @@ const rule$6 = createUntrackedRule({
4430
4399
  name: 'no-useless-untracked',
4431
4400
  });
4432
4401
 
4433
- const createRule$5 = ESLintUtils.RuleCreator((name) => name);
4434
- const rule$5 = createRule$5({
4402
+ const createRule$6 = ESLintUtils.RuleCreator((name) => name);
4403
+ const rule$6 = createRule$6({
4435
4404
  create(context, [{ printWidth }]) {
4436
4405
  const sourceCode = context.sourceCode;
4437
4406
  const getLineEndIndex = (lineStartIndex) => {
@@ -4683,6 +4652,169 @@ const rule$5 = createRule$5({
4683
4652
  name: 'object-single-line',
4684
4653
  });
4685
4654
 
4655
+ const createRule$5 = ESLintUtils.RuleCreator((name) => name);
4656
+ const EMPTY_ARGUMENT = '__EMPTY_ARGUMENT__';
4657
+ function getParenthesizedInner(node) {
4658
+ const maybeNode = node;
4659
+ if (maybeNode.type === 'ParenthesizedExpression') {
4660
+ return maybeNode.expression ?? null;
4661
+ }
4662
+ return null;
4663
+ }
4664
+ function unwrapParenthesized(node) {
4665
+ let current = node;
4666
+ let inner = getParenthesizedInner(current);
4667
+ while (inner) {
4668
+ current = inner;
4669
+ inner = getParenthesizedInner(current);
4670
+ }
4671
+ return current;
4672
+ }
4673
+ function isSupportedControlFlowStatement(node) {
4674
+ return (node.type === AST_NODE_TYPES.BreakStatement ||
4675
+ node.type === AST_NODE_TYPES.ContinueStatement ||
4676
+ node.type === AST_NODE_TYPES.ReturnStatement ||
4677
+ node.type === AST_NODE_TYPES.ThrowStatement);
4678
+ }
4679
+ function getControlFlowStatement(node) {
4680
+ if (isSupportedControlFlowStatement(node)) {
4681
+ return node;
4682
+ }
4683
+ if (node.type === AST_NODE_TYPES.BlockStatement &&
4684
+ node.body.length === 1 &&
4685
+ isSupportedControlFlowStatement(node.body[0])) {
4686
+ return node.body[0];
4687
+ }
4688
+ return null;
4689
+ }
4690
+ function getControlFlowSignature(node, sourceCode) {
4691
+ if (node.alternate) {
4692
+ return null;
4693
+ }
4694
+ const controlFlowStatement = getControlFlowStatement(node.consequent);
4695
+ if (!controlFlowStatement) {
4696
+ return null;
4697
+ }
4698
+ switch (controlFlowStatement.type) {
4699
+ case AST_NODE_TYPES.BreakStatement:
4700
+ return controlFlowStatement.label
4701
+ ? `break:${sourceCode.getText(controlFlowStatement.label)}`
4702
+ : `break:${EMPTY_ARGUMENT}`;
4703
+ case AST_NODE_TYPES.ContinueStatement:
4704
+ return controlFlowStatement.label
4705
+ ? `continue:${sourceCode.getText(controlFlowStatement.label)}`
4706
+ : `continue:${EMPTY_ARGUMENT}`;
4707
+ case AST_NODE_TYPES.ReturnStatement:
4708
+ return controlFlowStatement.argument
4709
+ ? `return:${sourceCode.getText(unwrapParenthesized(controlFlowStatement.argument))}`
4710
+ : `return:${EMPTY_ARGUMENT}`;
4711
+ case AST_NODE_TYPES.ThrowStatement:
4712
+ return `throw:${sourceCode.getText(unwrapParenthesized(controlFlowStatement.argument))}`;
4713
+ }
4714
+ }
4715
+ function hasNonWhitespaceBetween(sourceCode, left, right) {
4716
+ return sourceCode.text.slice(left.range[1], right.range[0]).trim() !== '';
4717
+ }
4718
+ function needsParenthesesInOrChain(node) {
4719
+ if (getParenthesizedInner(node)) {
4720
+ return false;
4721
+ }
4722
+ switch (node.type) {
4723
+ case AST_NODE_TYPES.AssignmentExpression:
4724
+ case AST_NODE_TYPES.ConditionalExpression:
4725
+ case AST_NODE_TYPES.SequenceExpression:
4726
+ case AST_NODE_TYPES.TSAsExpression:
4727
+ case AST_NODE_TYPES.TSSatisfiesExpression:
4728
+ case AST_NODE_TYPES.YieldExpression:
4729
+ return true;
4730
+ case AST_NODE_TYPES.LogicalExpression:
4731
+ return node.operator !== '||';
4732
+ default:
4733
+ return false;
4734
+ }
4735
+ }
4736
+ function renderTest(node, sourceCode) {
4737
+ const text = sourceCode.getText(node);
4738
+ return needsParenthesesInOrChain(node) ? `(${text})` : text;
4739
+ }
4740
+ const rule$5 = createRule$5({
4741
+ create(context) {
4742
+ const { sourceCode } = context;
4743
+ function checkBody(statements) {
4744
+ let i = 0;
4745
+ while (i < statements.length) {
4746
+ const statement = statements[i];
4747
+ if (statement.type !== AST_NODE_TYPES.IfStatement) {
4748
+ i++;
4749
+ continue;
4750
+ }
4751
+ const signature = getControlFlowSignature(statement, sourceCode);
4752
+ if (!signature) {
4753
+ i++;
4754
+ continue;
4755
+ }
4756
+ const group = [statement];
4757
+ let j = i + 1;
4758
+ while (j < statements.length) {
4759
+ const nextStatement = statements[j];
4760
+ if (nextStatement.type !== AST_NODE_TYPES.IfStatement) {
4761
+ break;
4762
+ }
4763
+ if (!hasNonWhitespaceBetween(sourceCode, group[group.length - 1], nextStatement) &&
4764
+ sourceCode.getCommentsInside(nextStatement).length === 0 &&
4765
+ getControlFlowSignature(nextStatement, sourceCode) === signature) {
4766
+ group.push(nextStatement);
4767
+ j++;
4768
+ continue;
4769
+ }
4770
+ break;
4771
+ }
4772
+ if (group.length > 1) {
4773
+ for (const [index, ifStatement] of group.entries()) {
4774
+ context.report({
4775
+ ...(index === 0
4776
+ ? {
4777
+ fix(fixer) {
4778
+ const firstIf = group[0];
4779
+ const lastIf = group[group.length - 1];
4780
+ const condition = group
4781
+ .map((item) => renderTest(item.test, sourceCode))
4782
+ .join(' || ');
4783
+ return fixer.replaceTextRange([firstIf.range[0], lastIf.range[1]], `if (${condition}) ${sourceCode.getText(firstIf.consequent)}`);
4784
+ },
4785
+ }
4786
+ : {}),
4787
+ messageId: 'preferCombinedIfControlFlow',
4788
+ node: ifStatement,
4789
+ });
4790
+ }
4791
+ }
4792
+ i = j;
4793
+ }
4794
+ }
4795
+ return {
4796
+ BlockStatement(node) {
4797
+ checkBody(node.body);
4798
+ },
4799
+ Program(node) {
4800
+ checkBody(node.body);
4801
+ },
4802
+ };
4803
+ },
4804
+ meta: {
4805
+ docs: {
4806
+ description: 'Combine consecutive if statements that use the same return, break, continue, or throw statement into a single if statement.',
4807
+ },
4808
+ fixable: 'code',
4809
+ messages: {
4810
+ preferCombinedIfControlFlow: 'Combine consecutive if statements with identical return, break, continue, or throw statements.',
4811
+ },
4812
+ schema: [],
4813
+ type: 'suggestion',
4814
+ },
4815
+ name: 'prefer-combined-if-control-flow',
4816
+ });
4817
+
4686
4818
  const MESSAGE_ID$1 = 'prefer-deep-imports';
4687
4819
  const ERROR_MESSAGE = 'Import via root entry point is prohibited when nested entry points exist';
4688
4820
  const createRule$4 = ESLintUtils.RuleCreator(() => ERROR_MESSAGE);
@@ -4700,14 +4832,10 @@ var preferDeepImports = createRule$4({
4700
4832
  return;
4701
4833
  }
4702
4834
  const rootPackageName = getRootPackageName(rawImportPath);
4703
- if (!rootPackageName) {
4704
- return;
4705
- }
4706
- if (!allowedPackages.includes(rootPackageName)) {
4707
- return;
4708
- }
4709
- if (!isStrictMode &&
4710
- isAlreadyNestedImport(rawImportPath, rootPackageName)) {
4835
+ if (!rootPackageName ||
4836
+ !allowedPackages.includes(rootPackageName) ||
4837
+ (!isStrictMode &&
4838
+ isAlreadyNestedImport(rawImportPath, rootPackageName))) {
4711
4839
  return;
4712
4840
  }
4713
4841
  const importedSymbols = extractNamedImportedSymbols(node);
@@ -4938,10 +5066,7 @@ function mapSymbolsToEntryPointsUsingTypeChecker(importedSymbols, candidateEntry
4938
5066
  for (const importedSymbol of importedSymbols) {
4939
5067
  for (const relativeEntryDir of candidateEntryPoints) {
4940
5068
  const exportedNames = exportTableByEntryPoint.get(relativeEntryDir);
4941
- if (!exportedNames) {
4942
- continue;
4943
- }
4944
- if (!exportedNames.has(importedSymbol)) {
5069
+ if (!exportedNames?.has(importedSymbol)) {
4945
5070
  continue;
4946
5071
  }
4947
5072
  symbolToEntryPoint.set(importedSymbol, relativeEntryDir);
@@ -5224,10 +5349,8 @@ function isDomImperativeCall(node, checker, esTreeNodeToTSNodeMap) {
5224
5349
  }
5225
5350
  const signature = checker.getResolvedSignature(tsNode);
5226
5351
  const declaration = signature?.declaration;
5227
- if (!declaration) {
5228
- return false;
5229
- }
5230
- if (!LIB_DOM_FILE_PATTERN.test(declaration.getSourceFile().fileName)) {
5352
+ if (!declaration ||
5353
+ !LIB_DOM_FILE_PATTERN.test(declaration.getSourceFile().fileName)) {
5231
5354
  return false;
5232
5355
  }
5233
5356
  const returnType = checker.typeToString(checker.getReturnTypeOfSignature(signature));
@@ -5459,18 +5582,15 @@ function getReturnedExpression(node) {
5459
5582
  }
5460
5583
  function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
5461
5584
  const [arg] = node.arguments;
5462
- if (!arg || arg.type === AST_NODE_TYPES.SpreadElement) {
5463
- return null;
5464
- }
5465
- if (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
5466
- arg.type !== AST_NODE_TYPES.FunctionExpression) {
5585
+ if (!arg ||
5586
+ arg.type === AST_NODE_TYPES.SpreadElement ||
5587
+ (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
5588
+ arg.type !== AST_NODE_TYPES.FunctionExpression)) {
5467
5589
  return null;
5468
5590
  }
5469
5591
  const body = getReturnedExpression(arg);
5470
- if (body?.type !== AST_NODE_TYPES.CallExpression) {
5471
- return null;
5472
- }
5473
- if (!isSignalReadCall(body, checker, esTreeNodeToTSNodeMap)) {
5592
+ if (body?.type !== AST_NODE_TYPES.CallExpression ||
5593
+ !isSignalReadCall(body, checker, esTreeNodeToTSNodeMap)) {
5474
5594
  return null;
5475
5595
  }
5476
5596
  const getter = unwrapExpression(body.callee);
@@ -5943,24 +6063,25 @@ const plugin = {
5943
6063
  'class-property-naming': classPropertyNaming,
5944
6064
  'decorator-key-sort': config$3,
5945
6065
  'flat-exports': flatExports,
5946
- 'host-attributes-sort': rule$i,
6066
+ 'host-attributes-sort': rule$j,
5947
6067
  'html-logical-properties': config$2,
5948
- 'injection-token-description': rule$h,
5949
- 'no-deep-imports': rule$g,
6068
+ 'injection-token-description': rule$i,
6069
+ 'no-deep-imports': rule$h,
5950
6070
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
5951
- 'no-fully-untracked-effect': rule$f,
6071
+ 'no-fully-untracked-effect': rule$g,
5952
6072
  'no-href-with-router-link': config$1,
5953
- 'no-implicit-public': rule$e,
5954
- 'no-legacy-peer-deps': rule$d,
5955
- 'no-playwright-empty-fill': rule$c,
6073
+ 'no-implicit-public': rule$f,
6074
+ 'no-legacy-peer-deps': rule$e,
6075
+ 'no-playwright-empty-fill': rule$d,
5956
6076
  'no-project-as-in-ng-template': config,
5957
- 'no-redundant-type-annotation': rule$b,
5958
- 'no-side-effects-in-computed': rule$a,
5959
- 'no-signal-reads-after-await-in-reactive-context': rule$9,
5960
- 'no-string-literal-concat': rule$8,
5961
- 'no-untracked-outside-reactive-context': rule$7,
5962
- 'no-useless-untracked': rule$6,
5963
- 'object-single-line': rule$5,
6077
+ 'no-redundant-type-annotation': rule$c,
6078
+ 'no-side-effects-in-computed': rule$b,
6079
+ 'no-signal-reads-after-await-in-reactive-context': rule$a,
6080
+ 'no-string-literal-concat': rule$9,
6081
+ 'no-untracked-outside-reactive-context': rule$8,
6082
+ 'no-useless-untracked': rule$7,
6083
+ 'object-single-line': rule$6,
6084
+ 'prefer-combined-if-control-flow': rule$5,
5964
6085
  'prefer-deep-imports': preferDeepImports,
5965
6086
  'prefer-multi-arg-push': rule$4,
5966
6087
  'prefer-untracked-incidental-signal-reads': rule$3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.472.0",
3
+ "version": "0.473.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,5 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const rule: ESLintUtils.RuleModule<"preferCombinedIfControlFlow", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default rule;