@depup/angular-eslint__eslint-plugin 21.3.1-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/LICENSE +21 -0
- package/README.md +31 -0
- package/changes.json +10 -0
- package/dist/configs/README.md +44 -0
- package/dist/configs/all.json +54 -0
- package/dist/configs/recommended.json +18 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/rules/component-class-suffix.d.ts +15 -0
- package/dist/rules/component-class-suffix.d.ts.map +1 -0
- package/dist/rules/component-class-suffix.js +56 -0
- package/dist/rules/component-max-inline-declarations.d.ts +17 -0
- package/dist/rules/component-max-inline-declarations.d.ts.map +1 -0
- package/dist/rules/component-max-inline-declarations.js +107 -0
- package/dist/rules/component-selector.d.ts +14 -0
- package/dist/rules/component-selector.d.ts.map +1 -0
- package/dist/rules/component-selector.js +218 -0
- package/dist/rules/computed-must-return.d.ts +11 -0
- package/dist/rules/computed-must-return.d.ts.map +1 -0
- package/dist/rules/computed-must-return.js +62 -0
- package/dist/rules/consistent-component-styles.d.ts +12 -0
- package/dist/rules/consistent-component-styles.d.ts.map +1 -0
- package/dist/rules/consistent-component-styles.js +97 -0
- package/dist/rules/contextual-decorator.d.ts +12 -0
- package/dist/rules/contextual-decorator.d.ts.map +1 -0
- package/dist/rules/contextual-decorator.js +61 -0
- package/dist/rules/contextual-lifecycle.d.ts +11 -0
- package/dist/rules/contextual-lifecycle.d.ts.map +1 -0
- package/dist/rules/contextual-lifecycle.js +61 -0
- package/dist/rules/directive-class-suffix.d.ts +13 -0
- package/dist/rules/directive-class-suffix.d.ts.map +1 -0
- package/dist/rules/directive-class-suffix.js +60 -0
- package/dist/rules/directive-selector.d.ts +14 -0
- package/dist/rules/directive-selector.d.ts.map +1 -0
- package/dist/rules/directive-selector.js +156 -0
- package/dist/rules/no-async-lifecycle-method.d.ts +11 -0
- package/dist/rules/no-async-lifecycle-method.d.ts.map +1 -0
- package/dist/rules/no-async-lifecycle-method.js +45 -0
- package/dist/rules/no-attribute-decorator.d.ts +11 -0
- package/dist/rules/no-attribute-decorator.d.ts.map +1 -0
- package/dist/rules/no-attribute-decorator.js +32 -0
- package/dist/rules/no-conflicting-lifecycle.d.ts +11 -0
- package/dist/rules/no-conflicting-lifecycle.d.ts.map +1 -0
- package/dist/rules/no-conflicting-lifecycle.js +74 -0
- package/dist/rules/no-developer-preview.d.ts +11 -0
- package/dist/rules/no-developer-preview.d.ts.map +1 -0
- package/dist/rules/no-developer-preview.js +45 -0
- package/dist/rules/no-duplicates-in-metadata-arrays.d.ts +11 -0
- package/dist/rules/no-duplicates-in-metadata-arrays.d.ts.map +1 -0
- package/dist/rules/no-duplicates-in-metadata-arrays.js +67 -0
- package/dist/rules/no-empty-lifecycle-method.d.ts +11 -0
- package/dist/rules/no-empty-lifecycle-method.d.ts.map +1 -0
- package/dist/rules/no-empty-lifecycle-method.js +75 -0
- package/dist/rules/no-experimental.d.ts +11 -0
- package/dist/rules/no-experimental.d.ts.map +1 -0
- package/dist/rules/no-experimental.js +45 -0
- package/dist/rules/no-forward-ref.d.ts +12 -0
- package/dist/rules/no-forward-ref.d.ts.map +1 -0
- package/dist/rules/no-forward-ref.js +33 -0
- package/dist/rules/no-implicit-take-until-destroyed.d.ts +11 -0
- package/dist/rules/no-implicit-take-until-destroyed.d.ts.map +1 -0
- package/dist/rules/no-implicit-take-until-destroyed.js +83 -0
- package/dist/rules/no-input-prefix.d.ts +13 -0
- package/dist/rules/no-input-prefix.d.ts.map +1 -0
- package/dist/rules/no-input-prefix.js +118 -0
- package/dist/rules/no-input-rename.d.ts +13 -0
- package/dist/rules/no-input-rename.d.ts.map +1 -0
- package/dist/rules/no-input-rename.js +213 -0
- package/dist/rules/no-inputs-metadata-property.d.ts +11 -0
- package/dist/rules/no-inputs-metadata-property.d.ts.map +1 -0
- package/dist/rules/no-inputs-metadata-property.js +53 -0
- package/dist/rules/no-lifecycle-call.d.ts +11 -0
- package/dist/rules/no-lifecycle-call.d.ts.map +1 -0
- package/dist/rules/no-lifecycle-call.js +49 -0
- package/dist/rules/no-output-native.d.ts +11 -0
- package/dist/rules/no-output-native.d.ts.map +1 -0
- package/dist/rules/no-output-native.js +47 -0
- package/dist/rules/no-output-on-prefix.d.ts +11 -0
- package/dist/rules/no-output-on-prefix.d.ts.map +1 -0
- package/dist/rules/no-output-on-prefix.js +48 -0
- package/dist/rules/no-output-rename.d.ts +11 -0
- package/dist/rules/no-output-rename.d.ts.map +1 -0
- package/dist/rules/no-output-rename.js +172 -0
- package/dist/rules/no-outputs-metadata-property.d.ts +11 -0
- package/dist/rules/no-outputs-metadata-property.d.ts.map +1 -0
- package/dist/rules/no-outputs-metadata-property.js +53 -0
- package/dist/rules/no-pipe-impure.d.ts +11 -0
- package/dist/rules/no-pipe-impure.d.ts.map +1 -0
- package/dist/rules/no-pipe-impure.js +42 -0
- package/dist/rules/no-queries-metadata-property.d.ts +11 -0
- package/dist/rules/no-queries-metadata-property.d.ts.map +1 -0
- package/dist/rules/no-queries-metadata-property.js +38 -0
- package/dist/rules/no-uncalled-signals.d.ts +12 -0
- package/dist/rules/no-uncalled-signals.d.ts.map +1 -0
- package/dist/rules/no-uncalled-signals.js +92 -0
- package/dist/rules/pipe-prefix.d.ts +15 -0
- package/dist/rules/pipe-prefix.d.ts.map +1 -0
- package/dist/rules/pipe-prefix.js +92 -0
- package/dist/rules/prefer-host-metadata-property.d.ts +10 -0
- package/dist/rules/prefer-host-metadata-property.d.ts.map +1 -0
- package/dist/rules/prefer-host-metadata-property.js +40 -0
- package/dist/rules/prefer-inject.d.ts +11 -0
- package/dist/rules/prefer-inject.d.ts.map +1 -0
- package/dist/rules/prefer-inject.js +86 -0
- package/dist/rules/prefer-on-push-component-change-detection.d.ts +11 -0
- package/dist/rules/prefer-on-push-component-change-detection.d.ts.map +1 -0
- package/dist/rules/prefer-on-push-component-change-detection.js +81 -0
- package/dist/rules/prefer-output-emitter-ref.d.ts +10 -0
- package/dist/rules/prefer-output-emitter-ref.d.ts.map +1 -0
- package/dist/rules/prefer-output-emitter-ref.js +30 -0
- package/dist/rules/prefer-output-readonly.d.ts +11 -0
- package/dist/rules/prefer-output-readonly.d.ts.map +1 -0
- package/dist/rules/prefer-output-readonly.js +51 -0
- package/dist/rules/prefer-signal-model.d.ts +10 -0
- package/dist/rules/prefer-signal-model.d.ts.map +1 -0
- package/dist/rules/prefer-signal-model.js +71 -0
- package/dist/rules/prefer-signals.d.ts +20 -0
- package/dist/rules/prefer-signals.d.ts.map +1 -0
- package/dist/rules/prefer-signals.js +174 -0
- package/dist/rules/prefer-standalone.d.ts +11 -0
- package/dist/rules/prefer-standalone.d.ts.map +1 -0
- package/dist/rules/prefer-standalone.js +66 -0
- package/dist/rules/relative-url-prefix.d.ts +11 -0
- package/dist/rules/relative-url-prefix.d.ts.map +1 -0
- package/dist/rules/relative-url-prefix.js +59 -0
- package/dist/rules/require-lifecycle-on-prototype.d.ts +11 -0
- package/dist/rules/require-lifecycle-on-prototype.d.ts.map +1 -0
- package/dist/rules/require-lifecycle-on-prototype.js +72 -0
- package/dist/rules/require-localize-metadata.d.ts +17 -0
- package/dist/rules/require-localize-metadata.d.ts.map +1 -0
- package/dist/rules/require-localize-metadata.js +123 -0
- package/dist/rules/runtime-localize.d.ts +11 -0
- package/dist/rules/runtime-localize.d.ts.map +1 -0
- package/dist/rules/runtime-localize.js +45 -0
- package/dist/rules/sort-keys-in-type-decorator.d.ts +16 -0
- package/dist/rules/sort-keys-in-type-decorator.d.ts.map +1 -0
- package/dist/rules/sort-keys-in-type-decorator.js +191 -0
- package/dist/rules/sort-lifecycle-methods.d.ts +11 -0
- package/dist/rules/sort-lifecycle-methods.d.ts.map +1 -0
- package/dist/rules/sort-lifecycle-methods.js +45 -0
- package/dist/rules/use-component-selector.d.ts +11 -0
- package/dist/rules/use-component-selector.d.ts.map +1 -0
- package/dist/rules/use-component-selector.js +41 -0
- package/dist/rules/use-component-view-encapsulation.d.ts +11 -0
- package/dist/rules/use-component-view-encapsulation.d.ts.map +1 -0
- package/dist/rules/use-component-view-encapsulation.js +49 -0
- package/dist/rules/use-injectable-provided-in.d.ts +13 -0
- package/dist/rules/use-injectable-provided-in.d.ts.map +1 -0
- package/dist/rules/use-injectable-provided-in.js +63 -0
- package/dist/rules/use-lifecycle-interface.d.ts +11 -0
- package/dist/rules/use-lifecycle-interface.d.ts.map +1 -0
- package/dist/rules/use-lifecycle-interface.js +65 -0
- package/dist/rules/use-pipe-transform-interface.d.ts +11 -0
- package/dist/rules/use-pipe-transform-interface.d.ts.map +1 -0
- package/dist/rules/use-pipe-transform-interface.js +49 -0
- package/dist/utils/create-eslint-rule.d.ts +8 -0
- package/dist/utils/create-eslint-rule.d.ts.map +1 -0
- package/dist/utils/create-eslint-rule.js +28 -0
- package/dist/utils/jsdoc.d.ts +11 -0
- package/dist/utils/jsdoc.d.ts.map +1 -0
- package/dist/utils/jsdoc.js +174 -0
- package/dist/utils/signals.d.ts +2 -0
- package/dist/utils/signals.d.ts.map +1 -0
- package/dist/utils/signals.js +10 -0
- package/package.json +56 -0
|
@@ -0,0 +1,56 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'component-class-suffix';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: `Classes decorated with @Component must have suffix "Component" (or custom) in their name. Note: As of v20, this is no longer recommended by the Angular Team.`,
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
suffixes: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
messages: {
|
|
29
|
+
componentClassSuffix: `Component class names should end with one of these suffixes: {{suffixes}}`,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultOptions: [
|
|
33
|
+
{
|
|
34
|
+
suffixes: ['Component'],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
create(context, [{ suffixes }]) {
|
|
38
|
+
return {
|
|
39
|
+
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
40
|
+
const classParent = node.parent;
|
|
41
|
+
const className = utils_1.ASTUtils.getClassName(classParent);
|
|
42
|
+
if (!className ||
|
|
43
|
+
!suffixes.some((suffix) => className.endsWith(suffix))) {
|
|
44
|
+
context.report({
|
|
45
|
+
node: classParent.id ? classParent.id : classParent,
|
|
46
|
+
messageId: 'componentClassSuffix',
|
|
47
|
+
data: { suffixes: (0, utils_1.toHumanReadableText)(suffixes) },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
exports.RULE_DOCS_EXTENSION = {
|
|
55
|
+
rationale: "Historically, appending 'Component' to component class names was recommended to make component files easily identifiable. However, as of Angular v20, the Angular Team no longer recommends this convention, favoring simpler class names. This rule remains available for teams that have established this naming pattern and wish to maintain consistency in their existing codebase.",
|
|
56
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type Options = [
|
|
2
|
+
{
|
|
3
|
+
readonly template?: number;
|
|
4
|
+
readonly styles?: number;
|
|
5
|
+
readonly animations?: number;
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
export type MessageIds = 'componentMaxInlineDeclarations';
|
|
9
|
+
export declare const RULE_NAME = "component-max-inline-declarations";
|
|
10
|
+
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"componentMaxInlineDeclarations", Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
14
|
+
export declare const RULE_DOCS_EXTENSION: {
|
|
15
|
+
rationale: string;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=component-max-inline-declarations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-max-inline-declarations.d.ts","sourceRoot":"","sources":["../../src/rules/component-max-inline-declarations.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B;CACF,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,gCAAgC,CAAC;AAC1D,eAAO,MAAM,SAAS,sCAAsC,CAAC;;;;AAkB7D,wBA4GG;AAEH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'component-max-inline-declarations';
|
|
7
|
+
const NEW_LINE_REGEXP = /\r\n|\r|\n/;
|
|
8
|
+
const DEFAULT_TEMPLATE_LIMIT = 3;
|
|
9
|
+
const DEFAULT_STYLES_LIMIT = 3;
|
|
10
|
+
const DEFAULT_ANIMATIONS_LIMIT = 15;
|
|
11
|
+
function getLinesCount(node) {
|
|
12
|
+
if (utils_1.ASTUtils.isTemplateLiteral(node)) {
|
|
13
|
+
return node.quasis[0].value.raw.trim().split(NEW_LINE_REGEXP).length;
|
|
14
|
+
}
|
|
15
|
+
if (utils_1.ASTUtils.isLiteral(node)) {
|
|
16
|
+
return node.raw.trim().split(NEW_LINE_REGEXP).length;
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
21
|
+
name: exports.RULE_NAME,
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'suggestion',
|
|
24
|
+
docs: {
|
|
25
|
+
description: `Enforces a maximum number of lines in inline template, styles and animations.`,
|
|
26
|
+
},
|
|
27
|
+
schema: [
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
template: { minimum: 0, type: 'number' },
|
|
32
|
+
styles: { minimum: 0, type: 'number' },
|
|
33
|
+
animations: { minimum: 0, type: 'number' },
|
|
34
|
+
},
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
messages: {
|
|
39
|
+
componentMaxInlineDeclarations: `\`{{propertyType}}\` has too many lines ({{lineCount}}). Maximum allowed is {{max}}`,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultOptions: [
|
|
43
|
+
{
|
|
44
|
+
template: DEFAULT_TEMPLATE_LIMIT,
|
|
45
|
+
styles: DEFAULT_STYLES_LIMIT,
|
|
46
|
+
animations: DEFAULT_ANIMATIONS_LIMIT,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
create(context, [{ template = DEFAULT_TEMPLATE_LIMIT, styles = DEFAULT_STYLES_LIMIT, animations = DEFAULT_ANIMATIONS_LIMIT, },]) {
|
|
50
|
+
return {
|
|
51
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='template']`]({ value, }) {
|
|
52
|
+
const lineCount = getLinesCount(value);
|
|
53
|
+
if (lineCount <= template)
|
|
54
|
+
return;
|
|
55
|
+
context.report({
|
|
56
|
+
node: value,
|
|
57
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
58
|
+
data: {
|
|
59
|
+
lineCount,
|
|
60
|
+
max: template,
|
|
61
|
+
propertyType: 'template',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styles']`]({ value, }) {
|
|
66
|
+
let lineCount;
|
|
67
|
+
if (!utils_1.ASTUtils.isArrayExpression(value)) {
|
|
68
|
+
lineCount = getLinesCount(value);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lineCount = value.elements.reduce((lines, element) => lines + (element ? getLinesCount(element) : 0), 0);
|
|
72
|
+
}
|
|
73
|
+
if (lineCount <= styles)
|
|
74
|
+
return;
|
|
75
|
+
context.report({
|
|
76
|
+
node: value,
|
|
77
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
78
|
+
data: {
|
|
79
|
+
lineCount,
|
|
80
|
+
max: styles,
|
|
81
|
+
propertyType: 'styles',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='animations']`]({ value, }) {
|
|
86
|
+
if (!utils_1.ASTUtils.isArrayExpression(value) || value.elements.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
const animationsBracketsSize = 2;
|
|
89
|
+
const lineCount = Math.max(value.loc.end.line - value.loc.start.line - animationsBracketsSize, 1);
|
|
90
|
+
if (lineCount <= animations)
|
|
91
|
+
return;
|
|
92
|
+
context.report({
|
|
93
|
+
node: value,
|
|
94
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
95
|
+
data: {
|
|
96
|
+
lineCount,
|
|
97
|
+
max: animations,
|
|
98
|
+
propertyType: 'animations',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
exports.RULE_DOCS_EXTENSION = {
|
|
106
|
+
rationale: `When templates or styles exceed a few lines, keeping them inline makes components harder to read and maintain. Inline templates and styles cannot be syntax-highlighted as effectively as separate files, complicate code reviews, and make it difficult to quickly understand a component's structure. Moving larger templates and styles to separate files improves readability, enables better IDE support, and follows the separation of concerns principle.`,
|
|
107
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SelectorUtils } from '@angular-eslint/utils';
|
|
2
|
+
export type Options = readonly [
|
|
3
|
+
SelectorUtils.SingleConfigOption | SelectorUtils.MultipleConfigOption
|
|
4
|
+
];
|
|
5
|
+
export type MessageIds = 'prefixFailure' | 'styleFailure' | 'styleAndPrefixFailure' | 'typeFailure' | 'shadowDomEncapsulatedStyleFailure' | 'selectorAfterPrefixFailure';
|
|
6
|
+
export declare const RULE_NAME = "component-selector";
|
|
7
|
+
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> & {
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
export declare const RULE_DOCS_EXTENSION: {
|
|
12
|
+
rationale: string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=component-selector.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,218 @@
|
|
|
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 = 'component-selector';
|
|
8
|
+
const VIEW_ENCAPSULATION_SHADOW_DOM = 'ShadowDom';
|
|
9
|
+
const VIEW_ENCAPSULATION = 'ViewEncapsulation';
|
|
10
|
+
const STYLE_GUIDE_LINK = 'https://angular.dev/style-guide#choosing-component-selectors';
|
|
11
|
+
const SHADOW_DOM_ENCAPSULATED_STYLE_LINK = 'https://github.com/angular-eslint/angular-eslint/issues/534';
|
|
12
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
13
|
+
name: exports.RULE_NAME,
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'suggestion',
|
|
16
|
+
docs: {
|
|
17
|
+
description: `Component selectors should follow given naming rules. See more at ${STYLE_GUIDE_LINK}.`,
|
|
18
|
+
},
|
|
19
|
+
schema: [
|
|
20
|
+
{
|
|
21
|
+
oneOf: [
|
|
22
|
+
// Single config object
|
|
23
|
+
{
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
type: {
|
|
27
|
+
oneOf: [
|
|
28
|
+
{ type: 'string' },
|
|
29
|
+
{
|
|
30
|
+
type: 'array',
|
|
31
|
+
items: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
enum: [
|
|
34
|
+
utils_1.SelectorUtils.OPTION_TYPE_ELEMENT,
|
|
35
|
+
utils_1.SelectorUtils.OPTION_TYPE_ATTRIBUTE,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
prefix: {
|
|
42
|
+
oneOf: [{ type: 'string' }, { type: 'array' }],
|
|
43
|
+
},
|
|
44
|
+
style: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: [
|
|
47
|
+
utils_1.ASTUtils.OPTION_STYLE_CAMEL_CASE,
|
|
48
|
+
utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE,
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['type', 'style'],
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
},
|
|
55
|
+
// Array of 1-2 config objects
|
|
56
|
+
{
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
type: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
enum: [
|
|
64
|
+
utils_1.SelectorUtils.OPTION_TYPE_ELEMENT,
|
|
65
|
+
utils_1.SelectorUtils.OPTION_TYPE_ATTRIBUTE,
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
prefix: {
|
|
69
|
+
oneOf: [{ type: 'string' }, { type: 'array' }],
|
|
70
|
+
},
|
|
71
|
+
style: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
enum: [
|
|
74
|
+
utils_1.ASTUtils.OPTION_STYLE_CAMEL_CASE,
|
|
75
|
+
utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE,
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
required: ['type', 'style'],
|
|
81
|
+
},
|
|
82
|
+
minItems: 1,
|
|
83
|
+
maxItems: 2,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
messages: {
|
|
89
|
+
prefixFailure: `The selector should start with one of these prefixes: {{prefix}} (${STYLE_GUIDE_LINK})`,
|
|
90
|
+
styleFailure: `The selector should be {{style}} (${STYLE_GUIDE_LINK})`,
|
|
91
|
+
styleAndPrefixFailure: `The selector should be {{style}} and start with one of these prefixes: {{prefix}} (${STYLE_GUIDE_LINK} and ${STYLE_GUIDE_LINK})`,
|
|
92
|
+
typeFailure: `The selector should be used as an {{type}} (${STYLE_GUIDE_LINK})`,
|
|
93
|
+
shadowDomEncapsulatedStyleFailure: `The selector of a ShadowDom-encapsulated component should be \`${utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE}\` (${SHADOW_DOM_ENCAPSULATED_STYLE_LINK})`,
|
|
94
|
+
selectorAfterPrefixFailure: `There should be a selector after the {{prefix}} prefix`,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
defaultOptions: [
|
|
98
|
+
{
|
|
99
|
+
type: undefined,
|
|
100
|
+
prefix: 'app', // Match default Angular CLI prefix
|
|
101
|
+
style: undefined,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
create(context, [options]) {
|
|
105
|
+
// Options are required by schema, so if undefined, ESLint will throw an error
|
|
106
|
+
if (!options) {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
// Normalize options to a consistent format using shared utility
|
|
110
|
+
const configByType = utils_1.SelectorUtils.normalizeOptionsToConfigs(options);
|
|
111
|
+
return {
|
|
112
|
+
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
113
|
+
const rawSelectors = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'selector');
|
|
114
|
+
if (!rawSelectors) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Parse selectors once for reuse
|
|
118
|
+
const parsedSelectors = utils_1.SelectorUtils.parseSelectorNode(rawSelectors);
|
|
119
|
+
if (!parsedSelectors || parsedSelectors.length === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const applicableConfig = utils_1.SelectorUtils.getApplicableConfig(rawSelectors, configByType);
|
|
123
|
+
if (!applicableConfig) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const { type, prefix, style } = applicableConfig;
|
|
127
|
+
const isValidOptions = utils_1.SelectorUtils.checkValidOptions(type, prefix, style);
|
|
128
|
+
if (!isValidOptions) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Override `style` for ShadowDom-encapsulated components. See https://github.com/angular-eslint/angular-eslint/issues/534.
|
|
132
|
+
const overrideStyle = style !== utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE &&
|
|
133
|
+
hasEncapsulationShadowDomProperty(node)
|
|
134
|
+
? utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE
|
|
135
|
+
: style;
|
|
136
|
+
const hasExpectedSelector = utils_1.SelectorUtils.checkSelector(rawSelectors, type, prefix, overrideStyle, parsedSelectors);
|
|
137
|
+
if (hasExpectedSelector === null) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Special check for ShadowDom-encapsulated components
|
|
141
|
+
// They must have a hyphen in the selector (e.g., 'app-selector' not 'appSelector')
|
|
142
|
+
const isShadowDom = style !== overrideStyle;
|
|
143
|
+
if (isShadowDom) {
|
|
144
|
+
// For ShadowDom components, check if any selector contains a hyphen
|
|
145
|
+
// We need to check the raw selector values from parsedSelectors
|
|
146
|
+
const hasHyphen = parsedSelectors.some((selector) => {
|
|
147
|
+
// Check if the element selector contains a hyphen
|
|
148
|
+
return selector.element && selector.element.includes('-');
|
|
149
|
+
});
|
|
150
|
+
if (!hasHyphen) {
|
|
151
|
+
context.report({
|
|
152
|
+
node: rawSelectors,
|
|
153
|
+
messageId: 'shadowDomEncapsulatedStyleFailure',
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Component-specific validation logic (includes styleAndPrefixFailure)
|
|
159
|
+
if (!hasExpectedSelector.hasExpectedType) {
|
|
160
|
+
utils_1.SelectorUtils.reportTypeError(rawSelectors, type, context);
|
|
161
|
+
}
|
|
162
|
+
else if (!hasExpectedSelector.hasSelectorAfterPrefix) {
|
|
163
|
+
// Only report selector after prefix error if prefix is actually required
|
|
164
|
+
if (prefix !== undefined) {
|
|
165
|
+
const prefixArray = (0, utils_1.arrayify)(prefix);
|
|
166
|
+
if (prefixArray.length > 0) {
|
|
167
|
+
utils_1.SelectorUtils.reportSelectorAfterPrefixError(rawSelectors, prefix, context);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (!hasExpectedSelector.hasExpectedStyle) {
|
|
172
|
+
if (style === overrideStyle) {
|
|
173
|
+
if (!hasExpectedSelector.hasExpectedPrefix) {
|
|
174
|
+
if (prefix !== undefined) {
|
|
175
|
+
// Only report style and prefix error if prefix is actually required
|
|
176
|
+
utils_1.SelectorUtils.reportStyleAndPrefixError(rawSelectors, style, prefix, context);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// If no prefix required, just report style error
|
|
180
|
+
utils_1.SelectorUtils.reportStyleError(rawSelectors, style, context);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
utils_1.SelectorUtils.reportStyleError(rawSelectors, style, context);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
context.report({
|
|
189
|
+
node: rawSelectors,
|
|
190
|
+
messageId: 'shadowDomEncapsulatedStyleFailure',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (!hasExpectedSelector.hasExpectedPrefix) {
|
|
195
|
+
// Only report prefix error if prefix is actually required (not empty)
|
|
196
|
+
if (prefix !== undefined) {
|
|
197
|
+
const prefixArray = (0, utils_1.arrayify)(prefix);
|
|
198
|
+
if (prefixArray.length > 0) {
|
|
199
|
+
utils_1.SelectorUtils.reportPrefixError(rawSelectors, prefix, context);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
function hasEncapsulationShadowDomProperty(node) {
|
|
208
|
+
const encapsulationValue = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'encapsulation');
|
|
209
|
+
return (encapsulationValue &&
|
|
210
|
+
utils_1.ASTUtils.isMemberExpression(encapsulationValue) &&
|
|
211
|
+
utils_2.ASTUtils.isIdentifier(encapsulationValue.object) &&
|
|
212
|
+
encapsulationValue.object.name === VIEW_ENCAPSULATION &&
|
|
213
|
+
utils_2.ASTUtils.isIdentifier(encapsulationValue.property) &&
|
|
214
|
+
encapsulationValue.property.name === VIEW_ENCAPSULATION_SHADOW_DOM);
|
|
215
|
+
}
|
|
216
|
+
exports.RULE_DOCS_EXTENSION = {
|
|
217
|
+
rationale: "Consistent component selector naming conventions provide several benefits: they make components easily identifiable in templates and browser DevTools, prevent naming collisions with native HTML elements and third-party components, enable teams to quickly identify which library or feature area a component belongs to, and align with the Web Components specification for custom elements. For example, prefixing selectors with 'app-' (like 'app-user-profile') clearly distinguishes your application components from third-party libraries.",
|
|
218
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Options = [];
|
|
2
|
+
export type MessageIds = 'computedMissingReturn';
|
|
3
|
+
export declare const RULE_NAME = "computed-must-return";
|
|
4
|
+
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"computedMissingReturn", [], 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=computed-must-return.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-must-return.d.ts","sourceRoot":"","sources":["../../src/rules/computed-must-return.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,uBAAuB,CAAC;AACjD,eAAO,MAAM,SAAS,yBAAyB,CAAC;;;;AAEhD,wBAqEG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
|
|
4
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
exports.RULE_NAME = 'computed-must-return';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
docs: {
|
|
12
|
+
description: `Ensures that computed() returns a value. Omitting the value is likely a mistake.`,
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
computedMissingReturn: 'computed() is missing a return value',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
const functionStack = [];
|
|
22
|
+
function enterFunction(node) {
|
|
23
|
+
functionStack.push({
|
|
24
|
+
node,
|
|
25
|
+
hasReturn: false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function exitFunction(node) {
|
|
29
|
+
const funcInfo = functionStack.pop();
|
|
30
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
31
|
+
node.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
32
|
+
node.parent.callee.name === 'computed' &&
|
|
33
|
+
node.parent.arguments[0] === node) {
|
|
34
|
+
// Arrow function without a body
|
|
35
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
36
|
+
node.expression) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!funcInfo?.hasReturn) {
|
|
40
|
+
context.report({
|
|
41
|
+
node: node.parent,
|
|
42
|
+
messageId: 'computedMissingReturn',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ArrowFunctionExpression: enterFunction,
|
|
49
|
+
'ArrowFunctionExpression:exit': exitFunction,
|
|
50
|
+
FunctionExpression: enterFunction,
|
|
51
|
+
'FunctionExpression:exit': exitFunction,
|
|
52
|
+
ReturnStatement(node) {
|
|
53
|
+
if (node.argument && functionStack.length > 0) {
|
|
54
|
+
functionStack[functionStack.length - 1].hasReturn = true;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
exports.RULE_DOCS_EXTENSION = {
|
|
61
|
+
rationale: "`computed()` is used to transform signal values. If no value is returned from `computed()`, it's likely a mistake.",
|
|
62
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Mode = 'array' | 'string';
|
|
2
|
+
export type Options = [mode: Mode];
|
|
3
|
+
export type MessageIds = 'useStylesArray' | 'useStylesString' | 'useStyleUrl' | 'useStyleUrls';
|
|
4
|
+
export declare const RULE_NAME = "consistent-component-styles";
|
|
5
|
+
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> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const RULE_DOCS_EXTENSION: {
|
|
10
|
+
rationale: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=consistent-component-styles.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'consistent-component-styles';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Ensures consistent usage of `styles`/`styleUrls`/`styleUrl` within Component metadata',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [
|
|
16
|
+
{
|
|
17
|
+
type: 'string',
|
|
18
|
+
enum: ['array', 'string'],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
messages: {
|
|
22
|
+
useStyleUrl: 'Use `styleUrl` instead of `styleUrls` for a single stylesheet',
|
|
23
|
+
useStyleUrls: 'Use `styleUrls` instead of `styleUrl`',
|
|
24
|
+
useStylesArray: 'Use a `string[]` instead of a `string` for the `styles` property',
|
|
25
|
+
useStylesString: 'Use a `string` instead of a `string[]` for the `styles` property',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultOptions: ['string'],
|
|
29
|
+
create(context, [mode]) {
|
|
30
|
+
const { COMPONENT_CLASS_DECORATOR, metadataProperty } = utils_1.Selectors;
|
|
31
|
+
const LITERAL_OR_TEMPLATE_LITERAL = ':matches(Literal, TemplateLiteral)';
|
|
32
|
+
if (mode === 'array') {
|
|
33
|
+
const stylesStringExpression = `${COMPONENT_CLASS_DECORATOR} ${metadataProperty('styles')} > ${LITERAL_OR_TEMPLATE_LITERAL}`;
|
|
34
|
+
const styleUrlProperty = `${COMPONENT_CLASS_DECORATOR} ${metadataProperty('styleUrl')}:has(:matches(Literal, TemplateElement))`;
|
|
35
|
+
return {
|
|
36
|
+
[stylesStringExpression](node) {
|
|
37
|
+
context.report({
|
|
38
|
+
node,
|
|
39
|
+
messageId: 'useStylesArray',
|
|
40
|
+
fix: (fixer) => {
|
|
41
|
+
return fixer.replaceText(node, utils_1.ASTUtils.isStringLiteral(node)
|
|
42
|
+
? `[${node.raw}]`
|
|
43
|
+
: `[${context.sourceCode.getText(node)}]`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
[styleUrlProperty](node) {
|
|
48
|
+
context.report({
|
|
49
|
+
node,
|
|
50
|
+
messageId: 'useStyleUrls',
|
|
51
|
+
fix: (fixer) => {
|
|
52
|
+
return fixer.replaceText(node, utils_1.ASTUtils.isStringLiteral(node.value)
|
|
53
|
+
? `styleUrls: [${node.value.raw}]`
|
|
54
|
+
: `styleUrls: [${context.sourceCode.getText(node.value)}]`);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const singleArrayStringLiteral = `ArrayExpression:matches([elements.length=1]:has(${LITERAL_OR_TEMPLATE_LITERAL}))`;
|
|
62
|
+
const singleStylesArrayExpression = `${COMPONENT_CLASS_DECORATOR} ${metadataProperty('styles')} > ${singleArrayStringLiteral}`;
|
|
63
|
+
const singleStyleUrlsProperty = `${COMPONENT_CLASS_DECORATOR} ${metadataProperty('styleUrls')}:has(${singleArrayStringLiteral})`;
|
|
64
|
+
return {
|
|
65
|
+
[singleStylesArrayExpression](node) {
|
|
66
|
+
// The selector ensures the element is not null.
|
|
67
|
+
const el = node.elements[0];
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'useStylesString',
|
|
71
|
+
fix: (fixer) => {
|
|
72
|
+
return fixer.replaceText(node, utils_1.ASTUtils.isStringLiteral(el)
|
|
73
|
+
? el.raw
|
|
74
|
+
: context.sourceCode.getText(el));
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
[singleStyleUrlsProperty](node) {
|
|
79
|
+
// The selector ensures the value is an array with a single non-null element.
|
|
80
|
+
const el = node.value.elements[0];
|
|
81
|
+
context.report({
|
|
82
|
+
node,
|
|
83
|
+
messageId: 'useStyleUrl',
|
|
84
|
+
fix: (fixer) => {
|
|
85
|
+
return fixer.replaceText(node, utils_1.ASTUtils.isStringLiteral(el)
|
|
86
|
+
? `styleUrl: ${el.raw}`
|
|
87
|
+
: `styleUrl: ${context.sourceCode.getText(el)}`);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
exports.RULE_DOCS_EXTENSION = {
|
|
96
|
+
rationale: "Consistency in how styles are declared makes codebases easier to navigate and maintain. When some components use styleUrl while others use styleUrls, or when styles are sometimes arrays and sometimes strings, it becomes harder for developers to predict what they'll find when opening a component. This rule enforces a consistent pattern across your codebase. Angular 17+ introduced the singular styleUrl property alongside the existing styleUrls array for common cases where components have only one stylesheet. Choosing one approach and sticking with it reduces cognitive load and makes the codebase feel more cohesive.",
|
|
97
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export type Options = [];
|
|
3
|
+
export type MessageIds = 'contextualDecorator';
|
|
4
|
+
export declare const RULE_NAME = "contextual-decorator";
|
|
5
|
+
declare const _default: TSESLint.RuleModule<"contextualDecorator", [], import("../utils/create-eslint-rule").RuleDocs, TSESLint.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const RULE_DOCS_EXTENSION: {
|
|
10
|
+
rationale: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=contextual-decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextual-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/contextual-decorator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAGnE,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAC/C,eAAO,MAAM,SAAS,yBAAyB,CAAC;;;;AAEhD,wBA2BG;AA2DH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
|