@benjavicente/lint-angular 0.0.3 → 0.0.4

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 +5 -0
  2. package/dist/index.mjs +253 -16
  3. package/package.json +12 -12
package/README.md CHANGED
@@ -10,6 +10,7 @@ Opinated Oxlint/ESLint-compatible plugin for Angular project rules.
10
10
  | [`rules-of-inject`](./src/rules/rules-of-inject/) | ✅ | | Restrict `inject()` usage to valid Angular injection contexts. |
11
11
  | [`avoid-explicit-injection-context`](./src/rules/avoid-explicit-injection-context/) | ✅ | | Avoid explicit injection-context APIs such as `inject(Injector)` and `runInInjectionContext`. |
12
12
  | [`avoid-explicit-subscription-management`](./src/rules/avoid-explicit-subscription-management/) | ✅ | | Avoid storing and manually managing RxJS subscriptions in Angular classes. |
13
+ | [`avoid-inappropriate-intimacy`](./src/rules/avoid-inappropriate-intimacy/) | ✅ | | Avoid passing Angular component, directive, and service instances as function arguments. |
13
14
  | [`avoid-ng-modules`](./src/rules/avoid-ng-modules/) | ✅ | | Avoid NgModules in favor of standalone Angular APIs. |
14
15
  | [`avoid-rxjs-state-in-component`](./src/rules/avoid-rxjs-state-in-component/) | ✅ | | Avoid RxJS subjects for component and directive-local state. |
15
16
  | [`avoid-writing-signals-in-reactive-context`](./src/rules/avoid-writing-signals-in-reactive-context/) | ✅ | | Avoid writing to signals from reactive Angular contexts. |
@@ -17,6 +18,10 @@ Opinated Oxlint/ESLint-compatible plugin for Angular project rules.
17
18
  | [`class-matches-filename`](./src/rules/class-matches-filename/) | ✅ | | Require Angular class names to match component, directive, and service filenames. |
18
19
  | [`component-resource-filenames`](./src/rules/component-resource-filenames/) | ✅ | | Require component resource filenames to match the component TypeScript filename. |
19
20
  | [`decorator-filename-suffix`](./src/rules/decorator-filename-suffix/) | ✅ | | Require Angular decorators to be declared in files with matching filename suffixes. |
21
+ | [`injects-tanstack-query-only-in-component-body`](./src/rules/injects-tanstack-query-only-in-component-body/) | ✅ | | Require TanStack Query inject helpers to be direct component/directive class fields. |
22
+ | [`no-resource-api`](./src/rules/no-resource-api/) | ✅ | | Avoid Angular resource APIs for server state. |
23
+ | [`no-route-resolvers`](./src/rules/no-route-resolvers/) | ✅ | | Avoid Angular route resolvers for data loading. |
24
+ | [`no-ui-inheritance`](./src/rules/no-ui-inheritance/) | ✅ | | Avoid inheritance for Angular components and directives. |
20
25
  | [`prefer-private-elements`](./src/rules/prefer-private-elements/) | ✅ | ✅ | Prefer ECMAScript private elements over TypeScript `private` members. |
21
26
  | [`prefer-load-component-over-load-children`](./src/rules/prefer-load-component-over-load-children/) | ✅ | | Prefer `loadComponent` for lazy routes that load a standalone component. |
