@angular-eslint/eslint-plugin 22.0.1-alpha.9 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,7 @@ The premade flat configs that combine these rules are provided by the umbrella [
23
23
  | --- | --- | --- | --- | --- |
24
24
  | [`computed-must-return`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/computed-must-return.md) | Ensures that computed() returns a value. Omitting the value is likely a mistake. | | | |
25
25
  | [`contextual-lifecycle`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/contextual-lifecycle.md) | Ensures that lifecycle methods are used in a correct context | :white_check_mark: | | |
26
+ | [`inject-at-top`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/inject-at-top.md) | Requires inject() calls to be declared at the top of the class, before any other member | | | |
26
27
  | [`no-async-lifecycle-method`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/no-async-lifecycle-method.md) | Angular Lifecycle methods should not be async. Angular does not wait for async lifecycle but the code incorrectly suggests it does. | | | |
27
28
  | [`no-attribute-decorator`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/no-attribute-decorator.md) | The @Attribute decorator is used to obtain a single value for an attribute. This is a much less common use case than getting a stream of values (using @Input), so the @Attribute decorator is often mistakenly used when @Input is intended. This rule disallows the usage of @Attribute decorator entirely to prevent these mistakes. | | | |
28
29
  | [`no-developer-preview`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/no-developer-preview.md) | Disallow using code which is marked as developer preview | | | |
@@ -74,6 +75,7 @@ The premade flat configs that combine these rules are provided by the umbrella [
74
75
  | [`prefer-on-push-component-change-detection`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-on-push-component-change-detection.md) | Ensures components do not opt out of the default `ChangeDetectionStrategy.OnPush` change detection strategy | :white_check_mark: | | :bulb: |
75
76
  | [`prefer-output-emitter-ref`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-output-emitter-ref.md) | Use `OutputEmitterRef` instead of `@Output()` | | | |
76
77
  | [`prefer-output-readonly`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-output-readonly.md) | Prefer to declare `@Output`, `OutputEmitterRef` and `OutputRef` as `readonly` since they are not supposed to be reassigned | | | :bulb: |
78
+ | [`prefer-service-decorator`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-service-decorator.md) | Prefer the `@Service()` decorator over `@Injectable({ providedIn: 'root' })` | | :wrench: | |
77
79
  | [`prefer-signal-model`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-signal-model.md) | Use `model` instead of `input` and `output` for two-way bindings | | :wrench: | |
78
80
  | [`prefer-signals`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-signals.md) | Use readonly signals instead of `@Input()`, `@ViewChild()` and other legacy decorators | | :wrench: | |
79
81
  | [`prefer-standalone`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-standalone.md) | Ensures Components, Directives and Pipes do not opt out of standalone. | :white_check_mark: | | :bulb: |
package/dist/index.d.ts CHANGED
@@ -27,6 +27,9 @@ declare const _default: {
27
27
  "directive-selector": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/directive-selector").MessageIds, import("./rules/directive-selector").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
28
28
  name: string;
29
29
  };
30
+ "inject-at-top": import("@typescript-eslint/utils/ts-eslint").RuleModule<"injectAtTop", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
31
+ name: string;
32
+ };
30
33
  "no-async-lifecycle-method": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noAsyncLifecycleMethod", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
31
34
  name: string;
32
35
  };
@@ -102,6 +105,9 @@ declare const _default: {
102
105
  "prefer-output-readonly": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-output-readonly").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
103
106
  name: string;
104
107
  };
108
+ "prefer-service-decorator": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferServiceDecorator", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
109
+ name: string;
110
+ };
105
111
  "prefer-signal-model": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSignalModel", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
106
112
  name: string;
