@depup/angular-eslint__eslint-plugin 22.4.0-depup.0 → 22.6.0-depup.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
@@ -13,8 +13,8 @@ npm install @depup/angular-eslint__eslint-plugin
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) @ 22.4.0 |
17
- | Processed | 2026-09-07 |
16
+ | Original | [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) @ 22.6.0 |
17
+ | Processed | 2026-10-11 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 1 |
20
20
 
package/changes.json CHANGED
@@ -5,6 +5,6 @@
5
5
  "to": "^2.5.0"
6
6
  }
7
7
  },
8
- "timestamp": "2026-09-07T16:09:04.652Z",
8
+ "timestamp": "2026-10-11T16:09:47.801Z",
9
9
  "totalUpdated": 1
10
10
  }
package/dist/index.d.ts CHANGED
@@ -122,7 +122,7 @@ declare const _default: {
122
122
  }], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
123
123
  name: string;
124
124
  };
125
- "prefer-standalone": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-standalone").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
125
+ "prefer-standalone": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-standalone").MessageIds, import("./rules/prefer-standalone").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
126
126
  name: string;
127
127
  };
128
128
  "reactive-context-must-read-signal": import("@typescript-eslint/utils/ts-eslint").RuleModule<"mustReadSignal", import("./rules/reactive-context-must-read-signal").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
@@ -140,7 +140,7 @@ declare const _default: {
140
140
  "runtime-localize": import("@typescript-eslint/utils/ts-eslint").RuleModule<"runtimeLocalize", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
141
141
  name: string;
142
142
  };
143
- "sort-keys-in-type-decorator": import("@typescript-eslint/utils/ts-eslint").RuleModule<"incorrectOrder", import("./rules/sort-keys-in-type-decorator").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
143
+ "sort-keys-in-type-decorator": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/sort-keys-in-type-decorator").MessageIds, import("./rules/sort-keys-in-type-decorator").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
144
144
  name: string;
145
145
  };
146
146
  "sort-lifecycle-methods": import("@typescript-eslint/utils/ts-eslint").RuleModule<"lifecycleMethodsNotSorted", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
@@ -1 +1 @@
1
- {"version":3,"file":"component-selector.d.ts","sourceRoot":"","sources":["../../src/rules/component-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACd,MAAM,uBAAuB,CAAC;AAK/B,MAAM,MAAM,OAAO,GAAG,SAAS;IAC7B,aAAa,CAAC,kBAAkB,GAAG,aAAa,CAAC,oBAAoB;CACtE,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,cAAc,GACd,uBAAuB,GACvB,aAAa,GACb,mCAAmC,GACnC,4BAA4B,CAAC;AACjC,eAAO,MAAM,SAAS,uBAAuB,CAAC;;;;AAS9C,wBAgOG;AAiBH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"component-selector.d.ts","sourceRoot":"","sources":["../../src/rules/component-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACd,MAAM,uBAAuB,CAAC;AAK/B,MAAM,MAAM,OAAO,GAAG,SAAS;IAC7B,aAAa,CAAC,kBAAkB,GAAG,aAAa,CAAC,oBAAoB;CACtE,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,cAAc,GACd,uBAAuB,GACvB,aAAa,GACb,mCAAmC,GACnC,4BAA4B,CAAC;AACjC,eAAO,MAAM,SAAS,uBAAuB,CAAC;;;;AAS9C,wBAsOG;AAiBH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -39,7 +39,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
39
39
  ],
40
40
  },
41
41
  prefix: {
42
- oneOf: [{ type: 'string' }, { type: 'array' }],
42
+ oneOf: [
43
+ { type: 'string' },
44
+ { type: 'array', items: { type: 'string' } },
45
+ ],
43
46
  },
44
47
  style: {
45
48
  type: 'string',
@@ -66,7 +69,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
66
69
  ],
67
70
  },
68
71
  prefix: {
69
- oneOf: [{ type: 'string' }, { type: 'array' }],
72
+ oneOf: [
73
+ { type: 'string' },
74
+ { type: 'array', items: { type: 'string' } },
75
+ ],
70
76
  },
