@taiga-ui/eslint-plugin-experience-next 0.474.0 → 0.475.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.
Files changed (49) hide show
  1. package/README.md +43 -0
  2. package/index.d.ts +3 -0
  3. package/index.esm.js +485 -507
  4. package/package.json +1 -1
  5. package/rules/no-fully-untracked-effect.d.ts +1 -2
  6. package/rules/no-infinite-loop.d.ts +6 -0
  7. package/rules/no-signal-reads-after-await-in-reactive-context.d.ts +1 -2
  8. package/rules/no-untracked-outside-reactive-context.d.ts +1 -2
  9. package/rules/no-useless-untracked.d.ts +1 -2
  10. package/rules/prefer-untracked-incidental-signal-reads.d.ts +1 -27
  11. package/rules/prefer-untracked-signal-getter.d.ts +1 -2
  12. package/rules/utils/{angular-signals.d.ts → angular/angular-signals.d.ts} +9 -5
  13. package/rules/utils/angular/pipes.d.ts +2 -0
  14. package/rules/utils/angular/providers.d.ts +3 -0
  15. package/rules/utils/ast/ancestors.d.ts +12 -0
  16. package/rules/utils/{ast-walk.d.ts → ast/ast-walk.d.ts} +1 -0
  17. package/rules/utils/ast/call-expressions.d.ts +2 -0
  18. package/rules/utils/ast/mutation-targets.d.ts +4 -0
  19. package/rules/utils/ast/parenthesized.d.ts +3 -0
  20. package/rules/utils/ast/property-names.d.ts +5 -0
  21. package/rules/utils/ast/returned-expression.d.ts +2 -0
  22. package/rules/utils/ast/string-literals.d.ts +10 -0
  23. package/rules/utils/text/dedent.d.ts +5 -0
  24. package/rules/utils/typescript/decorators.d.ts +2 -0
  25. package/rules/utils/typescript/function-usage.d.ts +5 -0
  26. package/rules/utils/typescript/node-map.d.ts +6 -0
  27. package/rules/utils/typescript/symbols.d.ts +4 -0
  28. package/rules/utils/typescript/type-aware-context.d.ts +14 -0
  29. /package/rules/utils/{angular-imports.d.ts → angular/angular-imports.d.ts} +0 -0
  30. /package/rules/utils/{get-decorator-metadata.d.ts → angular/get-decorator-metadata.d.ts} +0 -0
  31. /package/rules/utils/{get-imports-array.d.ts → angular/get-imports-array.d.ts} +0 -0
  32. /package/rules/utils/{import-fix-helpers.d.ts → angular/import-fix-helpers.d.ts} +0 -0
  33. /package/rules/utils/{is-imports-array-property.d.ts → angular/is-imports-array-property.d.ts} +0 -0
  34. /package/rules/utils/{untracked-docs.d.ts → angular/untracked-docs.d.ts} +0 -0
  35. /package/rules/utils/{ast-expressions.d.ts → ast/ast-expressions.d.ts} +0 -0
  36. /package/rules/utils/{get-const-array.d.ts → ast/get-const-array.d.ts} +0 -0
  37. /package/rules/utils/{is-array.d.ts → ast/is-array.d.ts} +0 -0
  38. /package/rules/utils/{is-object.d.ts → ast/is-object.d.ts} +0 -0
  39. /package/rules/utils/{is-spread.d.ts → ast/is-spread.d.ts} +0 -0
  40. /package/rules/utils/{name-of.d.ts → ast/name-of.d.ts} +0 -0
  41. /package/rules/utils/{intersect.d.ts → collections/intersect.d.ts} +0 -0
  42. /package/rules/utils/{same-order.d.ts → collections/same-order.d.ts} +0 -0
  43. /package/rules/utils/{get-imported-name.d.ts → imports/get-imported-name.d.ts} +0 -0
  44. /package/rules/utils/{npmrc-parser.d.ts → parsers/npmrc-parser.d.ts} +0 -0
  45. /package/rules/utils/{get-sorted-names.d.ts → sorting/get-sorted-names.d.ts} +0 -0
  46. /package/rules/utils/{get-field-types.d.ts → typescript/get-field-types.d.ts} +0 -0
  47. /package/rules/utils/{get-type-name.d.ts → typescript/get-type-name.d.ts} +0 -0
  48. /package/rules/utils/{is-class-type.d.ts → typescript/is-class-type.d.ts} +0 -0
  49. /package/rules/utils/{is-external-tuple.d.ts → typescript/is-external-tuple.d.ts} +0 -0