107
113
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiJA,kBAoDE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuJA,kBAsDE"}
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ const prefer_host_metadata_property_1 = __importStar(require("./rules/prefer-hos
61
61
  const prefer_on_push_component_change_detection_1 = __importStar(require("./rules/prefer-on-push-component-change-detection"));
62
62
  const prefer_output_emitter_ref_1 = __importStar(require("./rules/prefer-output-emitter-ref"));
63
63
  const prefer_output_readonly_1 = __importStar(require("./rules/prefer-output-readonly"));
64
+ const prefer_service_decorator_1 = __importStar(require("./rules/prefer-service-decorator"));
64
65
  const prefer_signal_model_1 = __importStar(require("./rules/prefer-signal-model"));
65
66
  const prefer_inject_1 = __importStar(require("./rules/prefer-inject"));
66
67
  const prefer_signals_1 = __importStar(require("./rules/prefer-signals"));
@@ -80,6 +81,7 @@ const no_experimental_1 = __importStar(require("./rules/no-experimental"));
80
81
  const no_developer_preview_1 = __importStar(require("./rules/no-developer-preview"));
81
82
  const no_implicit_take_until_destroyed_1 = __importStar(require("./rules/no-implicit-take-until-destroyed"));
82
83
  const computed_must_return_1 = __importStar(require("./rules/computed-must-return"));
84
+ const inject_at_top_1 = __importStar(require("./rules/inject-at-top"));
83
85
  module.exports = {
84
86
  rules: {
85
87
  [component_class_suffix_1.RULE_NAME]: component_class_suffix_1.default,
@@ -91,6 +93,7 @@ module.exports = {
91
93
  [contextual_lifecycle_1.RULE_NAME]: contextual_lifecycle_1.default,
92
94
  [directive_class_suffix_1.RULE_NAME]: directive_class_suffix_1.default,
93
95
  [directive_selector_1.RULE_NAME]: directive_selector_1.default,
96
+ [inject_at_top_1.RULE_NAME]: inject_at_top_1.default,
94
97
  [no_async_lifecycle_method_1.RULE_NAME]: no_async_lifecycle_method_1.default,
95
98
  [no_attribute_decorator_1.RULE_NAME]: no_attribute_decorator_1.default,
96
99
  [no_developer_preview_1.RULE_NAME]: no_developer_preview_1.default,
@@ -116,6 +119,7 @@ module.exports = {
116
119
  [prefer_on_push_component_change_detection_1.RULE_NAME]: prefer_on_push_component_change_detection_1.default,
117
120
  [prefer_output_emitter_ref_1.RULE_NAME]: prefer_output_emitter_ref_1.default,
118
121
  [prefer_output_readonly_1.RULE_NAME]: prefer_output_readonly_1.default,
122
+ [prefer_service_decorator_1.RULE_NAME]: prefer_service_decorator_1.default,
119
123
  [prefer_signal_model_1.RULE_NAME]: prefer_signal_model_1.default,
120
124
  [prefer_signals_1.RULE_NAME]: prefer_signals_1.default,
121
125
  [prefer_standalone_1.RULE_NAME]: prefer_standalone_1.default,
@@ -1 +1 @@
1
- {"version":3,"file":"consistent-component-styles.d.ts","sourceRoot":"","sources":["../../src/rules/consistent-component-styles.ts"],"names":[],"mappings":"AAIA,KAAK,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC/B,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,aAAa,GACb,cAAc,CAAC;AACnB,eAAO,MAAM,SAAS,gCAAgC,CAAC;;;;AAEvD,wBAsHG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"consistent-component-styles.d.ts","sourceRoot":"","sources":["../../src/rules/consistent-component-styles.ts"],"names":[],"mappings":"AAIA,KAAK,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC/B,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,MAAM,MAAM,UAAU,GACpB,gBAAgB,GAAG,iBAAiB,GAAG,aAAa,GAAG,cAAc,CAAC;AACxE,eAAO,MAAM,SAAS,gCAAgC,CAAC;;;;AAEvD,wBAsHG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'injectAtTop';
3
+ export declare const RULE_NAME = "inject-at-top";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"injectAtTop", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export default _default;
8
+ export declare const RULE_DOCS_EXTENSION: {
9
+ rationale: string;
10
+ };
11
+ //# sourceMappingURL=inject-at-top.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-at-top.d.ts","sourceRoot":"","sources":["../../src/rules/inject-at-top.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AACvC,eAAO,MAAM,SAAS,kBAAkB,CAAC;;;;AAEzC,wBAyFG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@angular-eslint/utils");
5
+ const utils_2 = require("@typescript-eslint/utils");
6
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ exports.RULE_NAME = 'inject-at-top';
8
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
+ name: exports.RULE_NAME,
10
+ meta: {
11
+ type: 'problem',
12
+ docs: {
13
+ description: 'Requires inject() calls to be declared at the top of the class, before any other member',
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ injectAtTop: 'Move this inject() to the top of the class. Class fields are initialized in the order they are written, so anything declared above this line cannot safely use the service yet.',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const angularDecoratorsPattern = (0, utils_1.toPattern)([
23
+ 'Component',
24
+ 'Directive',
25
+ 'Injectable',
26
+ 'Pipe',
27
+ 'Service',
28
+ ]);
29
+ function hasEagerInject(node) {
30
+ if (!node) {
31
+ return false;
32
+ }
33
+ if (node.type === utils_2.AST_NODE_TYPES.CallExpression &&
34
+ node.callee.type === utils_2.AST_NODE_TYPES.Identifier &&
35
+ node.callee.name === 'inject') {
36
+ return true;
37
+ }
38
+ const isFn = node.type === utils_2.AST_NODE_TYPES.ArrowFunctionExpression ||
39
+ node.type === utils_2.AST_NODE_TYPES.FunctionExpression;
40
+ const isIIFE = isFn &&
41
+ node.parent?.type === utils_2.AST_NODE_TYPES.CallExpression &&
42
+ node.parent.callee === node;
43
+ if (node.type === utils_2.AST_NODE_TYPES.ClassExpression || (isFn && !isIIFE)) {
44
+ return false;
45
+ }
46
+ return Object.values(node)
47
+ .flat()
48
+ .some((child) => child &&
49
+ typeof child === 'object' &&
50
+ 'type' in child &&
51
+ child !== node.parent &&
52
+ hasEagerInject(child));
53
+ }
54
+ return {
55
+ [`${utils_1.Selectors.decoratorDefinition(angularDecoratorsPattern)} > ClassBody`](node) {
56
+ let seenNonInject = false;
57
+ for (const member of node.body) {
58
+ if ('static' in member && member.static) {
59
+ continue;
60
+ }
61
+ if (member.type === utils_2.AST_NODE_TYPES.PropertyDefinition &&
62
+ hasEagerInject(member.value)) {
63
+ if (seenNonInject) {
64
+ context.report({ node: member, messageId: 'injectAtTop' });
65
+ }
66
+ continue;
67
+ }
68
+ seenNonInject = true;
69
+ }
70
+ },
71
+ };
72
+ },
73
+ });
74
+ exports.RULE_DOCS_EXTENSION = {
75
+ rationale: "Class fields are initialized in the order they are declared, so a field that calls inject() only creates its value when the initializer runs. Anything declared above that line which tries to read the injected service will see undefined, and the error message (usually something like 'Cannot read properties of undefined') points at the consumer, not at the field that was initialized out of order. TypeScript catches the obvious shape of this bug when a later-declared field is referenced directly from an earlier initializer, but it does not trace through a getter body or a method call, so the moment the read of this.someService happens behind that indirection the compiler lets it through and the failure only shows up at runtime. The scenario is deceptively easy to introduce: a getter that reads an injected service is defined below the inject() call, someone later adds a small convenience field above the getter that references it, and the class breaks at construction time even though nothing about the change looks suspicious. Keeping every inject() call at the very top of the class removes the ordering concern entirely, because every dependency is guaranteed to exist before any other field, getter, method, or constructor statement runs.",
76
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"no-empty-lifecycle-method.d.ts","sourceRoot":"","sources":["../../src/rules/no-empty-lifecycle-method.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAClB,wBAAwB,GACxB,8BAA8B,CAAC;AACnC,eAAO,MAAM,SAAS,8BAA8B,CAAC;;;;AAErD,wBAuFG;AAYH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"no-empty-lifecycle-method.d.ts","sourceRoot":"","sources":["../../src/rules/no-empty-lifecycle-method.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GACpB,wBAAwB,GAAG,8BAA8B,CAAC;AAC5D,eAAO,MAAM,SAAS,8BAA8B,CAAC;;;;AAErD,wBAuFG;AAYH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-on-push-component-change-detection.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-on-push-component-change-detection.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAClB,sCAAsC,GACtC,8BAA8B,CAAC;AACnC,eAAO,MAAM,SAAS,8CAA8C,CAAC;;;;AAKrE,wBAoEG;AAEH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
1
+ {"version":3,"file":"prefer-on-push-component-change-detection.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-on-push-component-change-detection.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GACpB,sCAAsC,GAAG,8BAA8B,CAAC;AAC1E,eAAO,MAAM,SAAS,8CAA8C,CAAC;;;;AAKrE,wBAoEG;AAEH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'preferServiceDecorator';
3
+ export declare const RULE_NAME = "prefer-service-decorator";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferServiceDecorator", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export default _default;
8
+ export declare const RULE_DOCS_EXTENSION: {
9
+ rationale: string;
10
+ };
11
+ //# sourceMappingURL=prefer-service-decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-service-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-service-decorator.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAElD,eAAO,MAAM,SAAS,6BAA6B,CAAC;;;;AAEpD,wBAwFG;AA0DH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@angular-eslint/utils");
5
+ const utils_2 = require("@typescript-eslint/utils");
6
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ exports.RULE_NAME = 'prefer-service-decorator';
8
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
+ name: exports.RULE_NAME,
10
+ meta: {
11
+ type: 'suggestion',
12
+ docs: {
13
+ description: "Prefer the `@Service()` decorator over `@Injectable({ providedIn: 'root' })`",
14
+ },
15
+ fixable: 'code',
16
+ schema: [],
17
+ messages: {
18
+ preferServiceDecorator: "Use the `@Service()` decorator instead of `@Injectable({ providedIn: 'root' })`",
19
+ },
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ return {
24
+ [utils_1.Selectors.INJECTABLE_CLASS_DECORATOR](node) {
25
+ const { expression } = node;
26
+ if (!utils_1.ASTUtils.isCallExpression(expression) ||
27
+ expression.arguments.length !== 1) {
28
+ return;
29
+ }
30
+ const [argument] = expression.arguments;
31
+ if (!utils_1.ASTUtils.isObjectExpression(argument)) {
32
+ return;
33
+ }
34
+ const migration = getMetadataMigration(argument);
35
+ if (!migration) {
36
+ return;
37
+ }
38
+ context.report({
39
+ node,
40
+ messageId: 'preferServiceDecorator',
41
+ fix: (fixer) => {
42
+ const fixes = [
43
+ // `@Injectable(...)` => `@Service(...)`.
44
+ fixer.replaceText(expression.callee, 'Service'),
45
+ // Migrate the `Injectable` import to `Service`.
46
+ ...utils_1.RuleFixes.getImportReplaceFix({
47
+ fixer,
48
+ fromName: 'Injectable',
49
+ moduleName: '@angular/core',
50
+ node,
51
+ sourceCode: context.sourceCode,
52
+ toName: 'Service',
53
+ }),
54
+ ];
55
+ switch (migration.type) {
56
+ // Only `providedIn: 'root'`, so drop the whole metadata object.
57
+ case 'drop-metadata': {
58
+ fixes.push(fixer.remove(argument));
59
+ break;
60
+ }
61
+ // Keep the factory, but rename `useFactory` => `factory` and drop
62
+ // `providedIn: 'root'` (which `@Service()` implies).
63
+ case 'rewrite-factory': {
64
+ fixes.push(fixer.replaceText(migration.useFactoryProperty.key, 'factory'), utils_1.RuleFixes.getNodeToCommaRemoveFix(context.sourceCode, migration.providedInProperty, fixer));
65
+ break;
66
+ }
67
+ }
68
+ return fixes.filter(utils_1.isNotNullOrUndefined);
69
+ },
70
+ });
71
+ },
72
+ };
73
+ },
74
+ });
75
+ /**
76
+ * `@Service()` only models `providedIn: 'root'` (implicit) and accepts a
77
+ * `factory` (which maps from `useFactory`). Any other provider metadata
78
+ * (`useClass`, `useExisting`, `useValue`, `deps`, ...) has no equivalent, so we
79
+ * bail out and leave such `@Injectable`s untouched for now.
80
+ */
81
+ function getMetadataMigration(argument) {
82
+ const properties = argument.properties.filter((property) => property.type === utils_2.AST_NODE_TYPES.Property);
83
+ const providedInProperty = properties.find((property) => utils_2.ASTUtils.getPropertyName(property) === 'providedIn');
84
+ const useFactoryProperty = properties.find((property) => utils_2.ASTUtils.getPropertyName(property) === 'useFactory');
85
+ // Bail out unless the only metadata is `providedIn: 'root'` plus an optional
86
+ // `useFactory`.
87
+ if (!providedInProperty ||
88
+ utils_2.ASTUtils.getStaticValue(providedInProperty.value)?.value !==
89
+ 'root' ||
90
+ properties.length !==
91
+ [providedInProperty, useFactoryProperty].filter(utils_1.isNotNullOrUndefined)
92
+ .length) {
93
+ return undefined;
94
+ }
95
+ return useFactoryProperty
96
+ ? { type: 'rewrite-factory', providedInProperty, useFactoryProperty }
97
+ : { type: 'drop-metadata' };
98
+ }
99
+ exports.RULE_DOCS_EXTENSION = {
100
+ rationale: "Angular 22 introduced the `@Service()` decorator as a concise shorthand for `@Injectable({ providedIn: 'root' })`, the most common way to declare a tree-shakable, application-wide service. Using `@Service()` removes the boilerplate of the metadata object while expressing the exact same intent. Because `providedIn: 'root'` is exactly what `@Service()` implies, this migration is safe and behavior-preserving: the `providedIn: 'root'` entry is dropped, and a `useFactory` is preserved by renaming it to `@Service()`'s `factory` option. Services that rely on other provider metadata (`useClass`, `useExisting`, `useValue`, `deps`) or a different `providedIn` value (`'platform'`, `'any'`, a specific module) have no `@Service()` equivalent and are intentionally left untouched.",
101
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"use-component-view-encapsulation.d.ts","sourceRoot":"","sources":["../../src/rules/use-component-view-encapsulation.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAClB,+BAA+B,GAC/B,oCAAoC,CAAC;AACzC,eAAO,MAAM,SAAS,qCAAqC,CAAC;;;;AAG5D,wBAwDG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"use-component-view-encapsulation.d.ts","sourceRoot":"","sources":["../../src/rules/use-component-view-encapsulation.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GACpB,+BAA+B,GAAG,oCAAoC,CAAC;AACzE,eAAO,MAAM,SAAS,qCAAqC,CAAC;;;;AAG5D,wBAwDG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin",
3
- "version": "22.0.1-alpha.9",
3
+ "version": "22.1.0",
4
4
  "description": "ESLint plugin for Angular applications, following https://angular.dev/style-guide",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,7 @@
10
10
  "url": "https://github.com/angular-eslint/angular-eslint.git",
11
11
  "directory": "packages/eslint-plugin"
12
12
  },
13
+ "bugs": "https://github.com/angular-eslint/angular-eslint/issues",
13
14
  "files": [
14
15
  "dist",
15
16
  "!**/*.tsbuildinfo",
@@ -19,11 +20,11 @@
19
20
  ],
20
21
  "dependencies": {
21
22
  "ts-api-utils": "^2.1.0",
22
- "@angular-eslint/bundled-angular-compiler": "22.0.1-alpha.9",
23
- "@angular-eslint/utils": "22.0.1-alpha.9"
23
+ "@angular-eslint/bundled-angular-compiler": "22.1.0",
24
+ "@angular-eslint/utils": "22.1.0"
24
25
  },
25
26
  "devDependencies": {
26
- "@angular-eslint/test-utils": "22.0.1-alpha.9"
27
+ "@angular-eslint/test-utils": "22.1.0"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "@typescript-eslint/utils": "^8.0.0",