71
77
  style: {
72
78
  type: 'string',
@@ -1 +1 @@
1
- {"version":3,"file":"directive-selector.d.ts","sourceRoot":"","sources":["../../src/rules/directive-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACd,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,OAAO,GAAG,SAAS;IAC7B,aAAa,CAAC,kBAAkB,GAAG,aAAa,CAAC,oBAAoB;CACtE,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,cAAc,GACd,aAAa,GACb,4BAA4B,CAAC;AACjC,eAAO,MAAM,SAAS,uBAAuB,CAAC;;;;AAE9C,wBA6KG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"directive-selector.d.ts","sourceRoot":"","sources":["../../src/rules/directive-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACd,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,OAAO,GAAG,SAAS;IAC7B,aAAa,CAAC,kBAAkB,GAAG,aAAa,CAAC,oBAAoB;CACtE,CAAC;AACF,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,cAAc,GACd,aAAa,GACb,4BAA4B,CAAC;AACjC,eAAO,MAAM,SAAS,uBAAuB,CAAC;;;;AAE9C,wBAmLG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -34,7 +34,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
34
34
  ],
35
35
  },
36
36
  prefix: {
37
- oneOf: [{ type: 'string' }, { type: 'array' }],
37
+ oneOf: [
38
+ { type: 'string' },
39
+ { type: 'array', items: { type: 'string' } },
40
+ ],
38
41
  },
39
42
  style: {
40
43
  type: 'string',
@@ -61,7 +64,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
61
64
  ],
62
65
  },
63
66
  prefix: {
64
- oneOf: [{ type: 'string' }, { type: 'array' }],
67
+ oneOf: [
68
+ { type: 'string' },
69
+ { type: 'array', items: { type: 'string' } },
70
+ ],
65
71
  },
66
72
  style: {
67
73
  type: 'string',
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-signal-model.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-signal-model.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvE,KAAK,OAAO,GAAG;IACb;QACE,eAAe,EAAE,OAAO,CAAC;KAC1B;CACF,CAAC;AAMF,MAAM,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAC7C,eAAO,MAAM,SAAS,wBAAwB,CAAC;;;;AA8B/C,wBAuKG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"prefer-signal-model.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-signal-model.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvE,KAAK,OAAO,GAAG;IACb;QACE,eAAe,EAAE,OAAO,CAAC;KAC1B;CACF,CAAC;AAMF,MAAM,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAC7C,eAAO,MAAM,SAAS,wBAAwB,CAAC;;;;AA+C/C,wBAiMG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -12,6 +12,18 @@ const DEFAULT_OPTIONS = {
12
12
  useTypeChecking: false,
13
13
  };
14
14
  exports.RULE_NAME = 'prefer-signal-model';
