@depup/angular-eslint__eslint-plugin 22.2.0-depup.0 → 22.4.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 +2 -2
- package/changes.json +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/rules/component-class-suffix.js +5 -5
- package/dist/rules/component-max-inline-declarations.js +7 -7
- package/dist/rules/computed-must-return.js +1 -1
- package/dist/rules/consistent-component-styles.js +1 -1
- package/dist/rules/contextual-decorator.js +1 -1
- package/dist/rules/contextual-lifecycle.js +1 -1
- package/dist/rules/directive-class-suffix.js +1 -1
- package/dist/rules/inject-at-top.d.ts +6 -2
- package/dist/rules/inject-at-top.d.ts.map +1 -1
- package/dist/rules/inject-at-top.js +53 -22
- package/dist/rules/no-async-lifecycle-method.js +1 -1
- package/dist/rules/no-attribute-decorator.js +1 -1
- package/dist/rules/no-developer-preview.js +1 -1
- package/dist/rules/no-duplicates-in-metadata-arrays.js +1 -1
- package/dist/rules/no-empty-lifecycle-method.js +1 -1
- package/dist/rules/no-experimental.js +1 -1
- package/dist/rules/no-forward-ref.js +1 -1
- package/dist/rules/no-implicit-take-until-destroyed.d.ts.map +1 -1
- package/dist/rules/no-implicit-take-until-destroyed.js +16 -6
- package/dist/rules/no-input-prefix.js +1 -1
- package/dist/rules/no-input-rename.js +1 -1
- package/dist/rules/no-inputs-metadata-property.js +1 -1
- package/dist/rules/no-lifecycle-call.js +1 -1
- package/dist/rules/no-output-native.js +1 -1
- package/dist/rules/no-output-on-prefix.js +1 -1
- package/dist/rules/no-output-rename.js +1 -1
- package/dist/rules/no-outputs-metadata-property.js +1 -1
- package/dist/rules/no-pipe-impure.js +1 -1
- package/dist/rules/no-queries-metadata-property.js +1 -1
- package/dist/rules/no-uncalled-signals.js +1 -1
- package/dist/rules/pipe-prefix.js +5 -5
- package/dist/rules/prefer-host-metadata-property.js +1 -1
- package/dist/rules/prefer-inject.js +1 -1
- package/dist/rules/prefer-on-push-component-change-detection.js +1 -1
- package/dist/rules/prefer-output-emitter-ref.js +1 -1
- package/dist/rules/prefer-output-readonly.js +1 -1
- package/dist/rules/prefer-service-decorator.js +1 -1
- package/dist/rules/prefer-signal-model.d.ts +7 -1
- package/dist/rules/prefer-signal-model.d.ts.map +1 -1
- package/dist/rules/prefer-signal-model.js +118 -40
- package/dist/rules/prefer-signals.js +1 -1
- package/dist/rules/prefer-standalone.js +1 -1
- package/dist/rules/reactive-context-must-read-signal.d.ts +16 -0
- package/dist/rules/reactive-context-must-read-signal.d.ts.map +1 -0
- package/dist/rules/reactive-context-must-read-signal.js +357 -0
- package/dist/rules/relative-url-prefix.js +1 -1
- package/dist/rules/require-lifecycle-on-prototype.js +1 -1
- package/dist/rules/require-localize-metadata.js +1 -1
- package/dist/rules/runtime-localize.js +1 -1
- package/dist/rules/sort-keys-in-type-decorator.js +1 -1
- package/dist/rules/sort-lifecycle-methods.js +1 -1
- package/dist/rules/use-component-selector.js +1 -1
- package/dist/rules/use-component-view-encapsulation.js +1 -1
- package/dist/rules/use-injectable-provided-in.d.ts +6 -3
- package/dist/rules/use-injectable-provided-in.d.ts.map +1 -1
- package/dist/rules/use-injectable-provided-in.js +14 -5
- package/dist/rules/use-lifecycle-interface.js +1 -1
- package/dist/rules/use-pipe-transform-interface.js +1 -1
- package/dist/utils/create-eslint-rule.d.ts.map +1 -1
- package/dist/utils/create-eslint-rule.js +1 -24
- package/package.json +7 -7
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.
|
|
17
|
-
| Processed | 2026-
|
|
16
|
+
| Original | [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) @ 22.4.0 |
|
|
17
|
+
| Processed | 2026-09-07 |
|
|
18
18
|
| Smoke test | passed |
|
|
19
19
|
| Deps updated | 1 |
|
|
20
20
|
|
package/changes.json
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ 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",
|
|
30
|
+
"inject-at-top": import("@typescript-eslint/utils/ts-eslint").RuleModule<"injectAtTop", import("./rules/inject-at-top").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
31
31
|
name: string;
|
|
32
32
|
};
|
|
33
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> & {
|
|
@@ -108,7 +108,9 @@ declare const _default: {
|
|
|
108
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
109
|
name: string;
|
|
110
110
|
};
|
|
111
|
-
"prefer-signal-model": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSignalModel", [
|
|
111
|
+
"prefer-signal-model": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSignalModel", [{
|
|
112
|
+
useTypeChecking: boolean;
|
|
113
|
+
}], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
112
114
|
name: string;
|
|
113
115
|
};
|
|
114
116
|
"prefer-signals": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-signals").MessageIds, [{
|
|
@@ -123,6 +125,9 @@ declare const _default: {
|
|
|
123
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> & {
|
|
124
126
|
name: string;
|
|
125
127
|
};
|
|
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> & {
|
|
129
|
+
name: string;
|
|
130
|
+
};
|
|
126
131
|
"relative-url-prefix": import("@typescript-eslint/utils/ts-eslint").RuleModule<"relativeUrlPrefix", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
127
132
|
name: string;
|
|
128
133
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0JA,kBAuDE"}
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,7 @@ const no_developer_preview_1 = __importStar(require("./rules/no-developer-previe
|
|
|
82
82
|
const no_implicit_take_until_destroyed_1 = __importStar(require("./rules/no-implicit-take-until-destroyed"));
|
|
83
83
|
const computed_must_return_1 = __importStar(require("./rules/computed-must-return"));
|
|
84
84
|
const inject_at_top_1 = __importStar(require("./rules/inject-at-top"));
|
|
85
|
+
const reactive_context_must_read_signal_1 = __importStar(require("./rules/reactive-context-must-read-signal"));
|
|
85
86
|
module.exports = {
|
|
86
87
|
rules: {
|
|
87
88
|
[component_class_suffix_1.RULE_NAME]: component_class_suffix_1.default,
|
|
@@ -123,6 +124,7 @@ module.exports = {
|
|
|
123
124
|
[prefer_signal_model_1.RULE_NAME]: prefer_signal_model_1.default,
|
|
124
125
|
[prefer_signals_1.RULE_NAME]: prefer_signals_1.default,
|
|
125
126
|
[prefer_standalone_1.RULE_NAME]: prefer_standalone_1.default,
|
|
127
|
+
[reactive_context_must_read_signal_1.RULE_NAME]: reactive_context_must_read_signal_1.default,
|
|
126
128
|
[relative_url_prefix_1.RULE_NAME]: relative_url_prefix_1.default,
|
|
127
129
|
[require_lifecycle_on_prototype_1.RULE_NAME]: require_lifecycle_on_prototype_1.default,
|
|
128
130
|
[require_localize_metadata_1.RULE_NAME]: require_localize_metadata_1.default,
|
|
@@ -28,12 +28,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
28
28
|
messages: {
|
|
29
29
|
componentClassSuffix: `Component class names should end with one of these suffixes: {{suffixes}}`,
|
|
30
30
|
},
|
|
31
|
+
defaultOptions: [
|
|
32
|
+
{
|
|
33
|
+
suffixes: ['Component'],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
31
36
|
},
|
|
32
|
-
defaultOptions: [
|
|
33
|
-
{
|
|
34
|
-
suffixes: ['Component'],
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
37
|
create(context, [{ suffixes }]) {
|
|
38
38
|
return {
|
|
39
39
|
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
@@ -38,14 +38,14 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
38
38
|
messages: {
|
|
39
39
|
componentMaxInlineDeclarations: `\`{{propertyType}}\` has too many lines ({{lineCount}}). Maximum allowed is {{max}}`,
|
|
40
40
|
},
|
|
41
|
+
defaultOptions: [
|
|
42
|
+
{
|
|
43
|
+
template: DEFAULT_TEMPLATE_LIMIT,
|
|
44
|
+
styles: DEFAULT_STYLES_LIMIT,
|
|
45
|
+
animations: DEFAULT_ANIMATIONS_LIMIT,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
41
48
|
},
|
|
42
|
-
defaultOptions: [
|
|
43
|
-
{
|
|
44
|
-
template: DEFAULT_TEMPLATE_LIMIT,
|
|
45
|
-
styles: DEFAULT_STYLES_LIMIT,
|
|
46
|
-
animations: DEFAULT_ANIMATIONS_LIMIT,
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
49
|
create(context, [{ template = DEFAULT_TEMPLATE_LIMIT, styles = DEFAULT_STYLES_LIMIT, animations = DEFAULT_ANIMATIONS_LIMIT, },]) {
|
|
50
50
|
return {
|
|
51
51
|
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='template']`]({ value, }) {
|
|
@@ -15,8 +15,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
15
15
|
messages: {
|
|
16
16
|
computedMissingReturn: 'computed() is missing a return value',
|
|
17
17
|
},
|
|
18
|
+
defaultOptions: [],
|
|
18
19
|
},
|
|
19
|
-
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
21
|
const functionStack = [];
|
|
22
22
|
function enterFunction(node) {
|
|
@@ -24,8 +24,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
24
24
|
useStylesArray: 'Use a `string[]` instead of a `string` for the `styles` property',
|
|
25
25
|
useStylesString: 'Use a `string` instead of a `string[]` for the `styles` property',
|
|
26
26
|
},
|
|
27
|
+
defaultOptions: ['string'],
|
|
27
28
|
},
|
|
28
|
-
defaultOptions: ['string'],
|
|
29
29
|
create(context, [mode]) {
|
|
30
30
|
const { COMPONENT_CLASS_DECORATOR, metadataProperty } = utils_1.Selectors;
|
|
31
31
|
const LITERAL_OR_TEMPLATE_LITERAL = ':matches(Literal, TemplateLiteral)';
|
|
@@ -15,8 +15,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
15
15
|
messages: {
|
|
16
16
|
contextualDecorator: 'Decorator out of context for "@{{classDecoratorName}}()"',
|
|
17
17
|
},
|
|
18
|
+
defaultOptions: [],
|
|
18
19
|
},
|
|
19
|
-
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
21
|
return {
|
|
22
22
|
'MethodDefinition[kind=/^(get|set|method)$/], PropertyDefinition, TSParameterProperty'(node) {
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
contextualLifecycle: `Angular will not invoke the \`{{methodName}}\` lifecycle method within \`@{{classDecoratorName}}()\` classes`,
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
function checkContext({ parent }, decorator) {
|
|
23
23
|
const classDeclaration = parent;
|
|
@@ -30,8 +30,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
30
30
|
messages: {
|
|
31
31
|
directiveClassSuffix: `Directive class names should end with one of these suffixes: {{suffixes}}`,
|
|
32
32
|
},
|
|
33
|
+
defaultOptions: [{ suffixes: DEFAULT_SUFFIXES }],
|
|
33
34
|
},
|
|
34
|
-
defaultOptions: [{ suffixes: DEFAULT_SUFFIXES }],
|
|
35
35
|
create(context, [{ suffixes }]) {
|
|
36
36
|
return {
|
|
37
37
|
[utils_1.Selectors.DIRECTIVE_CLASS_DECORATOR](node) {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export type Options = [
|
|
1
|
+
export type Options = [
|
|
2
|
+
{
|
|
3
|
+
readonly additionalInjectFunctions?: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
];
|
|
2
6
|
export type MessageIds = 'injectAtTop';
|
|
3
7
|
export declare const RULE_NAME = "inject-at-top";
|
|
4
|
-
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"injectAtTop",
|
|
8
|
+
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"injectAtTop", Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
5
9
|
name: string;
|
|
6
10
|
};
|
|
7
11
|
export default _default;
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"inject-at-top.d.ts","sourceRoot":"","sources":["../../src/rules/inject-at-top.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACxD;CACF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AACvC,eAAO,MAAM,SAAS,kBAAkB,CAAC;;;;AAIzC,wBAyHG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("@angular-eslint/utils");
|
|
|
5
5
|
const utils_2 = require("@typescript-eslint/utils");
|
|
6
6
|
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
7
7
|
exports.RULE_NAME = 'inject-at-top';
|
|
8
|
+
const DEFAULT_OPTIONS = { additionalInjectFunctions: [] };
|
|
8
9
|
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
10
|
name: exports.RULE_NAME,
|
|
10
11
|
meta: {
|
|
@@ -12,13 +13,28 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
12
13
|
docs: {
|
|
13
14
|
description: 'Requires inject() calls to be declared at the top of the class, before any other member',
|
|
14
15
|
},
|
|
15
|
-
schema: [
|
|
16
|
+
schema: [
|
|
17
|
+
{
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
additionalInjectFunctions: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
items: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
},
|
|
25
|
+
description: 'A list of additional functions which perform injection, such as your own `injectTranslations()` helper. Each entry is a regular expression which must match the whole function name, so plain names such as `injectTranslations` work as well as patterns such as `inject[A-Z].*`.',
|
|
26
|
+
uniqueItems: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
16
32
|
messages: {
|
|
17
|
-
injectAtTop: 'Move this
|
|
33
|
+
injectAtTop: 'Move this {{functionName}}() 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
34
|
},
|
|
35
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
19
36
|
},
|
|
20
|
-
|
|
21
|
-
create(context) {
|
|
37
|
+
create(context, [{ additionalInjectFunctions = [] }]) {
|
|
22
38
|
const angularDecoratorsPattern = (0, utils_1.toPattern)([
|
|
23
39
|
'Component',
|
|
24
40
|
'Directive',
|
|
@@ -26,14 +42,18 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
26
42
|
'Pipe',
|
|
27
43
|
'Service',
|
|
28
44
|
]);
|
|
29
|
-
|
|
45
|
+
const injectFunctionsPattern = (0, utils_1.toPattern)([
|
|
46
|
+
'inject',
|
|
47
|
+
...additionalInjectFunctions,
|
|
48
|
+
]);
|
|
49
|
+
function findEagerInjectName(node) {
|
|
30
50
|
if (!node) {
|
|
31
|
-
return
|
|
51
|
+
return null;
|
|
32
52
|
}
|
|
33
53
|
if (node.type === utils_2.AST_NODE_TYPES.CallExpression &&
|
|
34
54
|
node.callee.type === utils_2.AST_NODE_TYPES.Identifier &&
|
|
35
|
-
node.callee.name
|
|
36
|
-
return
|
|
55
|
+
injectFunctionsPattern.test(node.callee.name)) {
|
|
56
|
+
return node.callee.name;
|
|
37
57
|
}
|
|
38
58
|
const isFn = node.type === utils_2.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
39
59
|
node.type === utils_2.AST_NODE_TYPES.FunctionExpression;
|
|
@@ -41,15 +61,20 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
41
61
|
node.parent?.type === utils_2.AST_NODE_TYPES.CallExpression &&
|
|
42
62
|
node.parent.callee === node;
|
|
43
63
|
if (node.type === utils_2.AST_NODE_TYPES.ClassExpression || (isFn && !isIIFE)) {
|
|
44
|
-
return
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
for (const child of Object.values(node).flat()) {
|
|
67
|
+
if (child &&
|
|
68
|
+
typeof child === 'object' &&
|
|
69
|
+
'type' in child &&
|
|
70
|
+
child !== node.parent) {
|
|
71
|
+
const name = findEagerInjectName(child);
|
|
72
|
+
if (name) {
|
|
73
|
+
return name;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
45
76
|
}
|
|
46
|
-
return
|
|
47
|
-
.flat()
|
|
48
|
-
.some((child) => child &&
|
|
49
|
-
typeof child === 'object' &&
|
|
50
|
-
'type' in child &&
|
|
51
|
-
child !== node.parent &&
|
|
52
|
-
hasEagerInject(child));
|
|
77
|
+
return null;
|
|
53
78
|
}
|
|
54
79
|
return {
|
|
55
80
|
[`${utils_1.Selectors.decoratorDefinition(angularDecoratorsPattern)} > ClassBody`](node) {
|
|
@@ -58,12 +83,18 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
58
83
|
if ('static' in member && member.static) {
|
|
59
84
|
continue;
|
|
60
85
|
}
|
|
61
|
-
if (member.type === utils_2.AST_NODE_TYPES.PropertyDefinition
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
86
|
+
if (member.type === utils_2.AST_NODE_TYPES.PropertyDefinition) {
|
|
87
|
+
const functionName = findEagerInjectName(member.value);
|
|
88
|
+
if (functionName) {
|
|
89
|
+
if (seenNonInject) {
|
|
90
|
+
context.report({
|
|
91
|
+
node: member,
|
|
92
|
+
messageId: 'injectAtTop',
|
|
93
|
+
data: { functionName },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
65
97
|
}
|
|
66
|
-
continue;
|
|
67
98
|
}
|
|
68
99
|
seenNonInject = true;
|
|
69
100
|
}
|
|
@@ -72,5 +103,5 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
72
103
|
},
|
|
73
104
|
});
|
|
74
105
|
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.",
|
|
106
|
+
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. Codebases often wrap inject() in their own helpers, for example an injectTranslations() function that injects a translation service and derives a scoped accessor from it; such a helper has exactly the same ordering constraint, so it can be declared through the additionalInjectFunctions option to be treated like inject() itself.",
|
|
76
107
|
};
|
|
@@ -15,8 +15,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
15
15
|
messages: {
|
|
16
16
|
noAsyncLifecycleMethod: 'Angular Lifecycle method should not be async',
|
|
17
17
|
},
|
|
18
|
+
defaultOptions: [],
|
|
18
19
|
},
|
|
19
|
-
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
21
|
const angularDecoratorsPattern = (0, utils_1.toPattern)([
|
|
22
22
|
'Component',
|
|
@@ -14,8 +14,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
14
14
|
messages: {
|
|
15
15
|
noAttributeDecorator: '@Attribute can only obtain a single value and is rarely what is required. Use @Input instead to retrieve a stream of values.',
|
|
16
16
|
},
|
|
17
|
+
defaultOptions: [],
|
|
17
18
|
},
|
|
18
|
-
defaultOptions: [],
|
|
19
19
|
create(context) {
|
|
20
20
|
return {
|
|
21
21
|
'ClassDeclaration MethodDefinition[key.name="constructor"] Decorator[expression.callee.name="Attribute"]'(node) {
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noDeveloperPreview: '`{{name}}` is in developer preview',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
const services = (0, eslint_utils_1.getParserServices)(context);
|
|
23
23
|
const checker = services.program.getTypeChecker();
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noDuplicatesInMetadataArrays: 'Entry is duplicated in metadata array',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
const selectors = [
|
|
23
23
|
// https://angular.dev/api/core/NgModule
|
|
@@ -18,8 +18,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
18
18
|
noEmptyLifecycleMethod: 'Lifecycle methods should not be empty',
|
|
19
19
|
suggestRemoveLifecycleMethod: 'Remove lifecycle method',
|
|
20
20
|
},
|
|
21
|
+
defaultOptions: [],
|
|
21
22
|
},
|
|
22
|
-
defaultOptions: [],
|
|
23
23
|
create(context) {
|
|
24
24
|
const sourceCode = context.sourceCode;
|
|
25
25
|
const angularDecoratorsPattern = (0, utils_1.toPattern)([
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noExperimental: '`{{name}}` is experimental',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
const services = (0, eslint_utils_1.getParserServices)(context);
|
|
23
23
|
const checker = services.program.getTypeChecker();
|
|
@@ -15,8 +15,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
15
15
|
messages: {
|
|
16
16
|
noForwardRef: `Avoid using \`${exports.FORWARD_REF}\``,
|
|
17
17
|
},
|
|
18
|
+
defaultOptions: [],
|
|
18
19
|
},
|
|
19
|
-
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
21
|
return {
|
|
22
22
|
[`CallExpression[callee.type="Identifier"][callee.name="${exports.FORWARD_REF}"]`](node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-implicit-take-until-destroyed.d.ts","sourceRoot":"","sources":["../../src/rules/no-implicit-take-until-destroyed.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,qCAAqC,CAAC;;;;AAO5D,wBAgCG;
|
|
1
|
+
{"version":3,"file":"no-implicit-take-until-destroyed.d.ts","sourceRoot":"","sources":["../../src/rules/no-implicit-take-until-destroyed.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,qCAAqC,CAAC;;;;AAO5D,wBAgCG;AAiNH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
|
|
@@ -18,8 +18,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
18
18
|
messages: {
|
|
19
19
|
noImplicitTakeUntilDestroyed: `\`takeUntilDestroyed()\` must be called with an explicit \`DestroyRef\` parameter when used outside of an injection context. See more at ${DEPENDENCY_INJECTION_CONTEXT} and ${RXJS_INTEROP_LINK}`,
|
|
20
20
|
},
|
|
21
|
+
defaultOptions: [],
|
|
21
22
|
},
|
|
22
|
-
defaultOptions: [],
|
|
23
23
|
create(context) {
|
|
24
24
|
return {
|
|
25
25
|
CallExpression(node) {
|
|
@@ -89,16 +89,26 @@ function isMethodCalledFromInjectionContext(method, classDeclaration) {
|
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
91
|
const isPrivateField = method.key.type === utils_2.AST_NODE_TYPES.PrivateIdentifier;
|
|
92
|
+
let isCalledFromInjectionContext = false;
|
|
92
93
|
for (const member of classDeclaration.body.body) {
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
// Ignore recursive calls from the method to itself
|
|
95
|
+
if (member === method) {
|
|
95
96
|
continue;
|
|
96
97
|
}
|
|
97
|
-
if (containsCallToMethod(member, methodName, isPrivateField)) {
|
|
98
|
-
|
|
98
|
+
if (!containsCallToMethod(member, methodName, isPrivateField)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (isConstructorMethod(member) || utils_1.ASTUtils.isPropertyDefinition(member)) {
|
|
102
|
+
isCalledFromInjectionContext = true;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// The method is also called from somewhere that is not an injection
|
|
106
|
+
// context (e.g. a lifecycle hook or a regular method), so we cannot
|
|
107
|
+
// assume that `takeUntilDestroyed()` will always run within one.
|
|
108
|
+
return false;
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
|
-
return
|
|
111
|
+
return isCalledFromInjectionContext;
|
|
102
112
|
}
|
|
103
113
|
function isASTNode(value) {
|
|
104
114
|
return (value !== null &&
|
|
@@ -29,8 +29,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
29
29
|
messages: {
|
|
30
30
|
noInputPrefix: 'Input bindings, including aliases, should not be named, nor prefixed by {{prefixes}}',
|
|
31
31
|
},
|
|
32
|
+
defaultOptions: [{ prefixes: [] }],
|
|
32
33
|
},
|
|
33
|
-
defaultOptions: [{ prefixes: [] }],
|
|
34
34
|
create(context, [{ prefixes }]) {
|
|
35
35
|
return {
|
|
36
36
|
[utils_1.Selectors.INPUT_PROPERTY_OR_SETTER](node) {
|
|
@@ -37,8 +37,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
37
37
|
suggestRemoveAliasName: 'Remove alias name',
|
|
38
38
|
suggestReplaceOriginalNameWithAliasName: 'Remove alias name and use it as the original name',
|
|
39
39
|
},
|
|
40
|
+
defaultOptions: [{ allowedNames: [] }],
|
|
40
41
|
},
|
|
41
|
-
defaultOptions: [{ allowedNames: [] }],
|
|
42
42
|
create(context, [{ allowedNames = [] }]) {
|
|
43
43
|
let selectors = new Set();
|
|
44
44
|
const ariaAttributeKeys = (0, utils_1.getAriaAttributeKeys)();
|
|
@@ -18,8 +18,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
18
18
|
messages: {
|
|
19
19
|
noInputsMetadataProperty: `Use \`@Input\` rather than the \`${METADATA_PROPERTY_NAME}\` metadata property`,
|
|
20
20
|
},
|
|
21
|
+
defaultOptions: [],
|
|
21
22
|
},
|
|
22
|
-
defaultOptions: [],
|
|
23
23
|
create(context) {
|
|
24
24
|
return {
|
|
25
25
|
[`${utils_1.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR} ${utils_1.Selectors.metadataProperty(METADATA_PROPERTY_NAME)}`](node) {
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noLifecycleCall: 'Avoid explicit calls to lifecycle methods',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
const angularLifeCycleMethodsPattern = (0, utils_1.toPattern)([
|
|
23
23
|
...utils_1.ASTUtils.ANGULAR_LIFECYCLE_METHODS,
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noOutputNative: 'Output bindings, including aliases, should not be named as standard DOM events',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
const nativeEventNames = (0, utils_1.getNativeEventNames)();
|
|
23
23
|
const selectors = [
|
|
@@ -17,8 +17,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
17
17
|
messages: {
|
|
18
18
|
noOutputOnPrefix: `Output bindings, including aliases, should not be named "on", nor prefixed with it (${STYLE_GUIDE_LINK})`,
|
|
19
19
|
},
|
|
20
|
+
defaultOptions: [],
|
|
20
21
|
},
|
|
21
|
-
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
const outputOnPattern = /^on(([^a-z])|(?=$))/;
|
|
24
24
|
const selectors = [
|
|
@@ -22,8 +22,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
22
22
|
suggestRemoveAliasName: 'Remove alias name',
|
|
23
23
|
suggestReplaceOriginalNameWithAliasName: 'Remove alias name and use it as the original name',
|
|
24
24
|
},
|
|
25
|
+
defaultOptions: [],
|
|
25
26
|
},
|
|
26
|
-
defaultOptions: [],
|
|
27
27
|
create(context) {
|
|
28
28
|
let selectors = new Set();
|
|
29
29
|
return {
|
|
@@ -18,8 +18,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
18
18
|
messages: {
|
|
19
19
|
noOutputsMetadataProperty: `Use \`@Output\` rather than the \`${METADATA_PROPERTY_NAME}\` metadata property`,
|
|
20
20
|
},
|
|
21
|
+
defaultOptions: [],
|
|
21
22
|
},
|
|
22
|
-
defaultOptions: [],
|
|
23
23
|
create(context) {
|
|
24
24
|
return {
|
|
25
25
|
[`${utils_1.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR} ${utils_1.Selectors.metadataProperty(METADATA_PROPERTY_NAME)}`](node) {
|
|
@@ -17,8 +17,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
17
17
|
noPipeImpure: 'Impure pipes should be avoided because they are invoked on each change-detection cycle',
|
|
18
18
|
suggestRemovePipeImpure: 'Remove `pure` property',
|
|
19
19
|
},
|
|
20
|
+
defaultOptions: [],
|
|
20
21
|
},
|
|
21
|
-
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
const sourceCode = context.sourceCode;
|
|
24
24
|
return {
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
messages: {
|
|
17
17
|
noQueriesMetadataProperty: `Use @${utils_1.ASTUtils.AngularInnerClassDecorators.Output} rather than the \`${METADATA_PROPERTY_NAME}\` metadata property`,
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
return {
|
|
23
23
|
[utils_1.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR](node) {
|
|
@@ -33,8 +33,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
33
33
|
noUncalledSignals: 'Doing logic operations on signals will give unexpected results, you probably want to invoke the signal to get its value',
|
|
34
34
|
suggestCallSignal: 'Call this signal to get its value.',
|
|
35
35
|
},
|
|
36
|
+
defaultOptions: [],
|
|
36
37
|
},
|
|
37
|
-
defaultOptions: [],
|
|
38
38
|
create(context) {
|
|
39
39
|
const services = utils_1.ESLintUtils.getParserServices(context);
|
|
40
40
|
function checkForUncalledSignal(node) {
|
|
@@ -30,12 +30,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
30
30
|
pipePrefix: '@Pipes should be prefixed with {{prefixes}}',
|
|
31
31
|
selectorAfterPrefixFailure: '@Pipes should have a selector after the {{prefixes}} prefix',
|
|
32
32
|
},
|
|
33
|
+
defaultOptions: [
|
|
34
|
+
{
|
|
35
|
+
prefixes: [],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
33
38
|
},
|
|
34
|
-
defaultOptions: [
|
|
35
|
-
{
|
|
36
|
-
prefixes: [],
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
39
|
create(context, [{ prefixes }]) {
|
|
40
40
|
function checkValidOption(prefixes) {
|
|
41
41
|
return Array.isArray(prefixes) && prefixes.length > 0;
|
|
@@ -16,8 +16,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
16
16
|
preferHostMetadataPropertyForBinding: 'Prefer using the `host` metadata property in the `@Component` decorator for host bindings instead of the `@HostBinding` decorator.',
|
|
17
17
|
preferHostMetadataPropertyForListener: 'Prefer using the `host` metadata property in the `@Component` decorator for host listeners instead of the `@HostListener` decorator.',
|
|
18
18
|
},
|
|
19
|
+
defaultOptions: [],
|
|
19
20
|
},
|
|
20
|
-
defaultOptions: [],
|
|
21
21
|
create(context) {
|
|
22
22
|
return {
|
|
23
23
|
[utils_1.Selectors.HOST_BINDING_DECORATOR]: (node) => {
|
|
@@ -17,8 +17,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
17
17
|
messages: {
|
|
18
18
|
preferInject: "Prefer using the inject() function over constructor parameter injection. Use Angular's migration schematic to automatically refactor: ng generate @angular/core:inject",
|
|
19
19
|
},
|
|
20
|
+
defaultOptions: [],
|
|
20
21
|
},
|
|
21
|
-
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
const angularDecoratorsPattern = (0, utils_1.toPattern)([
|
|
24
24
|
'Component',
|
|
@@ -35,8 +35,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
35
35
|
suggestRemoveChangeDetection: `Remove \`${METADATA_PROPERTY_NAME}\` to use the default (\`${STRATEGY_ON_PUSH}\`)`,
|
|
36
36
|
redundantOnPushComponentChangeDetection: `\`${METADATA_PROPERTY_NAME}: ${STRATEGY_ON_PUSH}\` is redundant because \`${STRATEGY_ON_PUSH}\` is the default change detection strategy`,
|
|
37
37
|
},
|
|
38
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
38
39
|
},
|
|
39
|
-
defaultOptions: [DEFAULT_OPTIONS],
|
|
40
40
|
create(context, [{ allowExplicitOnPush }]) {
|
|
41
41
|
const sourceCode = context.sourceCode;
|
|
42
42
|
const changeDetectionMetadataProperty = utils_1.Selectors.metadataProperty(METADATA_PROPERTY_NAME);
|
|
@@ -15,8 +15,8 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
15
15
|
messages: {
|
|
16
16
|
preferOutputEmitterRef: 'Use `OutputEmitterRef` via `output()` for Component and Directive outputs rather than the legacy `@Output()` decorator',
|
|
17
17
|
},
|
|
18
|
+
defaultOptions: [],
|
|
18
19
|
},
|
|
19
|
-
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
21
|
return {
|
|
22
22
|
[utils_1.Selectors.OUTPUT_DECORATOR]: (node) => {
|