package/index.esm.js CHANGED
@@ -908,6 +908,7 @@ var recommended = defineConfig([
908
908
  '@taiga-ui/experience-next/no-deep-imports-to-indexed-packages': 'error',
909
909
  '@taiga-ui/experience-next/no-fully-untracked-effect': 'error',
910
910
  '@taiga-ui/experience-next/no-implicit-public': 'error',
911
+ '@taiga-ui/experience-next/no-infinite-loop': 'error',
911
912
  '@taiga-ui/experience-next/no-redundant-type-annotation': 'error',
912
913
  '@taiga-ui/experience-next/no-side-effects-in-computed': 'error',
913
914
  '@taiga-ui/experience-next/no-signal-reads-after-await-in-reactive-context': 'error',
@@ -1299,6 +1300,11 @@ const config$4 = {
1299
1300
  },
1300
1301
  };
1301
1302
 
1303
+ function intersect(a, b) {
1304
+ const origin = new Set(b);
1305
+ return a.some((type) => origin.has(type));
1306
+ }
1307
+
1302
1308
  function getFieldTypes(type, checker) {
1303
1309
  const typeNames = [];
1304
1310
  if (type.isUnionOrIntersection()) {
@@ -1331,16 +1337,24 @@ function getFieldTypes(type, checker) {
1331
1337
  return typeNames;
1332
1338
  }
1333
1339
 
1334
- function intersect(a, b) {
1335
- const origin = new Set(b);
1336
- return a.some((type) => origin.has(type));
1340
+ function getTypeAwareRuleContext(context) {
1341
+ const parserServices = ESLintUtils.getParserServices(context);
1342
+ const { sourceCode } = context;
1343
+ return {
1344
+ checker: parserServices.program.getTypeChecker(),
1345
+ esTreeNodeToTSNodeMap: parserServices.esTreeNodeToTSNodeMap,
1346
+ parserServices,
1347
+ program: sourceCode.ast,
1348
+ sourceCode,
1349
+ tsNodeToESTreeNodeMap: parserServices.tsNodeToESTreeNodeMap,
1350
+ tsProgram: parserServices.program,
1351
+ };
1337
1352
  }
1338
1353
 
1339
- const createRule$i = ESLintUtils.RuleCreator((name) => name);
1340
- var classPropertyNaming = createRule$i({
1354
+ const createRule$j = ESLintUtils.RuleCreator((name) => name);
1355
+ var classPropertyNaming = createRule$j({
1341
1356
  create(context, [configs]) {
1342
- const parserServices = ESLintUtils.getParserServices(context);
1343
- const typeChecker = parserServices.program.getTypeChecker();
1357
+ const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
1344
1358
  const flatConfig = configs.flat();
1345
1359
  return {
1346
1360
  PropertyDefinition(node) {
@@ -1348,7 +1362,7 @@ var classPropertyNaming = createRule$i({
1348
1362
  if (!fieldName) {
1349
1363
  return;
1350
1364
  }
1351
- const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
1365
+ const tsNode = esTreeNodeToTSNodeMap.get(node);
1352
1366
  const nodeType = typeChecker.getTypeAtLocation(tsNode);
1353
1367
  const fieldTypes = getFieldTypes(nodeType, typeChecker);
1354
1368
  const rule = flatConfig.find((rule) => intersect(fieldTypes, rule.withTypesSpecifier) &&
@@ -1404,6 +1418,10 @@ var classPropertyNaming = createRule$i({
1404
1418
  name: 'class-property-naming',
1405
1419
  });
1406
1420
 
1421
+ function sameOrder(a, b) {
1422
+ return a.length === b.length && a.every((value, index) => value === b[index]);
1423
+ }
1424
+
1407
1425
  const config$3 = {
1408
1426
  create(context) {
1409
1427
  const order = context.options[0] || {};
@@ -1422,7 +1440,7 @@ const config$3 = {
1422
1440
  .map((prop) => prop.key?.name)
1423
1441
  .filter(Boolean);
1424
1442
  const correct = getCorrectOrderRelative(orderList, current);
1425
- if (!isCorrectSortedAccording(correct, current)) {
1443
+ if (!sameOrder(correct, current.filter((item) => correct.includes(item)))) {
1426
1444
  context.report({
1427
1445
  fix: (fixer) => {
1428
1446
  const fileContent = context.sourceCode.text;
@@ -1458,10 +1476,6 @@ const config$3 = {
1458
1476
  type: 'problem',
1459
1477
  },
1460
1478
  };
1461
- function isCorrectSortedAccording(correct, current) {
1462
- return (JSON.stringify(correct) ===
1463
- JSON.stringify(current.filter((item) => correct.includes(item))));
1464
- }
1465
1479
  function getCorrectOrderRelative(correct, current) {
1466
1480
  return correct.filter((item) => current.includes(item));
1467
1481
  }
@@ -1506,12 +1520,11 @@ function isExternalPureTuple(typeChecker, type) {
1506
1520
  return typeArgs.every((item) => isClassType(item));
1507
1521
  }
1508
1522
 
1509
- const createRule$h = ESLintUtils.RuleCreator((name) => name);
1523
+ const createRule$i = ESLintUtils.RuleCreator((name) => name);
1510
1524
  const MESSAGE_ID$7 = 'spreadArrays';
1511
- var flatExports = createRule$h({
1525
+ var flatExports = createRule$i({
1512
1526
  create(context) {
1513
- const parserServices = ESLintUtils.getParserServices(context);
1514
- const typeChecker = parserServices.program.getTypeChecker();
1527
+ const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
1515
1528
  const arrays = new Map();
1516
1529
  const purityCache = new WeakMap();
1517
1530
  const isPureArray = (arr) => {
@@ -1595,7 +1608,7 @@ var flatExports = createRule$h({
1595
1608
  isDirty = true;
1596
1609
  continue;
1597
1610
  }
1598
- const tsNode = parserServices.esTreeNodeToTSNodeMap.get(el);
1611
+ const tsNode = esTreeNodeToTSNodeMap.get(el);
1599
1612
  const elType = typeChecker.getTypeAtLocation(tsNode);
1600
1613
  const isClass = isClassType(elType);
1601
1614
  const isArrayLike = typeChecker.isArrayLikeType(elType) ||
@@ -1675,8 +1688,53 @@ function getDecoratorMetadata(decorator, allowedNames) {
1675
1688
  return isObject(arg) ? arg : null;
1676
1689
  }
1677
1690
 
1678
- function sameOrder(a, b) {
1679
- return a.length === b.length && a.every((value, index) => value === b[index]);
1691
+ function isStringLiteral(node) {
1692
+ return node?.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
1693
+ }
1694
+ function isStaticTemplateLiteral(node) {
1695
+ return (node?.type === AST_NODE_TYPES.TemplateLiteral &&
1696
+ node.expressions.length === 0 &&
1697
+ node.quasis.length === 1);
1698
+ }
1699
+ function getStaticStringValue(node) {
1700
+ if (isStringLiteral(node)) {
1701
+ return node.value;
1702
+ }
1703
+ if (!isStaticTemplateLiteral(node)) {
1704
+ return null;
1705
+ }
1706
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? '';
1707
+ }
1708
+ function isEmptyStaticString(node) {
1709
+ return getStaticStringValue(node) === '';
1710
+ }
1711
+
1712
+ function getStaticPropertyName(key) {
1713
+ if (key.type === AST_NODE_TYPES.Identifier) {
1714
+ return key.name;
1715
+ }
1716
+ if (key.type === AST_NODE_TYPES.Literal &&
1717
+ (typeof key.value === 'string' || typeof key.value === 'number')) {
1718
+ return String(key.value);
1719
+ }
1720
+ return getStaticStringValue(key);
1721
+ }
1722
+ function getObjectPropertyName(node) {
1723
+ if (node.computed) {
1724
+ return null;
1725
+ }
1726
+ return getStaticPropertyName(node.key);
1727
+ }
1728
+ function getMemberExpressionPropertyName(node) {
1729
+ if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
1730
+ return node.property.name;
1731
+ }
1732
+ return node.computed ? getStaticStringValue(node.property) : null;
1733
+ }
1734
+ function getClassMemberName(member) {
1735
+ return member.key.type === AST_NODE_TYPES.PrivateIdentifier
1736
+ ? null
1737
+ : getStaticPropertyName(member.key);
1680
1738
  }
1681
1739
 
1682
1740
  const DEFAULT_GROUP = '$DEFAULT';
@@ -1744,8 +1802,8 @@ const PRESETS = {
1744
1802
  $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
1745
1803
  $VUE_ATTRIBUTE: /^v-/,
1746
1804
  };
1747
- const createRule$g = ESLintUtils.RuleCreator((name) => name);
1748
- const rule$j = createRule$g({
1805
+ const createRule$h = ESLintUtils.RuleCreator((name) => name);
1806
+ const rule$k = createRule$h({
1749
1807
  create(context, [options]) {
1750
1808
  const sourceCode = context.sourceCode;
1751
1809
  const settings = {
@@ -1867,21 +1925,6 @@ function getHostAttributeProperties(hostObject) {
1867
1925
  }
1868
1926
  return properties;
1869
1927
  }
1870
- function getStaticPropertyName(key) {
1871
- if (key.type === AST_NODE_TYPES$1.Identifier) {
1872
- return key.name;
1873
- }
1874
- if (key.type === AST_NODE_TYPES$1.Literal &&
1875
- (typeof key.value === 'string' || typeof key.value === 'number')) {
1876
- return String(key.value);
1877
- }
1878
- if (key.type === AST_NODE_TYPES$1.TemplateLiteral &&
1879
- key.expressions.length === 0 &&
1880
- key.quasis.length === 1) {
1881
- return key.quasis[0]?.value.cooked ?? null;
1882
- }
1883
- return null;
1884
- }
1885
1928
  function organizeProperties(properties, options) {
1886
1929
  const groups = getGroups(options.attributeGroups.length > 0 ? options.attributeGroups : ['$ANGULAR'], options.attributeIgnoreCase);
1887
1930
  const defaultGroup = ensureDefaultGroup(groups);
@@ -2021,7 +2064,7 @@ const config$2 = {
2021
2064
  const MESSAGE_ID$5 = 'invalid-injection-token-description';
2022
2065
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
2023
2066
  const NG_DEV_MODE = 'ngDevMode';
2024
- const createRule$f = ESLintUtils.RuleCreator((name) => name);
2067
+ const createRule$g = ESLintUtils.RuleCreator((name) => name);
2025
2068
  function getVariableName(node) {
2026
2069
  if (node.parent.type !== AST_NODE_TYPES$1.VariableDeclarator) {
2027
2070
  return undefined;
@@ -2029,21 +2072,18 @@ function getVariableName(node) {
2029
2072
  const { id } = node.parent;
2030
2073
  return id.type === AST_NODE_TYPES$1.Identifier ? id.name : undefined;
2031
2074
  }
2032
- function isStringLiteral$1(node) {
2033
- return node.type === AST_NODE_TYPES$1.Literal && typeof node.value === 'string';
2034
- }
2035
2075
  function isStringLike(node) {
2036
- return isStringLiteral$1(node) || node.type === AST_NODE_TYPES$1.TemplateLiteral;
2076
+ return isStringLiteral(node) || node.type === AST_NODE_TYPES$1.TemplateLiteral;
2037
2077
  }
2038
2078
  function getStringValue(node) {
2039
- if (isStringLiteral$1(node)) {
2079
+ if (isStringLiteral(node)) {
2040
2080
  return node.value;
2041
2081
  }
2042
2082
  return node.quasis[0]?.value.raw || '';
2043
2083
  }
2044
- function isEmptyString$1(node) {
2045
- return (getStringValue(node) === '' &&
2046
- (!('expressions' in node) || !node.expressions.length));
2084
+ function isEmptyString(node) {
2085
+ return (isEmptyStaticString(node) &&
2086
+ (!('expressions' in node) || node.expressions.length === 0));
2047
2087
  }
2048
2088
  function isNgDevModeConditional(node) {
2049
2089
  return (node.type === AST_NODE_TYPES$1.ConditionalExpression &&
@@ -2051,7 +2091,7 @@ function isNgDevModeConditional(node) {
2051
2091
  node.test.name === NG_DEV_MODE &&
2052
2092
  isStringLike(node.consequent) &&
2053
2093
  isStringLike(node.alternate) &&
2054
- isEmptyString$1(node.alternate));
2094
+ isEmptyString(node.alternate));
2055
2095
  }
2056
2096
  function getDescriptionValue(node) {
2057
2097
  if (isStringLike(node)) {
@@ -2092,7 +2132,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
2092
2132
  }
2093
2133
  return fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
2094
2134
  }
2095
- const rule$i = createRule$f({
2135
+ const rule$j = createRule$g({
2096
2136
  create(context) {
2097
2137
  const { sourceCode } = context;
2098
2138
  const program = sourceCode.ast;
@@ -2160,8 +2200,8 @@ const DEFAULT_OPTIONS = {
2160
2200
  importDeclaration: '^@taiga-ui*',
2161
2201
  projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
2162
2202
  };
2163
- const createRule$e = ESLintUtils.RuleCreator((name) => name);
2164
- const rule$h = createRule$e({
2203
+ const createRule$f = ESLintUtils.RuleCreator((name) => name);
2204
+ const rule$i = createRule$f({
2165
2205
  create(context) {
2166
2206
  const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
2167
2207
  const hasNonCodeExtension = (source) => {
@@ -2248,13 +2288,13 @@ const rule$h = createRule$e({
2248
2288
  name: 'no-deep-imports',
2249
2289
  });
2250
2290
 
2251
- const createRule$d = ESLintUtils.RuleCreator((name) => name);
2291
+ const createRule$e = ESLintUtils.RuleCreator((name) => name);
2252
2292
  const resolveCacheByOptions = new WeakMap();
2253
2293
  const nearestFileUpCache = new Map();
2254
2294
  const markerCache = new Map();
2255
2295
  const indexFileCache = new Map();
2256
2296
  const indexExportsCache = new Map();
2257
- var noDeepImportsToIndexedPackages = createRule$d({
2297
+ var noDeepImportsToIndexedPackages = createRule$e({
2258
2298
  create(context) {
2259
2299
  const parserServices = ESLintUtils.getParserServices(context);
2260
2300
  const program = parserServices.program;
@@ -2443,63 +2483,13 @@ function stripKnownExtensions(filePathOrSpecifier) {
2443
2483
  return filePathOrSpecifier.replace(/\.(?:d\.ts|ts|tsx|js|jsx|mjs|cjs)$/, '');
2444
2484
  }
2445
2485
 
2446
- const ANGULAR_CORE$1 = '@angular/core';
2447
- /**
2448
- * Returns the local name bound to a named import from a given source.
2449
- * Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
2450
- * returns `'ngUntracked'` for `exportedName = 'untracked'`.
2451
- */
2452
- function getLocalNameForImport(program, source, exportedName) {
2453
- for (const node of program.body) {
2454
- if (node.type !== AST_NODE_TYPES.ImportDeclaration ||
2455
- node.source.value !== source) {
2456
- continue;
2457
- }
2458
- for (const spec of node.specifiers) {
2459
- if (spec.type !== AST_NODE_TYPES.ImportSpecifier) {
2460
- continue;
2461
- }
2462
- const imported = spec.imported.type === AST_NODE_TYPES.Identifier
2463
- ? spec.imported.name
2464
- : spec.imported.value;
2465
- if (imported === exportedName) {
2466
- return spec.local.name;
2467
- }
2468
- }
2469
- }
2470
- return null;
2471
- }
2472
- function findAngularCoreImports(program) {
2473
- return program.body.filter((node) => node.type === AST_NODE_TYPES.ImportDeclaration &&
2474
- node.source.value === ANGULAR_CORE$1);
2475
- }
2476
- function findRuntimeAngularCoreImport(program) {
2477
- return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
2478
- }
2479
- function findAngularCoreImportSpecifier(program, exportedName) {
2480
- for (const importDecl of findAngularCoreImports(program)) {
2481
- for (const specifier of importDecl.specifiers) {
2482
- if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
2483
- continue;
2484
- }
2485
- const imported = specifier.imported.type === AST_NODE_TYPES.Identifier
2486
- ? specifier.imported.name
2487
- : specifier.imported.value;
2488
- if (imported === exportedName) {
2489
- return { importDecl, specifier };
2490
- }
2491
- }
2492
- }
2493
- return null;
2494
- }
2495
-
2496
- function isFunctionLike$1(node) {
2486
+ function isFunctionLike(node) {
2497
2487
  return (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
2498
2488
  node.type === AST_NODE_TYPES.FunctionDeclaration ||
2499
2489
  node.type === AST_NODE_TYPES.FunctionExpression);
2500
2490
  }
2501
2491
  function getOrderedChildren(node) {
2502
- if (isFunctionLike$1(node)) {
2492
+ if (isFunctionLike(node)) {
2503
2493
  const children = [
2504
2494
  ...node.params,
2505
2495
  node.body,
@@ -2568,7 +2558,7 @@ function getOrderedChildren(node) {
2568
2558
  function walkSynchronousAst(root, visitor) {
2569
2559
  traverse(root, true);
2570
2560
  function traverse(node, isRoot = false) {
2571
- if (visitor(node) === false || (!isRoot && isFunctionLike$1(node))) {
2561
+ if (visitor(node) === false || (!isRoot && isFunctionLike(node))) {
2572
2562
  return false;
2573
2563
  }
2574
2564
  if (node.type === AST_NODE_TYPES.AwaitExpression) {
@@ -2597,7 +2587,7 @@ function walkAfterAsyncBoundaryAst(root, visitor) {
2597
2587
  if (afterBoundary) {
2598
2588
  visitor(node);
2599
2589
  }
2600
- if (!isRoot && isFunctionLike$1(node)) {
2590
+ if (!isRoot && isFunctionLike(node)) {
2601
2591
  return false;
2602
2592
  }
2603
2593
  if (node.type === AST_NODE_TYPES.AwaitExpression) {
@@ -2660,6 +2650,56 @@ function walkAst(root, visitor) {
2660
2650
  }
2661
2651
  }
2662
2652
 
2653
+ const ANGULAR_CORE$1 = '@angular/core';
2654
+ /**
2655
+ * Returns the local name bound to a named import from a given source.
2656
+ * Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
2657
+ * returns `'ngUntracked'` for `exportedName = 'untracked'`.
2658
+ */
2659
+ function getLocalNameForImport(program, source, exportedName) {
2660
+ for (const node of program.body) {
2661
+ if (node.type !== AST_NODE_TYPES.ImportDeclaration ||
2662
+ node.source.value !== source) {
2663
+ continue;
2664
+ }
2665
+ for (const spec of node.specifiers) {
2666
+ if (spec.type !== AST_NODE_TYPES.ImportSpecifier) {
2667
+ continue;
2668
+ }
2669
+ const imported = spec.imported.type === AST_NODE_TYPES.Identifier
2670
+ ? spec.imported.name
2671
+ : spec.imported.value;
2672
+ if (imported === exportedName) {
2673
+ return spec.local.name;
2674
+ }
2675
+ }
2676
+ }
2677
+ return null;
2678
+ }
2679
+ function findAngularCoreImports(program) {
2680
+ return program.body.filter((node) => node.type === AST_NODE_TYPES.ImportDeclaration &&
2681
+ node.source.value === ANGULAR_CORE$1);
2682
+ }
2683
+ function findRuntimeAngularCoreImport(program) {
2684
+ return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
2685
+ }
2686
+ function findAngularCoreImportSpecifier(program, exportedName) {
2687
+ for (const importDecl of findAngularCoreImports(program)) {
2688
+ for (const specifier of importDecl.specifiers) {
2689
+ if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
2690
+ continue;
2691
+ }
2692
+ const imported = specifier.imported.type === AST_NODE_TYPES.Identifier
2693
+ ? specifier.imported.name
2694
+ : specifier.imported.value;
2695
+ if (imported === exportedName) {
2696
+ return { importDecl, specifier };
2697
+ }
2698
+ }
2699
+ }
2700
+ return null;
2701
+ }
2702
+
2663
2703
  const ANGULAR_CORE = '@angular/core';
2664
2704
  const SIGNAL_WRITE_METHODS = new Set(['mutate', 'set', 'update']);
2665
2705
  const AFTER_RENDER_EFFECT_PHASES = new Map([
@@ -2668,10 +2708,14 @@ const AFTER_RENDER_EFFECT_PHASES = new Map([
2668
2708
  ['read', 'afterRenderEffect().read'],
2669
2709
  ['write', 'afterRenderEffect().write'],
2670
2710
  ]);
2671
- function isReactiveCallback$1(node) {
2711
+ function isReactiveCallback(node) {
2672
2712
  return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
2673
2713
  node?.type === AST_NODE_TYPES.FunctionExpression);
2674
2714
  }
2715
+ function getReactiveCallbackArgument(node) {
2716
+ const [arg] = node.arguments;
2717
+ return isReactiveCallback(arg) ? arg : null;
2718
+ }
2675
2719
  function getPropertyName(property) {
2676
2720
  if (property.computed) {
2677
2721
  return null;
@@ -2691,11 +2735,11 @@ function isAngularCoreCall(node, program, exportedName) {
2691
2735
  node.arguments.length >= 1);
2692
2736
  }
2693
2737
  function appendFirstArgReactiveScope(scopes, call, kind) {
2694
- const [arg] = call.arguments;
2695
- if (!isReactiveCallback$1(arg)) {
2738
+ const callback = getReactiveCallbackArgument(call);
2739
+ if (!callback) {
2696
2740
  return;
2697
2741
  }
2698
- scopes.push({ callback: arg, kind, owner: call, reportNode: call });
2742
+ scopes.push({ callback, kind, owner: call, reportNode: call });
2699
2743
  }
2700
2744
  function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
2701
2745
  for (const property of object.properties) {
@@ -2704,7 +2748,7 @@ function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
2704
2748
  }
2705
2749
  const name = getPropertyName(property);
2706
2750
  const label = name ? labels.get(name) : null;
2707
- if (!label || !isReactiveCallback$1(property.value)) {
2751
+ if (!label || !isReactiveCallback(property.value)) {
2708
2752
  continue;
2709
2753
  }
2710
2754
  scopes.push({
@@ -2914,54 +2958,74 @@ function collectSignalUsages(scopeNode, checker, esTreeNodeToTSNodeMap, program)
2914
2958
  });
2915
2959
  return { reads, writes };
2916
2960
  }
2917
-
2918
- const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#reading-without-tracking-dependencies';
2919
- const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
2920
- const UNTRACKED_RULES_README_URL = 'https://github.com/taiga-family/taiga-ui/blob/main/projects/eslint-plugin-experience-next/README.md';
2921
- const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES_README_URL}#${name}`);
2922
-
2923
- /**
2924
- * Collects signal reads that appear inside `untracked(...)` callbacks within
2925
- * `root`, without descending into nested `untracked(...)` scopes.
2926
- */
2927
- function collectReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
2961
+ function collectSignalReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
2928
2962
  const reads = [];
2929
2963
  walkSynchronousAst(root, (node) => {
2930
2964
  if (node.type !== AST_NODE_TYPES.CallExpression ||
2931
2965
  !isAngularUntrackedCall(node, program)) {
2932
2966
  return;
2933
2967
  }
2934
- const [arg] = node.arguments;
2935
- if (!arg ||
2936
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
2937
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
2968
+ const callback = getReactiveCallbackArgument(node);
2969
+ if (!callback) {
2938
2970
  return false;
2939
2971
  }
2940
- walkSynchronousAst(arg, (inner) => {
2972
+ walkSynchronousAst(callback, (inner) => {
2941
2973
  if (inner.type === AST_NODE_TYPES.CallExpression &&
2942
2974
  isSignalReadCall(inner, checker, esTreeNodeToTSNodeMap)) {
2943
2975
  reads.push(inner);
2944
2976
  }
2945
2977
  });
2946
- return false; // Do not descend into nested untracked from the outer walk
2978
+ return false;
2947
2979
  });
2948
2980
  return reads;
2949
2981
  }
2950
- const rule$g = createUntrackedRule({
2982
+ function isReactiveOwnerCall(node, program) {
2983
+ return (node.type === AST_NODE_TYPES.CallExpression &&
2984
+ getReactiveScopes(node, program).length > 0);
2985
+ }
2986
+ function getReturnedReactiveOwnerCall(node, program) {
2987
+ const callback = getReactiveCallbackArgument(node);
2988
+ if (!callback) {
2989
+ return null;
2990
+ }
2991
+ if (isReactiveOwnerCall(callback.body, program)) {
2992
+ return callback.body;
2993
+ }
2994
+ if (callback.body.type !== AST_NODE_TYPES.BlockStatement ||
2995
+ callback.body.body.length !== 1) {
2996
+ return null;
2997
+ }
2998
+ const [statement] = callback.body.body;
2999
+ if (statement?.type === AST_NODE_TYPES.ReturnStatement &&
3000
+ statement.argument &&
3001
+ isReactiveOwnerCall(statement.argument, program)) {
3002
+ return statement.argument;
3003
+ }
3004
+ if (node.parent.type === AST_NODE_TYPES.ExpressionStatement &&
3005
+ statement?.type === AST_NODE_TYPES.ExpressionStatement &&
3006
+ isReactiveOwnerCall(statement.expression, program)) {
3007
+ return statement.expression;
3008
+ }
3009
+ return null;
3010
+ }
3011
+
3012
+ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#reading-without-tracking-dependencies';
3013
+ const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
3014
+ const UNTRACKED_RULES_README_URL = 'https://github.com/taiga-family/taiga-ui/blob/main/projects/eslint-plugin-experience-next/README.md';
3015
+ const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES_README_URL}#${name}`);
3016
+
3017
+ const rule$h = createUntrackedRule({
2951
3018
  create(context) {
2952
- const parserServices = ESLintUtils.getParserServices(context);
2953
- const checker = parserServices.program.getTypeChecker();
2954
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
2955
- const { sourceCode } = context;
2956
- const program = sourceCode.ast;
3019
+ const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
3020
+ const signalNodeMap = esTreeNodeToTSNodeMap;
2957
3021
  return {
2958
3022
  CallExpression(node) {
2959
3023
  for (const scope of getReactiveScopes(node, program)) {
2960
- const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, esTreeNodeToTSNodeMap, program);
3024
+ const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, signalNodeMap, program);
2961
3025
  if (trackedReads.length > 0) {
2962
3026
  continue;
2963
3027
  }
2964
- const untrackedReads = collectReadsInsideUntracked(scope.callback, checker, esTreeNodeToTSNodeMap, program);
3028
+ const untrackedReads = collectSignalReadsInsideUntracked(scope.callback, checker, signalNodeMap, program);
2965
3029
  if (untrackedReads.length === 0) {
2966
3030
  continue;
2967
3031
  }
@@ -3033,11 +3097,67 @@ const config$1 = {
3033
3097
  },
3034
3098
  };
3035
3099
 
3036
- const createRule$c = ESLintUtils.RuleCreator((name) => name);
3037
- const rule$f = createRule$c({
3100
+ function findAncestor(node, predicate) {
3101
+ for (let current = node?.parent; current; current = current.parent) {
3102
+ if (predicate(current)) {
3103
+ return current;
3104
+ }
3105
+ }
3106
+ return null;
3107
+ }
3108
+ function findSelfOrAncestor(node, predicate) {
3109
+ for (let current = node; current; current = current.parent) {
3110
+ if (predicate(current)) {
3111
+ return current;
3112
+ }
3113
+ }
3114
+ return null;
3115
+ }
3116
+ function hasAncestor(node, predicate) {
3117
+ for (let current = node?.parent; current; current = current.parent) {
3118
+ if (predicate(current)) {
3119
+ return true;
3120
+ }
3121
+ }
3122
+ return false;
3123
+ }
3124
+ function isNodeInside(node, ancestor) {
3125
+ for (let current = node; current; current = current.parent) {
3126
+ if (current === ancestor) {
3127
+ return true;
3128
+ }
3129
+ }
3130
+ return false;
3131
+ }
3132
+ function isNodeInsideAny(node, ancestors) {
3133
+ return ancestors.some((ancestor) => isNodeInside(node, ancestor));
3134
+ }
3135
+ function isClassLike(node) {
3136
+ return (node.type === AST_NODE_TYPES.ClassDeclaration ||
3137
+ node.type === AST_NODE_TYPES.ClassExpression);
3138
+ }
3139
+ function isClassMember(node) {
3140
+ return (node.type === AST_NODE_TYPES.MethodDefinition ||
3141
+ node.type === AST_NODE_TYPES.PropertyDefinition);
3142
+ }
3143
+ function getEnclosingFunction(node) {
3144
+ return findAncestor(node, isFunctionLike);
3145
+ }
3146
+ function getEnclosingClass(node) {
3147
+ return findSelfOrAncestor(node, isClassLike);
3148
+ }
3149
+ function getEnclosingClassMember(node) {
3150
+ return findAncestor(node, isClassMember);
3151
+ }
3152
+ function getScopeRoot(node) {
3153
+ return (findAncestor(node, (ancestor) => ancestor.type === AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
3154
+ }
3155
+
3156
+ const createRule$d = ESLintUtils.RuleCreator((name) => name);
3157
+ const rule$g = createRule$d({
3038
3158
  create(context) {
3039
3159
  const checkImplicitPublic = (node) => {
3040
- const classRef = getClass(node);
3160
+ const classRef = getEnclosingClass(node);
3041
3161
  if (!classRef ||
3042
3162
  node.kind === 'constructor' ||
3043
3163
  !!node?.accessibility ||
@@ -3095,16 +3215,64 @@ const rule$f = createRule$c({
3095
3215
  },
3096
3216
  name: 'explicit-public-member',
3097
3217
  });
3098
- function getClass(node) {
3099
- if (!node) {
3100
- return null;
3218
+
3219
+ function getParenthesizedInner(node) {
3220
+ const maybeNode = node;
3221
+ if (maybeNode.type === 'ParenthesizedExpression') {
3222
+ return maybeNode.expression ?? null;
3101
3223
  }
3102
- if (node.type === AST_NODE_TYPES.ClassDeclaration) {
3103
- return node;
3224
+ return null;
3225
+ }
3226
+ function unwrapParenthesized(node) {
3227
+ let current = node;
3228
+ let inner = getParenthesizedInner(current);
3229
+ while (inner) {
3230
+ current = inner;
3231
+ inner = getParenthesizedInner(current);
3104
3232
  }
3105
- return getClass(node.parent);
3233
+ return current;
3106
3234
  }
3107
3235
 
3236
+ const createRule$c = ESLintUtils.RuleCreator((name) => name);
3237
+ function isBooleanTrue(node) {
3238
+ const unwrapped = unwrapParenthesized(node);
3239
+ return unwrapped.type === AST_NODE_TYPES.Literal && unwrapped.value === true;
3240
+ }
3241
+ const rule$f = createRule$c({
3242
+ create(context) {
3243
+ return {
3244
+ ForStatement(node) {
3245
+ if (!node.test) {
3246
+ context.report({
3247
+ messageId: 'forLoop',
3248
+ node,
3249
+ });
3250
+ }
3251
+ },
3252
+ WhileStatement(node) {
3253
+ if (isBooleanTrue(node.test)) {
3254
+ context.report({
3255
+ messageId: 'whileLoop',
3256
+ node: node.test,
3257
+ });
3258
+ }
3259
+ },
3260
+ };
3261
+ },
3262
+ meta: {
3263
+ docs: {
3264
+ description: 'Disallow `while (true)` and `for` loops without an explicit condition. Prefer loops with meaningful exit conditions.',
3265
+ },
3266
+ messages: {
3267
+ forLoop: 'Use an explicit exit condition instead of a `for` loop without a condition.',
3268
+ whileLoop: 'Use an explicit exit condition instead of `while (true)`.',
3269
+ },
3270
+ schema: [],
3271
+ type: 'suggestion',
3272
+ },
3273
+ name: 'no-infinite-loop',
3274
+ });
3275
+
3108
3276
  const createRule$b = ESLintUtils.RuleCreator((name) => name);
3109
3277
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
3110
3278
  const rule$e = createRule$b({
@@ -3148,8 +3316,7 @@ const rule$e = createRule$b({
3148
3316
  const createRule$a = ESLintUtils.RuleCreator((name) => name);
3149
3317
  const rule$d = createRule$a({
3150
3318
  create(context) {
3151
- const services = ESLintUtils.getParserServices(context);
3152
- const checker = services.program.getTypeChecker();
3319
+ const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
3153
3320
  return {
3154
3321
  CallExpression(node) {
3155
3322
  const callee = node.callee;
@@ -3163,18 +3330,18 @@ const rule$d = createRule$a({
3163
3330
  return;
3164
3331
  }
3165
3332
  const [argument] = node.arguments;
3166
- if (!argument || !isEmptyString(argument)) {
3333
+ if (!argument || !isEmptyStaticString(argument)) {
3167
3334
  return;
3168
3335
  }
3169
3336
  const objectExpression = callee.object;
3170
- const tsNode = services.esTreeNodeToTSNodeMap.get(objectExpression);
3337
+ const tsNode = esTreeNodeToTSNodeMap.get(objectExpression);
3171
3338
  const type = checker.getTypeAtLocation(tsNode);
3172
3339
  if (!isPlaywrightLocator(type, checker)) {
3173
3340
  return;
3174
3341
  }
3175
3342
  context.report({
3176
3343
  fix(fixer) {
3177
- const objectText = context.sourceCode.getText(objectExpression);
3344
+ const objectText = sourceCode.getText(objectExpression);
3178
3345
  return fixer.replaceText(node, `${objectText}.clear()`);
3179
3346
  },
3180
3347
  messageId: 'useClear',
@@ -3194,13 +3361,6 @@ const rule$d = createRule$a({
3194
3361
  },
3195
3362
  name: 'no-playwright-empty-fill',
3196
3363
  });
3197
- function isEmptyString(node) {
3198
- return ((node.type === AST_NODE_TYPES$1.Literal && node.value === '') ||
3199
- (node.type === AST_NODE_TYPES$1.TemplateLiteral &&
3200
- node.expressions.length === 0 &&
3201
- node.quasis.length === 1 &&
3202
- node.quasis[0]?.value.cooked === ''));
3203
- }
3204
3364
  function isPlaywrightLocator(type, checker) {
3205
3365
  if (isPlaywrightLocatorType(type)) {
3206
3366
  return true;
@@ -3339,15 +3499,14 @@ function collectArrayExpressions(node) {
3339
3499
  }
3340
3500
  const rule$c = createRule$9({
3341
3501
  create(context) {
3342
- const parserServices = ESLintUtils.getParserServices(context);
3343
- const typeChecker = parserServices.program.getTypeChecker();
3502
+ const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
3344
3503
  const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
3345
3504
  function check(node, typeAnnotation, value) {
3346
3505
  if (!typeAnnotation || !value) {
3347
3506
  return;
3348
3507
  }
3349
- const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
3350
- const tsValueNode = parserServices.esTreeNodeToTSNodeMap.get(value);
3508
+ const tsNode = esTreeNodeToTSNodeMap.get(node);
3509
+ const tsValueNode = esTreeNodeToTSNodeMap.get(value);
3351
3510
  const declaredType = typeChecker.getTypeAtLocation(tsNode);
3352
3511
  const inferredType = typeChecker.getTypeAtLocation(tsValueNode);
3353
3512
  if (typeChecker.typeToString(declaredType) !==
@@ -3371,7 +3530,7 @@ const rule$c = createRule$9({
3371
3530
  if (ignoreTupleContextualTyping) {
3372
3531
  const arrayExpressions = collectArrayExpressions(value);
3373
3532
  for (const arrayExpression of arrayExpressions) {
3374
- const tsArrayNode = parserServices.esTreeNodeToTSNodeMap.get(arrayExpression);
3533
+ const tsArrayNode = esTreeNodeToTSNodeMap.get(arrayExpression);
3375
3534
  if (typeChecker.isTupleType(typeChecker.getTypeAtLocation(tsArrayNode))) {
3376
3535
  return;
3377
3536
  }
@@ -3465,16 +3624,6 @@ function unwrapExpression(expression) {
3465
3624
  return current;
3466
3625
  }
3467
3626
 
3468
- const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3469
- function isFunctionLikeScope(node) {
3470
- return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3471
- node?.type === AST_NODE_TYPES.FunctionDeclaration ||
3472
- node?.type === AST_NODE_TYPES.FunctionExpression);
3473
- }
3474
- function isReactiveCallback(node) {
3475
- return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3476
- node?.type === AST_NODE_TYPES.FunctionExpression);
3477
- }
3478
3627
  function unwrapMutationTarget(node) {
3479
3628
  let current = node;
3480
3629
  while (current.type === AST_NODE_TYPES.TSAsExpression ||
@@ -3508,6 +3657,7 @@ function collectMutationTargets(node) {
3508
3657
  return [];
3509
3658
  }
3510
3659
  }
3660
+
3511
3661
  function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
3512
3662
  const tsNode = esTreeNodeToTSNodeMap.get(node);
3513
3663
  if (!tsNode) {
@@ -3515,6 +3665,11 @@ function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
3515
3665
  }
3516
3666
  return checker.getSymbolAtLocation(tsNode) ?? null;
3517
3667
  }
3668
+
3669
+ const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3670
+ function isFunctionLikeScope(node) {
3671
+ return !!node && isFunctionLike(node);
3672
+ }
3518
3673
  function isLocalIdentifier(node, context, localScopes) {
3519
3674
  const symbol = getSymbolAtNode(node, context.checker, context.esTreeNodeToTSNodeMap);
3520
3675
  if (!symbol) {
@@ -3671,9 +3826,9 @@ function functionHasObservableSideEffects(root, context, localScopes, visitedFun
3671
3826
  return false;
3672
3827
  }
3673
3828
  if (isAngularUntrackedCall(node, context.program)) {
3674
- const [arg] = node.arguments;
3675
- if (isReactiveCallback(arg) &&
3676
- functionHasObservableSideEffects(arg, context, [...localScopes, arg], visitedFunctions)) {
3829
+ const callback = getReactiveCallbackArgument(node);
3830
+ if (callback &&
3831
+ functionHasObservableSideEffects(callback, context, [...localScopes, callback], visitedFunctions)) {
3677
3832
  hasSideEffect = true;
3678
3833
  }
3679
3834
  return false;
@@ -3727,9 +3882,9 @@ function inspectComputedBody(root, context, localScopes, visitedFunctions, repor
3727
3882
  return false;
3728
3883
  }
3729
3884
  if (isAngularUntrackedCall(node, context.program)) {
3730
- const [arg] = node.arguments;
3731
- if (isReactiveCallback(arg)) {
3732
- inspectComputedBody(arg, context, [...localScopes, arg], visitedFunctions, report);
3885
+ const callback = getReactiveCallbackArgument(node);
3886
+ if (callback) {
3887
+ inspectComputedBody(callback, context, [...localScopes, callback], visitedFunctions, report);
3733
3888
  }
3734
3889
  return false;
3735
3890
  }
@@ -3770,12 +3925,9 @@ function inspectComputedBody(root, context, localScopes, visitedFunctions, repor
3770
3925
  }
3771
3926
  const rule$b = createRule$8({
3772
3927
  create(context) {
3773
- const parserServices = ESLintUtils.getParserServices(context);
3774
- const checker = parserServices.program.getTypeChecker();
3775
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
3776
- const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
3777
- const { sourceCode } = context;
3778
- const program = sourceCode.ast;
3928
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
3929
+ const signalNodeMap = esTreeNodeToTSNodeMap;
3930
+ const estreeNodeMap = tsNodeToESTreeNodeMap;
3779
3931
  return {
3780
3932
  CallExpression(node) {
3781
3933
  for (const scope of getReactiveScopes(node, program)) {
@@ -3784,10 +3936,10 @@ const rule$b = createRule$8({
3784
3936
  }
3785
3937
  const analysisContext = {
3786
3938
  checker,
3787
- esTreeNodeToTSNodeMap,
3939
+ esTreeNodeToTSNodeMap: signalNodeMap,
3788
3940
  program,
3789
3941
  reported: new Set(),
3790
- tsNodeToESTreeNodeMap,
3942
+ tsNodeToESTreeNodeMap: estreeNodeMap,
3791
3943
  };
3792
3944
  inspectComputedBody(scope.callback, analysisContext, [scope.callback], new Set([String(scope.callback.range)]), (reportNode) => {
3793
3945
  context.report({
@@ -3816,11 +3968,8 @@ const rule$b = createRule$8({
3816
3968
 
3817
3969
  const rule$a = createUntrackedRule({
3818
3970
  create(context) {
3819
- const parserServices = ESLintUtils.getParserServices(context);
3820
- const checker = parserServices.program.getTypeChecker();
3821
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
3822
- const { sourceCode } = context;
3823
- const program = sourceCode.ast;
3971
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
3972
+ const signalNodeMap = esTreeNodeToTSNodeMap;
3824
3973
  return {
3825
3974
  CallExpression(node) {
3826
3975
  for (const scope of getReactiveScopes(node, program)) {
@@ -3828,7 +3977,7 @@ const rule$a = createUntrackedRule({
3828
3977
  walkAfterAsyncBoundaryAst(scope.callback, (inner) => {
3829
3978
  if (inner.type !== AST_NODE_TYPES.CallExpression ||
3830
3979
  isAngularUntrackedCall(inner, program) ||
3831
- !isSignalReadCall(inner, checker, esTreeNodeToTSNodeMap)) {
3980
+ !isSignalReadCall(inner, checker, signalNodeMap)) {
3832
3981
  return;
3833
3982
  }
3834
3983
  const key = String(inner.range);
@@ -3864,10 +4013,6 @@ const rule$a = createUntrackedRule({
3864
4013
  });
3865
4014
 
3866
4015
  const createRule$7 = ESLintUtils.RuleCreator((name) => name);
3867
- function isStringLiteral(node) {
3868
- return (node.type === AST_NODE_TYPES.Literal &&
3869
- typeof node.value === 'string');
3870
- }
3871
4016
  function collectParts(node) {
3872
4017
  if (node.type === AST_NODE_TYPES.BinaryExpression && node.operator === '+') {
3873
4018
  return [...collectParts(node.left), ...collectParts(node.right)];
@@ -3917,16 +4062,6 @@ function templateContent(template, renderExpr) {
3917
4062
  : ''}`)
3918
4063
  .join('');
3919
4064
  }
3920
- function hasTemplateLiteralAncestor(node) {
3921
- let current = node.parent;
3922
- while (current != null) {
3923
- if (current.type === AST_NODE_TYPES.TemplateLiteral) {
3924
- return true;
3925
- }
3926
- current = current.parent;
3927
- }
3928
- return false;
3929
- }
3930
4065
  const rule$9 = createRule$7({
3931
4066
  create(context) {
3932
4067
  const { sourceCode } = context;
@@ -3982,7 +4117,7 @@ const rule$9 = createRule$7({
3982
4117
  }
3983
4118
  // Nested inside a template but not direct child — would produce
3984
4119
  // `${`${a}${b}`.method()}`, so skip
3985
- if (hasTemplateLiteralAncestor(node)) {
4120
+ if (hasAncestor(node, (ancestor) => ancestor.type === AST_NODE_TYPES.TemplateLiteral)) {
3986
4121
  return;
3987
4122
  }
3988
4123
  context.report({
@@ -4096,67 +4231,6 @@ function buildImportRemovalFixes(program, fixer, sourceCode) {
4096
4231
  return [fixer.remove(untrackedSpec)];
4097
4232
  }
4098
4233
 
4099
- const IMPERATIVE_UNTRACKED_METHODS = new Set(['registerOnChange', 'writeValue']);
4100
- function dedent$1(text, extraSpaces) {
4101
- if (extraSpaces <= 0) {
4102
- return text;
4103
- }
4104
- const prefix = ' '.repeat(extraSpaces);
4105
- return text
4106
- .split('\n')
4107
- .map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
4108
- .join('\n');
4109
- }
4110
- function isFunctionLike(node) {
4111
- return (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
4112
- node.type === AST_NODE_TYPES.FunctionDeclaration ||
4113
- node.type === AST_NODE_TYPES.FunctionExpression);
4114
- }
4115
- function getObjectPropertyName(node) {
4116
- if (node.computed) {
4117
- return null;
4118
- }
4119
- if (node.key.type === AST_NODE_TYPES.Identifier) {
4120
- return node.key.name;
4121
- }
4122
- return typeof node.key.value === 'string' ? node.key.value : null;
4123
- }
4124
- function getMemberExpressionPropertyName(node) {
4125
- if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
4126
- return node.property.name;
4127
- }
4128
- return node.property.type === AST_NODE_TYPES.Literal &&
4129
- typeof node.property.value === 'string'
4130
- ? node.property.value
4131
- : null;
4132
- }
4133
- function getEnclosingClassMember(node) {
4134
- for (let current = node.parent; current; current = current.parent) {
4135
- if (current.type === AST_NODE_TYPES.MethodDefinition ||
4136
- current.type === AST_NODE_TYPES.PropertyDefinition) {
4137
- return current;
4138
- }
4139
- }
4140
- return null;
4141
- }
4142
- function getEnclosingFunction(node) {
4143
- for (let current = node.parent; current; current = current.parent) {
4144
- if (isFunctionLike(current)) {
4145
- return current;
4146
- }
4147
- }
4148
- return null;
4149
- }
4150
- function getClassMemberName(member) {
4151
- if (member.key.type === AST_NODE_TYPES.Identifier) {
4152
- return member.key.name;
4153
- }
4154
- if (member.key.type === AST_NODE_TYPES.Literal &&
4155
- typeof member.key.value === 'string') {
4156
- return member.key.value;
4157
- }
4158
- return null;
4159
- }
4160
4234
  function hasNamedDecorator(node, name) {
4161
4235
  return node.decorators.some((decorator) => {
4162
4236
  const expression = decorator.expression;
@@ -4168,68 +4242,75 @@ function hasNamedDecorator(node, name) {
4168
4242
  expression.callee.name === name);
4169
4243
  });
4170
4244
  }
4245
+
4171
4246
  function isPipeTransformMember(member) {
4172
4247
  if (getClassMemberName(member) !== 'transform') {
4173
4248
  return false;
4174
4249
  }
4175
- return hasNamedDecorator(member.parent.parent, 'Pipe');
4250
+ const ownerClass = getEnclosingClass(member);
4251
+ return !!ownerClass && hasNamedDecorator(ownerClass, 'Pipe');
4176
4252
  }
4177
- function hasAllowedImperativeAssignment(node) {
4178
- for (let current = node.parent; current; current = current.parent) {
4179
- if (!isFunctionLike(current)) {
4180
- continue;
4181
- }
4182
- const parent = current.parent;
4183
- if (parent.type === AST_NODE_TYPES.AssignmentExpression &&
4184
- parent.right === current &&
4185
- parent.left.type === AST_NODE_TYPES.MemberExpression) {
4186
- const memberName = getMemberExpressionPropertyName(parent.left);
4187
- if (memberName && IMPERATIVE_UNTRACKED_METHODS.has(memberName)) {
4188
- return true;
4189
- }
4190
- }
4253
+
4254
+ function isAngularInjectionTokenFactoryFunction(fn, program) {
4255
+ const parent = fn.parent;
4256
+ const injectionTokenName = getLocalNameForImport(program, '@angular/core', 'InjectionToken');
4257
+ if (!injectionTokenName ||
4258
+ parent.type !== AST_NODE_TYPES.Property ||
4259
+ getObjectPropertyName(parent) !== 'factory') {
4260
+ return false;
4191
4261
  }
4192
- return false;
4193
- }
4194
- function isAllowedImperativeAngularContext(node) {
4195
- const member = getEnclosingClassMember(node);
4196
- const memberName = member ? getClassMemberName(member) : null;
4197
- return ((!!member &&
4198
- !!memberName &&
4199
- (IMPERATIVE_UNTRACKED_METHODS.has(memberName) ||
4200
- isPipeTransformMember(member))) ||
4201
- hasAllowedImperativeAssignment(node));
4262
+ const objectExpression = parent.parent;
4263
+ return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4264
+ objectExpression.parent.type === AST_NODE_TYPES.NewExpression &&
4265
+ objectExpression.parent.arguments.includes(objectExpression) &&
4266
+ objectExpression.parent.callee.type === AST_NODE_TYPES.Identifier &&
4267
+ objectExpression.parent.callee.name === injectionTokenName);
4202
4268
  }
4203
- function isDirectCallbackArgument(fn) {
4269
+ function isAngularUseFactoryFunction(fn) {
4204
4270
  const parent = fn.parent;
4205
- if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4206
- fn.type !== AST_NODE_TYPES.FunctionExpression) {
4271
+ if (parent.type !== AST_NODE_TYPES.Property ||
4272
+ getObjectPropertyName(parent) !== 'useFactory') {
4207
4273
  return false;
4208
4274
  }
4209
- return ((parent.type === AST_NODE_TYPES.CallExpression ||
4210
- parent.type === AST_NODE_TYPES.NewExpression) &&
4211
- parent.arguments.includes(fn));
4275
+ const objectExpression = parent.parent;
4276
+ return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4277
+ objectExpression.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
4278
+ getObjectPropertyName(property) === 'provide'));
4212
4279
  }
4213
- function getScopeRoot(node) {
4214
- for (let current = node.parent; current; current = current.parent) {
4215
- if (current.type === AST_NODE_TYPES.Program || isFunctionLike(current)) {
4216
- return current;
4217
- }
4280
+
4281
+ /**
4282
+ * Removes `extraSpaces` leading spaces from every line of `text` that starts
4283
+ * with at least that many spaces.
4284
+ */
4285
+ function dedent(text, extraSpaces) {
4286
+ if (extraSpaces <= 0) {
4287
+ return text;
4218
4288
  }
4219
- return node;
4289
+ const prefix = ' '.repeat(extraSpaces);
4290
+ return text
4291
+ .split('\n')
4292
+ .map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
4293
+ .join('\n');
4294
+ }
4295
+
4296
+ function isDirectCallOrNewArgument(node) {
4297
+ const parent = node.parent;
4298
+ if (node.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4299
+ node.type !== AST_NODE_TYPES.FunctionExpression) {
4300
+ return false;
4301
+ }
4302
+ return ((parent.type === AST_NODE_TYPES.CallExpression ||
4303
+ parent.type === AST_NODE_TYPES.NewExpression) &&
4304
+ parent.arguments.includes(node));
4220
4305
  }
4221
- function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4306
+ function isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap) {
4222
4307
  const parent = fn.parent;
4223
4308
  if (parent.type !== AST_NODE_TYPES.VariableDeclarator ||
4224
4309
  parent.id.type !== AST_NODE_TYPES.Identifier) {
4225
4310
  return false;
4226
4311
  }
4227
4312
  const id = parent.id;
4228
- const tsNode = esTreeNodeToTSNodeMap.get(id);
4229
- if (!tsNode) {
4230
- return false;
4231
- }
4232
- const symbol = checker.getSymbolAtLocation(tsNode);
4313
+ const symbol = getSymbolAtNode(id, checker, esTreeNodeToTSNodeMap);
4233
4314
  if (!symbol) {
4234
4315
  return false;
4235
4316
  }
@@ -4241,10 +4322,7 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4241
4322
  node.name !== id.name) {
4242
4323
  return;
4243
4324
  }
4244
- const referenceTsNode = esTreeNodeToTSNodeMap.get(node);
4245
- const referenceSymbol = referenceTsNode
4246
- ? checker.getSymbolAtLocation(referenceTsNode)
4247
- : null;
4325
+ const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
4248
4326
  if (referenceSymbol !== symbol) {
4249
4327
  return;
4250
4328
  }
@@ -4259,81 +4337,50 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4259
4337
  });
4260
4338
  return found;
4261
4339
  }
4340
+
4341
+ const IMPERATIVE_UNTRACKED_METHODS = new Set(['registerOnChange', 'writeValue']);
4342
+ function hasAllowedImperativeAssignment(node) {
4343
+ for (let current = node.parent; current; current = current.parent) {
4344
+ if (current.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4345
+ current.type !== AST_NODE_TYPES.FunctionDeclaration &&
4346
+ current.type !== AST_NODE_TYPES.FunctionExpression) {
4347
+ continue;
4348
+ }
4349
+ const parent = current.parent;
4350
+ if (parent.type === AST_NODE_TYPES.AssignmentExpression &&
4351
+ parent.right === current &&
4352
+ parent.left.type === AST_NODE_TYPES.MemberExpression) {
4353
+ const memberName = getMemberExpressionPropertyName(parent.left);
4354
+ if (memberName && IMPERATIVE_UNTRACKED_METHODS.has(memberName)) {
4355
+ return true;
4356
+ }
4357
+ }
4358
+ }
4359
+ return false;
4360
+ }
4361
+ function isAllowedImperativeAngularContext(node) {
4362
+ const member = getEnclosingClassMember(node);
4363
+ const memberName = member ? getClassMemberName(member) : null;
4364
+ return ((!!member &&
4365
+ !!memberName &&
4366
+ (IMPERATIVE_UNTRACKED_METHODS.has(memberName) ||
4367
+ isPipeTransformMember(member))) ||
4368
+ hasAllowedImperativeAssignment(node));
4369
+ }
4262
4370
  function isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) {
4263
- const [arg] = node.arguments;
4264
- if (!arg ||
4265
- arg.type === AST_NODE_TYPES.SpreadElement ||
4266
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4267
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4371
+ if (!getReactiveCallbackArgument(node)) {
4268
4372
  return false;
4269
4373
  }
4270
4374
  const fn = getEnclosingFunction(node);
4271
4375
  if (!fn) {
4272
4376
  return false;
4273
4377
  }
4274
- return (isDirectCallbackArgument(fn) ||
4275
- isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap));
4276
- }
4277
- function isAngularInjectionTokenFactoryFunction(fn, program) {
4278
- const parent = fn.parent;
4279
- const injectionTokenName = getLocalNameForImport(program, '@angular/core', 'InjectionToken');
4280
- if (!injectionTokenName ||
4281
- parent.type !== AST_NODE_TYPES.Property ||
4282
- getObjectPropertyName(parent) !== 'factory') {
4283
- return false;
4284
- }
4285
- const objectExpression = parent.parent;
4286
- return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4287
- objectExpression.parent.type === AST_NODE_TYPES.NewExpression &&
4288
- objectExpression.parent.arguments.includes(objectExpression) &&
4289
- objectExpression.parent.callee.type === AST_NODE_TYPES.Identifier &&
4290
- objectExpression.parent.callee.name === injectionTokenName);
4291
- }
4292
- function isAngularUseFactoryFunction(fn) {
4293
- const parent = fn.parent;
4294
- if (parent.type !== AST_NODE_TYPES.Property ||
4295
- getObjectPropertyName(parent) !== 'useFactory') {
4296
- return false;
4297
- }
4298
- const objectExpression = parent.parent;
4299
- return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4300
- objectExpression.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
4301
- getObjectPropertyName(property) === 'provide'));
4302
- }
4303
- function isReactiveOwnerCall(node, program) {
4304
- return (node.type === AST_NODE_TYPES.CallExpression &&
4305
- getReactiveScopes(node, program).length > 0);
4306
- }
4307
- function getFixableReactiveCall(node, program) {
4308
- const [arg] = node.arguments;
4309
- if (!arg ||
4310
- arg.type === AST_NODE_TYPES.SpreadElement ||
4311
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4312
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4313
- return null;
4314
- }
4315
- if (isReactiveOwnerCall(arg.body, program)) {
4316
- return arg.body;
4317
- }
4318
- if (arg.body.type !== AST_NODE_TYPES.BlockStatement || arg.body.body.length !== 1) {
4319
- return null;
4320
- }
4321
- const [statement] = arg.body.body;
4322
- if (statement?.type === AST_NODE_TYPES.ReturnStatement &&
4323
- statement.argument &&
4324
- isReactiveOwnerCall(statement.argument, program)) {
4325
- return statement.argument;
4326
- }
4327
- if (node.parent.type === AST_NODE_TYPES.ExpressionStatement &&
4328
- statement?.type === AST_NODE_TYPES.ExpressionStatement &&
4329
- isReactiveOwnerCall(statement.expression, program)) {
4330
- return statement.expression;
4331
- }
4332
- return null;
4378
+ return (isDirectCallOrNewArgument(fn) ||
4379
+ isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap));
4333
4380
  }
4334
4381
  function isAllowedLazyAngularFactoryContext(node, program) {
4335
4382
  const fn = getEnclosingFunction(node);
4336
- if (!fn || !getFixableReactiveCall(node, program)) {
4383
+ if (!fn || !getReturnedReactiveOwnerCall(node, program)) {
4337
4384
  return false;
4338
4385
  }
4339
4386
  return (isAngularInjectionTokenFactoryFunction(fn, program) ||
@@ -4345,16 +4392,12 @@ function buildReactiveCallReplacement(outerUntrackedCall, reactiveCall, sourceCo
4345
4392
  outerUntrackedCall.parent.type !== AST_NODE_TYPES.ExpressionStatement) {
4346
4393
  return text;
4347
4394
  }
4348
- return dedent$1(text, reactiveCall.loc.start.column - outerUntrackedCall.parent.loc.start.column);
4395
+ return dedent(text, reactiveCall.loc.start.column - outerUntrackedCall.parent.loc.start.column);
4349
4396
  }
4350
4397
  const rule$8 = createUntrackedRule({
4351
4398
  create(context) {
4352
- const parserServices = ESLintUtils.getParserServices(context);
4353
- const checker = parserServices.program.getTypeChecker();
4354
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
4355
- const { sourceCode } = context;
4356
- const program = sourceCode.ast;
4357
- const getUntrackedLocalName = () => findUntrackedAlias(program);
4399
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
4400
+ const signalNodeMap = esTreeNodeToTSNodeMap;
4358
4401
  function isUntrackedUsedElsewhere(localName, excludeNode) {
4359
4402
  let found = false;
4360
4403
  walkAst(program, (node) => {
@@ -4375,18 +4418,18 @@ const rule$8 = createUntrackedRule({
4375
4418
  findEnclosingReactiveScope(node, program) ||
4376
4419
  findEnclosingReactiveScopeAfterAsyncBoundary(node, program) ||
4377
4420
  isAllowedImperativeAngularContext(node) ||
4378
- isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) ||
4421
+ isAllowedDeferredCallbackContext(node, checker, signalNodeMap) ||
4379
4422
  isAllowedLazyAngularFactoryContext(node, program)) {
4380
4423
  return;
4381
4424
  }
4382
- const reactiveCall = getFixableReactiveCall(node, program);
4425
+ const reactiveCall = getReturnedReactiveOwnerCall(node, program);
4383
4426
  context.report({
4384
4427
  fix: reactiveCall
4385
4428
  ? (fixer) => {
4386
4429
  const fixes = [
4387
4430
  fixer.replaceText(node, buildReactiveCallReplacement(node, reactiveCall, sourceCode)),
4388
4431
  ];
4389
- const untrackedLocalName = getUntrackedLocalName();
4432
+ const untrackedLocalName = findUntrackedAlias(program);
4390
4433
  const stillUsed = untrackedLocalName !== null &&
4391
4434
  isUntrackedUsedElsewhere(untrackedLocalName, node);
4392
4435
  if (!stillUsed) {
@@ -4418,20 +4461,16 @@ const rule$8 = createUntrackedRule({
4418
4461
  name: 'no-untracked-outside-reactive-context',
4419
4462
  });
4420
4463
 
4421
- /**
4422
- * Removes `extraSpaces` leading spaces from every line of `text` that starts
4423
- * with at least that many spaces.
4424
- */
4425
- function dedent(text, extraSpaces) {
4426
- if (extraSpaces <= 0) {
4427
- return text;
4428
- }
4429
- const prefix = ' '.repeat(extraSpaces);
4430
- return text
4431
- .split('\n')
4432
- .map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
4433
- .join('\n');
4464
+ function collectCallExpressions(root) {
4465
+ const result = [];
4466
+ walkAst(root, (node) => {
4467
+ if (node.type === AST_NODE_TYPES.CallExpression) {
4468
+ result.push(node);
4469
+ }
4470
+ });
4471
+ return result;
4434
4472
  }
4473
+
4435
4474
  /**
4436
4475
  * Builds the replacement text for the parent ExpressionStatement of an
4437
4476
  * `untracked(...)` call.
@@ -4443,13 +4482,11 @@ function dedent(text, extraSpaces) {
4443
4482
  * Returns null if the untracked argument is not a function expression.
4444
4483
  */
4445
4484
  function buildReplacement(untrackedCall, parentStatement, sourceCode) {
4446
- const [arg] = untrackedCall.arguments;
4447
- if (!arg ||
4448
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4449
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4485
+ const callback = getReactiveCallbackArgument(untrackedCall);
4486
+ if (!callback) {
4450
4487
  return null;
4451
4488
  }
4452
- const { body } = arg;
4489
+ const { body } = callback;
4453
4490
  if (body.type === AST_NODE_TYPES.BlockStatement) {
4454
4491
  const { body: stmts } = body;
4455
4492
  if (stmts.length === 0) {
@@ -4464,15 +4501,6 @@ function buildReplacement(untrackedCall, parentStatement, sourceCode) {
4464
4501
  // Expression body: arrow `() => expr` — just emit `expr;`
4465
4502
  return `${sourceCode.getText(body)};`;
4466
4503
  }
4467
- function getAllCallExpressions(root) {
4468
- const result = [];
4469
- walkAst(root, (node) => {
4470
- if (node.type === AST_NODE_TYPES.CallExpression) {
4471
- result.push(node);
4472
- }
4473
- });
4474
- return result;
4475
- }
4476
4504
  function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program) {
4477
4505
  let found = false;
4478
4506
  walkSynchronousAst(root, (node) => {
@@ -4496,27 +4524,21 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
4496
4524
  }
4497
4525
  const rule$7 = createUntrackedRule({
4498
4526
  create(context) {
4499
- const parserServices = ESLintUtils.getParserServices(context);
4500
- const checker = parserServices.program.getTypeChecker();
4501
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
4502
- const { sourceCode } = context;
4503
- const program = sourceCode.ast;
4504
- const getUntrackedLocalName = () => getLocalNameForImport(program, '@angular/core', 'untracked');
4527
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
4528
+ const signalNodeMap = esTreeNodeToTSNodeMap;
4505
4529
  function isUntrackedUsedElsewhere(localName, excludeNode) {
4506
- return getAllCallExpressions(program).some((n) => n !== excludeNode &&
4530
+ return collectCallExpressions(program).some((n) => n !== excludeNode &&
4507
4531
  n.callee.type === AST_NODE_TYPES.Identifier &&
4508
4532
  n.callee.name === localName);
4509
4533
  }
4510
4534
  function checkUntrackedCall(untrackedCall, kind) {
4511
- const [arg] = untrackedCall.arguments;
4512
- if (!arg ||
4513
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4514
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4535
+ const callback = getReactiveCallbackArgument(untrackedCall);
4536
+ if (!callback) {
4515
4537
  return;
4516
4538
  }
4517
- const { reads } = collectSignalUsages(arg, checker, esTreeNodeToTSNodeMap, program);
4539
+ const { reads } = collectSignalUsages(callback, checker, signalNodeMap, program);
4518
4540
  if (reads.length > 0 ||
4519
- hasOpaqueSynchronousCalls(arg, checker, esTreeNodeToTSNodeMap, program)) {
4541
+ hasOpaqueSynchronousCalls(callback, checker, signalNodeMap, program)) {
4520
4542
  // Snapshot reads inside reactive callbacks are a valid Angular
4521
4543
  // pattern even when the snapshot later influences branching.
4522
4544
  return;
@@ -4534,7 +4556,7 @@ const rule$7 = createUntrackedRule({
4534
4556
  if (replacement === null) {
4535
4557
  return null;
4536
4558
  }
4537
- const untrackedLocalName = getUntrackedLocalName();
4559
+ const untrackedLocalName = findUntrackedAlias(program);
4538
4560
  const stillUsed = untrackedLocalName !== null &&
4539
4561
  isUntrackedUsedElsewhere(untrackedLocalName, untrackedCall);
4540
4562
  const fixes = [fixer.replaceText(parentStmt, replacement)];
@@ -4837,22 +4859,6 @@ const rule$6 = createRule$6({
4837
4859
 
4838
4860
  const createRule$5 = ESLintUtils.RuleCreator((name) => name);
4839
4861
  const EMPTY_ARGUMENT = '__EMPTY_ARGUMENT__';
4840
- function getParenthesizedInner(node) {
4841
- const maybeNode = node;
4842
- if (maybeNode.type === 'ParenthesizedExpression') {
4843
- return maybeNode.expression ?? null;
4844
- }
4845
- return null;
4846
- }
4847
- function unwrapParenthesized(node) {
4848
- let current = node;
4849
- let inner = getParenthesizedInner(current);
4850
- while (inner) {
4851
- current = inner;
4852
- inner = getParenthesizedInner(current);
4853
- }
4854
- return current;
4855
- }
4856
4862
  function isSupportedControlFlowStatement(node) {
4857
4863
  return (node.type === AST_NODE_TYPES.BreakStatement ||
4858
4864
  node.type === AST_NODE_TYPES.ContinueStatement ||
@@ -5492,17 +5498,6 @@ function unwrapUsageExpression(node) {
5492
5498
  }
5493
5499
  return current;
5494
5500
  }
5495
- function isNodeInside(node, ancestor) {
5496
- for (let current = node; current; current = current.parent) {
5497
- if (current === ancestor) {
5498
- return true;
5499
- }
5500
- }
5501
- return false;
5502
- }
5503
- function isNodeInsideAny(node, ancestors) {
5504
- return ancestors.some((ancestor) => isNodeInside(node, ancestor));
5505
- }
5506
5501
  function isStatementPositionCall(node) {
5507
5502
  const usage = unwrapUsageExpression(node);
5508
5503
  if (usage.parent?.type === AST_NODE_TYPES.ExpressionStatement) {
@@ -5555,11 +5550,7 @@ function isAliasDeclarationIdentifier(node) {
5555
5550
  node.parent.id === node);
5556
5551
  }
5557
5552
  function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSNodeMap) {
5558
- const tsNode = esTreeNodeToTSNodeMap.get(alias);
5559
- if (!tsNode) {
5560
- return false;
5561
- }
5562
- const symbol = checker.getSymbolAtLocation(tsNode);
5553
+ const symbol = getSymbolAtNode(alias, checker, esTreeNodeToTSNodeMap);
5563
5554
  if (!symbol) {
5564
5555
  return false;
5565
5556
  }
@@ -5571,10 +5562,7 @@ function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSN
5571
5562
  isNodeInsideAny(node, consumers)) {
5572
5563
  return;
5573
5564
  }
5574
- const referenceTsNode = esTreeNodeToTSNodeMap.get(node);
5575
- const referenceSymbol = referenceTsNode
5576
- ? checker.getSymbolAtLocation(referenceTsNode)
5577
- : null;
5565
+ const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
5578
5566
  if (referenceSymbol !== symbol) {
5579
5567
  return;
5580
5568
  }
@@ -5601,11 +5589,7 @@ function resolveSignalReadAlias(expression, context, seen = new Set()) {
5601
5589
  if (unwrapped.type !== AST_NODE_TYPES.Identifier) {
5602
5590
  return null;
5603
5591
  }
5604
- const tsNode = context.esTreeNodeToTSNodeMap.get(unwrapped);
5605
- if (!tsNode) {
5606
- return null;
5607
- }
5608
- const symbol = context.checker.getSymbolAtLocation(tsNode);
5592
+ const symbol = getSymbolAtNode(unwrapped, context.checker, context.esTreeNodeToTSNodeMap);
5609
5593
  if (!symbol) {
5610
5594
  return null;
5611
5595
  }
@@ -5693,12 +5677,9 @@ function collectSuspiciousReads(scope, checker, esTreeNodeToTSNodeMap, tsNodeToE
5693
5677
  }
5694
5678
  const rule$3 = createUntrackedRule({
5695
5679
  create(context) {
5696
- const parserServices = ESLintUtils.getParserServices(context);
5697
- const checker = parserServices.program.getTypeChecker();
5698
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
5699
- const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
5700
- const { sourceCode } = context;
5701
- const program = sourceCode.ast;
5680
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
5681
+ const signalNodeMap = esTreeNodeToTSNodeMap;
5682
+ const estreeNodeMap = tsNodeToESTreeNodeMap;
5702
5683
  function buildFix(read) {
5703
5684
  const untrackedAlias = findUntrackedAlias(program);
5704
5685
  const alreadyHasUntracked = untrackedAlias !== null;
@@ -5714,13 +5695,13 @@ const rule$3 = createUntrackedRule({
5714
5695
  return {
5715
5696
  CallExpression(node) {
5716
5697
  for (const scope of getReactiveScopes(node, program)) {
5717
- const suspicious = collectSuspiciousReads(scope, checker, esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap, program);
5718
- const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, esTreeNodeToTSNodeMap, program);
5698
+ const suspicious = collectSuspiciousReads(scope, checker, signalNodeMap, estreeNodeMap, program);
5699
+ const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, signalNodeMap, program);
5719
5700
  const suspiciousReads = new Set(suspicious.map(({ read }) => read));
5720
5701
  for (const { aliases, consumers, read } of suspicious) {
5721
5702
  const hasTrackedDependency = consumers.some((consumer) => trackedReads.some((trackedRead) => !suspiciousReads.has(trackedRead) &&
5722
5703
  !isNodeInside(trackedRead, consumer)));
5723
- const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSNodeMap));
5704
+ const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker, signalNodeMap));
5724
5705
  if (!hasTrackedDependency || hasExternalAliasUsage) {
5725
5706
  continue;
5726
5707
  }
@@ -5752,7 +5733,7 @@ const rule$3 = createUntrackedRule({
5752
5733
 
5753
5734
  function getReturnedExpression(node) {
5754
5735
  if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
5755
- return unwrapExpression(node.body);
5736
+ return node.body;
5756
5737
  }
5757
5738
  if (node.body.body.length !== 1) {
5758
5739
  return null;
@@ -5761,17 +5742,16 @@ function getReturnedExpression(node) {
5761
5742
  if (statement?.type !== AST_NODE_TYPES.ReturnStatement || !statement.argument) {
5762
5743
  return null;
5763
5744
  }
5764
- return unwrapExpression(statement.argument);
5745
+ return statement.argument;
5765
5746
  }
5747
+
5766
5748
  function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
5767
- const [arg] = node.arguments;
5768
- if (!arg ||
5769
- arg.type === AST_NODE_TYPES.SpreadElement ||
5770
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
5771
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
5749
+ const callback = getReactiveCallbackArgument(node);
5750
+ if (!callback) {
5772
5751
  return null;
5773
5752
  }
5774
- const body = getReturnedExpression(arg);
5753
+ const returnedExpression = getReturnedExpression(callback);
5754
+ const body = returnedExpression ? unwrapExpression(returnedExpression) : null;
5775
5755
  if (body?.type !== AST_NODE_TYPES.CallExpression ||
5776
5756
  !isSignalReadCall(body, checker, esTreeNodeToTSNodeMap)) {
5777
5757
  return null;
@@ -5785,17 +5765,14 @@ function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
5785
5765
  }
5786
5766
  const rule$2 = createUntrackedRule({
5787
5767
  create(context) {
5788
- const parserServices = ESLintUtils.getParserServices(context);
5789
- const checker = parserServices.program.getTypeChecker();
5790
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
5791
- const { sourceCode } = context;
5792
- const program = sourceCode.ast;
5768
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
5769
+ const signalNodeMap = esTreeNodeToTSNodeMap;
5793
5770
  return {
5794
5771
  CallExpression(node) {
5795
5772
  if (!isAngularUntrackedCall(node, program)) {
5796
5773
  return;
5797
5774
  }
5798
- const getter = getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap);
5775
+ const getter = getWrappedSignalGetter(node, checker, signalNodeMap);
5799
5776
  if (!getter) {
5800
5777
  return;
5801
5778
  }
@@ -5822,13 +5799,6 @@ const rule$2 = createUntrackedRule({
5822
5799
  name: 'prefer-untracked-signal-getter',
5823
5800
  });
5824
5801
 
5825
- function getImportedName(spec) {
5826
- if (spec.imported.type === AST_NODE_TYPES.Identifier) {
5827
- return spec.imported.name;
5828
- }
5829
- return spec.imported.value;
5830
- }
5831
-
5832
5802
  function isImportsArrayProperty(property) {
5833
5803
  const isProperty = property?.type === AST_NODE_TYPES.Property;
5834
5804
  const hasIdentifierKey = property?.key.type === AST_NODE_TYPES.Identifier &&
@@ -5836,6 +5806,13 @@ function isImportsArrayProperty(property) {
5836
5806
  return isProperty && hasIdentifierKey && isArray(property.value);
5837
5807
  }
5838
5808
 
5809
+ function getImportedName(spec) {
5810
+ if (spec.imported.type === AST_NODE_TYPES.Identifier) {
5811
+ return spec.imported.name;
5812
+ }
5813
+ return spec.imported.value;
5814
+ }
5815
+
5839
5816
  const MESSAGE_ID = 'replaceTuiImport';
5840
5817
  const DEFAULT_DECORATORS = ['Component', 'Directive', 'NgModule', 'Pipe'];
5841
5818
  const DEFAULT_EXCEPTIONS = [
@@ -6246,14 +6223,15 @@ const plugin = {
6246
6223
  'class-property-naming': classPropertyNaming,
6247
6224
  'decorator-key-sort': config$3,
6248
6225
  'flat-exports': flatExports,
6249
- 'host-attributes-sort': rule$j,
6226
+ 'host-attributes-sort': rule$k,
6250
6227
  'html-logical-properties': config$2,
6251
- 'injection-token-description': rule$i,
6252
- 'no-deep-imports': rule$h,
6228
+ 'injection-token-description': rule$j,
6229
+ 'no-deep-imports': rule$i,
6253
6230
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
6254
- 'no-fully-untracked-effect': rule$g,
6231
+ 'no-fully-untracked-effect': rule$h,
6255
6232
  'no-href-with-router-link': config$1,
6256
- 'no-implicit-public': rule$f,
6233
+ 'no-implicit-public': rule$g,
6234
+ 'no-infinite-loop': rule$f,
6257
6235
  'no-legacy-peer-deps': rule$e,
6258
6236
  'no-playwright-empty-fill': rule$d,
6259
6237
  'no-project-as-in-ng-template': config,