15
+ function getEmitCallee(reference) {
16
+ const { parent } = reference;
17
+ return parent?.type === utils_2.AST_NODE_TYPES.MemberExpression &&
18
+ parent.object === reference &&
19
+ !parent.computed &&
20
+ parent.property.type === utils_2.AST_NODE_TYPES.Identifier &&
21
+ parent.property.name === 'emit' &&
22
+ parent.parent?.type === utils_2.AST_NODE_TYPES.CallExpression &&
23
+ parent.parent.callee === parent
24
+ ? parent
25
+ : undefined;
26
+ }
15
27
  function hasTransformOption(options) {
16
28
  return (options?.type === utils_2.AST_NODE_TYPES.ObjectExpression &&
17
29
  options.properties.some((property) => property.type === utils_2.AST_NODE_TYPES.Property &&
@@ -49,6 +61,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
49
61
  const { sourceCode } = context;
50
62
  const inputs = new Map();
51
63
  const outputs = new Map();
64
+ const thisReferences = new Map();
52
65
  let services;
53
66
  function getTypeServices() {
54
67
  return (services ??= utils_2.ESLintUtils.getParserServices(context));
@@ -114,32 +127,49 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
114
127
  "PropertyDefinition > CallExpression[callee.name='input']": createSignalCollector(inputs, { hasInitialValueArgument: true }),
115
128
  "PropertyDefinition > CallExpression[callee.object.name='input'][callee.property.name='required']": createSignalCollector(inputs, { hasInitialValueArgument: false }),
116
129
  "PropertyDefinition > CallExpression[callee.name='output']": createSignalCollector(outputs, { hasInitialValueArgument: false }),
130
+ 'MemberExpression[object.type="ThisExpression"][computed=false][property.type="Identifier"]'(node) {
131
+ const { name } = node.property;
132
+ const references = thisReferences.get(name);
133
+ if (references) {
134
+ references.push(node);
135
+ }
136
+ else {
137
+ thisReferences.set(name, [node]);
138
+ }
139
+ },
117
140
  'ClassDeclaration:exit'() {
118
141
  const twoWayBindings = [...inputs]
119
142
  .map(([name, input]) => ({
143
+ name,
120
144
  input,
121
145
  output: outputs.get(`${name}Change`),
122
146
  }))
123
147
  .filter((binding) => binding.output !== undefined &&
124
148
  haveMergeableTypes(binding.input, binding.output));
125
- for (const { input, output } of twoWayBindings) {
149
+ for (const { name, input, output } of twoWayBindings) {
150
+ const emitCallees = (thisReferences.get(`${name}Change`) ?? []).map(getEmitCallee);
151
+ const isRewritable = emitCallees.every(utils_1.isNotNullOrUndefined);
126
152
  context.report({
127
153
  node: input.property,
128
154
  messageId: 'preferSignalModel',
129
- fix: (fixer) => [
130
- utils_1.RuleFixes.getImportAddFix({
131
- fixer,
132
- importName: 'model',
133
- moduleName: '@angular/core',
134
- node: input.property,
135
- }),
136
- fixer.replaceText(input.callee, 'model'),
137
- fixer.remove(output.property),
138
- ].filter(utils_1.isNotNullOrUndefined),
155
+ fix: isRewritable
156
+ ? (fixer) => [
157
+ utils_1.RuleFixes.getImportAddFix({
158
+ fixer,
159
+ importName: 'model',
160
+ moduleName: '@angular/core',
161
+ node: input.property,
162
+ }),
163
+ fixer.replaceText(input.callee, 'model'),
164
+ fixer.remove(output.property),
165
+ ...emitCallees.map((callee) => fixer.replaceText(callee, `this.${name}.set`)),
166
+ ].filter(utils_1.isNotNullOrUndefined)
167
+ : undefined,
139
168
  });
140
169
  }
141
170
  inputs.clear();
142
171
  outputs.clear();
172
+ thisReferences.clear();
143
173
  },
144
174
  };
145
175
  },
@@ -1,7 +1,9 @@
1
- export type Options = [];
2
- export type MessageIds = 'preferStandalone' | 'removeStandaloneFalse';
1
+ export type Options = [{
2
+ readonly allowExplicitStandalone?: boolean;
3
+ }];
4
+ export type MessageIds = 'preferStandalone' | 'removeStandaloneFalse' | 'redundantStandalone';
3
5
  export declare const RULE_NAME = "prefer-standalone";
4
- declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
6
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
7
  name: string;
6
8
  };
7
9
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-standalone.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-standalone.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG,kBAAkB,GAAG,uBAAuB,CAAC;AACtE,eAAO,MAAM,SAAS,sBAAsB,CAAC;;;;AAK7C,wBAkEG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"prefer-standalone.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-standalone.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEvE,MAAM,MAAM,UAAU,GACpB,kBAAkB,GAAG,uBAAuB,GAAG,qBAAqB,CAAC;AACvE,eAAO,MAAM,SAAS,sBAAsB,CAAC;;;;AAM7C,wBAiGG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -4,6 +4,7 @@ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
4
  const utils_1 = require("@angular-eslint/utils");
5
5
  const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
6
  exports.RULE_NAME = 'prefer-standalone';
7
+ const DEFAULT_OPTIONS = { allowExplicitStandalone: true };
7
8
  const RECOMMENDED_GUIDE_URL = 'https://angular.dev/reference/migrations/standalone';
8
9
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
10
  name: exports.RULE_NAME,
@@ -13,25 +14,48 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
13
14
  description: `Ensures Components, Directives and Pipes do not opt out of standalone.`,
14
15
  recommended: 'recommended',
15
16
  },
17
+ fixable: 'code',
16
18
  hasSuggestions: true,
17
- schema: [],
19
+ schema: [
20
+ {
21
+ type: 'object',
22
+ properties: {
23
+ allowExplicitStandalone: {
24
+ type: 'boolean',
25
+ default: DEFAULT_OPTIONS.allowExplicitStandalone,
26
+ description: 'Whether to allow a Component, Directive or Pipe to set `standalone: true` explicitly even though it is now the default.',
27
+ },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ ],
18
32
  messages: {
19
33
  preferStandalone: `Components, Directives and Pipes should not opt out of standalone. Following this guide is highly recommended: ${RECOMMENDED_GUIDE_URL}`,
20
34
  removeStandaloneFalse: `Quickly remove 'standalone: false'. NOTE - Following this guide is highly recommended: ${RECOMMENDED_GUIDE_URL}`,
35
+ redundantStandalone: '`standalone: true` is redundant because `standalone: true` is the default',
21
36
  },
22
- defaultOptions: [],
37
+ defaultOptions: [DEFAULT_OPTIONS],
23
38
  },
24
- create(context) {
39
+ create(context, [{ allowExplicitStandalone }]) {
25
40
  const standaloneRuleFactory = (type) => (node) => {
26
41
  const standalone = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'standalone');
27
- // Leave the standalone property alone if it was set to true or not present
28
- if (!standalone ||
29
- (utils_1.ASTUtils.isLiteral(standalone) && standalone.value === true)) {
42
+ if (!standalone) {
30
43
  return;
31
44
  }
32
45
  if (!utils_1.ASTUtils.getDecoratorArgument(node)) {
33
46
  return;
34
47
  }
48
+ if (utils_1.ASTUtils.isLiteral(standalone) && standalone.value === true) {
49
+ if (allowExplicitStandalone) {
50
+ return;
51
+ }
52
+ context.report({
53
+ node: standalone.parent,
54
+ messageId: 'redundantStandalone',
55
+ fix: (fixer) => utils_1.RuleFixes.getNodeToCommaRemoveFix(context.sourceCode, standalone.parent, fixer),
56
+ });
57
+ return;
58
+ }
35
59
  context.report({
36
60
  node: standalone.parent,
37
61
  messageId: 'preferStandalone',
@@ -62,5 +86,5 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
62
86
  },
63
87
  });
64
88
  exports.RULE_DOCS_EXTENSION = {
65
- rationale: 'Standalone components, directives, and pipes are the recommended way to build Angular applications. Setting standalone: false opts out of the standalone API, tying your code to the older NgModule-based architecture. Standalone components simplify Angular applications by eliminating the need for NgModules in most cases, reducing boilerplate and making dependencies more explicit. Each standalone component declares its own dependencies directly, making it self-contained and easier to understand, test, and reuse. Standalone components also enable better tree-shaking and lazy loading. Angular provides comprehensive migration guides to help transition existing applications, such as https://angular.dev/reference/migrations/standalone. New projects should use standalone components from the start, and existing projects should avoid adding new non-standalone components as they will make future migrations harder.',
89
+ rationale: 'Standalone components, directives, and pipes are the recommended way to build Angular applications. Setting standalone: false opts out of the standalone API, tying your code to the older NgModule-based architecture. Standalone components simplify Angular applications by eliminating the need for NgModules in most cases, reducing boilerplate and making dependencies more explicit. Each standalone component declares its own dependencies directly, making it self-contained and easier to understand, test, and reuse. Standalone components also enable better tree-shaking and lazy loading. Angular provides comprehensive migration guides to help transition existing applications, such as https://angular.dev/reference/migrations/standalone. New projects should use standalone components from the start, and existing projects should avoid adding new non-standalone components as they will make future migrations harder. As of Angular v19, `standalone: true` is the default for components, directives, and pipes, so declaring `standalone: true` explicitly is redundant; set `allowExplicitStandalone` to `false` to have the rule flag and autofix redundant `standalone: true` declarations too.',
66
90
  };
@@ -313,13 +313,11 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
313
313
  }
314
314
  }
315
315
  // Register this call if it is one of the reactive contexts.
316
- if (node.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
316
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.Identifier ||
317
+ !Object.hasOwn(primitives, node.callee.name)) {
317
318
  return;
318
319
  }
319
320
  const config = primitives[node.callee.name];
320
- if (!config) {
321
- return;
322
- }
323
321
  const newAnalysis = {
324
322
  call: node,
325
323
  primitiveName: node.callee.name,
@@ -1,12 +1,19 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
+ type OrderConfig = {
3
+ readonly Component?: string[];
4
+ readonly Directive?: string[];
5
+ readonly Injectable?: string[];
6
+ readonly NgModule?: string[];
7
+ readonly Pipe?: string[];
8
+ };
2
9
  export type Options = [
3
- {
4
- [key: string]: string[];
10
+ OrderConfig & {
11
+ readonly allowUnconfiguredProperties?: boolean;
5
12
  }
6
13
  ];
7
- export type MessageIds = 'incorrectOrder';
14
+ export type MessageIds = 'incorrectOrder' | 'unconfiguredProperty';
8
15
  export declare const RULE_NAME = "sort-keys-in-type-decorator";
9
- declare const _default: TSESLint.RuleModule<"incorrectOrder", Options, import("../utils/create-eslint-rule").RuleDocs, TSESLint.RuleListener> & {
16
+ declare const _default: TSESLint.RuleModule<MessageIds, Options, import("../utils/create-eslint-rule").RuleDocs, TSESLint.RuleListener> & {
10
17
  name: string;
11
18
  };
12
19
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"sort-keys-in-type-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/sort-keys-in-type-decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAG9D,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KACzB;CACF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,gBAAgB,CAAC;AA2D1C,eAAO,MAAM,SAAS,gCAAgC,CAAC;;;;AAEvD,wBA+IG;AAsFH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"sort-keys-in-type-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/sort-keys-in-type-decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAG9D,KAAK,WAAW,GAAG;IACjB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,WAAW,GAAG;QACZ,QAAQ,CAAC,2BAA2B,CAAC,EAAE,OAAO,CAAC;KAChD;CACF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG,sBAAsB,CAAC;AAyEnE,eAAO,MAAM,SAAS,gCAAgC,CAAC;;;;AAEvD,wBAgLG;AAsFH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -45,6 +45,15 @@ const DEFAULT_ORDER = {
45
45
  'queries',
46
46
  'jit',
47
47
  ],
48
+ // https://angular.dev/api/core/Injectable
49
+ Injectable: [
50
+ 'providedIn',
51
+ 'useClass',
52
+ 'useExisting',
53
+ 'useFactory',
54
+ 'useValue',
55
+ 'deps',
56
+ ],
48
57
  // https://angular.dev/api/core/NgModule
49
58
  NgModule: [
50
59
  'id', // rarely used but good to have first if set
@@ -59,13 +68,17 @@ const DEFAULT_ORDER = {
59
68
  // https://angular.dev/api/core/Pipe
60
69
  Pipe: ['name', 'standalone', 'pure'],
61
70
  };
71
+ const DEFAULT_OPTIONS = {
72
+ ...DEFAULT_ORDER,
73
+ allowUnconfiguredProperties: true,
74
+ };
62
75
  exports.RULE_NAME = 'sort-keys-in-type-decorator';
63
76
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
64
77
  name: exports.RULE_NAME,
65
78
  meta: {
66
79
  type: 'suggestion',
67
80
  docs: {
68
- description: 'Ensures that keys in type decorators (Component, Directive, NgModule, Pipe) are sorted in a consistent order',
81
+ description: 'Ensures that keys in type decorators (Component, Directive, Injectable, NgModule, Pipe) are sorted in a consistent order',
69
82
  },
70
83
  fixable: 'code',
71
84
  schema: [
@@ -84,6 +97,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
84
97
  type: 'string',
85
98
  },
86
99
  },
100
+ Injectable: {
101
+ type: 'array',
102
+ items: {
103
+ type: 'string',
104
+ },
105
+ },
87
106
  NgModule: {
88
107
  type: 'array',
89
108
  items: {
@@ -96,14 +115,19 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
96
115
  type: 'string',
97
116
  },
98
117
  },
118
+ allowUnconfiguredProperties: {
119
+ type: 'boolean',
120
+ default: DEFAULT_OPTIONS.allowUnconfiguredProperties,
121
+ },
99
122
  },
100
123
  additionalProperties: false,
101
124
  },
102
125
  ],
103
126
  messages: {
104
127
  incorrectOrder: 'Keys in @{{decorator}} decorator should be ordered: {{expectedOrder}}',
128
+ unconfiguredProperty: 'Property "{{property}}" is not in the configured key order for @{{decorator}}.',
105
129
  },
106
- defaultOptions: [DEFAULT_ORDER],
130
+ defaultOptions: [DEFAULT_OPTIONS],
107
131
  },
108
132
  create(context, [orderConfig]) {
109
133
  function checkContext(node, decoratorName) {
@@ -116,12 +140,29 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
116
140
  return;
117
141
  }
118
142
  const properties = utils_1.ASTUtils.getDecoratorProperties(node);
143
+ const allowUnconfiguredProperties = orderConfig.allowUnconfiguredProperties ?? true;
144
+ if (!allowUnconfiguredProperties) {
145
+ for (const property of properties) {
146
+ const propertyName = property.key.name;
147
+ if (!expectedOrder.includes(propertyName)) {
148
+ context.report({
149
+ node: property,
150
+ messageId: 'unconfiguredProperty',
151
+ data: {
152
+ decorator: decoratorName,
153
+ property: propertyName,
154
+ },
155
+ });
156
+ }
157
+ }
158
+ }
119
159
  if (properties.length <= 1) {
120
160
  return;
121
161
  }
122
162
  const firstConfiguredIndex = properties.findIndex(({ key }) => expectedOrder.includes(key.name));
123
163
  const lastNonConfiguredIndex = properties.findIndex(({ key }) => !expectedOrder.includes(key.name));
124
- if (firstConfiguredIndex !== -1 &&
164
+ if (allowUnconfiguredProperties &&
165
+ firstConfiguredIndex !== -1 &&
125
166
  lastNonConfiguredIndex !== -1 &&
126
167
  lastNonConfiguredIndex < firstConfiguredIndex) {
127
168
  createInvalidSortRuleForDecorator(context, decoratorName, expectedOrder, properties, properties[lastNonConfiguredIndex]);
@@ -187,5 +228,5 @@ function reportAndFix(context, node, messageId, data, properties, expectedOrder,
187
228
  });
188
229
  }
189
230
  exports.RULE_DOCS_EXTENSION = {
190
- rationale: 'Maintaining a consistent order for properties in Angular decorators (@Component, @Directive, @NgModule, @Pipe) makes code more predictable and easier to scan. When all components in a codebase follow the same property order, developers can quickly locate specific metadata without searching. For example, if selector always comes first and providers always comes before changeDetection, you develop muscle memory for where to look. This is especially helpful in large components with many properties. The recommended default order groups related properties logically: identification (selector, name) first, then dependencies (imports, providers), then templates/styles, then configuration options. Consistent ordering also makes code reviews easier, reduces merge conflicts when multiple developers edit decorators, and creates a professional, well-organized codebase.',
231
+ rationale: 'Maintaining a consistent order for properties in Angular decorators (@Component, @Directive, @Injectable, @NgModule, @Pipe) makes code more predictable and easier to scan. When all components in a codebase follow the same property order, developers can quickly locate specific metadata without searching. For example, if selector always comes first and providers always comes before changeDetection, you develop muscle memory for where to look. This is especially helpful in large components with many properties. The recommended default order groups related properties logically: identification (selector, name) first, then dependencies (imports, providers), then templates/styles, then configuration options. Consistent ordering also makes code reviews easier, reduces merge conflicts when multiple developers edit decorators, and creates a professional, well-organized codebase. When `allowUnconfiguredProperties` is `false`, decorators omitted from the user configuration are still checked against their built-in default key order.',
191
232
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/angular-eslint__eslint-plugin",
3
- "version": "22.4.0-depup.0",
3
+ "version": "22.6.0-depup.0",
4
4
  "description": "ESLint plugin for Angular applications, following https://angular.dev/style-guide (with updated dependencies)",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,12 +20,12 @@
20
20
  "changes.json"
21
21
  ],
22
22
  "dependencies": {
23
- "@angular-eslint/bundled-angular-compiler": "22.4.0",
24
- "@angular-eslint/utils": "22.4.0",
23
+ "@angular-eslint/bundled-angular-compiler": "22.6.0",
24
+ "@angular-eslint/utils": "22.6.0",
25
25
  "ts-api-utils": "^2.5.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@angular-eslint/test-utils": "22.4.0"
28
+ "@angular-eslint/test-utils": "22.6.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@typescript-eslint/utils": "^8.0.0",
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "depsUpdated": 1,
52
52
  "originalPackage": "@angular-eslint/eslint-plugin",
53
- "originalVersion": "22.4.0",
54
- "processedAt": "2026-09-07T16:09:13.044Z",
53
+ "originalVersion": "22.6.0",
54
+ "processedAt": "2026-10-11T16:09:52.435Z",
55
55
  "smokeTest": "passed"
56
56
  }
57
57
  }