22
27
  | [`prefer-style-url`](./src/rules/prefer-style-url/) | ✅ | ✅ | Prefer `styleUrl` when a component has exactly one stylesheet. |
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;
@@ -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;
@@ -485,7 +485,7 @@ 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"
@@ -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;
@@ -793,7 +793,7 @@ 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
799
  walkNode$1(classBody, classNode, (current) => {
@@ -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,7 +1062,7 @@ 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",
@@ -1033,9 +1073,9 @@ const FIELD_NODE_TYPES$1 = new Set([
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");
@@ -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,198 @@ const decoratorFilenameSuffix = defineRule({
1682
1722
  }
1683
1723
  });
1684
1724
  //#endregion
1725
+ //#region src/rules/injects-tanstack-query-only-in-component-body/index.ts
1726
+ const TARGET_DECORATORS$1 = new Set(["Component", "Directive"]);
1727
+ const TANSTACK_QUERY_APIS = new Set(["injectQuery", "injectMutation"]);
1728
+ const TANSTACK_QUERY_SOURCES = new Set(["@tanstack/angular-query", "@benjavicente/angular-query"]);
1729
+ const CLASS_FIELD_TYPES$1 = new Set([
1730
+ "AccessorProperty",
1731
+ "FieldDefinition",
1732
+ "PropertyDefinition"
1733
+ ]);
1734
+ const FUNCTION_TYPES$1 = new Set([
1735
+ "ArrowFunctionExpression",
1736
+ "FunctionDeclaration",
1737
+ "FunctionExpression"
1738
+ ]);
1739
+ function hasTargetDecorator$1(context, classNode) {
1740
+ if (!classNode || !Array.isArray(classNode.decorators)) return false;
1741
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, TARGET_DECORATORS$1));
1742
+ }
1743
+ function isTanstackQueryInjectCall(context, callNode) {
1744
+ return [...TANSTACK_QUERY_SOURCES].some((source) => isImportedReference(context, callNode.callee, source, TANSTACK_QUERY_APIS));
1745
+ }
1746
+ function isDirectComponentOrDirectiveFieldInitializer(context, callNode) {
1747
+ const ancestors = context.sourceCode.getAncestors(callNode);
1748
+ const classField = ancestors.findLast((ancestor) => CLASS_FIELD_TYPES$1.has(ancestor.type));
1749
+ if (!classField || unwrapExpression(classField.value) !== callNode) return false;
1750
+ const classBodyIndex = ancestors.findLastIndex((ancestor) => ancestor.type === "ClassBody");
1751
+ if (classBodyIndex === -1) return false;
1752
+ const classBody = ancestors[classBodyIndex];
1753
+ if (ancestors.slice(classBodyIndex + 1).some((ancestor) => FUNCTION_TYPES$1.has(ancestor.type))) return false;
1754
+ return hasTargetDecorator$1(context, classBody?.parent);
1755
+ }
1756
+ const injectsTanstackQueryOnlyInComponentBody = defineRule({
1757
+ meta: {
1758
+ type: "problem",
1759
+ docs: {
1760
+ description: "Require Angular TanStack Query injectQuery/injectMutation calls to be direct component/directive class fields.",
1761
+ recommended: true
1762
+ },
1763
+ schema: [],
1764
+ messages: { onlyInComponentBody: "Call {{name}} only as a direct class field initializer in an Angular component or directive." }
1765
+ },
1766
+ createOnce(context) {
1767
+ return { CallExpression(node) {
1768
+ const callNode = node;
1769
+ if (!isTanstackQueryInjectCall(context, callNode)) return;
1770
+ if (isDirectComponentOrDirectiveFieldInitializer(context, callNode)) return;
1771
+ context.report({
1772
+ node: callNode.callee ?? callNode,
1773
+ messageId: "onlyInComponentBody",
1774
+ data: { name: callNode.callee?.name ?? "this TanStack Query inject API" }
1775
+ });
1776
+ } };
1777
+ }
1778
+ });
1779
+ //#endregion
1780
+ //#region src/rules/no-resource-api/index.ts
1781
+ const RESOURCE_APIS = new Set(["resource"]);
1782
+ const RXJS_RESOURCE_APIS = new Set(["rxResource"]);
1783
+ const HTTP_RESOURCE_APIS = new Set(["httpResource"]);
1784
+ function isResourceApiCall(context, callNode) {
1785
+ const callee = callNode.callee;
1786
+ if (isImportedReference(context, callee, "@angular/core", RESOURCE_APIS)) return true;
1787
+ if (isImportedNamespaceMember(context, callee, "@angular/core", RESOURCE_APIS)) return true;
1788
+ if (isImportedReference(context, callee, "@angular/core/rxjs-interop", RXJS_RESOURCE_APIS)) return true;
1789
+ if (isImportedNamespaceMember(context, callee, "@angular/core/rxjs-interop", RXJS_RESOURCE_APIS)) return true;
1790
+ if (isImportedReference(context, callee, "@angular/common/http", HTTP_RESOURCE_APIS)) return true;
1791
+ if (isImportedNamespaceMember(context, callee, "@angular/common/http", HTTP_RESOURCE_APIS)) return true;
1792
+ 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;
1793
+ return callee?.type === "MemberExpression" && isImportedReference(context, callee.object, "@angular/common/http", HTTP_RESOURCE_APIS);
1794
+ }
1795
+ const noResourceApi = defineRule({
1796
+ meta: {
1797
+ type: "suggestion",
1798
+ docs: {
1799
+ description: "Disallow Angular resource APIs in favor of a dedicated server-state library.",
1800
+ recommended: true
1801
+ },
1802
+ schema: [],
1803
+ messages: { noResourceApi: "Avoid Angular resource APIs for server state. Prefer a dedicated server-state helper such as TanStack Query." }
1804
+ },
1805
+ createOnce(context) {
1806
+ return { CallExpression(node) {
1807
+ const callNode = node;
1808
+ if (!isResourceApiCall(context, callNode)) return;
1809
+ context.report({
1810
+ node: callNode.callee ?? callNode,
1811
+ messageId: "noResourceApi"
1812
+ });
1813
+ } };
1814
+ }
1815
+ });
1816
+ //#endregion
1817
+ //#region src/rules/no-route-resolvers/index.ts
1818
+ const ROUTE_TYPE_NAMES$1 = new Set(["Route"]);
1819
+ const ROUTES_TYPE_NAMES$1 = new Set(["Routes"]);
1820
+ function isImportedTypeName$1(context, typeNode, importedNames) {
1821
+ if (typeNode?.type === "Identifier") {
1822
+ const importedName = getImportedName(context, typeNode, "@angular/router");
1823
+ return !!importedName && importedNames.has(importedName);
1824
+ }
1825
+ return typeNode?.type === "TSQualifiedName" && typeNode.left?.type === "Identifier" && isNamespaceImport(context, typeNode.left, "@angular/router") && importedNames.has(getPropertyName(typeNode.right) ?? "");
1826
+ }
1827
+ function getTypeParameterNodes$1(typeNode) {
1828
+ return typeNode.typeParameters?.params ?? typeNode.typeArguments?.params ?? [];
1829
+ }
1830
+ function isRouteType$1(context, typeNode) {
1831
+ return typeNode?.type === "TSTypeReference" && isImportedTypeName$1(context, typeNode.typeName, ROUTE_TYPE_NAMES$1);
1832
+ }
1833
+ function isRouteArrayType$1(context, typeNode) {
1834
+ if (!typeNode) return false;
1835
+ if (typeNode.type === "TSTypeReference" && isImportedTypeName$1(context, typeNode.typeName, ROUTES_TYPE_NAMES$1)) return true;
1836
+ if (typeNode.type === "TSArrayType") return isRouteType$1(context, typeNode.elementType);
1837
+ if (typeNode.type !== "TSTypeReference") return false;
1838
+ if (getPropertyName(typeNode.typeName) !== "Array" && getPropertyName(typeNode.typeName) !== "ReadonlyArray") return false;
1839
+ const [elementType] = getTypeParameterNodes$1(typeNode);
1840
+ return isRouteType$1(context, elementType);
1841
+ }
1842
+ function getTypeAnnotation(node) {
1843
+ const typeAnnotation = node?.typeAnnotation;
1844
+ return typeAnnotation?.type === "TSTypeAnnotation" ? typeAnnotation.typeAnnotation : null;
1845
+ }
1846
+ const noRouteResolvers = defineRule({
1847
+ meta: {
1848
+ type: "suggestion",
1849
+ docs: {
1850
+ description: "Disallow Angular route resolvers in typed Route/Routes declarations.",
1851
+ recommended: true
1852
+ },
1853
+ schema: [],
1854
+ messages: { noRouteResolvers: "Avoid Angular route resolvers for data loading. Prefer component-level loading with signals or server-state helpers." }
1855
+ },
1856
+ createOnce(context) {
1857
+ function reportResolveInRouteObject(routeObject) {
1858
+ for (const property of routeObject.properties ?? []) {
1859
+ if (property.type !== "Property" || property.computed) continue;
1860
+ const propertyName = getPropertyName(property.key);
1861
+ if (propertyName === "resolve") {
1862
+ context.report({
1863
+ node: property.key ?? property,
1864
+ messageId: "noRouteResolvers"
1865
+ });
1866
+ continue;
1867
+ }
1868
+ if (propertyName === "children" && property.value?.type === "ArrayExpression") reportResolveInRouteArray(property.value);
1869
+ }
1870
+ }
1871
+ function reportResolveInRouteArray(routeArray) {
1872
+ for (const element of routeArray.elements ?? []) if (element?.type === "ObjectExpression") reportResolveInRouteObject(element);
1873
+ }
1874
+ return { VariableDeclarator(node) {
1875
+ const declarator = node;
1876
+ const typeNode = getTypeAnnotation(declarator.id);
1877
+ if (isRouteArrayType$1(context, typeNode) && declarator.init?.type === "ArrayExpression") {
1878
+ reportResolveInRouteArray(declarator.init);
1879
+ return;
1880
+ }
1881
+ if (isRouteType$1(context, typeNode) && declarator.init?.type === "ObjectExpression") reportResolveInRouteObject(declarator.init);
1882
+ } };
1883
+ }
1884
+ });
1885
+ //#endregion
1886
+ //#region src/rules/no-ui-inheritance/index.ts
1887
+ const UI_DECORATORS = new Set(["Component", "Directive"]);
1888
+ function hasUiDecorator(context, classNode) {
1889
+ if (!Array.isArray(classNode.decorators)) return false;
1890
+ return classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, UI_DECORATORS));
1891
+ }
1892
+ const noUiInheritance = defineRule({
1893
+ meta: {
1894
+ type: "suggestion",
1895
+ docs: {
1896
+ description: "Disallow inheritance for Angular components and directives.",
1897
+ recommended: true
1898
+ },
1899
+ schema: [],
1900
+ messages: { noUiInheritance: "Avoid inheritance for Angular {{kind}} classes. Prefer composition with services or inject* helpers." }
1901
+ },
1902
+ createOnce(context) {
1903
+ return { "ClassDeclaration, ClassExpression"(node) {
1904
+ const classNode = node;
1905
+ if (!classNode.superClass) return;
1906
+ if (!hasUiDecorator(context, classNode)) return;
1907
+ const kind = classNode.decorators.some((decorator) => isAngularCoreDecorator(context, decorator, new Set(["Component"]))) ? "component" : "directive";
1908
+ context.report({
1909
+ node: classNode.superClass,
1910
+ messageId: "noUiInheritance",
1911
+ data: { kind }
1912
+ });
1913
+ } };
1914
+ }
1915
+ });
1916
+ //#endregion
1685
1917
  //#region src/rules/prefer-load-component-over-load-children/index.ts
