@taiga-ui/eslint-plugin-experience-next 0.473.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 +52 -4
  2. package/index.d.ts +3 -0
  3. package/index.esm.js +724 -563
  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} +10 -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({
@@ -2718,6 +2762,9 @@ function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
2718
2762
  function isAngularEffectCall(node, program) {
2719
2763
  return isAngularCoreCall(node, program, 'effect');
2720
2764
  }
2765
+ function isAngularInjectCall(node, program) {
2766
+ return isAngularCoreCall(node, program, 'inject');
2767
+ }
2721
2768
  function isAngularUntrackedCall(node, program) {
2722
2769
  return isAngularCoreCall(node, program, 'untracked');
2723
2770
  }
@@ -2911,54 +2958,74 @@ function collectSignalUsages(scopeNode, checker, esTreeNodeToTSNodeMap, program)
2911
2958
  });
2912
2959
  return { reads, writes };
2913
2960
  }
2914
-
2915
- const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#reading-without-tracking-dependencies';
2916
- const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
2917
- const UNTRACKED_RULES_README_URL = 'https://github.com/taiga-family/taiga-ui/blob/main/projects/eslint-plugin-experience-next/README.md';
2918
- const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES_README_URL}#${name}`);
2919
-
2920
- /**
2921
- * Collects signal reads that appear inside `untracked(...)` callbacks within
2922
- * `root`, without descending into nested `untracked(...)` scopes.
2923
- */
2924
- function collectReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
2961
+ function collectSignalReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
2925
2962
  const reads = [];
