@angular-eslint/eslint-plugin 19.0.0-alpha.3 → 19.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/configs/recommended.json +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/rules/no-input-rename.d.ts.map +1 -1
- package/dist/rules/no-input-rename.js +55 -31
- package/dist/rules/no-output-rename.d.ts.map +1 -1
- package/dist/rules/no-output-rename.js +48 -3
- package/dist/rules/prefer-signals.d.ts +15 -0
- package/dist/rules/prefer-signals.d.ts.map +1 -0
- package/dist/rules/prefer-signals.js +168 -0
- package/dist/rules/prefer-standalone.d.ts.map +1 -1
- package/dist/rules/prefer-standalone.js +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Please see https://github.com/angular-eslint/angular-eslint for full usage instr
|
|
|
66
66
|
| [`pipe-prefix`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/pipe-prefix.md) | Enforce consistent prefix for pipes. | | | |
|
|
67
67
|
| [`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 component's `changeDetection` is set to `ChangeDetectionStrategy.OnPush` | | | :bulb: |
|
|
68
68
|
| [`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: |
|
|
69
|
-
| [`prefer-standalone`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-standalone.md) | Ensures component, directive and pipe `standalone` property is set to `true` in the component decorator |
|
|
69
|
+
| [`prefer-standalone`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-standalone.md) | Ensures component, directive and pipe `standalone` property is set to `true` in the component decorator | :white_check_mark: | :wrench: | |
|
|
70
70
|
| [`relative-url-prefix`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/relative-url-prefix.md) | The ./ and ../ prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix. See more at https://angular.dev/style-guide#style-05-04 | | | |
|
|
71
71
|
| [`require-localize-metadata`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/require-localize-metadata.md) | Ensures that $localize tagged messages contain helpful metadata to aid with translations. | | | |
|
|
72
72
|
| [`runtime-localize`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/runtime-localize.md) | Ensures that $localize tagged messages can use runtime-loaded translations. | | | |
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"@angular-eslint/no-output-on-prefix": "error",
|
|
13
13
|
"@angular-eslint/no-output-rename": "error",
|
|
14
14
|
"@angular-eslint/no-outputs-metadata-property": "error",
|
|
15
|
+
"@angular-eslint/prefer-standalone": "error",
|
|
15
16
|
"@angular-eslint/use-pipe-transform-interface": "error",
|
|
16
17
|
"@angular-eslint/use-lifecycle-interface": "warn"
|
|
17
18
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ declare const _default: {
|
|
|
57
57
|
"@angular-eslint/no-output-on-prefix": string;
|
|
58
58
|
"@angular-eslint/no-output-rename": string;
|
|
59
59
|
"@angular-eslint/no-outputs-metadata-property": string;
|
|
60
|
+
"@angular-eslint/prefer-standalone": string;
|
|
60
61
|
"@angular-eslint/use-pipe-transform-interface": string;
|
|
61
62
|
"@angular-eslint/use-lifecycle-interface": string;
|
|
62
63
|
};
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmHA,kBA6CE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-input-rename.d.ts","sourceRoot":"","sources":["../../src/rules/no-input-rename.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AACtE,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,wBAAwB,GACxB,yCAAyC,CAAC;AAC9C,eAAO,MAAM,SAAS,oBAAoB,CAAC;;AAG3C,
|
|
1
|
+
{"version":3,"file":"no-input-rename.d.ts","sourceRoot":"","sources":["../../src/rules/no-input-rename.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AACtE,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,wBAAwB,GACxB,yCAAyC,CAAC;AAC9C,eAAO,MAAM,SAAS,oBAAoB,CAAC;;AAG3C,wBAgQG"}
|
|
@@ -58,14 +58,6 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
58
58
|
!utils_2.ASTUtils.isIdentifier(propertyOrMethodDefinition.key)) {
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
let isAliasMetadataProperty = false;
|
|
62
|
-
if (node.parent && utils_1.ASTUtils.isProperty(node.parent)) {
|
|
63
|
-
if (utils_1.ASTUtils.getRawText(node.parent.key) !== 'alias') {
|
|
64
|
-
// We're within an Input decorator metadata object, but it is not the alias property
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
isAliasMetadataProperty = true;
|
|
68
|
-
}
|
|
69
61
|
const aliasName = utils_1.ASTUtils.getRawText(node);
|
|
70
62
|
const propertyName = utils_1.ASTUtils.getRawText(propertyOrMethodDefinition.key);
|
|
71
63
|
if (allowedNames.includes(aliasName) ||
|
|
@@ -73,28 +65,65 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
73
65
|
propertyName === (0, utils_1.kebabToCamelCase)(aliasName))) {
|
|
74
66
|
return;
|
|
75
67
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
// The alias is either a string in the `@Input()` decorator function,
|
|
69
|
+
// or a string on an `alias` property that is in an object expression
|
|
70
|
+
// that is in the `@Input()` decorator or the `input()` function or the
|
|
71
|
+
// `input.required()` function. If it's on the `alias` property, then we
|
|
72
|
+
// want to remove that whole property rather than just the string literal.
|
|
73
|
+
const stringToRemove = utils_1.ASTUtils.isTemplateElement(node)
|
|
74
|
+
? node.parent
|
|
75
|
+
: node;
|
|
76
|
+
let rangeToRemove = stringToRemove.range;
|
|
77
|
+
if (utils_1.ASTUtils.isProperty(stringToRemove.parent)) {
|
|
78
|
+
const property = stringToRemove.parent;
|
|
79
|
+
rangeToRemove = property.range;
|
|
80
|
+
if (utils_1.ASTUtils.isObjectExpression(property.parent)) {
|
|
81
|
+
const objectExpression = property.parent;
|
|
82
|
+
if (objectExpression.properties.length === 1) {
|
|
83
|
+
// The property is the only property in the
|
|
84
|
+
// object, so we can remove the whole object.
|
|
85
|
+
rangeToRemove = objectExpression.range;
|
|
86
|
+
// If the object is in an `input()` function, then
|
|
87
|
+
// the object will be the second argument. The first
|
|
88
|
+
// argument will be the default value. We need to
|
|
89
|
+
// remove the comma after the default value.
|
|
90
|
+
const tokenBefore = context.sourceCode.getTokenBefore(objectExpression);
|
|
91
|
+
if (tokenBefore && utils_2.ASTUtils.isCommaToken(tokenBefore)) {
|
|
92
|
+
rangeToRemove = [tokenBefore.range[0], rangeToRemove[1]];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// There are other properties in the object, so we
|
|
97
|
+
// can only remove the property. How we remove it
|
|
98
|
+
// will depend on where the property is in the object.
|
|
99
|
+
const propertyIndex = objectExpression.properties.indexOf(property);
|
|
100
|
+
if (propertyIndex < objectExpression.properties.length - 1) {
|
|
101
|
+
// The property is not the last one, so we can
|
|
102
|
+
// remove everything up to the next property
|
|
103
|
+
// which will remove the comma after it.
|
|
104
|
+
rangeToRemove = [
|
|
105
|
+
property.range[0],
|
|
106
|
+
objectExpression.properties[propertyIndex + 1].range[0],
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// The property is the last one. If the object has a
|
|
111
|
+
// trailing comma, then we want to keep the trailing comma.
|
|
112
|
+
// The simplest way to do that is to remove the property
|
|
113
|
+
// and the comma that precedes it.
|
|
114
|
+
const tokenBefore = context.sourceCode.getTokenBefore(property);
|
|
115
|
+
if (tokenBefore && utils_2.ASTUtils.isCommaToken(tokenBefore)) {
|
|
116
|
+
rangeToRemove = [tokenBefore.range[0], rangeToRemove[1]];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
86
120
|
}
|
|
87
121
|
}
|
|
88
122
|
if (aliasName === propertyName) {
|
|
89
123
|
context.report({
|
|
90
124
|
node,
|
|
91
125
|
messageId: 'noInputRename',
|
|
92
|
-
fix: (fixer) =>
|
|
93
|
-
if (node.parent && isAliasMetadataProperty) {
|
|
94
|
-
return fixer.remove(node.parent);
|
|
95
|
-
}
|
|
96
|
-
return fixer.remove(node);
|
|
97
|
-
},
|
|
126
|
+
fix: (fixer) => fixer.removeRange(rangeToRemove),
|
|
98
127
|
});
|
|
99
128
|
}
|
|
100
129
|
else if (!isAliasNameAllowed(selectors, propertyName, aliasName, selectorDirectiveName)) {
|
|
@@ -104,17 +133,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
104
133
|
suggest: [
|
|
105
134
|
{
|
|
106
135
|
messageId: 'suggestRemoveAliasName',
|
|
107
|
-
fix: (fixer) =>
|
|
108
|
-
if (node.parent && isAliasMetadataProperty) {
|
|
109
|
-
return fixer.remove(node.parent);
|
|
110
|
-
}
|
|
111
|
-
return fixer.remove(node);
|
|
112
|
-
},
|
|
136
|
+
fix: (fixer) => fixer.removeRange(rangeToRemove),
|
|
113
137
|
},
|
|
114
138
|
{
|
|
115
139
|
messageId: 'suggestReplaceOriginalNameWithAliasName',
|
|
116
140
|
fix: (fixer) => [
|
|
117
|
-
fixer.
|
|
141
|
+
fixer.removeRange(rangeToRemove),
|
|
118
142
|
fixer.replaceText(propertyOrMethodDefinition.key, aliasName.includes('-') ? `'${aliasName}'` : aliasName),
|
|
119
143
|
],
|
|
120
144
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-output-rename.d.ts","sourceRoot":"","sources":["../../src/rules/no-output-rename.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,wBAAwB,GACxB,yCAAyC,CAAC;AAC9C,eAAO,MAAM,SAAS,qBAAqB,CAAC;;AAG5C,
|
|
1
|
+
{"version":3,"file":"no-output-rename.d.ts","sourceRoot":"","sources":["../../src/rules/no-output-rename.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,wBAAwB,GACxB,yCAAyC,CAAC;AAC9C,eAAO,MAAM,SAAS,qBAAqB,CAAC;;AAG5C,wBAgMG"}
|
|
@@ -38,11 +38,56 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
38
38
|
}
|
|
39
39
|
const aliasName = utils_1.ASTUtils.getRawText(node);
|
|
40
40
|
const propertyName = utils_1.ASTUtils.getRawText(propertyOrMethodDefinition.key);
|
|
41
|
+
// The alias is either a string in the `@Output()` decorator function,
|
|
42
|
+
// or a string on an `alias` property that is in an object expression
|
|
43
|
+
// that is in the `output()` function. If it's the latter, then we want
|
|
44
|
+
// to remove that whole property rather than just the string literal.
|
|
45
|
+
const stringToRemove = utils_1.ASTUtils.isTemplateElement(node)
|
|
46
|
+
? node.parent
|
|
47
|
+
: node;
|
|
48
|
+
let rangeToRemove = stringToRemove.range;
|
|
49
|
+
if (utils_1.ASTUtils.isProperty(stringToRemove.parent)) {
|
|
50
|
+
const property = stringToRemove.parent;
|
|
51
|
+
rangeToRemove = property.range;
|
|
52
|
+
if (utils_1.ASTUtils.isObjectExpression(property.parent)) {
|
|
53
|
+
const objectExpression = property.parent;
|
|
54
|
+
if (objectExpression.properties.length === 1) {
|
|
55
|
+
// The property is the only property in the
|
|
56
|
+
// object, so we can remove the whole object.
|
|
57
|
+
rangeToRemove = objectExpression.range;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// There are other properties in the object, so we
|
|
61
|
+
// can only remove the property. How we remove it
|
|
62
|
+
// will depend on where the property is in the object.
|
|
63
|
+
const propertyIndex = objectExpression.properties.indexOf(property);
|
|
64
|
+
if (propertyIndex < objectExpression.properties.length - 1) {
|
|
65
|
+
// The property is not the last one, so we can
|
|
66
|
+
// remove everything up to the next property
|
|
67
|
+
// which will remove the comma after it.
|
|
68
|
+
rangeToRemove = [
|
|
69
|
+
property.range[0],
|
|
70
|
+
objectExpression.properties[propertyIndex + 1].range[0],
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// The property is the last one. If the object has a
|
|
75
|
+
// trailing comma, then we want to keep the trailing comma.
|
|
76
|
+
// The simplest way to do that is to remove the property
|
|
77
|
+
// and the comma that precedes it.
|
|
78
|
+
const tokenBefore = context.sourceCode.getTokenBefore(property);
|
|
79
|
+
if (tokenBefore && utils_2.ASTUtils.isCommaToken(tokenBefore)) {
|
|
80
|
+
rangeToRemove = [tokenBefore.range[0], rangeToRemove[1]];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
41
86
|
if (aliasName === propertyName) {
|
|
42
87
|
context.report({
|
|
43
88
|
node,
|
|
44
89
|
messageId: 'noOutputRename',
|
|
45
|
-
fix: (fixer) => fixer.
|
|
90
|
+
fix: (fixer) => fixer.removeRange(rangeToRemove),
|
|
46
91
|
});
|
|
47
92
|
}
|
|
48
93
|
else if (!isAliasNameAllowed(selectors, propertyName, aliasName)) {
|
|
@@ -52,12 +97,12 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
52
97
|
suggest: [
|
|
53
98
|
{
|
|
54
99
|
messageId: 'suggestRemoveAliasName',
|
|
55
|
-
fix: (fixer) => fixer.
|
|
100
|
+
fix: (fixer) => fixer.removeRange(rangeToRemove),
|
|
56
101
|
},
|
|
57
102
|
{
|
|
58
103
|
messageId: 'suggestReplaceOriginalNameWithAliasName',
|
|
59
104
|
fix: (fixer) => [
|
|
60
|
-
fixer.
|
|
105
|
+
fixer.removeRange(rangeToRemove),
|
|
61
106
|
fixer.replaceText(propertyOrMethodDefinition.key, aliasName.includes('-') ? `'${aliasName}'` : aliasName),
|
|
62
107
|
],
|
|
63
108
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
preferReadonlySignalProperties: boolean;
|
|
5
|
+
preferInputSignals: boolean;
|
|
6
|
+
preferQuerySignals: boolean;
|
|
7
|
+
useTypeChecking: boolean;
|
|
8
|
+
additionalSignalCreationFunctions: string[];
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
export type MessageIds = 'preferInputSignals' | 'preferQuerySignals' | 'preferReadonlySignalProperties' | 'suggestAddReadonlyModifier';
|
|
12
|
+
export declare const RULE_NAME = "prefer-signals";
|
|
13
|
+
declare const _default: ESLintUtils.RuleModule<MessageIds, Options, import("../utils/create-eslint-rule").RuleDocs, ESLintUtils.RuleListener>;
|
|
14
|
+
export default _default;
|
|
15
|
+
//# sourceMappingURL=prefer-signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-signals.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-signals.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvE,KAAK,OAAO,GAAG;IACb;QACE,8BAA8B,EAAE,OAAO,CAAC;QACxC,kBAAkB,EAAE,OAAO,CAAC;QAC5B,kBAAkB,EAAE,OAAO,CAAC;QAC5B,eAAe,EAAE,OAAO,CAAC;QACzB,iCAAiC,EAAE,MAAM,EAAE,CAAC;KAC7C;CACF,CAAC;AA4BF,MAAM,MAAM,UAAU,GAClB,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,4BAA4B,CAAC;AACjC,eAAO,MAAM,SAAS,mBAAmB,CAAC;;AAE1C,wBA6KG"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
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
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
preferReadonlySignalProperties: true,
|
|
9
|
+
preferInputSignals: true,
|
|
10
|
+
preferQuerySignals: true,
|
|
11
|
+
useTypeChecking: false,
|
|
12
|
+
additionalSignalCreationFunctions: [],
|
|
13
|
+
};
|
|
14
|
+
const KNOWN_SIGNAL_TYPES = new Set([
|
|
15
|
+
'InputSignal',
|
|
16
|
+
'ModelSignal',
|
|
17
|
+
'Signal',
|
|
18
|
+
'WritableSignal',
|
|
19
|
+
]);
|
|
20
|
+
const KNOWN_SIGNAL_CREATION_FUNCTIONS = new Set([
|
|
21
|
+
'computed',
|
|
22
|
+
'contentChild',
|
|
23
|
+
'contentChildren',
|
|
24
|
+
'input',
|
|
25
|
+
'model',
|
|
26
|
+
'signal',
|
|
27
|
+
'toSignal',
|
|
28
|
+
'viewChild',
|
|
29
|
+
'viewChildren',
|
|
30
|
+
]);
|
|
31
|
+
exports.RULE_NAME = 'prefer-signals';
|
|
32
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
33
|
+
name: exports.RULE_NAME,
|
|
34
|
+
meta: {
|
|
35
|
+
type: 'suggestion',
|
|
36
|
+
docs: {
|
|
37
|
+
description: 'Use readonly signals instead of `@Input()`, `@ViewChild()` and other legacy decorators',
|
|
38
|
+
recommended: 'recommended',
|
|
39
|
+
},
|
|
40
|
+
hasSuggestions: true,
|
|
41
|
+
schema: [
|
|
42
|
+
{
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
preferReadonlySignalProperties: {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
default: DEFAULT_OPTIONS.preferReadonlySignalProperties,
|
|
48
|
+
},
|
|
49
|
+
preferInputSignals: {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
default: DEFAULT_OPTIONS.preferInputSignals,
|
|
52
|
+
},
|
|
53
|
+
preferQuerySignals: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
default: DEFAULT_OPTIONS.preferQuerySignals,
|
|
56
|
+
},
|
|
57
|
+
useTypeChecking: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: DEFAULT_OPTIONS.useTypeChecking,
|
|
60
|
+
},
|
|
61
|
+
additionalSignalCreationFunctions: {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: { type: 'string' },
|
|
64
|
+
default: DEFAULT_OPTIONS.additionalSignalCreationFunctions,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
messages: {
|
|
71
|
+
preferInputSignals: 'Use `InputSignal`s (e.g. via `input()`) for Component input properties rather than the legacy `@Input()` decorator',
|
|
72
|
+
preferQuerySignals: 'Use the `{{function}}` function instead of the `{{decorator}}` decorator',
|
|
73
|
+
preferReadonlySignalProperties: 'Properties declared using signals should be marked as `readonly` since they should not be reassigned',
|
|
74
|
+
suggestAddReadonlyModifier: 'Add `readonly` modifier',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
defaultOptions: [{ ...DEFAULT_OPTIONS }],
|
|
78
|
+
create(context, [{ preferReadonlySignalProperties = DEFAULT_OPTIONS.preferReadonlySignalProperties, preferInputSignals = DEFAULT_OPTIONS.preferInputSignals, preferQuerySignals = DEFAULT_OPTIONS.preferQuerySignals, additionalSignalCreationFunctions = DEFAULT_OPTIONS.additionalSignalCreationFunctions, useTypeChecking = DEFAULT_OPTIONS.useTypeChecking, },]) {
|
|
79
|
+
let services;
|
|
80
|
+
const listener = {};
|
|
81
|
+
if (preferReadonlySignalProperties) {
|
|
82
|
+
listener[`PropertyDefinition:not([readonly=true])`] = (node) => {
|
|
83
|
+
let shouldBeReadonly = false;
|
|
84
|
+
if (node.typeAnnotation) {
|
|
85
|
+
// Use the type annotation to determine
|
|
86
|
+
// whether the property is a signal.
|
|
87
|
+
if (node.typeAnnotation.typeAnnotation.type ===
|
|
88
|
+
utils_2.AST_NODE_TYPES.TSTypeReference) {
|
|
89
|
+
const type = node.typeAnnotation.typeAnnotation;
|
|
90
|
+
if (type.typeArguments &&
|
|
91
|
+
type.typeName.type === utils_2.AST_NODE_TYPES.Identifier &&
|
|
92
|
+
KNOWN_SIGNAL_TYPES.has(type.typeName.name)) {
|
|
93
|
+
shouldBeReadonly = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// There is no type annotation, so try to
|
|
99
|
+
// use the value assigned to the property
|
|
100
|
+
// to determine whether it would be a signal.
|
|
101
|
+
if (node.value?.type === utils_2.AST_NODE_TYPES.CallExpression) {
|
|
102
|
+
let callee = node.value.callee;
|
|
103
|
+
// Some signal-creating functions have a `.required`
|
|
104
|
+
// member. For example, `input.required()`.
|
|
105
|
+
if (callee.type === utils_2.AST_NODE_TYPES.MemberExpression) {
|
|
106
|
+
if (callee.property.type === utils_2.AST_NODE_TYPES.Identifier &&
|
|
107
|
+
callee.property.name !== 'required') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
callee = callee.object;
|
|
111
|
+
}
|
|
112
|
+
if (callee.type === utils_2.AST_NODE_TYPES.Identifier &&
|
|
113
|
+
(KNOWN_SIGNAL_CREATION_FUNCTIONS.has(callee.name) ||
|
|
114
|
+
additionalSignalCreationFunctions.includes(callee.name))) {
|
|
115
|
+
shouldBeReadonly = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!shouldBeReadonly && useTypeChecking && node.value) {
|
|
119
|
+
services ??= utils_2.ESLintUtils.getParserServices(context);
|
|
120
|
+
const name = services
|
|
121
|
+
.getTypeAtLocation(node.value)
|
|
122
|
+
.getSymbol()?.name;
|
|
123
|
+
shouldBeReadonly =
|
|
124
|
+
name !== undefined && KNOWN_SIGNAL_TYPES.has(name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (shouldBeReadonly) {
|
|
128
|
+
context.report({
|
|
129
|
+
node: node.key,
|
|
130
|
+
messageId: 'preferReadonlySignalProperties',
|
|
131
|
+
suggest: [
|
|
132
|
+
{
|
|
133
|
+
messageId: 'suggestAddReadonlyModifier',
|
|
134
|
+
fix: (fixer) => fixer.insertTextBefore(node.key, 'readonly '),
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (preferInputSignals) {
|
|
142
|
+
listener[utils_1.Selectors.INPUT_DECORATOR] = (node) => {
|
|
143
|
+
context.report({
|
|
144
|
+
node,
|
|
145
|
+
messageId: 'preferInputSignals',
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (preferQuerySignals) {
|
|
150
|
+
listener['Decorator[expression.callee.name=/^(ContentChild|ContentChildren|ViewChild|ViewChildren)$/]'] = (node) => {
|
|
151
|
+
if (node.expression.type === utils_2.AST_NODE_TYPES.CallExpression &&
|
|
152
|
+
node.expression.callee.type === utils_2.AST_NODE_TYPES.Identifier) {
|
|
153
|
+
const decoratorName = node.expression.callee.name;
|
|
154
|
+
context.report({
|
|
155
|
+
node,
|
|
156
|
+
messageId: 'preferQuerySignals',
|
|
157
|
+
data: {
|
|
158
|
+
function: decoratorName.slice(0, 1).toLowerCase() +
|
|
159
|
+
decoratorName.slice(1),
|
|
160
|
+
decorator: decoratorName,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return listener;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-standalone.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-standalone.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAC5C,eAAO,MAAM,SAAS,sBAAsB,CAAC;;AAI7C,
|
|
1
|
+
{"version":3,"file":"prefer-standalone.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-standalone.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAC5C,eAAO,MAAM,SAAS,sBAAsB,CAAC;;AAI7C,wBAkEG"}
|
|
@@ -12,6 +12,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
|
12
12
|
type: 'suggestion',
|
|
13
13
|
docs: {
|
|
14
14
|
description: `Ensures component, directive and pipe \`${METADATA_PROPERTY_NAME}\` property is set to \`${IS_STANDALONE}\` in the component decorator`,
|
|
15
|
+
recommended: 'recommended',
|
|
15
16
|
},
|
|
16
17
|
fixable: 'code',
|
|
17
18
|
schema: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/eslint-plugin",
|
|
3
|
-
"version": "19.0.0-alpha.
|
|
3
|
+
"version": "19.0.0-alpha.5",
|
|
4
4
|
"description": "ESLint plugin for Angular applications, following https://angular.dev/style-guide",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.
|
|
22
|
-
"@angular-eslint/utils": "19.0.0-alpha.
|
|
21
|
+
"@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.5",
|
|
22
|
+
"@angular-eslint/utils": "19.0.0-alpha.5"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@angular-eslint/test-utils": "19.0.0-alpha.
|
|
25
|
+
"@angular-eslint/test-utils": "19.0.0-alpha.5"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|