1686
1918
  const ROUTE_TYPE_NAMES = new Set(["Route"]);
1687
1919
  const ROUTES_TYPE_NAMES = new Set(["Routes"]);
@@ -2119,7 +2351,7 @@ const restrictInjectableProvidedIn = defineRule({
2119
2351
  //#endregion
2120
2352
  //#region src/rules/rules-of-inject/index.ts
2121
2353
  const DEFAULT_ALLOWED_FUNCTION_NAMES = [];
2122
- const DEFAULT_INJECT_FUNCTION_PREFIXES = ["injext"];
2354
+ const DEFAULT_INJECT_FUNCTION_PREFIXES = ["inject"];
2123
2355
  const DEFAULT_INJECT_FUNCTION_SUFFIXES = ["Guard"];
2124
2356
  const DEFAULT_RUNS_IN_INJECTION_CONTEXT = [];
2125
2357
  const ROUTER_CONTEXT_PROPERTY_NAMES = new Set([
@@ -2336,6 +2568,7 @@ const plugin = eslintCompatPlugin({
2336
2568
  rules: {
2337
2569
  "avoid-explicit-injection-context": avoidExplicitInjectionContext,
2338
2570
  "avoid-explicit-subscription-management": avoidExplicitSubscriptionManagement,
2571
+ "avoid-inappropriate-intimacy": avoidInappropriateIntimacy,
2339
2572
  "avoid-ng-modules": avoidNgModules,
2340
2573
  "avoid-rxjs-state-in-component": avoidRxjsStateInComponent,
2341
2574
  "avoid-writing-signals-in-reactive-context": avoidWritingSignalsInReactiveContext,
@@ -2343,6 +2576,10 @@ const plugin = eslintCompatPlugin({
2343
2576
  "class-matches-filename": classMatchesFilename,
2344
2577
  "component-resource-filenames": componentResourceFilenames,
2345
2578
  "decorator-filename-suffix": decoratorFilenameSuffix,
2579
+ "injects-tanstack-query-only-in-component-body": injectsTanstackQueryOnlyInComponentBody,
2580
+ "no-resource-api": noResourceApi,
2581
+ "no-route-resolvers": noRouteResolvers,
2582
+ "no-ui-inheritance": noUiInheritance,
2346
2583
  "prefer-load-component-over-load-children": preferLoadComponentOverLoadChildren,
2347
2584
  "prefer-private-elements": preferPrivateElements,
2348
2585
  "prefer-style-url": preferStyleUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benjavicente/lint-angular",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Oxlint/ESLint-compatible rules for Angular.",
5
5
  "keywords": [
6
6
  "angular",
@@ -24,20 +24,20 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "scripts": {
28
- "build": "vp pack",
29
- "fmt": "vp fmt src",
30
- "lint": "vp lint src",
31
- "test": "vp test run"
32
- },
33
27
  "dependencies": {
34
28
  "@oxlint/plugins": "^1.63.0"
35
29
  },
36
30
  "devDependencies": {
37
31
  "@types/estree": "^1.0.9",
38
- "oxlint-vitest-rule-tester": "workspace:*",
39
- "typescript": "catalog:",
40
- "vite-plus": "catalog:",
41
- "vitest": "catalog:"
32
+ "typescript": "^6.0.3",
33
+ "vite-plus": "^0.1.20",
34
+ "vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.20",
35
+ "oxlint-vitest-rule-tester": "0.0.1"
36
+ },
37
+ "scripts": {
38
+ "build": "vp pack",
39
+ "fmt": "vp fmt src",
40
+ "lint": "vp lint src",
41
+ "test": "vp test run"
42
42
  }
43
- }
43
+ }