2926
2963
  walkSynchronousAst(root, (node) => {
2927
2964
  if (node.type !== AST_NODE_TYPES.CallExpression ||
2928
2965
  !isAngularUntrackedCall(node, program)) {
2929
2966
  return;
2930
2967
  }
2931
- const [arg] = node.arguments;
2932
- if (!arg ||
2933
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
2934
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
2968
+ const callback = getReactiveCallbackArgument(node);
2969
+ if (!callback) {
2935
2970
  return false;
2936
2971
  }
2937
- walkSynchronousAst(arg, (inner) => {
2972
+ walkSynchronousAst(callback, (inner) => {
2938
2973
  if (inner.type === AST_NODE_TYPES.CallExpression &&
2939
2974
  isSignalReadCall(inner, checker, esTreeNodeToTSNodeMap)) {
2940
2975
  reads.push(inner);
2941
2976
  }
2942
2977
  });
2943
- return false; // Do not descend into nested untracked from the outer walk
2978
+ return false;
2944
2979
  });
2945
2980
  return reads;
2946
2981
  }
2947
- 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({
2948
3018
  create(context) {
2949
- const parserServices = ESLintUtils.getParserServices(context);
2950
- const checker = parserServices.program.getTypeChecker();
2951
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
2952
- const { sourceCode } = context;
2953
- const program = sourceCode.ast;
3019
+ const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
3020
+ const signalNodeMap = esTreeNodeToTSNodeMap;
2954
3021
  return {
2955
3022
  CallExpression(node) {
2956
3023
  for (const scope of getReactiveScopes(node, program)) {
2957
- const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, esTreeNodeToTSNodeMap, program);
3024
+ const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, signalNodeMap, program);
2958
3025
  if (trackedReads.length > 0) {
2959
3026
  continue;
2960
3027
  }
2961
- const untrackedReads = collectReadsInsideUntracked(scope.callback, checker, esTreeNodeToTSNodeMap, program);
3028
+ const untrackedReads = collectSignalReadsInsideUntracked(scope.callback, checker, signalNodeMap, program);
2962
3029
  if (untrackedReads.length === 0) {
2963
3030
  continue;
2964
3031
  }
@@ -3030,46 +3097,102 @@ const config$1 = {
3030
3097
  },
3031
3098
  };
3032
3099
 
3033
- const createRule$c = ESLintUtils.RuleCreator((name) => name);
3034
- const rule$f = createRule$c({
3035
- create(context) {
3036
- const checkImplicitPublic = (node) => {
3037
- const classRef = getClass(node);
3038
- if (!classRef ||
3039
- node.kind === 'constructor' ||
3040
- !!node?.accessibility ||
3041
- node.key?.type === AST_NODE_TYPES.PrivateIdentifier) {
3042
- return;
3043
- }
3044
- const name = node?.key?.name ||
3045
- node?.parameter?.name ||
3046
- (node?.key?.type === 'Identifier' ? node.key.name : 'member');
3047
- let range = node?.parameter?.range ??
3048
- node?.key?.range ??
3049
- node?.range ?? [0, 0];
3050
- if (node.kind === 'set' || node.kind === 'get') {
3051
- const [start, end] = node.key.range;
3052
- range = [start - node.kind.length - 1, end - node.kind.length - 1];
3053
- }
3054
- else if (node.kind === 'method' && node.key?.object?.name === 'Symbol') {
3055
- const [start, end] = range;
3056
- range = [start - 1, end - 1];
3057
- }
3058
- if (node.type === 'PropertyDefinition' && node.decorators?.length > 0) {
3059
- const [, end] = node.decorators[node.decorators.length - 1].range ?? [];
3060
- range = [end + 1, end + 2];
3061
- }
3062
- context.report({
3063
- data: {
3064
- kind: node.kind || 'property',
3065
- name,
3066
- },
3067
- fix: (fixer) => fixer.insertTextBeforeRange(range, ' public '),
3068
- messageId: 'implicitPublic',
3069
- node,
3070
- });
3071
- };
3072
- return {
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({
3158
+ create(context) {
3159
+ const checkImplicitPublic = (node) => {
3160
+ const classRef = getEnclosingClass(node);
3161
+ if (!classRef ||
3162
+ node.kind === 'constructor' ||
3163
+ !!node?.accessibility ||
3164
+ node.key?.type === AST_NODE_TYPES.PrivateIdentifier) {
3165
+ return;
3166
+ }
3167
+ const name = node?.key?.name ||
3168
+ node?.parameter?.name ||
3169
+ (node?.key?.type === 'Identifier' ? node.key.name : 'member');
3170
+ let range = node?.parameter?.range ??
3171
+ node?.key?.range ??
3172
+ node?.range ?? [0, 0];
3173
+ if (node.kind === 'set' || node.kind === 'get') {
3174
+ const [start, end] = node.key.range;
3175
+ range = [start - node.kind.length - 1, end - node.kind.length - 1];
3176
+ }
3177
+ else if (node.kind === 'method' && node.key?.object?.name === 'Symbol') {
3178
+ const [start, end] = range;
3179
+ range = [start - 1, end - 1];
3180
+ }
3181
+ if (node.type === 'PropertyDefinition' && node.decorators?.length > 0) {
3182
+ const [, end] = node.decorators[node.decorators.length - 1].range ?? [];
3183
+ range = [end + 1, end + 2];
3184
+ }
3185
+ context.report({
3186
+ data: {
3187
+ kind: node.kind || 'property',
3188
+ name,
3189
+ },
3190
+ fix: (fixer) => fixer.insertTextBeforeRange(range, ' public '),
3191
+ messageId: 'implicitPublic',
3192
+ node,
3193
+ });
3194
+ };
3195
+ return {
3073
3196
  MethodDefinition(node) {
3074
3197
  checkImplicitPublic(node);
3075
3198
  },
@@ -3092,16 +3215,64 @@ const rule$f = createRule$c({
3092
3215
  },
3093
3216
  name: 'explicit-public-member',
3094
3217
  });
3095
- function getClass(node) {
3096
- if (!node) {
3097
- return null;
3218
+
3219
+ function getParenthesizedInner(node) {
3220
+ const maybeNode = node;
3221
+ if (maybeNode.type === 'ParenthesizedExpression') {
3222
+ return maybeNode.expression ?? null;
3098
3223
  }
3099
- if (node.type === AST_NODE_TYPES.ClassDeclaration) {
3100
- 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);
3101
3232
  }
3102
- return getClass(node.parent);
3233
+ return current;
3103
3234
  }
3104
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
+
3105
3276
  const createRule$b = ESLintUtils.RuleCreator((name) => name);
3106
3277
  const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
3107
3278
  const rule$e = createRule$b({
@@ -3145,8 +3316,7 @@ const rule$e = createRule$b({
3145
3316
  const createRule$a = ESLintUtils.RuleCreator((name) => name);
3146
3317
  const rule$d = createRule$a({
3147
3318
  create(context) {
3148
- const services = ESLintUtils.getParserServices(context);
3149
- const checker = services.program.getTypeChecker();
3319
+ const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
3150
3320
  return {
3151
3321
  CallExpression(node) {
3152
3322
  const callee = node.callee;
@@ -3160,18 +3330,18 @@ const rule$d = createRule$a({
3160
3330
  return;
3161
3331
  }
3162
3332
  const [argument] = node.arguments;
3163
- if (!argument || !isEmptyString(argument)) {
3333
+ if (!argument || !isEmptyStaticString(argument)) {
3164
3334
  return;
3165
3335
  }
3166
3336
  const objectExpression = callee.object;
3167
- const tsNode = services.esTreeNodeToTSNodeMap.get(objectExpression);
3337
+ const tsNode = esTreeNodeToTSNodeMap.get(objectExpression);
3168
3338
  const type = checker.getTypeAtLocation(tsNode);
3169
3339
  if (!isPlaywrightLocator(type, checker)) {
3170
3340
  return;
3171
3341
  }
3172
3342
  context.report({
3173
3343
  fix(fixer) {
3174
- const objectText = context.sourceCode.getText(objectExpression);
3344
+ const objectText = sourceCode.getText(objectExpression);
3175
3345
  return fixer.replaceText(node, `${objectText}.clear()`);
3176
3346
  },
3177
3347
  messageId: 'useClear',
@@ -3191,13 +3361,6 @@ const rule$d = createRule$a({
3191
3361
  },
3192
3362
  name: 'no-playwright-empty-fill',
3193
3363
  });
3194
- function isEmptyString(node) {
3195
- return ((node.type === AST_NODE_TYPES$1.Literal && node.value === '') ||
3196
- (node.type === AST_NODE_TYPES$1.TemplateLiteral &&
3197
- node.expressions.length === 0 &&
3198
- node.quasis.length === 1 &&
3199
- node.quasis[0]?.value.cooked === ''));
3200
- }
3201
3364
  function isPlaywrightLocator(type, checker) {
3202
3365
  if (isPlaywrightLocatorType(type)) {
3203
3366
  return true;
@@ -3336,15 +3499,14 @@ function collectArrayExpressions(node) {
3336
3499
  }
3337
3500
  const rule$c = createRule$9({
3338
3501
  create(context) {
3339
- const parserServices = ESLintUtils.getParserServices(context);
3340
- const typeChecker = parserServices.program.getTypeChecker();
3502
+ const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
3341
3503
  const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
3342
3504
  function check(node, typeAnnotation, value) {
3343
3505
  if (!typeAnnotation || !value) {
3344
3506
  return;
3345
3507
  }
3346
- const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
3347
- const tsValueNode = parserServices.esTreeNodeToTSNodeMap.get(value);
3508
+ const tsNode = esTreeNodeToTSNodeMap.get(node);
3509
+ const tsValueNode = esTreeNodeToTSNodeMap.get(value);
3348
3510
  const declaredType = typeChecker.getTypeAtLocation(tsNode);
3349
3511
  const inferredType = typeChecker.getTypeAtLocation(tsValueNode);
3350
3512
  if (typeChecker.typeToString(declaredType) !==
@@ -3368,7 +3530,7 @@ const rule$c = createRule$9({
3368
3530
  if (ignoreTupleContextualTyping) {
3369
3531
  const arrayExpressions = collectArrayExpressions(value);
3370
3532
  for (const arrayExpression of arrayExpressions) {
3371
- const tsArrayNode = parserServices.esTreeNodeToTSNodeMap.get(arrayExpression);
3533
+ const tsArrayNode = esTreeNodeToTSNodeMap.get(arrayExpression);
3372
3534
  if (typeChecker.isTupleType(typeChecker.getTypeAtLocation(tsArrayNode))) {
3373
3535
  return;
3374
3536
  }
@@ -3462,11 +3624,6 @@ function unwrapExpression(expression) {
3462
3624
  return current;
3463
3625
  }
3464
3626
 
3465
- const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3466
- function isReactiveCallback(node) {
3467
- return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3468
- node?.type === AST_NODE_TYPES.FunctionExpression);
3469
- }
3470
3627
  function unwrapMutationTarget(node) {
3471
3628
  let current = node;
3472
3629
  while (current.type === AST_NODE_TYPES.TSAsExpression ||
@@ -3500,6 +3657,7 @@ function collectMutationTargets(node) {
3500
3657
  return [];
3501
3658
  }
3502
3659
  }
3660
+
3503
3661
  function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
3504
3662
  const tsNode = esTreeNodeToTSNodeMap.get(node);
3505
3663
  if (!tsNode) {
@@ -3507,7 +3665,12 @@ function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
3507
3665
  }
3508
3666
  return checker.getSymbolAtLocation(tsNode) ?? null;
3509
3667
  }
3510
- function isLocalIdentifier(node, context) {
3668
+
3669
+ const createRule$8 = ESLintUtils.RuleCreator((name) => name);
3670
+ function isFunctionLikeScope(node) {
3671
+ return !!node && isFunctionLike(node);
3672
+ }
3673
+ function isLocalIdentifier(node, context, localScopes) {
3511
3674
  const symbol = getSymbolAtNode(node, context.checker, context.esTreeNodeToTSNodeMap);
3512
3675
  if (!symbol) {
3513
3676
  return false;
@@ -3515,10 +3678,24 @@ function isLocalIdentifier(node, context) {
3515
3678
  return (symbol.declarations ?? []).some((declaration) => {
3516
3679
  const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
3517
3680
  return (!!estreeDeclaration &&
3518
- isNodeInsideSynchronousReactiveScope(estreeDeclaration, context.callback));
3681
+ isDeclaredInsideLocalScope(estreeDeclaration, localScopes));
3682
+ });
3683
+ }
3684
+ function isDeclaredInsideLocalScope(node, localScopes) {
3685
+ return localScopes.some((scope) => isNodeInsideFunctionScope(node, scope));
3686
+ }
3687
+ function isNodeInsideFunctionScope(node, scope) {
3688
+ let found = false;
3689
+ walkSynchronousAst(scope, (inner) => {
3690
+ if (inner !== node) {
3691
+ return;
3692
+ }
3693
+ found = true;
3694
+ return false;
3519
3695
  });
3696
+ return found;
3520
3697
  }
3521
- function isLocallyCreatedExpression(node, context) {
3698
+ function isLocallyCreatedExpression(node, context, localScopes) {
3522
3699
  const expression = unwrapExpression(node);
3523
3700
  switch (expression.type) {
3524
3701
  case AST_NODE_TYPES.ArrayExpression:
@@ -3526,87 +3703,245 @@ function isLocallyCreatedExpression(node, context) {
3526
3703
  case AST_NODE_TYPES.ObjectExpression:
3527
3704
  return true;
3528
3705
  case AST_NODE_TYPES.Identifier:
3529
- return isLocalIdentifier(expression, context);
3706
+ return isLocalIdentifier(expression, context, localScopes);
3530
3707
  case AST_NODE_TYPES.MemberExpression:
3531
- return isLocallyCreatedExpression(expression.object, context);
3708
+ return isLocallyCreatedExpression(expression.object, context, localScopes);
3532
3709
  default:
3533
3710
  return false;
3534
3711
  }
3535
3712
  }
3536
- function hasObservableMutationTarget(node, context) {
3713
+ function hasObservableMutationTarget(node, context, localScopes) {
3537
3714
  return collectMutationTargets(node).some((target) => {
3538
3715
  if (target.type === AST_NODE_TYPES.Identifier) {
3539
- return !isLocalIdentifier(target, context);
3716
+ return !isLocalIdentifier(target, context, localScopes);
3540
3717
  }
3541
- return !isLocallyCreatedExpression(target.object, context);
3718
+ return !isLocallyCreatedExpression(target.object, context, localScopes);
3542
3719
  });
3543
3720
  }
3544
- function reportSideEffect(node, inspectionContext, report) {
3721
+ function reportSideEffect(node, context, report) {
3545
3722
  const key = String(node.range);
3546
- if (inspectionContext.reported.has(key)) {
3723
+ if (context.reported.has(key)) {
3547
3724
  return;
3548
3725
  }
3549
- inspectionContext.reported.add(key);
3726
+ context.reported.add(key);
3550
3727
  report(node);
3551
3728
  }
3552
- function inspectComputedBody(root, inspectionContext, report) {
3729
+ function isDirectAngularSideEffectCall(node, context) {
3730
+ return (isWritableSignalWrite(node, context.checker, context.esTreeNodeToTSNodeMap) ||
3731
+ isAngularEffectCall(node, context.program) ||
3732
+ isAngularInjectCall(node, context.program));
3733
+ }
3734
+ function isInspectableFunctionContainer(node) {
3735
+ return (!!node &&
3736
+ (isFunctionLikeScope(node) ||
3737
+ node.type === AST_NODE_TYPES.MethodDefinition ||
3738
+ node.type === AST_NODE_TYPES.Property ||
3739
+ node.type === AST_NODE_TYPES.PropertyDefinition ||
3740
+ node.type === AST_NODE_TYPES.VariableDeclarator));
3741
+ }
3742
+ function resolveFunctionLikeFromContainer(node, context, seenSymbols = new Set()) {
3743
+ if (isFunctionLikeScope(node)) {
3744
+ return [node];
3745
+ }
3746
+ if ((node.type === AST_NODE_TYPES.MethodDefinition &&
3747
+ isFunctionLikeScope(node.value)) ||
3748
+ (node.type === AST_NODE_TYPES.Property && isFunctionLikeScope(node.value))) {
3749
+ return [node.value];
3750
+ }
3751
+ if (node.type === AST_NODE_TYPES.Property &&
3752
+ node.value.type === AST_NODE_TYPES.Identifier) {
3753
+ return resolveFunctionLikeFromIdentifier(node.value, context, seenSymbols);
3754
+ }
3755
+ if (node.type === AST_NODE_TYPES.PropertyDefinition &&
3756
+ node.value &&
3757
+ isFunctionLikeScope(node.value)) {
3758
+ return [node.value];
3759
+ }
3760
+ if (node.type === AST_NODE_TYPES.PropertyDefinition &&
3761
+ node.value?.type === AST_NODE_TYPES.Identifier) {
3762
+ return resolveFunctionLikeFromIdentifier(node.value, context, seenSymbols);
3763
+ }
3764
+ if (node.type === AST_NODE_TYPES.VariableDeclarator) {
3765
+ const { init } = node;
3766
+ if (init && isFunctionLikeScope(init)) {
3767
+ return [init];
3768
+ }
3769
+ if (init?.type === AST_NODE_TYPES.Identifier) {
3770
+ return resolveFunctionLikeFromIdentifier(init, context, seenSymbols);
3771
+ }
3772
+ }
3773
+ return [];
3774
+ }
3775
+ function resolveFunctionLikeFromIdentifier(node, context, seenSymbols = new Set()) {
3776
+ const symbol = getSymbolAtNode(node, context.checker, context.esTreeNodeToTSNodeMap);
3777
+ if (!symbol) {
3778
+ return [];
3779
+ }
3780
+ const symbolId = `${symbol.name}:${symbol.declarations?.[0]?.pos ?? -1}`;
3781
+ if (seenSymbols.has(symbolId)) {
3782
+ return [];
3783
+ }
3784
+ seenSymbols.add(symbolId);
3785
+ return (symbol.declarations ?? []).flatMap((declaration) => {
3786
+ const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
3787
+ if (!estreeDeclaration || !isInspectableFunctionContainer(estreeDeclaration)) {
3788
+ return [];
3789
+ }
3790
+ return resolveFunctionLikeFromContainer(estreeDeclaration, context, seenSymbols);
3791
+ });
3792
+ }
3793
+ function resolveCalledFunctions(node, context) {
3794
+ const resolved = new Map();
3795
+ const tsNode = context.esTreeNodeToTSNodeMap.get(node);
3796
+ const signature = tsNode ? context.checker.getResolvedSignature(tsNode) : undefined;
3797
+ const declarations = new Set();
3798
+ if (signature?.declaration) {
3799
+ declarations.add(signature.declaration);
3800
+ }
3801
+ const callee = unwrapExpression(node.callee);
3802
+ if (callee.type === AST_NODE_TYPES.Identifier ||
3803
+ callee.type === AST_NODE_TYPES.MemberExpression) {
3804
+ const symbol = getSymbolAtNode(callee, context.checker, context.esTreeNodeToTSNodeMap);
3805
+ for (const declaration of symbol?.declarations ?? []) {
3806
+ declarations.add(declaration);
3807
+ }
3808
+ }
3809
+ for (const declaration of declarations) {
3810
+ const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
3811
+ if (!estreeDeclaration || !isInspectableFunctionContainer(estreeDeclaration)) {
3812
+ continue;
3813
+ }
3814
+ for (const fn of resolveFunctionLikeFromContainer(estreeDeclaration, context)) {
3815
+ resolved.set(String(fn.range), fn);
3816
+ }
3817
+ }
3818
+ return [...resolved.values()];
3819
+ }
3820
+ function functionHasObservableSideEffects(root, context, localScopes, visitedFunctions) {
3821
+ let hasSideEffect = false;
3553
3822
  walkSynchronousAst(root, (node) => {
3554
3823
  if (node.type === AST_NODE_TYPES.CallExpression) {
3555
- if (isWritableSignalWrite(node, inspectionContext.checker, inspectionContext.esTreeNodeToTSNodeMap)) {
3556
- reportSideEffect(node, inspectionContext, report);
3824
+ if (isDirectAngularSideEffectCall(node, context)) {
3825
+ hasSideEffect = true;
3826
+ return false;
3827
+ }
3828
+ if (isAngularUntrackedCall(node, context.program)) {
3829
+ const callback = getReactiveCallbackArgument(node);
3830
+ if (callback &&
3831
+ functionHasObservableSideEffects(callback, context, [...localScopes, callback], visitedFunctions)) {
3832
+ hasSideEffect = true;
3833
+ }
3834
+ return false;
3557
3835
  }
3558
- if (isAngularUntrackedCall(node, inspectionContext.program)) {
3559
- const [arg] = node.arguments;
3560
- if (isReactiveCallback(arg)) {
3561
- inspectComputedBody(arg, inspectionContext, report);
3836
+ if (isReactiveCallback(node.callee)) {
3837
+ if (functionHasObservableSideEffects(node.callee, context, [...localScopes, node.callee], visitedFunctions)) {
3838
+ hasSideEffect = true;
3562
3839
  }
3563
3840
  return false;
3564
3841
  }
3565
- if (node.callee.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3566
- node.callee.type === AST_NODE_TYPES.FunctionExpression) {
3567
- inspectComputedBody(node.callee, inspectionContext, report);
3842
+ for (const calledFunction of resolveCalledFunctions(node, context)) {
3843
+ const key = String(calledFunction.range);
3844
+ if (visitedFunctions.has(key)) {
3845
+ continue;
3846
+ }
3847
+ visitedFunctions.add(key);
3848
+ const calledFunctionHasSideEffects = functionHasObservableSideEffects(calledFunction, context, [...localScopes, calledFunction], visitedFunctions);
3849
+ visitedFunctions.delete(key);
3850
+ if (!calledFunctionHasSideEffects) {
3851
+ continue;
3852
+ }
3853
+ hasSideEffect = true;
3568
3854
  return false;
3569
3855
  }
3570
3856
  }
3571
3857
  if (node.type === AST_NODE_TYPES.AssignmentExpression &&
3572
- hasObservableMutationTarget(node.left, inspectionContext)) {
3573
- reportSideEffect(node.left, inspectionContext, report);
3858
+ hasObservableMutationTarget(node.left, context, localScopes)) {
3859
+ hasSideEffect = true;
3860
+ return false;
3574
3861
  }
3575
3862
  if (node.type === AST_NODE_TYPES.UpdateExpression &&
3576
- hasObservableMutationTarget(node.argument, inspectionContext)) {
3577
- reportSideEffect(node.argument, inspectionContext, report);
3863
+ hasObservableMutationTarget(node.argument, context, localScopes)) {
3864
+ hasSideEffect = true;
3865
+ return false;
3578
3866
  }
3579
3867
  if (node.type === AST_NODE_TYPES.UnaryExpression &&
3580
3868
  node.operator === 'delete' &&
3581
- hasObservableMutationTarget(node.argument, inspectionContext)) {
3582
- reportSideEffect(node.argument, inspectionContext, report);
3869
+ hasObservableMutationTarget(node.argument, context, localScopes)) {
3870
+ hasSideEffect = true;
3871
+ return false;
3872
+ }
3873
+ return;
3874
+ });
3875
+ return hasSideEffect;
3876
+ }
3877
+ function inspectComputedBody(root, context, localScopes, visitedFunctions, report) {
3878
+ walkSynchronousAst(root, (node) => {
3879
+ if (node.type === AST_NODE_TYPES.CallExpression) {
3880
+ if (isDirectAngularSideEffectCall(node, context)) {
3881
+ reportSideEffect(node, context, report);
3882
+ return false;
3883
+ }
3884
+ if (isAngularUntrackedCall(node, context.program)) {
3885
+ const callback = getReactiveCallbackArgument(node);
3886
+ if (callback) {
3887
+ inspectComputedBody(callback, context, [...localScopes, callback], visitedFunctions, report);
3888
+ }
3889
+ return false;
3890
+ }
3891
+ if (isReactiveCallback(node.callee)) {
3892
+ inspectComputedBody(node.callee, context, [...localScopes, node.callee], visitedFunctions, report);
3893
+ return false;
3894
+ }
3895
+ for (const calledFunction of resolveCalledFunctions(node, context)) {
3896
+ const key = String(calledFunction.range);
3897
+ if (visitedFunctions.has(key)) {
3898
+ continue;
3899
+ }
3900
+ visitedFunctions.add(key);
3901
+ const calledFunctionHasSideEffects = functionHasObservableSideEffects(calledFunction, context, [...localScopes, calledFunction], visitedFunctions);
3902
+ visitedFunctions.delete(key);
3903
+ if (!calledFunctionHasSideEffects) {
3904
+ continue;
3905
+ }
3906
+ reportSideEffect(node, context, report);
3907
+ return false;
3908
+ }
3909
+ }
3910
+ if (node.type === AST_NODE_TYPES.AssignmentExpression &&
3911
+ hasObservableMutationTarget(node.left, context, localScopes)) {
3912
+ reportSideEffect(node.left, context, report);
3913
+ }
3914
+ if (node.type === AST_NODE_TYPES.UpdateExpression &&
3915
+ hasObservableMutationTarget(node.argument, context, localScopes)) {
3916
+ reportSideEffect(node.argument, context, report);
3917
+ }
3918
+ if (node.type === AST_NODE_TYPES.UnaryExpression &&
3919
+ node.operator === 'delete' &&
3920
+ hasObservableMutationTarget(node.argument, context, localScopes)) {
3921
+ reportSideEffect(node.argument, context, report);
3583
3922
  }
3584
3923
  return;
3585
3924
  });
3586
3925
  }
3587
3926
  const rule$b = createRule$8({
3588
3927
  create(context) {
3589
- const parserServices = ESLintUtils.getParserServices(context);
3590
- const checker = parserServices.program.getTypeChecker();
3591
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
3592
- const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
3593
- const { sourceCode } = context;
3594
- const program = sourceCode.ast;
3928
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
3929
+ const signalNodeMap = esTreeNodeToTSNodeMap;
3930
+ const estreeNodeMap = tsNodeToESTreeNodeMap;
3595
3931
  return {
3596
3932
  CallExpression(node) {
3597
3933
  for (const scope of getReactiveScopes(node, program)) {
3598
3934
  if (scope.kind !== 'computed()') {
3599
3935
  continue;
3600
3936
  }
3601
- const inspectionContext = {
3602
- callback: scope.callback,
3937
+ const analysisContext = {
3603
3938
  checker,
3604
- esTreeNodeToTSNodeMap,
3939
+ esTreeNodeToTSNodeMap: signalNodeMap,
3605
3940
  program,
3606
3941
  reported: new Set(),
3607
- tsNodeToESTreeNodeMap,
3942
+ tsNodeToESTreeNodeMap: estreeNodeMap,
3608
3943
  };
3609
- inspectComputedBody(scope.callback, inspectionContext, (reportNode) => {
3944
+ inspectComputedBody(scope.callback, analysisContext, [scope.callback], new Set([String(scope.callback.range)]), (reportNode) => {
3610
3945
  context.report({
3611
3946
  data: { expression: sourceCode.getText(reportNode) },
3612
3947
  messageId: 'sideEffectInComputed',
@@ -3633,11 +3968,8 @@ const rule$b = createRule$8({
3633
3968
 
3634
3969
  const rule$a = createUntrackedRule({
3635
3970
  create(context) {
3636
- const parserServices = ESLintUtils.getParserServices(context);
3637
- const checker = parserServices.program.getTypeChecker();
3638
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
3639
- const { sourceCode } = context;
3640
- const program = sourceCode.ast;
3971
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
3972
+ const signalNodeMap = esTreeNodeToTSNodeMap;
3641
3973
  return {
3642
3974
  CallExpression(node) {
3643
3975
  for (const scope of getReactiveScopes(node, program)) {
@@ -3645,7 +3977,7 @@ const rule$a = createUntrackedRule({
3645
3977
  walkAfterAsyncBoundaryAst(scope.callback, (inner) => {
3646
3978
  if (inner.type !== AST_NODE_TYPES.CallExpression ||
3647
3979
  isAngularUntrackedCall(inner, program) ||
3648
- !isSignalReadCall(inner, checker, esTreeNodeToTSNodeMap)) {
3980
+ !isSignalReadCall(inner, checker, signalNodeMap)) {
3649
3981
  return;
3650
3982
  }
3651
3983
  const key = String(inner.range);
@@ -3681,10 +4013,6 @@ const rule$a = createUntrackedRule({
3681
4013
  });
3682
4014
 
3683
4015
  const createRule$7 = ESLintUtils.RuleCreator((name) => name);
3684
- function isStringLiteral(node) {
3685
- return (node.type === AST_NODE_TYPES.Literal &&
3686
- typeof node.value === 'string');
3687
- }
3688
4016
  function collectParts(node) {
3689
4017
  if (node.type === AST_NODE_TYPES.BinaryExpression && node.operator === '+') {
3690
4018
  return [...collectParts(node.left), ...collectParts(node.right)];
@@ -3734,16 +4062,6 @@ function templateContent(template, renderExpr) {
3734
4062
  : ''}`)
3735
4063
  .join('');
3736
4064
  }
3737
- function hasTemplateLiteralAncestor(node) {
3738
- let current = node.parent;
3739
- while (current != null) {
3740
- if (current.type === AST_NODE_TYPES.TemplateLiteral) {
3741
- return true;
3742
- }
3743
- current = current.parent;
3744
- }
3745
- return false;
3746
- }
3747
4065
  const rule$9 = createRule$7({
3748
4066
  create(context) {
3749
4067
  const { sourceCode } = context;
@@ -3799,7 +4117,7 @@ const rule$9 = createRule$7({
3799
4117
  }
3800
4118
  // Nested inside a template but not direct child — would produce
3801
4119
  // `${`${a}${b}`.method()}`, so skip
3802
- if (hasTemplateLiteralAncestor(node)) {
4120
+ if (hasAncestor(node, (ancestor) => ancestor.type === AST_NODE_TYPES.TemplateLiteral)) {
3803
4121
  return;
3804
4122
  }
3805
4123
  context.report({
@@ -3913,67 +4231,6 @@ function buildImportRemovalFixes(program, fixer, sourceCode) {
3913
4231
  return [fixer.remove(untrackedSpec)];
3914
4232
  }
3915
4233
 
3916
- const IMPERATIVE_UNTRACKED_METHODS = new Set(['registerOnChange', 'writeValue']);
3917
- function dedent$1(text, extraSpaces) {
3918
- if (extraSpaces <= 0) {
3919
- return text;
3920
- }
3921
- const prefix = ' '.repeat(extraSpaces);
3922
- return text
3923
- .split('\n')
3924
- .map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
3925
- .join('\n');
3926
- }
3927
- function isFunctionLike(node) {
3928
- return (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
3929
- node.type === AST_NODE_TYPES.FunctionDeclaration ||
3930
- node.type === AST_NODE_TYPES.FunctionExpression);
3931
- }
3932
- function getObjectPropertyName(node) {
3933
- if (node.computed) {
3934
- return null;
3935
- }
3936
- if (node.key.type === AST_NODE_TYPES.Identifier) {
3937
- return node.key.name;
3938
- }
3939
- return typeof node.key.value === 'string' ? node.key.value : null;
3940
- }
3941
- function getMemberExpressionPropertyName(node) {
3942
- if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
3943
- return node.property.name;
3944
- }
3945
- return node.property.type === AST_NODE_TYPES.Literal &&
3946
- typeof node.property.value === 'string'
3947
- ? node.property.value
3948
- : null;
3949
- }
3950
- function getEnclosingClassMember(node) {
3951
- for (let current = node.parent; current; current = current.parent) {
3952
- if (current.type === AST_NODE_TYPES.MethodDefinition ||
3953
- current.type === AST_NODE_TYPES.PropertyDefinition) {
3954
- return current;
3955
- }
3956
- }
3957
- return null;
3958
- }
3959
- function getEnclosingFunction(node) {
3960
- for (let current = node.parent; current; current = current.parent) {
3961
- if (isFunctionLike(current)) {
3962
- return current;
3963
- }
3964
- }
3965
- return null;
3966
- }
3967
- function getClassMemberName(member) {
3968
- if (member.key.type === AST_NODE_TYPES.Identifier) {
3969
- return member.key.name;
3970
- }
3971
- if (member.key.type === AST_NODE_TYPES.Literal &&
3972
- typeof member.key.value === 'string') {
3973
- return member.key.value;
3974
- }
3975
- return null;
3976
- }
3977
4234
  function hasNamedDecorator(node, name) {
3978
4235
  return node.decorators.some((decorator) => {
3979
4236
  const expression = decorator.expression;
@@ -3985,68 +4242,75 @@ function hasNamedDecorator(node, name) {
3985
4242
  expression.callee.name === name);
3986
4243
  });
3987
4244
  }
4245
+
3988
4246
  function isPipeTransformMember(member) {
3989
4247
  if (getClassMemberName(member) !== 'transform') {
3990
4248
  return false;
3991
4249
  }
3992
- return hasNamedDecorator(member.parent.parent, 'Pipe');
4250
+ const ownerClass = getEnclosingClass(member);
4251
+ return !!ownerClass && hasNamedDecorator(ownerClass, 'Pipe');
3993
4252
  }
3994
- function hasAllowedImperativeAssignment(node) {
3995
- for (let current = node.parent; current; current = current.parent) {
3996
- if (!isFunctionLike(current)) {
3997
- continue;
3998
- }
3999
- const parent = current.parent;
4000
- if (parent.type === AST_NODE_TYPES.AssignmentExpression &&
4001
- parent.right === current &&
4002
- parent.left.type === AST_NODE_TYPES.MemberExpression) {
4003
- const memberName = getMemberExpressionPropertyName(parent.left);
4004
- if (memberName && IMPERATIVE_UNTRACKED_METHODS.has(memberName)) {
4005
- return true;
4006
- }
4007
- }
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;
4008
4261
  }
4009
- return false;
4010
- }
4011
- function isAllowedImperativeAngularContext(node) {
4012
- const member = getEnclosingClassMember(node);
4013
- const memberName = member ? getClassMemberName(member) : null;
4014
- return ((!!member &&
4015
- !!memberName &&
4016
- (IMPERATIVE_UNTRACKED_METHODS.has(memberName) ||
4017
- isPipeTransformMember(member))) ||
4018
- 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);
4019
4268
  }
4020
- function isDirectCallbackArgument(fn) {
4269
+ function isAngularUseFactoryFunction(fn) {
4021
4270
  const parent = fn.parent;
4022
- if (fn.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4023
- fn.type !== AST_NODE_TYPES.FunctionExpression) {
4271
+ if (parent.type !== AST_NODE_TYPES.Property ||
4272
+ getObjectPropertyName(parent) !== 'useFactory') {
4024
4273
  return false;
4025
4274
  }
4026
- return ((parent.type === AST_NODE_TYPES.CallExpression ||
4027
- parent.type === AST_NODE_TYPES.NewExpression) &&
4028
- 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'));
4029
4279
  }
4030
- function getScopeRoot(node) {
4031
- for (let current = node.parent; current; current = current.parent) {
4032
- if (current.type === AST_NODE_TYPES.Program || isFunctionLike(current)) {
4033
- return current;
4034
- }
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;
4288
+ }
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;
4035
4301
  }
4036
- return node;
4302
+ return ((parent.type === AST_NODE_TYPES.CallExpression ||
4303
+ parent.type === AST_NODE_TYPES.NewExpression) &&
4304
+ parent.arguments.includes(node));
4037
4305
  }
4038
- function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4306
+ function isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap) {
4039
4307
  const parent = fn.parent;
4040
4308
  if (parent.type !== AST_NODE_TYPES.VariableDeclarator ||
4041
4309
  parent.id.type !== AST_NODE_TYPES.Identifier) {
4042
4310
  return false;
4043
4311
  }
4044
4312
  const id = parent.id;
4045
- const tsNode = esTreeNodeToTSNodeMap.get(id);
4046
- if (!tsNode) {
4047
- return false;
4048
- }
4049
- const symbol = checker.getSymbolAtLocation(tsNode);
4313
+ const symbol = getSymbolAtNode(id, checker, esTreeNodeToTSNodeMap);
4050
4314
  if (!symbol) {
4051
4315
  return false;
4052
4316
  }
@@ -4058,10 +4322,7 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4058
4322
  node.name !== id.name) {
4059
4323
  return;
4060
4324
  }
4061
- const referenceTsNode = esTreeNodeToTSNodeMap.get(node);
4062
- const referenceSymbol = referenceTsNode
4063
- ? checker.getSymbolAtLocation(referenceTsNode)
4064
- : null;
4325
+ const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
4065
4326
  if (referenceSymbol !== symbol) {
4066
4327
  return;
4067
4328
  }
@@ -4076,81 +4337,50 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
4076
4337
  });
4077
4338
  return found;
4078
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
+ }
4079
4370
  function isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) {
4080
- const [arg] = node.arguments;
4081
- if (!arg ||
4082
- arg.type === AST_NODE_TYPES.SpreadElement ||
4083
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4084
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4371
+ if (!getReactiveCallbackArgument(node)) {
4085
4372
  return false;
4086
4373
  }
4087
4374
  const fn = getEnclosingFunction(node);
4088
4375
  if (!fn) {
4089
4376
  return false;
4090
4377
  }
4091
- return (isDirectCallbackArgument(fn) ||
4092
- isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap));
4093
- }
4094
- function isAngularInjectionTokenFactoryFunction(fn, program) {
4095
- const parent = fn.parent;
4096
- const injectionTokenName = getLocalNameForImport(program, '@angular/core', 'InjectionToken');
4097
- if (!injectionTokenName ||
4098
- parent.type !== AST_NODE_TYPES.Property ||
4099
- getObjectPropertyName(parent) !== 'factory') {
4100
- return false;
4101
- }
4102
- const objectExpression = parent.parent;
4103
- return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4104
- objectExpression.parent.type === AST_NODE_TYPES.NewExpression &&
4105
- objectExpression.parent.arguments.includes(objectExpression) &&
4106
- objectExpression.parent.callee.type === AST_NODE_TYPES.Identifier &&
4107
- objectExpression.parent.callee.name === injectionTokenName);
4108
- }
4109
- function isAngularUseFactoryFunction(fn) {
4110
- const parent = fn.parent;
4111
- if (parent.type !== AST_NODE_TYPES.Property ||
4112
- getObjectPropertyName(parent) !== 'useFactory') {
4113
- return false;
4114
- }
4115
- const objectExpression = parent.parent;
4116
- return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
4117
- objectExpression.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
4118
- getObjectPropertyName(property) === 'provide'));
4119
- }
4120
- function isReactiveOwnerCall(node, program) {
4121
- return (node.type === AST_NODE_TYPES.CallExpression &&
4122
- getReactiveScopes(node, program).length > 0);
4123
- }
4124
- function getFixableReactiveCall(node, program) {
4125
- const [arg] = node.arguments;
4126
- if (!arg ||
4127
- arg.type === AST_NODE_TYPES.SpreadElement ||
4128
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4129
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4130
- return null;
4131
- }
4132
- if (isReactiveOwnerCall(arg.body, program)) {
4133
- return arg.body;
4134
- }
4135
- if (arg.body.type !== AST_NODE_TYPES.BlockStatement || arg.body.body.length !== 1) {
4136
- return null;
4137
- }
4138
- const [statement] = arg.body.body;
4139
- if (statement?.type === AST_NODE_TYPES.ReturnStatement &&
4140
- statement.argument &&
4141
- isReactiveOwnerCall(statement.argument, program)) {
4142
- return statement.argument;
4143
- }
4144
- if (node.parent.type === AST_NODE_TYPES.ExpressionStatement &&
4145
- statement?.type === AST_NODE_TYPES.ExpressionStatement &&
4146
- isReactiveOwnerCall(statement.expression, program)) {
4147
- return statement.expression;
4148
- }
4149
- return null;
4378
+ return (isDirectCallOrNewArgument(fn) ||
4379
+ isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap));
4150
4380
  }
4151
4381
  function isAllowedLazyAngularFactoryContext(node, program) {
4152
4382
  const fn = getEnclosingFunction(node);
4153
- if (!fn || !getFixableReactiveCall(node, program)) {
4383
+ if (!fn || !getReturnedReactiveOwnerCall(node, program)) {
4154
4384
  return false;
4155
4385
  }
4156
4386
  return (isAngularInjectionTokenFactoryFunction(fn, program) ||
@@ -4162,16 +4392,12 @@ function buildReactiveCallReplacement(outerUntrackedCall, reactiveCall, sourceCo
4162
4392
  outerUntrackedCall.parent.type !== AST_NODE_TYPES.ExpressionStatement) {
4163
4393
  return text;
4164
4394
  }
4165
- 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);
4166
4396
  }
4167
4397
  const rule$8 = createUntrackedRule({
4168
4398
  create(context) {
4169
- const parserServices = ESLintUtils.getParserServices(context);
4170
- const checker = parserServices.program.getTypeChecker();
4171
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
4172
- const { sourceCode } = context;
4173
- const program = sourceCode.ast;
4174
- const getUntrackedLocalName = () => findUntrackedAlias(program);
4399
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
4400
+ const signalNodeMap = esTreeNodeToTSNodeMap;
4175
4401
  function isUntrackedUsedElsewhere(localName, excludeNode) {
4176
4402
  let found = false;
4177
4403
  walkAst(program, (node) => {
@@ -4192,18 +4418,18 @@ const rule$8 = createUntrackedRule({
4192
4418
  findEnclosingReactiveScope(node, program) ||
4193
4419
  findEnclosingReactiveScopeAfterAsyncBoundary(node, program) ||
4194
4420
  isAllowedImperativeAngularContext(node) ||
4195
- isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) ||
4421
+ isAllowedDeferredCallbackContext(node, checker, signalNodeMap) ||
4196
4422
  isAllowedLazyAngularFactoryContext(node, program)) {
4197
4423
  return;
4198
4424
  }
4199
- const reactiveCall = getFixableReactiveCall(node, program);
4425
+ const reactiveCall = getReturnedReactiveOwnerCall(node, program);
4200
4426
  context.report({
4201
4427
  fix: reactiveCall
4202
4428
  ? (fixer) => {
4203
4429
  const fixes = [
4204
4430
  fixer.replaceText(node, buildReactiveCallReplacement(node, reactiveCall, sourceCode)),
4205
4431
  ];
4206
- const untrackedLocalName = getUntrackedLocalName();
4432
+ const untrackedLocalName = findUntrackedAlias(program);
4207
4433
  const stillUsed = untrackedLocalName !== null &&
4208
4434
  isUntrackedUsedElsewhere(untrackedLocalName, node);
4209
4435
  if (!stillUsed) {
@@ -4235,20 +4461,16 @@ const rule$8 = createUntrackedRule({
4235
4461
  name: 'no-untracked-outside-reactive-context',
4236
4462
  });
4237
4463
 
4238
- /**
4239
- * Removes `extraSpaces` leading spaces from every line of `text` that starts
4240
- * with at least that many spaces.
4241
- */
4242
- function dedent(text, extraSpaces) {
4243
- if (extraSpaces <= 0) {
4244
- return text;
4245
- }
4246
- const prefix = ' '.repeat(extraSpaces);
4247
- return text
4248
- .split('\n')
4249
- .map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
4250
- .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;
4251
4472
  }
4473
+
4252
4474
  /**
4253
4475
  * Builds the replacement text for the parent ExpressionStatement of an
4254
4476
  * `untracked(...)` call.
@@ -4260,13 +4482,11 @@ function dedent(text, extraSpaces) {
4260
4482
  * Returns null if the untracked argument is not a function expression.
4261
4483
  */
4262
4484
  function buildReplacement(untrackedCall, parentStatement, sourceCode) {
4263
- const [arg] = untrackedCall.arguments;
4264
- if (!arg ||
4265
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4266
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4485
+ const callback = getReactiveCallbackArgument(untrackedCall);
4486
+ if (!callback) {
4267
4487
  return null;
4268
4488
  }
4269
- const { body } = arg;
4489
+ const { body } = callback;
4270
4490
  if (body.type === AST_NODE_TYPES.BlockStatement) {
4271
4491
  const { body: stmts } = body;
4272
4492
  if (stmts.length === 0) {
@@ -4281,15 +4501,6 @@ function buildReplacement(untrackedCall, parentStatement, sourceCode) {
4281
4501
  // Expression body: arrow `() => expr` — just emit `expr;`
4282
4502
  return `${sourceCode.getText(body)};`;
4283
4503
  }
4284
- function getAllCallExpressions(root) {
4285
- const result = [];
4286
- walkAst(root, (node) => {
4287
- if (node.type === AST_NODE_TYPES.CallExpression) {
4288
- result.push(node);
4289
- }
4290
- });
4291
- return result;
4292
- }
4293
4504
  function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program) {
4294
4505
  let found = false;
4295
4506
  walkSynchronousAst(root, (node) => {
@@ -4313,27 +4524,21 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
4313
4524
  }
4314
4525
  const rule$7 = createUntrackedRule({
4315
4526
  create(context) {
4316
- const parserServices = ESLintUtils.getParserServices(context);
4317
- const checker = parserServices.program.getTypeChecker();
4318
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
4319
- const { sourceCode } = context;
4320
- const program = sourceCode.ast;
4321
- const getUntrackedLocalName = () => getLocalNameForImport(program, '@angular/core', 'untracked');
4527
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
4528
+ const signalNodeMap = esTreeNodeToTSNodeMap;
4322
4529
  function isUntrackedUsedElsewhere(localName, excludeNode) {
4323
- return getAllCallExpressions(program).some((n) => n !== excludeNode &&
4530
+ return collectCallExpressions(program).some((n) => n !== excludeNode &&
4324
4531
  n.callee.type === AST_NODE_TYPES.Identifier &&
4325
4532
  n.callee.name === localName);
4326
4533
  }
4327
4534
  function checkUntrackedCall(untrackedCall, kind) {
4328
- const [arg] = untrackedCall.arguments;
4329
- if (!arg ||
4330
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
4331
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
4535
+ const callback = getReactiveCallbackArgument(untrackedCall);
4536
+ if (!callback) {
4332
4537
  return;
4333
4538
  }
4334
- const { reads } = collectSignalUsages(arg, checker, esTreeNodeToTSNodeMap, program);
4539
+ const { reads } = collectSignalUsages(callback, checker, signalNodeMap, program);
4335
4540
  if (reads.length > 0 ||
4336
- hasOpaqueSynchronousCalls(arg, checker, esTreeNodeToTSNodeMap, program)) {
4541
+ hasOpaqueSynchronousCalls(callback, checker, signalNodeMap, program)) {
4337
4542
  // Snapshot reads inside reactive callbacks are a valid Angular
4338
4543
  // pattern even when the snapshot later influences branching.
4339
4544
  return;
@@ -4351,7 +4556,7 @@ const rule$7 = createUntrackedRule({
4351
4556
  if (replacement === null) {
4352
4557
  return null;
4353
4558
  }
4354
- const untrackedLocalName = getUntrackedLocalName();
4559
+ const untrackedLocalName = findUntrackedAlias(program);
4355
4560
  const stillUsed = untrackedLocalName !== null &&
4356
4561
  isUntrackedUsedElsewhere(untrackedLocalName, untrackedCall);
4357
4562
  const fixes = [fixer.replaceText(parentStmt, replacement)];
@@ -4654,22 +4859,6 @@ const rule$6 = createRule$6({
4654
4859
 
4655
4860
  const createRule$5 = ESLintUtils.RuleCreator((name) => name);
4656
4861
  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
4862
  function isSupportedControlFlowStatement(node) {
4674
4863
  return (node.type === AST_NODE_TYPES.BreakStatement ||
4675
4864
  node.type === AST_NODE_TYPES.ContinueStatement ||
@@ -5309,17 +5498,6 @@ function unwrapUsageExpression(node) {
5309
5498
  }
5310
5499
  return current;
5311
5500
  }
5312
- function isNodeInside(node, ancestor) {
5313
- for (let current = node; current; current = current.parent) {
5314
- if (current === ancestor) {
5315
- return true;
5316
- }
5317
- }
5318
- return false;
5319
- }
5320
- function isNodeInsideAny(node, ancestors) {
5321
- return ancestors.some((ancestor) => isNodeInside(node, ancestor));
5322
- }
5323
5501
  function isStatementPositionCall(node) {
5324
5502
  const usage = unwrapUsageExpression(node);
5325
5503
  if (usage.parent?.type === AST_NODE_TYPES.ExpressionStatement) {
@@ -5372,11 +5550,7 @@ function isAliasDeclarationIdentifier(node) {
5372
5550
  node.parent.id === node);
5373
5551
  }
5374
5552
  function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSNodeMap) {
5375
- const tsNode = esTreeNodeToTSNodeMap.get(alias);
5376
- if (!tsNode) {
5377
- return false;
5378
- }
5379
- const symbol = checker.getSymbolAtLocation(tsNode);
5553
+ const symbol = getSymbolAtNode(alias, checker, esTreeNodeToTSNodeMap);
5380
5554
  if (!symbol) {
5381
5555
  return false;
5382
5556
  }
@@ -5388,10 +5562,7 @@ function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSN
5388
5562
  isNodeInsideAny(node, consumers)) {
5389
5563
  return;
5390
5564
  }
5391
- const referenceTsNode = esTreeNodeToTSNodeMap.get(node);
5392
- const referenceSymbol = referenceTsNode
5393
- ? checker.getSymbolAtLocation(referenceTsNode)
5394
- : null;
5565
+ const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
5395
5566
  if (referenceSymbol !== symbol) {
5396
5567
  return;
5397
5568
  }
@@ -5418,11 +5589,7 @@ function resolveSignalReadAlias(expression, context, seen = new Set()) {
5418
5589
  if (unwrapped.type !== AST_NODE_TYPES.Identifier) {
5419
5590
  return null;
5420
5591
  }
5421
- const tsNode = context.esTreeNodeToTSNodeMap.get(unwrapped);
5422
- if (!tsNode) {
5423
- return null;
5424
- }
5425
- const symbol = context.checker.getSymbolAtLocation(tsNode);
5592
+ const symbol = getSymbolAtNode(unwrapped, context.checker, context.esTreeNodeToTSNodeMap);
5426
5593
  if (!symbol) {
5427
5594
  return null;
5428
5595
  }
@@ -5510,12 +5677,9 @@ function collectSuspiciousReads(scope, checker, esTreeNodeToTSNodeMap, tsNodeToE
5510
5677
  }
5511
5678
  const rule$3 = createUntrackedRule({
5512
5679
  create(context) {
5513
- const parserServices = ESLintUtils.getParserServices(context);
5514
- const checker = parserServices.program.getTypeChecker();
5515
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
5516
- const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
5517
- const { sourceCode } = context;
5518
- const program = sourceCode.ast;
5680
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
5681
+ const signalNodeMap = esTreeNodeToTSNodeMap;
5682
+ const estreeNodeMap = tsNodeToESTreeNodeMap;
5519
5683
  function buildFix(read) {
5520
5684
  const untrackedAlias = findUntrackedAlias(program);
5521
5685
  const alreadyHasUntracked = untrackedAlias !== null;
@@ -5531,13 +5695,13 @@ const rule$3 = createUntrackedRule({
5531
5695
  return {
5532
5696
  CallExpression(node) {
5533
5697
  for (const scope of getReactiveScopes(node, program)) {
5534
- const suspicious = collectSuspiciousReads(scope, checker, esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap, program);
5535
- 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);
5536
5700
  const suspiciousReads = new Set(suspicious.map(({ read }) => read));
5537
5701
  for (const { aliases, consumers, read } of suspicious) {
5538
5702
  const hasTrackedDependency = consumers.some((consumer) => trackedReads.some((trackedRead) => !suspiciousReads.has(trackedRead) &&
5539
5703
  !isNodeInside(trackedRead, consumer)));
5540
- const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSNodeMap));
5704
+ const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker, signalNodeMap));
5541
5705
  if (!hasTrackedDependency || hasExternalAliasUsage) {
5542
5706
  continue;
5543
5707
  }
@@ -5569,7 +5733,7 @@ const rule$3 = createUntrackedRule({
5569
5733
 
5570
5734
  function getReturnedExpression(node) {
5571
5735
  if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
5572
- return unwrapExpression(node.body);
5736
+ return node.body;
5573
5737
  }
5574
5738
  if (node.body.body.length !== 1) {
5575
5739
  return null;
@@ -5578,17 +5742,16 @@ function getReturnedExpression(node) {
5578
5742
  if (statement?.type !== AST_NODE_TYPES.ReturnStatement || !statement.argument) {
5579
5743
  return null;
5580
5744
  }
5581
- return unwrapExpression(statement.argument);
5745
+ return statement.argument;
5582
5746
  }
5747
+
5583
5748
  function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
5584
- const [arg] = node.arguments;
5585
- if (!arg ||
5586
- arg.type === AST_NODE_TYPES.SpreadElement ||
5587
- (arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
5588
- arg.type !== AST_NODE_TYPES.FunctionExpression)) {
5749
+ const callback = getReactiveCallbackArgument(node);
5750
+ if (!callback) {
5589
5751
  return null;
5590
5752
  }
5591
- const body = getReturnedExpression(arg);
5753
+ const returnedExpression = getReturnedExpression(callback);
5754
+ const body = returnedExpression ? unwrapExpression(returnedExpression) : null;
5592
5755
  if (body?.type !== AST_NODE_TYPES.CallExpression ||
5593
5756
  !isSignalReadCall(body, checker, esTreeNodeToTSNodeMap)) {
5594
5757
  return null;
@@ -5602,17 +5765,14 @@ function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
5602
5765
  }
5603
5766
  const rule$2 = createUntrackedRule({
5604
5767
  create(context) {
5605
- const parserServices = ESLintUtils.getParserServices(context);
5606
- const checker = parserServices.program.getTypeChecker();
5607
- const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
5608
- const { sourceCode } = context;
5609
- const program = sourceCode.ast;
5768
+ const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
5769
+ const signalNodeMap = esTreeNodeToTSNodeMap;
5610
5770
  return {
5611
5771
  CallExpression(node) {
5612
5772
  if (!isAngularUntrackedCall(node, program)) {
5613
5773
  return;
5614
5774
  }
5615
- const getter = getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap);
5775
+ const getter = getWrappedSignalGetter(node, checker, signalNodeMap);
5616
5776
  if (!getter) {
5617
5777
  return;
5618
5778
  }
@@ -5639,13 +5799,6 @@ const rule$2 = createUntrackedRule({
5639
5799
  name: 'prefer-untracked-signal-getter',
5640
5800
  });
5641
5801
 
5642
- function getImportedName(spec) {
5643
- if (spec.imported.type === AST_NODE_TYPES.Identifier) {
5644
- return spec.imported.name;
5645
- }
5646
- return spec.imported.value;
5647
- }
5648
-
5649
5802
  function isImportsArrayProperty(property) {
5650
5803
  const isProperty = property?.type === AST_NODE_TYPES.Property;
5651
5804
  const hasIdentifierKey = property?.key.type === AST_NODE_TYPES.Identifier &&
@@ -5653,6 +5806,13 @@ function isImportsArrayProperty(property) {
5653
5806
  return isProperty && hasIdentifierKey && isArray(property.value);
5654
5807
  }
5655
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
+
5656
5816
  const MESSAGE_ID = 'replaceTuiImport';
5657
5817
  const DEFAULT_DECORATORS = ['Component', 'Directive', 'NgModule', 'Pipe'];
5658
5818
  const DEFAULT_EXCEPTIONS = [
@@ -6063,14 +6223,15 @@ const plugin = {
6063
6223
  'class-property-naming': classPropertyNaming,
6064
6224
  'decorator-key-sort': config$3,
6065
6225
  'flat-exports': flatExports,
6066
- 'host-attributes-sort': rule$j,
6226
+ 'host-attributes-sort': rule$k,
6067
6227
  'html-logical-properties': config$2,
6068
- 'injection-token-description': rule$i,
6069
- 'no-deep-imports': rule$h,
6228
+ 'injection-token-description': rule$j,
6229
+ 'no-deep-imports': rule$i,
6070
6230
  'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
6071
- 'no-fully-untracked-effect': rule$g,
6231
+ 'no-fully-untracked-effect': rule$h,
6072
6232
  'no-href-with-router-link': config$1,
6073
- 'no-implicit-public': rule$f,
6233
+ 'no-implicit-public': rule$g,
6234
+ 'no-infinite-loop': rule$f,
6074
6235
  'no-legacy-peer-deps': rule$e,
6075
6236
  'no-playwright-empty-fill': rule$d,
6076
6237
  'no-project-as-in-ng-template': config,