@benjavicente/lint-angular 0.0.3 → 0.0.5

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 (3) hide show
  1. package/README.md +26 -19
  2. package/dist/index.mjs +884 -131
  3. package/package.json +12 -12
package/dist/index.mjs CHANGED
@@ -55,13 +55,13 @@ function unwrapExpression(node) {
55
55
  }
56
56
  //#endregion
57
57
  //#region src/utilities/scope.ts
58
- const FUNCTION_TYPES$1 = new Set([
58
+ const FUNCTION_TYPES$2 = new Set([
59
59
  "ArrowFunctionExpression",
60
60
  "FunctionDeclaration",
61
61
  "FunctionExpression"
62
62
  ]);
63
63
  function isFunction$1(node) {
64
- return !!node && FUNCTION_TYPES$1.has(node.type);
64
+ return !!node && FUNCTION_TYPES$2.has(node.type);
65
65
  }
66
66
  function addBindingIdentifierNodes(node, identifiers) {
67
67
  if (!node) return;
@@ -222,7 +222,7 @@ const ANGULAR_CLASS_DECORATOR_NAMES$1 = new Set([
222
222
  ]);
223
223
  const INPUT_MODEL_CALL_NAMES = new Set(["input", "model"]);
224
224
  const OUTPUT_CALL_NAMES = new Set(["output", "outputFromObservable"]);
225
- const CLASS_FIELD_TYPES$1 = new Set([
225
+ const CLASS_FIELD_TYPES$2 = new Set([
226
226
  "AccessorProperty",
227
227
  "FieldDefinition",
228
228
  "PropertyDefinition"
@@ -254,7 +254,7 @@ function hasDecorator(context, element, decoratorNames) {
254
254
  return Array.isArray(element.decorators) ? element.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, decoratorNames)) : false;
255
255
  }
256
256
  function classifyMember(context, element) {
257
- if (CLASS_FIELD_TYPES$1.has(element.type)) {
257
+ if (CLASS_FIELD_TYPES$2.has(element.type)) {
258
258
  if (isApiCall(context, element.value, new Set(["inject"]))) return 0;
259
259
  if (isApiCall(context, element.value, INPUT_MODEL_CALL_NAMES)) return 1;
260
260
  if (hasDecorator(context, element, new Set(["Input"]))) return 1;
@@ -265,7 +265,7 @@ function classifyMember(context, element) {
265
265
  if (element.type === "MethodDefinition") return 3;
266
266
  return 3;
267
267
  }
268
- function getMemberName$1(node) {
268
+ function getMemberName$2(node) {
269
269
  if (!node) return null;
270
270
  if (node.type === "Identifier" || node.type === "PrivateIdentifier") return node.name;
271
271
  return null;
@@ -335,7 +335,7 @@ function getSortedMembersFix(context, classBody, classifiedMembers) {
335
335
  for (const member of classifiedMembers) if (member.name) membersByName.set(member.name, member);
336
336
  for (const member of classifiedMembers) {
337
337
  const { element } = member;
338
- if (!CLASS_FIELD_TYPES$1.has(element.type)) return void 0;
338
+ if (!CLASS_FIELD_TYPES$2.has(element.type)) return void 0;
339
339
  if (element.type === "AccessorProperty") return void 0;
340
340
  if (element.computed) return void 0;
341
341
  if (Array.isArray(element.decorators) && element.decorators.length > 0) return void 0;
@@ -388,7 +388,7 @@ const classMemberOrder = defineRule({
388
388
  element,
389
389
  group,
390
390
  effectiveGroup: group,
391
- name: getMemberName$1(element.key),
391
+ name: getMemberName$2(element.key),
392
392
  dependencies: collectThisMemberReferences(element.value)
393
393
  });
394
394
  }
@@ -423,17 +423,17 @@ const classMemberOrder = defineRule({
423
423
  const DEFAULT_DISALLOW_INJECT_INJECTOR = true;
424
424
  const DEFAULT_DISALLOW_RUN_IN_INJECTION_CONTEXT = true;
425
425
  const RUN_IN_INJECTION_CONTEXT_NAMES = new Set(["runInInjectionContext", "runInContext"]);
426
- const INJECT_NAMES = new Set(["inject"]);
426
+ const INJECT_NAMES$2 = new Set(["inject"]);
427
427
  const INJECTOR_NAMES = new Set(["Injector"]);
428
- function isInjectCall(context, callNode) {
428
+ function isInjectCall$1(context, callNode) {
429
429
  const callee = callNode.callee;
430
- return isImportedReference(context, callee, "@angular/core", INJECT_NAMES) || isImportedNamespaceMember(context, callee, "@angular/core", INJECT_NAMES);
430
+ return isImportedReference(context, callee, "@angular/core", INJECT_NAMES$2) || isImportedNamespaceMember(context, callee, "@angular/core", INJECT_NAMES$2);
431
431
  }
432
432
  function isInjectorReference(context, node) {
433
433
  return isImportedReference(context, node, "@angular/core", INJECTOR_NAMES) || isImportedNamespaceMember(context, node, "@angular/core", INJECTOR_NAMES);
434
434
  }
435
435
  function isDisallowedInjectInjector(context, callNode) {
436
- if (!isInjectCall(context, callNode)) return false;
436
+ if (!isInjectCall$1(context, callNode)) return false;
437
437
  return isInjectorReference(context, callNode.arguments?.[0]);
438
438
  }
439
439
  function isDisallowedRunInInjectionContext(context, callNode) {
@@ -485,12 +485,12 @@ const avoidExplicitInjectionContext = defineRule({
485
485
  });
486
486
  //#endregion
487
487
  //#region src/rules/avoid-explicit-subscription-management/index.ts
488
- const TARGET_DECORATORS$2 = new Set([
488
+ const TARGET_DECORATORS$4 = new Set([
489
489
  "Component",
490
490
  "Directive",
491
491
  "Injectable"
492
492
  ]);
493
- const FIELD_NODE_TYPES$2 = new Set([
493
+ const FIELD_NODE_TYPES$3 = new Set([
494
494
  "AccessorProperty",
495
495
  "FieldDefinition",
496
496
  "PropertyDefinition"
@@ -502,9 +502,9 @@ const RXJS_SUBSCRIBABLE_NAMES = new Set([
502
502
  "ReplaySubject",
503
503
  "Subject"
504
504
  ]);
505
- function hasTargetDecorator$3(context, classNode) {
505
+ function hasTargetDecorator$4(context, classNode) {
506
506
  if (!classNode || !Array.isArray(classNode.decorators)) return false;
507
- return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$2));
507
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$4));
508
508
  }
509
509
  function hasSubscriptionTypeReference(context, node, subscriptionLocalNames, rxjsNamespaces) {
510
510
  if (!node) return false;
@@ -644,10 +644,10 @@ function isUnmanagedSubscribeCall(context, node, rxjsSubscribableReferences, rxj
644
644
  if (getPropertyName(callee.property) !== "subscribe") return false;
645
645
  return isKnownRxjsSubscribableExpression(context, callee.object, rxjsSubscribableReferences, rxjsSubscribableLocalNames, rxjsNamespaces) && !isTakeUntilDestroyedSubscribe(node, takeUntilDestroyedLocalNames, interopNamespaces);
646
646
  }
647
- function walkNode$1(node, containingClass, visitor) {
647
+ function walkNode$2(node, containingClass, visitor) {
648
648
  if (!node) return;
649
649
  if (Array.isArray(node)) {
650
- for (const child of node) walkNode$1(child, containingClass, visitor);
650
+ for (const child of node) walkNode$2(child, containingClass, visitor);
651
651
  return;
652
652
  }
653
653
  if (node !== containingClass && (node.type === "ClassDeclaration" || node.type === "ClassExpression")) return;
@@ -655,12 +655,12 @@ function walkNode$1(node, containingClass, visitor) {
655
655
  for (const [key, value] of Object.entries(node)) {
656
656
  if (key === "parent") continue;
657
657
  if (!value || typeof value !== "object") continue;
658
- walkNode$1(value, containingClass, visitor);
658
+ walkNode$2(value, containingClass, visitor);
659
659
  }
660
660
  }
661
661
  function containsUnmanagedSubscribeCall(context, node, containingClass, rxjsSubscribableReferences, rxjsSubscribableLocalNames, rxjsNamespaces, takeUntilDestroyedLocalNames, interopNamespaces) {
662
662
  let found = false;
663
- walkNode$1(node, containingClass, (current) => {
663
+ walkNode$2(node, containingClass, (current) => {
664
664
  if (isUnmanagedSubscribeCall(context, current, rxjsSubscribableReferences, rxjsSubscribableLocalNames, rxjsNamespaces, takeUntilDestroyedLocalNames, interopNamespaces)) {
665
665
  found = true;
666
666
  return false;
@@ -673,8 +673,8 @@ function collectRxjsSubscribableReferences(context, classBody, classNode, rxjsSu
673
673
  let changed = true;
674
674
  while (changed) {
675
675
  changed = false;
676
- walkNode$1(classBody, classNode, (node) => {
677
- if (FIELD_NODE_TYPES$2.has(node.type)) {
676
+ walkNode$2(classBody, classNode, (node) => {
677
+ if (FIELD_NODE_TYPES$3.has(node.type)) {
678
678
  const name = getDeclaredName(node.key);
679
679
  if (!name || hasTrackedReferenceName(references, name)) return;
680
680
  if (isObservableReferenceName(name) || hasRxjsSubscribableTypeReference(context, node.typeAnnotation?.typeAnnotation, rxjsSubscribableLocalNames, rxjsNamespaces) || isRxjsSubscribableConstructor(context, node.value, rxjsSubscribableLocalNames, rxjsNamespaces) || isKnownRxjsSubscribableExpression(context, node.value, references, rxjsSubscribableLocalNames, rxjsNamespaces)) {
@@ -705,8 +705,8 @@ function collectRxjsSubscribableReferences(context, classBody, classNode, rxjsSu
705
705
  }
706
706
  function collectSubscriptionReferences(context, classBody, classNode, subscriptionLocalNames, rxjsNamespaces, rxjsSubscribableReferences, rxjsSubscribableLocalNames, takeUntilDestroyedLocalNames, interopNamespaces) {
707
707
  const references = /* @__PURE__ */ new Map();
708
- walkNode$1(classBody, classNode, (node) => {
709
- if (FIELD_NODE_TYPES$2.has(node.type)) {
708
+ walkNode$2(classBody, classNode, (node) => {
709
+ if (FIELD_NODE_TYPES$3.has(node.type)) {
710
710
  const name = getDeclaredName(node.key);
711
711
  if (!name) return;
712
712
  if (hasSubscriptionTypeReference(context, node.typeAnnotation?.typeAnnotation, subscriptionLocalNames, rxjsNamespaces) || isSubscriptionConstructor(context, node.value, subscriptionLocalNames, rxjsNamespaces)) addTrackedReference(references, name, node.key);
@@ -793,11 +793,11 @@ const avoidExplicitSubscriptionManagement = defineRule({
793
793
  ClassBody(node) {
794
794
  const classBody = node;
795
795
  const classNode = classBody.parent;
796
- if (!classNode || !hasTargetDecorator$3(context, classNode)) return;
796
+ if (!classNode || !hasTargetDecorator$4(context, classNode)) return;
797
797
  const rxjsSubscribableReferences = collectRxjsSubscribableReferences(context, classBody, classNode, rxjsSubscribableLocalNames, rxjsNamespaces);
798
798
  const subscriptionReferences = collectSubscriptionReferences(context, classBody, classNode, subscriptionLocalNames, rxjsNamespaces, rxjsSubscribableReferences, rxjsSubscribableLocalNames, takeUntilDestroyedLocalNames, interopNamespaces);
799
- walkNode$1(classBody, classNode, (current) => {
800
- if (FIELD_NODE_TYPES$2.has(current.type)) {
799
+ walkNode$2(classBody, classNode, (current) => {
800
+ if (FIELD_NODE_TYPES$3.has(current.type)) {
801
801
  if (hasSubscriptionTypeReference(context, current.typeAnnotation?.typeAnnotation, subscriptionLocalNames, rxjsNamespaces)) context.report({
802
802
  node: current.key ?? current,
803
803
  messageId: "explicitSubscriptionType"
@@ -845,6 +845,46 @@ const avoidExplicitSubscriptionManagement = defineRule({
845
845
  }
846
846
  });
847
847
  //#endregion
848
+ //#region src/rules/avoid-inappropriate-intimacy/index.ts
849
+ const TARGET_DECORATORS$3 = new Set([
850
+ "Component",
851
+ "Directive",
852
+ "Injectable",
853
+ "Service"
854
+ ]);
855
+ function getAngularClassKind(context, classNode) {
856
+ if (!classNode || !Array.isArray(classNode.decorators)) return null;
857
+ if (classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, new Set(["Component"])))) return "component";
858
+ if (classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, new Set(["Directive"])))) return "directive";
859
+ if (classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$3))) return "service";
860
+ return null;
861
+ }
862
+ const avoidInappropriateIntimacy = defineRule({
863
+ meta: {
864
+ type: "problem",
865
+ docs: {
866
+ description: "Disallow passing Angular component, directive, and service instances as function arguments.",
867
+ recommended: true
868
+ },
869
+ schema: [],
870
+ messages: { avoidThisArgument: "Avoid passing this {{kind}} instance as an argument. Pass the specific values or callbacks the callee needs." }
871
+ },
872
+ createOnce(context) {
873
+ return { CallExpression(node) {
874
+ const callNode = node;
875
+ const thisArguments = (callNode.arguments ?? []).filter((argument) => argument.type === "ThisExpression");
876
+ if (!thisArguments.length) return;
877
+ const kind = getAngularClassKind(context, context.sourceCode.getAncestors(callNode).findLast((ancestor) => ancestor.type === "ClassDeclaration" || ancestor.type === "ClassExpression"));
878
+ if (!kind) return;
879
+ for (const argument of thisArguments) context.report({
880
+ node: argument,
881
+ messageId: "avoidThisArgument",
882
+ data: { kind }
883
+ });
884
+ } };
885
+ }
886
+ });
887
+ //#endregion
848
888
  //#region src/rules/avoid-ng-modules/index.ts
849
889
  const DEFAULT_ALLOW_FOR_GROUPING = true;
850
890
  const DEFAULT_ALLOW_FOR_PROVIDING = false;
@@ -1022,20 +1062,20 @@ const avoidNgModules = defineRule({
1022
1062
  });
1023
1063
  //#endregion
1024
1064
  //#region src/rules/avoid-rxjs-state-in-component/index.ts
1025
- const TARGET_DECORATORS$1 = new Set(["Component", "Directive"]);
1065
+ const TARGET_DECORATORS$2 = new Set(["Component", "Directive"]);
1026
1066
  const SUBJECT_NAMES = new Set([
1027
1067
  "BehaviorSubject",
1028
1068
  "ReplaySubject",
1029
1069
  "Subject"
1030
1070
  ]);
1031
- const FIELD_NODE_TYPES$1 = new Set([
1071
+ const FIELD_NODE_TYPES$2 = new Set([
1032
1072
  "AccessorProperty",
1033
1073
  "FieldDefinition",
1034
1074
  "PropertyDefinition"
1035
1075
  ]);
1036
- function hasTargetDecorator$2(context, classNode) {
1076
+ function hasTargetDecorator$3(context, classNode) {
1037
1077
  if (!classNode || !Array.isArray(classNode.decorators)) return false;
1038
- return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$1));
1078
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$2));
1039
1079
  }
1040
1080
  function getImportedSubjectKind(context, node) {
1041
1081
  const importedName = getImportedName(context, node, "rxjs");
@@ -1061,7 +1101,7 @@ function getSubjectKindFromConstructor(context, node) {
1061
1101
  const memberName = getPropertyName(callee.property);
1062
1102
  return SUBJECT_NAMES.has(memberName) ? memberName : null;
1063
1103
  }
1064
- function getMemberName(node) {
1104
+ function getMemberName$1(node) {
1065
1105
  const expression = unwrapExpression(node);
1066
1106
  if (!expression) return null;
1067
1107
  if (expression.type === "Identifier") return expression.name;
@@ -1091,17 +1131,17 @@ function isCallOnThisField(node, fields) {
1091
1131
  function isNgOnDestroyMethod(member) {
1092
1132
  return member.type === "MethodDefinition" && getPropertyName(member.key) === "ngOnDestroy" && !!member.value;
1093
1133
  }
1094
- function walkNode(node, visitor) {
1134
+ function walkNode$1(node, visitor) {
1095
1135
  if (!node) return;
1096
1136
  if (Array.isArray(node)) {
1097
- for (const child of node) walkNode(child, visitor);
1137
+ for (const child of node) walkNode$1(child, visitor);
1098
1138
  return;
1099
1139
  }
1100
1140
  visitor(node);
1101
1141
  for (const [key, value] of Object.entries(node)) {
1102
1142
  if (key === "parent") continue;
1103
1143
  if (!value || typeof value !== "object") continue;
1104
- walkNode(value, visitor);
1144
+ walkNode$1(value, visitor);
1105
1145
  }
1106
1146
  }
1107
1147
  function getUsage(usages, fieldName) {
@@ -1119,8 +1159,8 @@ function getUsage(usages, fieldName) {
1119
1159
  function collectSubjectFields(context, classBody) {
1120
1160
  const fields = /* @__PURE__ */ new Map();
1121
1161
  for (const member of classBody.body ?? []) {
1122
- if (!FIELD_NODE_TYPES$1.has(member.type)) continue;
1123
- const fieldName = getMemberName(member.key);
1162
+ if (!FIELD_NODE_TYPES$2.has(member.type)) continue;
1163
+ const fieldName = getMemberName$1(member.key);
1124
1164
  if (!fieldName) continue;
1125
1165
  const typeNode = member.typeAnnotation?.typeAnnotation;
1126
1166
  const kind = getSubjectKindFromConstructor(context, member.value) ?? getSubjectKindFromType(context, typeNode);
@@ -1136,7 +1176,7 @@ function collectFieldUsages(classBody, fields) {
1136
1176
  const usages = /* @__PURE__ */ new Map();
1137
1177
  const ngOnDestroyMethods = /* @__PURE__ */ new Set();
1138
1178
  for (const member of classBody.body ?? []) if (isNgOnDestroyMethod(member)) ngOnDestroyMethods.add(member.value);
1139
- walkNode(classBody, (node) => {
1179
+ walkNode$1(classBody, (node) => {
1140
1180
  const call = isCallOnThisField(node, fields);
1141
1181
  if (!call) return;
1142
1182
  const usage = getUsage(usages, call.fieldName);
@@ -1174,7 +1214,7 @@ const avoidRxjsStateInComponent = defineRule({
1174
1214
  return { ClassBody(node) {
1175
1215
  const classBody = node;
1176
1216
  const classNode = classBody.parent;
1177
- if (!hasTargetDecorator$2(context, classNode)) return;
1217
+ if (!hasTargetDecorator$3(context, classNode)) return;
1178
1218
  const fields = collectSubjectFields(context, classBody);
1179
1219
  const usages = collectFieldUsages(classBody, fields);
1180
1220
  for (const [fieldName, field] of fields) {
@@ -1417,7 +1457,7 @@ function getNodeName(node) {
1417
1457
  function getBaseFilename$1(filename) {
1418
1458
  return filename.split(/[/\\]/u).at(-1) ?? filename;
1419
1459
  }
1420
- function hasTargetDecorator$1(context, classNode, decoratorNames) {
1460
+ function hasTargetDecorator$2(context, classNode, decoratorNames) {
1421
1461
  if (!Array.isArray(classNode.decorators)) return false;
1422
1462
  return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, decoratorNames));
1423
1463
  }
@@ -1471,7 +1511,7 @@ const classMatchesFilename = defineRule({
1471
1511
  ClassDeclaration(node) {
1472
1512
  if (!fileMatcher) return;
1473
1513
  const classNode = node;
1474
- if (hasTargetDecorator$1(context, classNode, fileDecoratorNames)) decoratedClasses.push(classNode);
1514
+ if (hasTargetDecorator$2(context, classNode, fileDecoratorNames)) decoratedClasses.push(classNode);
1475
1515
  },
1476
1516
  after() {
1477
1517
  const filename = context.filename ?? "";
@@ -1682,6 +1722,284 @@ const decoratorFilenameSuffix = defineRule({
1682
1722
  }
1683
1723
  });
1684
1724
  //#endregion
1725
+ //#region src/rules/no-manual-change-detection/index.ts
1726
+ const ANGULAR_CORE_SOURCE$1 = "@angular/core";
1727
+ const CHANGE_DETECTOR_REF_NAMES = new Set(["ChangeDetectorRef"]);
1728
+ const INJECT_NAMES$1 = new Set(["inject"]);
1729
+ const MANUAL_CHANGE_DETECTION_METHODS = new Set([
1730
+ "checkNoChanges",
1731
+ "detach",
1732
+ "detectChanges",
1733
+ "markForCheck",
1734
+ "reattach"
1735
+ ]);
1736
+ const FIELD_NODE_TYPES$1 = new Set([
1737
+ "AccessorProperty",
1738
+ "FieldDefinition",
1739
+ "PropertyDefinition"
1740
+ ]);
1741
+ function isChangeDetectorRefReference(context, node) {
1742
+ return isImportedReference(context, node, ANGULAR_CORE_SOURCE$1, CHANGE_DETECTOR_REF_NAMES) || isImportedNamespaceMember(context, node, ANGULAR_CORE_SOURCE$1, CHANGE_DETECTOR_REF_NAMES);
1743
+ }
1744
+ function isInjectCall(context, node) {
1745
+ const expression = unwrapExpression(node);
1746
+ if (expression?.type !== "CallExpression") return false;
1747
+ return isImportedReference(context, expression.callee, ANGULAR_CORE_SOURCE$1, INJECT_NAMES$1) && isChangeDetectorRefReference(context, expression.arguments?.[0]);
1748
+ }
1749
+ function isChangeDetectorRefType(context, node) {
1750
+ if (!node) return false;
1751
+ if (node.type === "TSTypeReference") {
1752
+ if (isChangeDetectorRefReference(context, node.typeName)) return true;
1753
+ if (node.typeName?.type === "TSQualifiedName" && node.typeName.left?.type === "Identifier") return isNamespaceImport(context, node.typeName.left, ANGULAR_CORE_SOURCE$1) && getPropertyName(node.typeName.right) === "ChangeDetectorRef";
1754
+ }
1755
+ return false;
1756
+ }
1757
+ function getMemberName(node) {
1758
+ const expression = unwrapExpression(node);
1759
+ if (!expression) return null;
1760
+ if (expression.type === "Identifier") return expression.name;
1761
+ if (expression.type === "PrivateIdentifier") return expression.name;
1762
+ return null;
1763
+ }
1764
+ function getParameterName(node) {
1765
+ const parameter = node?.type === "TSParameterProperty" ? node.parameter : node;
1766
+ if (parameter?.type === "Identifier") return parameter.name;
1767
+ if (parameter?.type === "AssignmentPattern" && parameter.left?.type === "Identifier") return parameter.left.name;
1768
+ return null;
1769
+ }
1770
+ function getParameterType(node) {
1771
+ const parameter = node?.type === "TSParameterProperty" ? node.parameter : node;
1772
+ if (parameter?.type === "AssignmentPattern") return parameter.left?.typeAnnotation?.typeAnnotation ?? null;
1773
+ return parameter?.typeAnnotation?.typeAnnotation ?? null;
1774
+ }
1775
+ function getParameterBinding(node) {
1776
+ const parameter = node?.type === "TSParameterProperty" ? node.parameter : node;
1777
+ if (parameter?.type === "Identifier") return parameter;
1778
+ if (parameter?.type === "AssignmentPattern" && parameter.left?.type === "Identifier") return parameter.left;
1779
+ return null;
1780
+ }
1781
+ function collectChangeDetectorRefBindings(context, classNode) {
1782
+ const fields = /* @__PURE__ */ new Set();
1783
+ const parameters = /* @__PURE__ */ new Set();
1784
+ for (const member of classNode.body?.body ?? []) {
1785
+ if (member.type === "MethodDefinition" && getPropertyName(member.key) === "constructor") for (const parameter of member.value?.params ?? []) {
1786
+ const name = getParameterName(parameter);
1787
+ if (name && isChangeDetectorRefType(context, getParameterType(parameter))) {
1788
+ const binding = getParameterBinding(parameter);
1789
+ if (binding) parameters.add(binding);
1790
+ if (parameter.type === "TSParameterProperty") fields.add(name);
1791
+ }
1792
+ }
1793
+ if (!FIELD_NODE_TYPES$1.has(member.type)) continue;
1794
+ const name = getMemberName(member.key);
1795
+ if (!name) continue;
1796
+ const typeNode = member.typeAnnotation?.typeAnnotation;
1797
+ if (isChangeDetectorRefType(context, typeNode) || isInjectCall(context, member.value)) fields.add(name);
1798
+ }
1799
+ return {
1800
+ fields,
1801
+ parameters
1802
+ };
1803
+ }
1804
+ function getManualChangeDetectionCall(context, callNode, bindings) {
1805
+ const callee = unwrapExpression(callNode.callee);
1806
+ if (callee?.type !== "MemberExpression") return null;
1807
+ const methodName = getPropertyName(callee.property);
1808
+ if (!methodName || !MANUAL_CHANGE_DETECTION_METHODS.has(methodName)) return null;
1809
+ const object = unwrapExpression(callee.object);
1810
+ if (object?.type === "Identifier") {
1811
+ const binding = findNearestBindingIdentifier(context, object);
1812
+ return binding && bindings.parameters.has(binding) ? {
1813
+ methodName,
1814
+ node: callee.property ?? callee
1815
+ } : null;
1816
+ }
1817
+ if (object?.type !== "MemberExpression") return null;
1818
+ if (object.object?.type !== "ThisExpression") return null;
1819
+ const fieldName = getPropertyName(object.property);
1820
+ return fieldName && bindings.fields.has(fieldName) ? {
1821
+ methodName,
1822
+ node: callee.property ?? callee
1823
+ } : null;
1824
+ }
1825
+ function walkNode(node, visitor) {
1826
+ if (!node) return;
1827
+ if (Array.isArray(node)) {
1828
+ for (const child of node) walkNode(child, visitor);
1829
+ return;
1830
+ }
1831
+ visitor(node);
1832
+ for (const [key, value] of Object.entries(node)) {
1833
+ if (key === "parent") continue;
1834
+ if (!value || typeof value !== "object") continue;
1835
+ walkNode(value, visitor);
1836
+ }
1837
+ }
1838
+ const noManualChangeDetection = defineRule({
1839
+ meta: {
1840
+ type: "suggestion",
1841
+ docs: {
1842
+ description: "Disallow manual Angular change detection through ChangeDetectorRef APIs.",
1843
+ recommended: true
1844
+ },
1845
+ schema: [],
1846
+ messages: { noManualChangeDetection: "Avoid manual change detection with ChangeDetectorRef. Prefer Angular's normal change detection triggers, signals, async bindings, or input updates." }
1847
+ },
1848
+ createOnce(context) {
1849
+ return { ClassDeclaration(node) {
1850
+ const classNode = node;
1851
+ const changeDetectorRefBindings = collectChangeDetectorRefBindings(context, classNode);
1852
+ if (changeDetectorRefBindings.fields.size === 0 && changeDetectorRefBindings.parameters.size === 0) return;
1853
+ walkNode(classNode.body, (child) => {
1854
+ if (child.type !== "CallExpression") return;
1855
+ const manualCall = getManualChangeDetectionCall(context, child, changeDetectorRefBindings);
1856
+ if (!manualCall) return;
1857
+ context.report({
1858
+ node: manualCall.node,
1859
+ messageId: "noManualChangeDetection"
1860
+ });
1861
+ });
1862
+ } };
1863
+ }
1864
+ });
1865
+ //#endregion
1866
+ //#region src/rules/no-resource-api/index.ts
1867
+ const RESOURCE_APIS = new Set(["resource"]);
1868
+ const RXJS_RESOURCE_APIS = new Set(["rxResource"]);
1869
+ const HTTP_RESOURCE_APIS = new Set(["httpResource"]);
1870
+ function isResourceApiCall(context, callNode) {
1871
+ const callee = callNode.callee;
1872
+ if (isImportedReference(context, callee, "@angular/core", RESOURCE_APIS)) return true;
1873
+ if (isImportedNamespaceMember(context, callee, "@angular/core", RESOURCE_APIS)) return true;
1874
+ if (isImportedReference(context, callee, "@angular/core/rxjs-interop", RXJS_RESOURCE_APIS)) return true;
1875
+ if (isImportedNamespaceMember(context, callee, "@angular/core/rxjs-interop", RXJS_RESOURCE_APIS)) return true;
1876
+ if (isImportedReference(context, callee, "@angular/common/http", HTTP_RESOURCE_APIS)) return true;
1877
+ if (isImportedNamespaceMember(context, callee, "@angular/common/http", HTTP_RESOURCE_APIS)) return true;
1878
+ if (callee?.type === "MemberExpression" && callee.object?.type === "MemberExpression" && callee.object.object?.type === "Identifier" && isNamespaceImport(context, callee.object.object, "@angular/common/http") && HTTP_RESOURCE_APIS.has(getPropertyName(callee.object.property) ?? "")) return true;
1879
+ return callee?.type === "MemberExpression" && isImportedReference(context, callee.object, "@angular/common/http", HTTP_RESOURCE_APIS);
1880
+ }
1881
+ const noResourceApi = defineRule({
1882
+ meta: {
1883
+ type: "suggestion",
1884
+ docs: {
1885
+ description: "Disallow Angular resource APIs in favor of a dedicated server-state library.",
1886
+ recommended: true
1887
+ },
1888
+ schema: [],
1889
+ messages: { noResourceApi: "Avoid Angular resource APIs for server state. Prefer a dedicated server-state helper such as TanStack Query." }
1890
+ },
1891
+ createOnce(context) {
1892
+ return { CallExpression(node) {
1893
+ const callNode = node;
1894
+ if (!isResourceApiCall(context, callNode)) return;
1895
+ context.report({
1896
+ node: callNode.callee ?? callNode,
1897
+ messageId: "noResourceApi"
1898
+ });
1899
+ } };
1900
+ }
1901
+ });
1902
+ //#endregion
1903
+ //#region src/rules/no-route-resolvers/index.ts
1904
+ const ROUTE_TYPE_NAMES$1 = new Set(["Route"]);
1905
+ const ROUTES_TYPE_NAMES$1 = new Set(["Routes"]);
1906
+ function isImportedTypeName$1(context, typeNode, importedNames) {
1907
+ if (typeNode?.type === "Identifier") {
1908
+ const importedName = getImportedName(context, typeNode, "@angular/router");
1909
+ return !!importedName && importedNames.has(importedName);
1910
+ }
1911
+ return typeNode?.type === "TSQualifiedName" && typeNode.left?.type === "Identifier" && isNamespaceImport(context, typeNode.left, "@angular/router") && importedNames.has(getPropertyName(typeNode.right) ?? "");
1912
+ }
1913
+ function getTypeParameterNodes$1(typeNode) {
1914
+ return typeNode.typeParameters?.params ?? typeNode.typeArguments?.params ?? [];
1915
+ }
1916
+ function isRouteType$1(context, typeNode) {
1917
+ return typeNode?.type === "TSTypeReference" && isImportedTypeName$1(context, typeNode.typeName, ROUTE_TYPE_NAMES$1);
1918
+ }
1919
+ function isRouteArrayType$1(context, typeNode) {
1920
+ if (!typeNode) return false;
1921
+ if (typeNode.type === "TSTypeReference" && isImportedTypeName$1(context, typeNode.typeName, ROUTES_TYPE_NAMES$1)) return true;
1922
+ if (typeNode.type === "TSArrayType") return isRouteType$1(context, typeNode.elementType);
1923
+ if (typeNode.type !== "TSTypeReference") return false;
1924
+ if (getPropertyName(typeNode.typeName) !== "Array" && getPropertyName(typeNode.typeName) !== "ReadonlyArray") return false;
1925
+ const [elementType] = getTypeParameterNodes$1(typeNode);
1926
+ return isRouteType$1(context, elementType);
1927
+ }
1928
+ function getTypeAnnotation(node) {
1929
+ const typeAnnotation = node?.typeAnnotation;
1930
+ return typeAnnotation?.type === "TSTypeAnnotation" ? typeAnnotation.typeAnnotation : null;
1931
+ }
1932
+ const noRouteResolvers = defineRule({
1933
+ meta: {
1934
+ type: "suggestion",
1935
+ docs: {
1936
+ description: "Disallow Angular route resolvers in typed Route/Routes declarations.",
1937
+ recommended: true
1938
+ },
1939
+ schema: [],
1940
+ messages: { noRouteResolvers: "Avoid Angular route resolvers for data loading. Prefer component-level loading with signals or server-state helpers." }
1941
+ },
1942
+ createOnce(context) {
1943
+ function reportResolveInRouteObject(routeObject) {
1944
+ for (const property of routeObject.properties ?? []) {
1945
+ if (property.type !== "Property" || property.computed) continue;
1946
+ const propertyName = getPropertyName(property.key);
1947
+ if (propertyName === "resolve") {
1948
+ context.report({
1949
+ node: property.key ?? property,
1950
+ messageId: "noRouteResolvers"
1951
+ });
1952
+ continue;
1953
+ }
1954
+ if (propertyName === "children" && property.value?.type === "ArrayExpression") reportResolveInRouteArray(property.value);
1955
+ }
1956
+ }
1957
+ function reportResolveInRouteArray(routeArray) {
1958
+ for (const element of routeArray.elements ?? []) if (element?.type === "ObjectExpression") reportResolveInRouteObject(element);
1959
+ }
1960
+ return { VariableDeclarator(node) {
1961
+ const declarator = node;
1962
+ const typeNode = getTypeAnnotation(declarator.id);
1963
+ if (isRouteArrayType$1(context, typeNode) && declarator.init?.type === "ArrayExpression") {
1964
+ reportResolveInRouteArray(declarator.init);
1965
+ return;
1966
+ }
1967
+ if (isRouteType$1(context, typeNode) && declarator.init?.type === "ObjectExpression") reportResolveInRouteObject(declarator.init);
1968
+ } };
1969
+ }
1970
+ });
1971
+ //#endregion
1972
+ //#region src/rules/no-ui-inheritance/index.ts
1973
+ const UI_DECORATORS = new Set(["Component", "Directive"]);
1974
+ function hasUiDecorator(context, classNode) {
1975
+ if (!Array.isArray(classNode.decorators)) return false;
1976
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, UI_DECORATORS));
1977
+ }
1978
+ const noUiInheritance = defineRule({
1979
+ meta: {
1980
+ type: "suggestion",
1981
+ docs: {
1982
+ description: "Disallow inheritance for Angular components and directives.",
1983
+ recommended: true
1984
+ },
1985
+ schema: [],
1986
+ messages: { noUiInheritance: "Avoid inheritance for Angular {{kind}} classes. Prefer composition with services or inject* helpers." }
1987
+ },
1988
+ createOnce(context) {
1989
+ return { "ClassDeclaration, ClassExpression"(node) {
1990
+ const classNode = node;
1991
+ if (!classNode.superClass) return;
1992
+ if (!hasUiDecorator(context, classNode)) return;
1993
+ const kind = classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, new Set(["Component"]))) ? "component" : "directive";
1994
+ context.report({
1995
+ node: classNode.superClass,
1996
+ messageId: "noUiInheritance",
1997
+ data: { kind }
1998
+ });
1999
+ } };
2000
+ }
2001
+ });
2002
+ //#endregion
1685
2003
  //#region src/rules/prefer-load-component-over-load-children/index.ts
1686
2004
  const ROUTE_TYPE_NAMES = new Set(["Route"]);
1687
2005
  const ROUTES_TYPE_NAMES = new Set(["Routes"]);
@@ -1966,7 +2284,7 @@ const preferStyleUrl = defineRule({
1966
2284
  });
1967
2285
  //#endregion
1968
2286
  //#region src/rules/public-component-interface/index.ts
1969
- const TARGET_DECORATORS = new Set(["Component", "Directive"]);
2287
+ const TARGET_DECORATORS$1 = new Set(["Component", "Directive"]);
1970
2288
  const INPUT_MODEL_APIS = new Set(["input", "model"]);
1971
2289
  const OUTPUT_APIS = new Set(["output", "outputFromObservable"]);
1972
2290
  const INJECT_APIS = new Set(["inject"]);
@@ -1975,9 +2293,9 @@ const FIELD_NODE_TYPES = new Set([
1975
2293
  "FieldDefinition",
1976
2294
  "PropertyDefinition"
1977
2295
  ]);
1978
- function hasTargetDecorator(context, classNode) {
2296
+ function hasTargetDecorator$1(context, classNode) {
1979
2297
  if (!classNode || !Array.isArray(classNode.decorators)) return false;
1980
- return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS));
2298
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$1));
1981
2299
  }
1982
2300
  function isApiCallFromAngularCore(context, node, apiNames) {
1983
2301
  if (!node || node.type !== "CallExpression") return false;
@@ -2036,7 +2354,7 @@ const publicComponentInterface = defineRule({
2036
2354
  createOnce(context) {
2037
2355
  return { ClassBody(node) {
2038
2356
  const classNode = node.parent;
2039
- if (!hasTargetDecorator(context, classNode)) return;
2357
+ if (!hasTargetDecorator$1(context, classNode)) return;
2040
2358
  for (const member of node.body ?? []) {
2041
2359
  if (!FIELD_NODE_TYPES.has(member.type)) continue;
2042
2360
  const isInputModelMember = isApiCallFromAngularCore(context, member.value, INPUT_MODEL_APIS);
@@ -2119,7 +2437,7 @@ const restrictInjectableProvidedIn = defineRule({
2119
2437
  //#endregion
2120
2438
  //#region src/rules/rules-of-inject/index.ts
2121
2439
  const DEFAULT_ALLOWED_FUNCTION_NAMES = [];
2122
- const DEFAULT_INJECT_FUNCTION_PREFIXES = ["injext"];
2440
+ const DEFAULT_INJECT_FUNCTION_PREFIXES = ["inject"];
2123
2441
  const DEFAULT_INJECT_FUNCTION_SUFFIXES = ["Guard"];
2124
2442
  const DEFAULT_RUNS_IN_INJECTION_CONTEXT = [];
2125
2443
  const ROUTER_CONTEXT_PROPERTY_NAMES = new Set([
@@ -2184,18 +2502,18 @@ const INJECTION_CONTEXT_FUNCTION_TYPE_NAMES = new Set([
2184
2502
  "RedirectFunction",
2185
2503
  "ResolveFn"
2186
2504
  ]);
2187
- const FUNCTION_TYPES = new Set([
2505
+ const FUNCTION_TYPES$1 = new Set([
2188
2506
  "ArrowFunctionExpression",
2189
2507
  "FunctionDeclaration",
2190
2508
  "FunctionExpression"
2191
2509
  ]);
2192
- const CLASS_FIELD_TYPES = new Set([
2510
+ const CLASS_FIELD_TYPES$1 = new Set([
2193
2511
  "AccessorProperty",
2194
2512
  "FieldDefinition",
2195
2513
  "PropertyDefinition"
2196
2514
  ]);
2197
2515
  function isFunction(node) {
2198
- return !!node && FUNCTION_TYPES.has(node.type);
2516
+ return !!node && FUNCTION_TYPES$1.has(node.type);
2199
2517
  }
2200
2518
  function getAncestors(context, node) {
2201
2519
  return context.sourceCode.getAncestors(node);
@@ -2251,7 +2569,7 @@ function getFunctionContextTypeName(functionNode) {
2251
2569
  const parent = skipTransparentExpressionParents(functionNode.parent);
2252
2570
  if (parent?.type === "VariableDeclarator" && parent.id?.type === "Identifier") return getTypeName(parent.id.typeAnnotation?.typeAnnotation);
2253
2571
  if (parent?.type === "Property" && parent.value === functionNode) return getTypeName(parent.typeAnnotation?.typeAnnotation);
2254
- if (parent && CLASS_FIELD_TYPES.has(parent.type) && parent.value === functionNode) return getTypeName(parent.typeAnnotation?.typeAnnotation);
2572
+ if (parent && CLASS_FIELD_TYPES$1.has(parent.type) && parent.value === functionNode) return getTypeName(parent.typeAnnotation?.typeAnnotation);
2255
2573
  return null;
2256
2574
  }
2257
2575
  function isTypedInjectionContextFunction(functionNode) {
@@ -2285,7 +2603,7 @@ function isConstructorFunction(functionNode) {
2285
2603
  return functionNode.parent?.type === "MethodDefinition" && functionNode.parent.kind === "constructor" && functionNode.parent.value === functionNode;
2286
2604
  }
2287
2605
  function isDirectClassFieldInitializer(ancestors, nearestFunction) {
2288
- return !!ancestors.findLast((ancestor) => CLASS_FIELD_TYPES.has(ancestor.type)) && !nearestFunction;
2606
+ return !!ancestors.findLast((ancestor) => CLASS_FIELD_TYPES$1.has(ancestor.type)) && !nearestFunction;
2289
2607
  }
2290
2608
  function hasSupportedAngularClassDecorator(classNode) {
2291
2609
  if (!classNode) return false;
@@ -2329,6 +2647,494 @@ function isInjectLikeHelperCall(context, node, injectionContextApiLocalNames, in
2329
2647
  if (runsInInjectionContextFunctionNames.has(callee.name)) return true;
2330
2648
  return !injectionContextApiLocalNames.has(callee.name) && callee.name !== "inject" && (injectFunctionPrefixes.some((prefix) => callee.name.startsWith(prefix)) || injectFunctionSuffixes.some((suffix) => callee.name.endsWith(suffix)));
2331
2649
  }
2650
+ const rulesOfInject = defineRule({
2651
+ meta: {
2652
+ type: "problem",
2653
+ docs: {
2654
+ description: "Require Angular APIs that depend on injection context to appear only in known injection contexts.",
2655
+ recommended: true
2656
+ },
2657
+ schema: [{
2658
+ type: "object",
2659
+ additionalProperties: false,
2660
+ properties: {
2661
+ allowedFunctionNames: {
2662
+ type: "array",
2663
+ items: { type: "string" },
2664
+ default: DEFAULT_ALLOWED_FUNCTION_NAMES
2665
+ },
2666
+ checkUnimportedInject: {
2667
+ type: "boolean",
2668
+ default: false
2669
+ },
2670
+ injectFunctionPrefixes: {
2671
+ type: "array",
2672
+ items: { type: "string" },
2673
+ default: DEFAULT_INJECT_FUNCTION_PREFIXES
2674
+ },
2675
+ injectFunctionSuffixes: {
2676
+ type: "array",
2677
+ items: { type: "string" },
2678
+ default: DEFAULT_INJECT_FUNCTION_SUFFIXES
2679
+ },
2680
+ runsInInjectionContext: {
2681
+ type: "array",
2682
+ items: {
2683
+ type: "object",
2684
+ additionalProperties: false,
2685
+ required: ["from", "imports"],
2686
+ properties: {
2687
+ from: { type: "string" },
2688
+ imports: { anyOf: [{ const: "all" }, {
2689
+ type: "array",
2690
+ items: { type: "string" }
2691
+ }] }
2692
+ }
2693
+ },
2694
+ default: DEFAULT_RUNS_IN_INJECTION_CONTEXT
2695
+ }
2696
+ }
2697
+ }],
2698
+ messages: { disallowedInject: "Angular APIs that depend on injection context must be called from an injection context: a class field initializer or constructor in an Angular-decorated class, provider factory, InjectionToken factory, runInInjectionContext/runInContext callback, Angular route callback property (for example loadComponent/canActivate), an inject* or *Guard function, or configured allowed function." }
2699
+ },
2700
+ createOnce(context) {
2701
+ const injectionContextApiLocalNames = /* @__PURE__ */ new Set();
2702
+ const injectionContextApiNamespaceMembers = /* @__PURE__ */ new Map();
2703
+ const runsInInjectionContextFunctionNames = /* @__PURE__ */ new Set();
2704
+ let runsInInjectionContextRules = [];
2705
+ return {
2706
+ before() {
2707
+ injectionContextApiLocalNames.clear();
2708
+ injectionContextApiNamespaceMembers.clear();
2709
+ runsInInjectionContextFunctionNames.clear();
2710
+ runsInInjectionContextRules = (context.options[0] ?? {}).runsInInjectionContext ?? DEFAULT_RUNS_IN_INJECTION_CONTEXT;
2711
+ },
2712
+ ImportDeclaration(node) {
2713
+ const source = node.source?.value;
2714
+ const matchingRule = typeof source === "string" ? runsInInjectionContextRules.find((rule) => rule.from === source) : null;
2715
+ if (matchingRule) for (const specifier of node.specifiers ?? []) {
2716
+ if (specifier.type === "ImportSpecifier" && (matchingRule.imports === "all" || matchingRule.imports.includes(getPropertyName(specifier.imported) ?? ""))) runsInInjectionContextFunctionNames.add(specifier.local.name);
2717
+ if ((specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") && matchingRule.imports === "all") runsInInjectionContextFunctionNames.add(specifier.local.name);
2718
+ }
2719
+ const knownApiImports = typeof source === "string" ? getKnownInjectionContextApiImports(source) : null;
2720
+ if (knownApiImports) for (const specifier of node.specifiers ?? []) {
2721
+ if (specifier.type === "ImportSpecifier" && knownApiImports.has(getPropertyName(specifier.imported) ?? "")) injectionContextApiLocalNames.add(specifier.local.name);
2722
+ if (specifier.type === "ImportNamespaceSpecifier") injectionContextApiNamespaceMembers.set(specifier.local.name, knownApiImports);
2723
+ }
2724
+ },
2725
+ CallExpression(node) {
2726
+ const options = context.options[0] ?? {};
2727
+ const allowedFunctionNames = new Set(options.allowedFunctionNames ?? DEFAULT_ALLOWED_FUNCTION_NAMES);
2728
+ const checkUnimportedInject = options.checkUnimportedInject ?? false;
2729
+ const injectFunctionPrefixes = options.injectFunctionPrefixes ?? DEFAULT_INJECT_FUNCTION_PREFIXES;
2730
+ const injectFunctionSuffixes = options.injectFunctionSuffixes ?? DEFAULT_INJECT_FUNCTION_SUFFIXES;
2731
+ const inAllowedContext = isAllowedInjectionContext(context, node, allowedFunctionNames, injectFunctionPrefixes, injectFunctionSuffixes);
2732
+ if (isInjectLikeHelperCall(context, node, injectionContextApiLocalNames, injectFunctionPrefixes, injectFunctionSuffixes, runsInInjectionContextFunctionNames) && !inAllowedContext) {
2733
+ context.report({
2734
+ node: node.callee,
2735
+ messageId: "disallowedInject"
2736
+ });
2737
+ return;
2738
+ }
2739
+ if (!isKnownInjectionContextApiCall(context, node, injectionContextApiLocalNames, injectionContextApiNamespaceMembers, checkUnimportedInject)) return;
2740
+ if (inAllowedContext) return;
2741
+ context.report({
2742
+ node: node.callee,
2743
+ messageId: "disallowedInject"
2744
+ });
2745
+ }
2746
+ };
2747
+ }
2748
+ });
2749
+ //#endregion
2750
+ //#region src/utilities/tanstack-query.ts
2751
+ const DEFAULT_TANSTACK_QUERY_SOURCES = [
2752
+ "@tanstack/angular-query",
2753
+ "@benjavicente/angular-query",
2754
+ "@tanstack/angular-query-experimental"
2755
+ ];
2756
+ const QUERY_CORE_SOURCE = "@tanstack/query-core";
2757
+ const QUERY_OPTIONS_BUILDERS = new Set(["queryOptions", "infiniteQueryOptions"]);
2758
+ function getTanstackQuerySources(options) {
2759
+ return new Set(options.tanstackQuerySources ?? DEFAULT_TANSTACK_QUERY_SOURCES);
2760
+ }
2761
+ function getTanstackQueryImportName(context, node, sources) {
2762
+ const expression = unwrapExpression(node);
2763
+ if (expression?.type === "Identifier") {
2764
+ for (const source of sources) {
2765
+ const importedName = getImportedName(context, expression, source);
2766
+ if (importedName) return importedName;
2767
+ }
2768
+ return null;
2769
+ }
2770
+ if (expression?.type !== "MemberExpression" || expression.object?.type !== "Identifier") return null;
2771
+ for (const source of sources) if (isNamespaceImport(context, expression.object, source)) return getPropertyName(expression.property);
2772
+ return null;
2773
+ }
2774
+ function isTanstackQueryImportedReference(context, node, sources, names) {
2775
+ for (const source of sources) if (isImportedReference(context, node, source, names)) return true;
2776
+ return false;
2777
+ }
2778
+ //#endregion
2779
+ //#region src/rules/tanstack-query-injects-only-in-component-body/index.ts
2780
+ const TARGET_DECORATORS = new Set(["Component", "Directive"]);
2781
+ const TANSTACK_QUERY_APIS = new Set(["injectQuery", "injectMutation"]);
2782
+ const CLASS_FIELD_TYPES = new Set([
2783
+ "AccessorProperty",
2784
+ "FieldDefinition",
2785
+ "PropertyDefinition"
2786
+ ]);
2787
+ const FUNCTION_TYPES = new Set([
2788
+ "ArrowFunctionExpression",
2789
+ "FunctionDeclaration",
2790
+ "FunctionExpression"
2791
+ ]);
2792
+ function hasTargetDecorator(context, classNode) {
2793
+ if (!classNode || !Array.isArray(classNode.decorators)) return false;
2794
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS));
2795
+ }
2796
+ function isTanstackQueryInjectCall(context, callNode, tanstackQuerySources) {
2797
+ return isTanstackQueryImportedReference(context, callNode.callee, tanstackQuerySources, TANSTACK_QUERY_APIS);
2798
+ }
2799
+ function isDirectComponentOrDirectiveFieldInitializer(context, callNode) {
2800
+ const ancestors = context.sourceCode.getAncestors(callNode);
2801
+ const classField = ancestors.findLast((ancestor) => CLASS_FIELD_TYPES.has(ancestor.type));
2802
+ if (!classField || unwrapExpression(classField.value) !== callNode) return false;
2803
+ const classBodyIndex = ancestors.findLastIndex((ancestor) => ancestor.type === "ClassBody");
2804
+ if (classBodyIndex === -1) return false;
2805
+ const classBody = ancestors[classBodyIndex];
2806
+ if (ancestors.slice(classBodyIndex + 1).some((ancestor) => FUNCTION_TYPES.has(ancestor.type))) return false;
2807
+ return hasTargetDecorator(context, classBody?.parent);
2808
+ }
2809
+ const tanstackQueryInjectsOnlyInComponentBody = defineRule({
2810
+ meta: {
2811
+ type: "problem",
2812
+ docs: {
2813
+ description: "Require Angular TanStack Query injectQuery/injectMutation calls to be direct component/directive class fields.",
2814
+ recommended: true
2815
+ },
2816
+ schema: [{
2817
+ type: "object",
2818
+ additionalProperties: false,
2819
+ properties: { tanstackQuerySources: {
2820
+ type: "array",
2821
+ items: { type: "string" },
2822
+ default: DEFAULT_TANSTACK_QUERY_SOURCES
2823
+ } }
2824
+ }],
2825
+ messages: { onlyInComponentBody: "Call {{name}} only as a direct class field initializer in an Angular component or directive." }
2826
+ },
2827
+ createOnce(context) {
2828
+ let tanstackQuerySources = new Set(DEFAULT_TANSTACK_QUERY_SOURCES);
2829
+ return {
2830
+ before() {
2831
+ tanstackQuerySources = getTanstackQuerySources(context.options?.[0] ?? {});
2832
+ },
2833
+ CallExpression(node) {
2834
+ const callNode = node;
2835
+ if (!isTanstackQueryInjectCall(context, callNode, tanstackQuerySources)) return;
2836
+ if (isDirectComponentOrDirectiveFieldInitializer(context, callNode)) return;
2837
+ context.report({
2838
+ node: callNode.callee ?? callNode,
2839
+ messageId: "onlyInComponentBody",
2840
+ data: { name: callNode.callee?.name ?? "this TanStack Query inject API" }
2841
+ });
2842
+ }
2843
+ };
2844
+ }
2845
+ });
2846
+ //#endregion
2847
+ //#region src/rules/tanstack-query-inlined-keys/index.ts
2848
+ function getProperty$1(node, name) {
2849
+ for (const property of node.properties ?? []) if (property.type === "Property" && getPropertyName(property.key) === name) return property;
2850
+ return null;
2851
+ }
2852
+ function isInlineArrayExpression$1(node) {
2853
+ return unwrapExpression(node)?.type === "ArrayExpression";
2854
+ }
2855
+ const tanstackQueryInlinedKeys = defineRule({
2856
+ meta: {
2857
+ type: "problem",
2858
+ docs: {
2859
+ description: "Require TanStack Query queryOptions() keys to be inline arrays.",
2860
+ recommended: true
2861
+ },
2862
+ schema: [{
2863
+ type: "object",
2864
+ additionalProperties: false,
2865
+ properties: { tanstackQuerySources: {
2866
+ type: "array",
2867
+ items: { type: "string" },
2868
+ default: DEFAULT_TANSTACK_QUERY_SOURCES
2869
+ } }
2870
+ }],
2871
+ messages: { inlinedKeys: "Inline queryKey as an array in queryOptions(). Query keys are implementation details and should be read through query options." }
2872
+ },
2873
+ createOnce(context) {
2874
+ let tanstackQuerySources = new Set(DEFAULT_TANSTACK_QUERY_SOURCES);
2875
+ return {
2876
+ before() {
2877
+ tanstackQuerySources = getTanstackQuerySources(context.options?.[0] ?? {});
2878
+ },
2879
+ CallExpression(node) {
2880
+ const callNode = node;
2881
+ const importName = getTanstackQueryImportName(context, callNode.callee, tanstackQuerySources);
2882
+ if (!importName || !QUERY_OPTIONS_BUILDERS.has(importName)) return;
2883
+ const options = unwrapExpression(callNode.arguments?.[0]);
2884
+ if (options?.type !== "ObjectExpression") return;
2885
+ const queryKey = getProperty$1(options, "queryKey");
2886
+ if (!queryKey || isInlineArrayExpression$1(queryKey.value)) return;
2887
+ context.report({
2888
+ node: queryKey.value ?? queryKey,
2889
+ messageId: "inlinedKeys"
2890
+ });
2891
+ }
2892
+ };
2893
+ }
2894
+ });
2895
+ //#endregion
2896
+ //#region src/rules/tanstack-query-prefer-query-options/index.ts
2897
+ const ANGULAR_CORE_SOURCE = "@angular/core";
2898
+ const INJECT_NAMES = new Set(["inject"]);
2899
+ const QUERY_INJECT_APIS = new Set(["injectQuery", "injectInfiniteQuery"]);
2900
+ const QUERIES_INJECT_APIS = new Set(["injectQueries"]);
2901
+ const FILTER_INJECT_APIS = new Set(["injectIsFetching"]);
2902
+ const QUERY_CLIENT_OPTION_METHODS = new Set([
2903
+ "ensureInfiniteQueryData",
2904
+ "ensureQueryData",
2905
+ "fetchInfiniteQuery",
2906
+ "fetchQuery",
2907
+ "prefetchInfiniteQuery",
2908
+ "prefetchQuery"
2909
+ ]);
2910
+ const QUERY_CLIENT_QUERY_KEY_METHODS = new Set([
2911
+ "getQueryData",
2912
+ "getQueryDefaults",
2913
+ "getQueryState",
2914
+ "setQueryData",
2915
+ "setQueryDefaults"
2916
+ ]);
2917
+ const QUERY_CLIENT_FILTER_METHODS = new Set([
2918
+ "cancelQueries",
2919
+ "getQueriesData",
2920
+ "invalidateQueries",
2921
+ "isFetching",
2922
+ "refetchQueries",
2923
+ "removeQueries",
2924
+ "resetQueries",
2925
+ "setQueriesData"
2926
+ ]);
2927
+ const QUERY_CLIENT_NAMES = new Set(["QueryClient"]);
2928
+ const SKIP_TOKEN_NAMES = new Set(["skipToken"]);
2929
+ function getProperty(node, name) {
2930
+ for (const property of node.properties ?? []) if (property.type === "Property" && getPropertyName(property.key) === name) return property;
2931
+ return null;
2932
+ }
2933
+ function isObjectExpression(node) {
2934
+ return unwrapExpression(node)?.type === "ObjectExpression";
2935
+ }
2936
+ function isInlineArrayExpression(node) {
2937
+ return unwrapExpression(node)?.type === "ArrayExpression";
2938
+ }
2939
+ function isSkipToken(context, node, sources) {
2940
+ const expression = unwrapExpression(node);
2941
+ if (!expression) return false;
2942
+ if (expression.type === "ConditionalExpression") return isSkipToken(context, expression.consequent, sources) || isSkipToken(context, expression.alternate, sources);
2943
+ if (expression.type === "LogicalExpression") return isSkipToken(context, expression.left, sources) || isSkipToken(context, expression.right, sources);
2944
+ for (const source of [...sources, QUERY_CORE_SOURCE]) if (isImportedReference(context, expression, source, SKIP_TOKEN_NAMES)) return true;
2945
+ return false;
2946
+ }
2947
+ function hasObjectSpread(node) {
2948
+ return (node.properties ?? []).some((property) => property.type === "SpreadElement");
2949
+ }
2950
+ function hasInlineQueryOptions(context, node, sources) {
2951
+ if (getProperty(node, "queryKey")) return true;
2952
+ const queryFn = getProperty(node, "queryFn");
2953
+ if (!queryFn) return false;
2954
+ return !(hasObjectSpread(node) && isSkipToken(context, queryFn.value, sources));
2955
+ }
2956
+ function hasInlineFilterQueryKey(node) {
2957
+ const queryKey = getProperty(node, "queryKey")?.value;
2958
+ return isInlineArrayExpression(queryKey);
2959
+ }
2960
+ function getReturnedObjectExpressions(node) {
2961
+ const expression = unwrapExpression(node);
2962
+ if (!expression) return [];
2963
+ if (expression.type === "ObjectExpression") return [expression];
2964
+ if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression") return getReturnedObjectExpressions(expression.body);
2965
+ if (expression.type === "BlockStatement") return (expression.body ?? []).flatMap((statement) => statement.type === "ReturnStatement" ? getReturnedObjectExpressions(statement.argument) : []);
2966
+ if (expression.type === "ConditionalExpression") return [...getReturnedObjectExpressions(expression.consequent), ...getReturnedObjectExpressions(expression.alternate)];
2967
+ if (expression.type === "LogicalExpression") return [...getReturnedObjectExpressions(expression.left), ...getReturnedObjectExpressions(expression.right)];
2968
+ if (expression.type === "SequenceExpression") return (expression.expressions ?? []).flatMap((child) => getReturnedObjectExpressions(child));
2969
+ return [];
2970
+ }
2971
+ function getQueryObjects(node) {
2972
+ const expression = unwrapExpression(node);
2973
+ if (!expression) return [];
2974
+ if (expression.type === "ArrayExpression") return (expression.elements ?? []).filter(isObjectExpression).map((element) => unwrapExpression(element));
2975
+ if (expression.type === "CallExpression" && expression.callee?.type === "MemberExpression" && getPropertyName(expression.callee.property) === "map") {
2976
+ const mapper = expression.arguments?.[0];
2977
+ if (mapper?.type === "ArrowFunctionExpression" || mapper?.type === "FunctionExpression") return getReturnedObjectExpressions(mapper);
2978
+ }
2979
+ return [];
2980
+ }
2981
+ function getBindingInitializer(binding) {
2982
+ const parent = binding?.parent;
2983
+ if (!parent) return null;
2984
+ if (parent.type === "VariableDeclarator" && parent.id === binding) return parent.init ?? null;
2985
+ if (parent.type === "AssignmentPattern" && parent.left === binding) return parent.right ?? null;
2986
+ return null;
2987
+ }
2988
+ function getThisFieldInitializer(context, node, fieldName) {
2989
+ const classBody = context.sourceCode.getAncestors(node).findLast((ancestor) => ancestor.type === "ClassBody");
2990
+ if (!classBody) return null;
2991
+ for (const member of classBody.body ?? []) if (getPropertyName(member.key) === fieldName) return member.value ?? null;
2992
+ return null;
2993
+ }
2994
+ function isQueryClientReference(context, node, sources) {
2995
+ const expression = unwrapExpression(node);
2996
+ if (!expression) return false;
2997
+ return isTanstackQueryImportedReference(context, expression, sources, QUERY_CLIENT_NAMES) || isImportedReference(context, expression, "@tanstack/query-core", QUERY_CLIENT_NAMES);
2998
+ }
2999
+ function isInjectQueryClientCall(context, node, sources) {
3000
+ const expression = unwrapExpression(node);
3001
+ return expression?.type === "CallExpression" && isImportedReference(context, expression.callee, ANGULAR_CORE_SOURCE, INJECT_NAMES) && isQueryClientReference(context, expression.arguments?.[0], sources);
3002
+ }
3003
+ function isQueryClientSource(context, node, sources) {
3004
+ const expression = unwrapExpression(node);
3005
+ if (!expression) return false;
3006
+ if (expression.type === "NewExpression") return isQueryClientReference(context, expression.callee, sources);
3007
+ return isInjectQueryClientCall(context, expression, sources);
3008
+ }
3009
+ function resolveQueryClientSource(context, node, sources) {
3010
+ let current = unwrapExpression(node);
3011
+ const visited = /* @__PURE__ */ new Set();
3012
+ while (current && !visited.has(current)) {
3013
+ visited.add(current);
3014
+ if (isQueryClientSource(context, current, sources)) return current;
3015
+ if (current.type === "Identifier") {
3016
+ const initializer = getBindingInitializer(findNearestBindingIdentifier(context, current));
3017
+ if (!initializer) return current;
3018
+ current = unwrapExpression(initializer);
3019
+ continue;
3020
+ }
3021
+ if (current.type === "MemberExpression" && current.object?.type === "ThisExpression") {
3022
+ const initializer = getThisFieldInitializer(context, current, getPropertyName(current.property) ?? "");
3023
+ if (!initializer) return current;
3024
+ current = unwrapExpression(initializer);
3025
+ continue;
3026
+ }
3027
+ return current;
3028
+ }
3029
+ return current ?? null;
3030
+ }
3031
+ function isTanstackQueryClient(context, node, sources) {
3032
+ return isQueryClientSource(context, resolveQueryClientSource(context, node, sources), sources);
3033
+ }
3034
+ function reportInlineQueryOptions(context, node, sources) {
3035
+ const expression = unwrapExpression(node);
3036
+ if (!expression || expression.type !== "ObjectExpression") return;
3037
+ if (!hasInlineQueryOptions(context, expression, sources)) return;
3038
+ context.report({
3039
+ node: expression,
3040
+ messageId: "preferQueryOptions"
3041
+ });
3042
+ }
3043
+ function reportInlineFilterQueryKey(context, node) {
3044
+ const expression = unwrapExpression(node);
3045
+ if (!expression || expression.type !== "ObjectExpression") return;
3046
+ if (!hasInlineFilterQueryKey(expression)) return;
3047
+ context.report({
3048
+ node: expression,
3049
+ messageId: "preferQueryOptionsQueryKey"
3050
+ });
3051
+ }
3052
+ const tanstackQueryPreferQueryOptions = defineRule({
3053
+ meta: {
3054
+ type: "problem",
3055
+ docs: {
3056
+ description: "Prefer queryOptions() to co-locate TanStack Query queryKey and queryFn.",
3057
+ recommended: true
3058
+ },
3059
+ schema: [{
3060
+ type: "object",
3061
+ additionalProperties: false,
3062
+ properties: { tanstackQuerySources: {
3063
+ type: "array",
3064
+ items: { type: "string" },
3065
+ default: DEFAULT_TANSTACK_QUERY_SOURCES
3066
+ } }
3067
+ }],
3068
+ messages: {
3069
+ preferQueryOptions: "Prefer using queryOptions() or infiniteQueryOptions() to co-locate queryKey and queryFn.",
3070
+ preferQueryOptionsQueryKey: "Prefer referencing a queryKey from a queryOptions() result instead of typing it manually."
3071
+ }
3072
+ },
3073
+ createOnce(context) {
3074
+ let tanstackQuerySources = new Set(DEFAULT_TANSTACK_QUERY_SOURCES);
3075
+ return {
3076
+ before() {
3077
+ tanstackQuerySources = getTanstackQuerySources(context.options?.[0] ?? {});
3078
+ },
3079
+ CallExpression(node) {
3080
+ const callNode = node;
3081
+ const importName = getTanstackQueryImportName(context, callNode.callee, tanstackQuerySources);
3082
+ if (importName && QUERY_OPTIONS_BUILDERS.has(importName)) return;
3083
+ if (importName && QUERY_INJECT_APIS.has(importName)) {
3084
+ for (const objectExpression of getReturnedObjectExpressions(callNode.arguments?.[0])) reportInlineQueryOptions(context, objectExpression, tanstackQuerySources);
3085
+ return;
3086
+ }
3087
+ if (importName && QUERIES_INJECT_APIS.has(importName)) {
3088
+ for (const objectExpression of getReturnedObjectExpressions(callNode.arguments?.[0])) {
3089
+ const queries = getProperty(objectExpression, "queries")?.value;
3090
+ for (const query of getQueryObjects(queries)) reportInlineQueryOptions(context, query, tanstackQuerySources);
3091
+ }
3092
+ return;
3093
+ }
3094
+ if (importName && FILTER_INJECT_APIS.has(importName)) {
3095
+ reportInlineFilterQueryKey(context, callNode.arguments?.[0]);
3096
+ return;
3097
+ }
3098
+ const callee = unwrapExpression(callNode.callee);
3099
+ if (callee?.type !== "MemberExpression" || !isTanstackQueryClient(context, callee.object, tanstackQuerySources)) return;
3100
+ const method = getPropertyName(callee.property);
3101
+ const options = callNode.arguments?.[0];
3102
+ if (QUERY_CLIENT_OPTION_METHODS.has(method ?? "")) {
3103
+ reportInlineQueryOptions(context, options, tanstackQuerySources);
3104
+ return;
3105
+ }
3106
+ if (QUERY_CLIENT_QUERY_KEY_METHODS.has(method ?? "") && isInlineArrayExpression(options)) {
3107
+ context.report({
3108
+ node: unwrapExpression(options) ?? options,
3109
+ messageId: "preferQueryOptionsQueryKey"
3110
+ });
3111
+ return;
3112
+ }
3113
+ if (QUERY_CLIENT_FILTER_METHODS.has(method ?? "")) reportInlineFilterQueryKey(context, options);
3114
+ }
3115
+ };
3116
+ }
3117
+ });
3118
+ //#endregion
3119
+ //#region src/rules/vitest-no-incompatible-angular-testing-apis/index.ts
3120
+ const ANGULAR_TESTING_SOURCE = "@angular/core/testing";
3121
+ const VITEST_INCOMPATIBLE_APIS = new Set([
3122
+ "discardPeriodicTasks",
3123
+ "fakeAsync",
3124
+ "flush",
3125
+ "flushMicrotasks",
3126
+ "resetFakeAsyncZone",
3127
+ "tick",
3128
+ "waitForAsync"
3129
+ ]);
3130
+ function getIncompatibleImportName(specifier) {
3131
+ if (specifier.type !== "ImportSpecifier") return null;
3132
+ const importedName = getPropertyName(specifier.imported);
3133
+ return importedName && VITEST_INCOMPATIBLE_APIS.has(importedName) ? importedName : null;
3134
+ }
3135
+ function isIncompatibleNamespaceCall(context, callNode) {
3136
+ return isImportedNamespaceMember(context, callNode.callee, ANGULAR_TESTING_SOURCE, VITEST_INCOMPATIBLE_APIS);
3137
+ }
2332
3138
  //#endregion
2333
3139
  //#region src/index.ts
2334
3140
  const plugin = eslintCompatPlugin({
@@ -2336,6 +3142,7 @@ const plugin = eslintCompatPlugin({
2336
3142
  rules: {
2337
3143
  "avoid-explicit-injection-context": avoidExplicitInjectionContext,
2338
3144
  "avoid-explicit-subscription-management": avoidExplicitSubscriptionManagement,
3145
+ "avoid-inappropriate-intimacy": avoidInappropriateIntimacy,
2339
3146
  "avoid-ng-modules": avoidNgModules,
2340
3147
  "avoid-rxjs-state-in-component": avoidRxjsStateInComponent,
2341
3148
  "avoid-writing-signals-in-reactive-context": avoidWritingSignalsInReactiveContext,
@@ -2343,105 +3150,51 @@ const plugin = eslintCompatPlugin({
2343
3150
  "class-matches-filename": classMatchesFilename,
2344
3151
  "component-resource-filenames": componentResourceFilenames,
2345
3152
  "decorator-filename-suffix": decoratorFilenameSuffix,
3153
+ "no-manual-change-detection": noManualChangeDetection,
3154
+ "no-resource-api": noResourceApi,
3155
+ "no-route-resolvers": noRouteResolvers,
3156
+ "no-ui-inheritance": noUiInheritance,
2346
3157
  "prefer-load-component-over-load-children": preferLoadComponentOverLoadChildren,
2347
3158
  "prefer-private-elements": preferPrivateElements,
2348
3159
  "prefer-style-url": preferStyleUrl,
2349
3160
  "public-component-interface": publicComponentInterface,
2350
3161
  "restrict-injectable-provided-in": restrictInjectableProvidedIn,
2351
- "rules-of-inject": defineRule({
3162
+ "rules-of-inject": rulesOfInject,
3163
+ "tanstack-query-injects-only-in-component-body": tanstackQueryInjectsOnlyInComponentBody,
3164
+ "tanstack-query-inlined-keys": tanstackQueryInlinedKeys,
3165
+ "tanstack-query-prefer-query-options": tanstackQueryPreferQueryOptions,
3166
+ "vitest-no-incompatible-angular-testing-apis": defineRule({
2352
3167
  meta: {
2353
3168
  type: "problem",
2354
3169
  docs: {
2355
- description: "Require Angular APIs that depend on injection context to appear only in known injection contexts.",
3170
+ description: "Disallow Angular testing APIs that depend on Zone.js and are incompatible with Vitest.",
2356
3171
  recommended: true
2357
3172
  },
2358
- schema: [{
2359
- type: "object",
2360
- additionalProperties: false,
2361
- properties: {
2362
- allowedFunctionNames: {
2363
- type: "array",
2364
- items: { type: "string" },
2365
- default: DEFAULT_ALLOWED_FUNCTION_NAMES
2366
- },
2367
- checkUnimportedInject: {
2368
- type: "boolean",
2369
- default: false
2370
- },
2371
- injectFunctionPrefixes: {
2372
- type: "array",
2373
- items: { type: "string" },
2374
- default: DEFAULT_INJECT_FUNCTION_PREFIXES
2375
- },
2376
- injectFunctionSuffixes: {
2377
- type: "array",
2378
- items: { type: "string" },
2379
- default: DEFAULT_INJECT_FUNCTION_SUFFIXES
2380
- },
2381
- runsInInjectionContext: {
2382
- type: "array",
2383
- items: {
2384
- type: "object",
2385
- additionalProperties: false,
2386
- required: ["from", "imports"],
2387
- properties: {
2388
- from: { type: "string" },
2389
- imports: { anyOf: [{ const: "all" }, {
2390
- type: "array",
2391
- items: { type: "string" }
2392
- }] }
2393
- }
2394
- },
2395
- default: DEFAULT_RUNS_IN_INJECTION_CONTEXT
2396
- }
2397
- }
2398
- }],
2399
- messages: { disallowedInject: "Angular APIs that depend on injection context must be called from an injection context: a class field initializer or constructor in an Angular-decorated class, provider factory, InjectionToken factory, runInInjectionContext/runInContext callback, Angular route callback property (for example loadComponent/canActivate), an inject* or *Guard function, or configured allowed function." }
3173
+ schema: [],
3174
+ messages: { vitestNoIncompatibleAngularTestingApi: "Avoid Angular testing API '{{name}}'. It depends on Zone.js and is not compatible with Angular tests running on Vitest." }
2400
3175
  },
2401
3176
  createOnce(context) {
2402
- const injectionContextApiLocalNames = /* @__PURE__ */ new Set();
2403
- const injectionContextApiNamespaceMembers = /* @__PURE__ */ new Map();
2404
- const runsInInjectionContextFunctionNames = /* @__PURE__ */ new Set();
2405
- let runsInInjectionContextRules = [];
2406
3177
  return {
2407
- before() {
2408
- injectionContextApiLocalNames.clear();
2409
- injectionContextApiNamespaceMembers.clear();
2410
- runsInInjectionContextFunctionNames.clear();
2411
- runsInInjectionContextRules = (context.options[0] ?? {}).runsInInjectionContext ?? DEFAULT_RUNS_IN_INJECTION_CONTEXT;
2412
- },
2413
3178
  ImportDeclaration(node) {
2414
- const source = node.source?.value;
2415
- const matchingRule = typeof source === "string" ? runsInInjectionContextRules.find((rule) => rule.from === source) : null;
2416
- if (matchingRule) for (const specifier of node.specifiers ?? []) {
2417
- if (specifier.type === "ImportSpecifier" && (matchingRule.imports === "all" || matchingRule.imports.includes(getPropertyName(specifier.imported) ?? ""))) runsInInjectionContextFunctionNames.add(specifier.local.name);
2418
- if ((specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") && matchingRule.imports === "all") runsInInjectionContextFunctionNames.add(specifier.local.name);
2419
- }
2420
- const knownApiImports = typeof source === "string" ? getKnownInjectionContextApiImports(source) : null;
2421
- if (knownApiImports) for (const specifier of node.specifiers ?? []) {
2422
- if (specifier.type === "ImportSpecifier" && knownApiImports.has(getPropertyName(specifier.imported) ?? "")) injectionContextApiLocalNames.add(specifier.local.name);
2423
- if (specifier.type === "ImportNamespaceSpecifier") injectionContextApiNamespaceMembers.set(specifier.local.name, knownApiImports);
2424
- }
2425
- },
2426
- CallExpression(node) {
2427
- const options = context.options[0] ?? {};
2428
- const allowedFunctionNames = new Set(options.allowedFunctionNames ?? DEFAULT_ALLOWED_FUNCTION_NAMES);
2429
- const checkUnimportedInject = options.checkUnimportedInject ?? false;
2430
- const injectFunctionPrefixes = options.injectFunctionPrefixes ?? DEFAULT_INJECT_FUNCTION_PREFIXES;
2431
- const injectFunctionSuffixes = options.injectFunctionSuffixes ?? DEFAULT_INJECT_FUNCTION_SUFFIXES;
2432
- const inAllowedContext = isAllowedInjectionContext(context, node, allowedFunctionNames, injectFunctionPrefixes, injectFunctionSuffixes);
2433
- if (isInjectLikeHelperCall(context, node, injectionContextApiLocalNames, injectFunctionPrefixes, injectFunctionSuffixes, runsInInjectionContextFunctionNames) && !inAllowedContext) {
3179
+ const importNode = node;
3180
+ if (importNode.source?.value !== ANGULAR_TESTING_SOURCE) return;
3181
+ for (const specifier of importNode.specifiers ?? []) {
3182
+ const name = getIncompatibleImportName(specifier);
3183
+ if (!name) continue;
2434
3184
  context.report({
2435
- node: node.callee,
2436
- messageId: "disallowedInject"
3185
+ node: specifier,
3186
+ messageId: "vitestNoIncompatibleAngularTestingApi",
3187
+ data: { name }
2437
3188
  });
2438
- return;
2439
3189
  }
2440
- if (!isKnownInjectionContextApiCall(context, node, injectionContextApiLocalNames, injectionContextApiNamespaceMembers, checkUnimportedInject)) return;
2441
- if (inAllowedContext) return;
3190
+ },
3191
+ CallExpression(node) {
3192
+ const callNode = node;
3193
+ if (!isIncompatibleNamespaceCall(context, callNode)) return;
2442
3194
  context.report({
2443
- node: node.callee,
2444
- messageId: "disallowedInject"
3195
+ node: callNode.callee,
3196
+ messageId: "vitestNoIncompatibleAngularTestingApi",
3197
+ data: { name: getPropertyName(callNode.callee?.property) ?? "this API" }
2445
3198
  });
2446
3199
  }
2447
3200
  };