@angular-eslint/eslint-plugin 15.2.2-alpha.9 → 16.0.0-alpha.1
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/dist/configs/README.md +9 -24
- package/dist/configs/all.json +2 -1
- package/dist/configs/recommended.json +2 -1
- package/dist/index.js +101 -1
- package/dist/rules/component-class-suffix.js +55 -0
- package/dist/rules/component-max-inline-declarations.js +102 -0
- package/dist/rules/component-selector.js +124 -0
- package/dist/rules/contextual-decorator.js +60 -0
- package/dist/rules/contextual-lifecycle.js +58 -0
- package/dist/rules/directive-class-suffix.js +60 -0
- package/dist/rules/directive-selector.js +89 -0
- package/dist/rules/no-attribute-decorator.js +30 -0
- package/dist/rules/no-conflicting-lifecycle.js +71 -0
- package/dist/rules/no-empty-lifecycle-method.js +66 -0
- package/dist/rules/no-forward-ref.js +31 -0
- package/dist/rules/no-host-metadata-property.js +68 -0
- package/dist/rules/no-input-prefix.js +105 -0
- package/dist/rules/no-input-rename.js +175 -0
- package/dist/rules/no-inputs-metadata-property.js +52 -0
- package/dist/rules/no-lifecycle-call.js +47 -0
- package/dist/rules/no-output-native.js +44 -0
- package/dist/rules/no-output-on-prefix.js +45 -0
- package/dist/rules/no-output-rename.js +107 -0
- package/dist/rules/no-outputs-metadata-property.js +33 -0
- package/dist/rules/no-pipe-impure.js +40 -0
- package/dist/rules/no-queries-metadata-property.js +37 -0
- package/dist/rules/pipe-prefix.js +79 -0
- package/dist/rules/prefer-on-push-component-change-detection.js +79 -0
- package/dist/rules/prefer-output-readonly.js +39 -0
- package/dist/rules/relative-url-prefix.js +55 -0
- package/dist/rules/require-localize-metadata.js +74 -0
- package/dist/rules/sort-ngmodule-metadata-arrays.js +63 -0
- package/dist/rules/use-component-selector.js +37 -0
- package/dist/rules/use-component-view-encapsulation.js +48 -0
- package/dist/rules/use-injectable-provided-in.js +62 -0
- package/dist/rules/use-lifecycle-interface.js +45 -0
- package/dist/rules/use-pipe-transform-interface.js +47 -0
- package/dist/utils/create-eslint-rule.js +26 -0
- package/package.json +4 -4
- package/dist/configs/base.json +0 -4
- package/dist/configs/ng-cli-compat--formatting-add-on.json +0 -51
- package/dist/configs/ng-cli-compat.json +0 -188
- package/dist/configs/recommended--extra.json +0 -40
- package/dist/eslint-plugin/src/index.d.ts +0 -309
- package/dist/eslint-plugin/src/rules/component-class-suffix.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/component-max-inline-declarations.d.ts +0 -11
- package/dist/eslint-plugin/src/rules/component-selector.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/contextual-decorator.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/contextual-lifecycle.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/directive-class-suffix.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/directive-selector.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/no-attribute-decorator.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-conflicting-lifecycle.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-empty-lifecycle-method.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-forward-ref.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/no-host-metadata-property.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-input-prefix.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-input-rename.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-inputs-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-lifecycle-call.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-native.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-on-prefix.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-rename.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-outputs-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-pipe-impure.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-queries-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/pipe-prefix.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/prefer-on-push-component-change-detection.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/prefer-output-readonly.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/relative-url-prefix.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/require-localize-metadata.d.ts +0 -10
- package/dist/eslint-plugin/src/rules/sort-ngmodule-metadata-arrays.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/use-component-selector.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-component-view-encapsulation.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-injectable-provided-in.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/use-lifecycle-interface.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-pipe-transform-interface.d.ts +0 -4
- package/dist/eslint-plugin/src/utils/create-eslint-rule.d.ts +0 -2
|
@@ -0,0 +1,79 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'prefer-on-push-component-change-detection';
|
|
7
|
+
const METADATA_PROPERTY_NAME = 'changeDetection';
|
|
8
|
+
const STRATEGY_ON_PUSH = 'ChangeDetectionStrategy.OnPush';
|
|
9
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
10
|
+
name: exports.RULE_NAME,
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: `Ensures component's \`${METADATA_PROPERTY_NAME}\` is set to \`${STRATEGY_ON_PUSH}\``,
|
|
15
|
+
recommended: false,
|
|
16
|
+
},
|
|
17
|
+
hasSuggestions: true,
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
preferOnPushComponentChangeDetection: `The component's \`${METADATA_PROPERTY_NAME}\` value should be set to \`${STRATEGY_ON_PUSH}\``,
|
|
21
|
+
suggestAddChangeDetectionOnPush: `Add \`${STRATEGY_ON_PUSH}\``,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
const changeDetectionMetadataProperty = utils_1.Selectors.metadataProperty(METADATA_PROPERTY_NAME);
|
|
27
|
+
const withoutChangeDetectionDecorator = `${utils_1.Selectors.COMPONENT_CLASS_DECORATOR}:matches([expression.arguments.length=0], [expression.arguments.0.type='ObjectExpression']:not(:has(${changeDetectionMetadataProperty})))`;
|
|
28
|
+
const nonChangeDetectionOnPushProperty = `${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} > CallExpression > ObjectExpression > ${changeDetectionMetadataProperty}:matches([value.type='Identifier'][value.name='undefined'], [value.object.name='ChangeDetectionStrategy'][value.property.name!='OnPush'])`;
|
|
29
|
+
const selectors = [
|
|
30
|
+
withoutChangeDetectionDecorator,
|
|
31
|
+
nonChangeDetectionOnPushProperty,
|
|
32
|
+
].join(',');
|
|
33
|
+
return {
|
|
34
|
+
[selectors](node) {
|
|
35
|
+
context.report({
|
|
36
|
+
node: nodeToReport(node),
|
|
37
|
+
messageId: 'preferOnPushComponentChangeDetection',
|
|
38
|
+
suggest: [
|
|
39
|
+
{
|
|
40
|
+
messageId: 'suggestAddChangeDetectionOnPush',
|
|
41
|
+
fix: (fixer) => {
|
|
42
|
+
if (utils_1.ASTUtils.isProperty(node)) {
|
|
43
|
+
return [
|
|
44
|
+
utils_1.RuleFixes.getImportAddFix({
|
|
45
|
+
fixer,
|
|
46
|
+
importName: 'ChangeDetectionStrategy',
|
|
47
|
+
moduleName: '@angular/core',
|
|
48
|
+
node: node.parent.parent.parent.parent,
|
|
49
|
+
}),
|
|
50
|
+
utils_1.ASTUtils.isMemberExpression(node.value)
|
|
51
|
+
? fixer.replaceText(node.value.property, 'OnPush')
|
|
52
|
+
: fixer.replaceText(node.value, STRATEGY_ON_PUSH),
|
|
53
|
+
].filter(utils_1.isNotNullOrUndefined);
|
|
54
|
+
}
|
|
55
|
+
return [
|
|
56
|
+
utils_1.RuleFixes.getImportAddFix({
|
|
57
|
+
fixer,
|
|
58
|
+
importName: 'ChangeDetectionStrategy',
|
|
59
|
+
moduleName: '@angular/core',
|
|
60
|
+
node: node.parent,
|
|
61
|
+
}),
|
|
62
|
+
utils_1.RuleFixes.getDecoratorPropertyAddFix(node, fixer, `${METADATA_PROPERTY_NAME}: ${STRATEGY_ON_PUSH}`),
|
|
63
|
+
].filter(utils_1.isNotNullOrUndefined);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
function nodeToReport(node) {
|
|
73
|
+
if (!utils_1.ASTUtils.isProperty(node)) {
|
|
74
|
+
return node;
|
|
75
|
+
}
|
|
76
|
+
return utils_1.ASTUtils.isMemberExpression(node.value)
|
|
77
|
+
? node.value.property
|
|
78
|
+
: node.value;
|
|
79
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'prefer-output-readonly';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Prefer to declare `@Output` as `readonly` since they are not supposed to be reassigned',
|
|
13
|
+
recommended: false,
|
|
14
|
+
},
|
|
15
|
+
hasSuggestions: true,
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
preferOutputReadonly: 'Prefer to declare `@Output` as `readonly` since they are not supposed to be reassigned',
|
|
19
|
+
suggestAddReadonlyModifier: 'Add `readonly` modifier',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
[`PropertyDefinition:not([readonly]) > ${utils_1.Selectors.OUTPUT_DECORATOR}`]({ parent: { key }, }) {
|
|
26
|
+
context.report({
|
|
27
|
+
node: key,
|
|
28
|
+
messageId: 'preferOutputReadonly',
|
|
29
|
+
suggest: [
|
|
30
|
+
{
|
|
31
|
+
messageId: 'suggestAddReadonlyModifier',
|
|
32
|
+
fix: (fixer) => fixer.insertTextBefore(key, 'readonly '),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'relative-url-prefix';
|
|
7
|
+
const STYLE_GUIDE_LINK = 'https://angular.io/styleguide#style-05-04';
|
|
8
|
+
const RELATIVE_URL_PREFIX_MATCHER = /^\.\.?\/.+/;
|
|
9
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
10
|
+
name: exports.RULE_NAME,
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: `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 ${STYLE_GUIDE_LINK}`,
|
|
15
|
+
recommended: false,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
relativeUrlPrefix: `The ./ and ../ prefix is standard syntax for relative URLs. (${STYLE_GUIDE_LINK})`,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='templateUrl']`]({ value, }) {
|
|
26
|
+
if (!isUrlInvalid(value))
|
|
27
|
+
return;
|
|
28
|
+
context.report({
|
|
29
|
+
node: value,
|
|
30
|
+
messageId: 'relativeUrlPrefix',
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styleUrls']`]({ value, }) {
|
|
34
|
+
if (!utils_1.ASTUtils.isArrayExpression(value))
|
|
35
|
+
return;
|
|
36
|
+
value.elements.filter(isUrlInvalid).forEach((element) => {
|
|
37
|
+
if (!element) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
context.report({
|
|
41
|
+
node: element,
|
|
42
|
+
messageId: 'relativeUrlPrefix',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
function isUrlInvalid(node) {
|
|
50
|
+
if (!node) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return (!utils_1.ASTUtils.isStringLiteral(node) ||
|
|
54
|
+
!RELATIVE_URL_PREFIX_MATCHER.test(node.value));
|
|
55
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
requireDescription: false,
|
|
8
|
+
requireMeaning: false,
|
|
9
|
+
};
|
|
10
|
+
const VALID_LOCALIZED_STRING_WITH_DESCRIPTION = new RegExp(/:(.*\|)?([\w\s]+){1}(@@.*)?:.+/);
|
|
11
|
+
const VALID_LOCALIZED_STRING_WITH_MEANING = new RegExp(/:([\w\s]+\|)(.*)?(@@.*)?:.+/);
|
|
12
|
+
const STYLE_GUIDE_LINK = 'https://angular.io/guide/i18n';
|
|
13
|
+
const STYLE_GUIDE_LINK_COMMON_PREPARE = `${STYLE_GUIDE_LINK}-common-prepare`;
|
|
14
|
+
const STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION = `${STYLE_GUIDE_LINK_COMMON_PREPARE}#i18n-metadata-for-translation`;
|
|
15
|
+
exports.RULE_NAME = 'require-localize-metadata';
|
|
16
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
17
|
+
name: exports.RULE_NAME,
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'suggestion',
|
|
20
|
+
docs: {
|
|
21
|
+
description: 'Ensures that $localize tagged messages contain helpful metadata to aid with translations.',
|
|
22
|
+
recommended: false,
|
|
23
|
+
},
|
|
24
|
+
schema: [
|
|
25
|
+
{
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
requireDescription: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
default: DEFAULT_OPTIONS.requireDescription,
|
|
31
|
+
},
|
|
32
|
+
requireMeaning: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
default: DEFAULT_OPTIONS.requireMeaning,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
messages: {
|
|
41
|
+
requireLocalizeDescription: `$localize tagged messages should contain a description. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
|
|
42
|
+
requireLocalizeMeaning: `$localize tagged messages should contain a meaning. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
46
|
+
create(context, [{ requireDescription, requireMeaning }]) {
|
|
47
|
+
return {
|
|
48
|
+
TaggedTemplateExpression(taggedTemplateExpression) {
|
|
49
|
+
if ((requireDescription || requireMeaning) &&
|
|
50
|
+
utils_1.ASTUtils.isIdentifier(taggedTemplateExpression.tag)) {
|
|
51
|
+
const identifierName = taggedTemplateExpression.tag.name;
|
|
52
|
+
const templateElement = taggedTemplateExpression.quasi.quasis[0];
|
|
53
|
+
if (identifierName === '$localize' && !!templateElement) {
|
|
54
|
+
const templateElementRawValue = templateElement.value.raw;
|
|
55
|
+
if (requireDescription &&
|
|
56
|
+
!VALID_LOCALIZED_STRING_WITH_DESCRIPTION.test(templateElementRawValue)) {
|
|
57
|
+
context.report({
|
|
58
|
+
loc: templateElement.loc,
|
|
59
|
+
messageId: 'requireLocalizeDescription',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (requireMeaning &&
|
|
63
|
+
!VALID_LOCALIZED_STRING_WITH_MEANING.test(templateElementRawValue)) {
|
|
64
|
+
context.report({
|
|
65
|
+
loc: templateElement.loc,
|
|
66
|
+
messageId: 'requireLocalizeMeaning',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
exports.RULE_NAME = 'sort-ngmodule-metadata-arrays';
|
|
8
|
+
const DEFAULT_LOCALE = 'en-US';
|
|
9
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
10
|
+
name: exports.RULE_NAME,
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Ensures ASC alphabetical order for `NgModule` metadata arrays for easy visual scanning',
|
|
15
|
+
recommended: false,
|
|
16
|
+
},
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
schema: [
|
|
19
|
+
{
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
locale: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'A string with a BCP 47 language tag.',
|
|
25
|
+
default: DEFAULT_LOCALE,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
messages: {
|
|
32
|
+
sortNgmoduleMetadataArrays: '`NgModule` metadata arrays should be sorted in ASC alphabetical order',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [
|
|
36
|
+
{
|
|
37
|
+
locale: DEFAULT_LOCALE,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
create(context, [{ locale }]) {
|
|
41
|
+
return {
|
|
42
|
+
[`${utils_1.Selectors.MODULE_CLASS_DECORATOR} Property[key.name!="deps"] > ArrayExpression`]({ elements, }) {
|
|
43
|
+
const unorderedNodes = elements
|
|
44
|
+
.filter(utils_2.ASTUtils.isIdentifier)
|
|
45
|
+
.map((current, index, list) => [current, list[index + 1]])
|
|
46
|
+
.find(([current, next]) => {
|
|
47
|
+
return next && current.name.localeCompare(next.name, locale) === 1;
|
|
48
|
+
});
|
|
49
|
+
if (!unorderedNodes)
|
|
50
|
+
return;
|
|
51
|
+
const [unorderedNode, nextNode] = unorderedNodes;
|
|
52
|
+
context.report({
|
|
53
|
+
node: nextNode,
|
|
54
|
+
messageId: 'sortNgmoduleMetadataArrays',
|
|
55
|
+
fix: (fixer) => [
|
|
56
|
+
fixer.replaceText(unorderedNode, nextNode.name),
|
|
57
|
+
fixer.replaceText(nextNode, unorderedNode.name),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'use-component-selector';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Component selector must be declared',
|
|
13
|
+
recommended: false,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
useComponentSelector: 'The selector of the component is mandatory',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
24
|
+
const selector = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'selector');
|
|
25
|
+
if (selector &&
|
|
26
|
+
utils_1.ASTUtils.isStringLiteral(selector) &&
|
|
27
|
+
selector.value.length) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: 'useComponentSelector',
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'use-component-view-encapsulation';
|
|
7
|
+
const VIEW_ENCAPSULATION_NONE = 'ViewEncapsulation.None';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: `Disallows using \`${VIEW_ENCAPSULATION_NONE}\``,
|
|
14
|
+
recommended: false,
|
|
15
|
+
},
|
|
16
|
+
hasSuggestions: true,
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
useComponentViewEncapsulation: `Using \`${VIEW_ENCAPSULATION_NONE}\` makes your styles global, which may have an unintended effect`,
|
|
20
|
+
suggestRemoveViewEncapsulationNone: `Remove \`${VIEW_ENCAPSULATION_NONE}\``,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
const sourceCode = context.getSourceCode();
|
|
26
|
+
return {
|
|
27
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} ${utils_1.Selectors.metadataProperty('encapsulation')} > MemberExpression[object.name='ViewEncapsulation'] > Identifier[name='None']`](node) {
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
messageId: 'useComponentViewEncapsulation',
|
|
31
|
+
suggest: [
|
|
32
|
+
{
|
|
33
|
+
messageId: 'suggestRemoveViewEncapsulationNone',
|
|
34
|
+
fix: (fixer) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const importDeclarations = (_a = utils_1.ASTUtils.getImportDeclarations(node, '@angular/core')) !== null && _a !== void 0 ? _a : [];
|
|
37
|
+
return [
|
|
38
|
+
utils_1.RuleFixes.getNodeToCommaRemoveFix(sourceCode, node.parent.parent, fixer),
|
|
39
|
+
utils_1.RuleFixes.getImportRemoveFix(sourceCode, importDeclarations, 'ViewEncapsulation', fixer),
|
|
40
|
+
].filter(utils_1.isNotNullOrUndefined);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'use-injectable-provided-in';
|
|
7
|
+
const METADATA_PROPERTY_NAME = 'providedIn';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: `Using the \`${METADATA_PROPERTY_NAME}\` property makes \`Injectables\` tree-shakable`,
|
|
14
|
+
recommended: false,
|
|
15
|
+
},
|
|
16
|
+
hasSuggestions: true,
|
|
17
|
+
schema: [
|
|
18
|
+
{
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
ignoreClassNamePattern: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
messages: {
|
|
29
|
+
useInjectableProvidedIn: `The \`${METADATA_PROPERTY_NAME}\` property is mandatory for \`Injectables\``,
|
|
30
|
+
suggestInjector: `Use \`${METADATA_PROPERTY_NAME}: '{{injector}}'\``,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultOptions: [{}],
|
|
34
|
+
create(context, [{ ignoreClassNamePattern }]) {
|
|
35
|
+
const injectableClassDecorator = `ClassDeclaration:not([id.name=${ignoreClassNamePattern}]):not(:has(TSClassImplements:matches([expression.property.name='HttpInterceptor'], [expression.name='HttpInterceptor']))) > Decorator[expression.callee.name="Injectable"]`;
|
|
36
|
+
const providedInMetadataProperty = utils_1.Selectors.metadataProperty(METADATA_PROPERTY_NAME);
|
|
37
|
+
const withoutProvidedInDecorator = `${injectableClassDecorator}:matches([expression.arguments.length=0], [expression.arguments.0.type='ObjectExpression']:not(:has(${providedInMetadataProperty})))`;
|
|
38
|
+
const nullableProvidedInProperty = `${injectableClassDecorator} ${providedInMetadataProperty}:matches([value.type='Identifier'][value.name='undefined'], [value.type='Literal'][value.raw='null'])`;
|
|
39
|
+
const selectors = [
|
|
40
|
+
withoutProvidedInDecorator,
|
|
41
|
+
nullableProvidedInProperty,
|
|
42
|
+
].join(',');
|
|
43
|
+
return {
|
|
44
|
+
[selectors](node) {
|
|
45
|
+
context.report({
|
|
46
|
+
node: utils_1.ASTUtils.isProperty(node) ? node.value : node,
|
|
47
|
+
messageId: 'useInjectableProvidedIn',
|
|
48
|
+
suggest: ['any', 'platform', 'root'].map((injector) => ({
|
|
49
|
+
messageId: 'suggestInjector',
|
|
50
|
+
fix: (fixer) => {
|
|
51
|
+
var _a;
|
|
52
|
+
return utils_1.ASTUtils.isProperty(node)
|
|
53
|
+
? fixer.replaceText(node.value, `'${injector}'`)
|
|
54
|
+
: (_a = utils_1.RuleFixes.getDecoratorPropertyAddFix(node, fixer, `${METADATA_PROPERTY_NAME}: '${injector}'`)) !== null && _a !== void 0 ? _a : [];
|
|
55
|
+
},
|
|
56
|
+
data: { injector },
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'use-lifecycle-interface';
|
|
7
|
+
const STYLE_GUIDE_LINK = 'https://angular.io/styleguide#style-09-01';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: `Ensures that classes implement lifecycle interfaces corresponding to the declared lifecycle methods. See more at ${STYLE_GUIDE_LINK}`,
|
|
14
|
+
recommended: 'warn',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
useLifecycleInterface: `Lifecycle interface '{{interfaceName}}' should be implemented for method '{{methodName}}'. (${STYLE_GUIDE_LINK})`,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const angularLifecycleMethodsPattern = (0, utils_1.toPattern)([
|
|
24
|
+
...utils_1.ASTUtils.ANGULAR_LIFECYCLE_METHODS,
|
|
25
|
+
]);
|
|
26
|
+
return {
|
|
27
|
+
[`MethodDefinition[key.name=${angularLifecycleMethodsPattern}]`]({ key, parent: { parent }, }) {
|
|
28
|
+
if (!utils_1.ASTUtils.getAngularClassDecorator(parent))
|
|
29
|
+
return;
|
|
30
|
+
const declaredLifecycleInterfaces = utils_1.ASTUtils.getDeclaredAngularLifecycleInterfaces(parent);
|
|
31
|
+
const methodName = key
|
|
32
|
+
.name;
|
|
33
|
+
const interfaceName = utils_1.ASTUtils.getLifecycleInterfaceByMethodName(methodName);
|
|
34
|
+
const isMethodImplemented = declaredLifecycleInterfaces.includes(utils_1.ASTUtils.AngularLifecycleInterfaces[interfaceName]);
|
|
35
|
+
if (isMethodImplemented)
|
|
36
|
+
return;
|
|
37
|
+
context.report({
|
|
38
|
+
node: key,
|
|
39
|
+
messageId: 'useLifecycleInterface',
|
|
40
|
+
data: { interfaceName, methodName },
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
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 create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'use-pipe-transform-interface';
|
|
7
|
+
const PIPE_TRANSFORM = 'PipeTransform';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: `Ensures that \`Pipes\` implement \`${PIPE_TRANSFORM}\` interface`,
|
|
14
|
+
recommended: 'error',
|
|
15
|
+
},
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
usePipeTransformInterface: `Pipes should implement \`${PIPE_TRANSFORM}\` interface`,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
[`ClassDeclaration:not(:has(TSClassImplements:matches([expression.name='${PIPE_TRANSFORM}'], [expression.property.name='${PIPE_TRANSFORM}']))) > Decorator[expression.callee.name='Pipe']`]({ parent: classDeclaration, }) {
|
|
26
|
+
var _a;
|
|
27
|
+
context.report({
|
|
28
|
+
node: (_a = classDeclaration.id) !== null && _a !== void 0 ? _a : classDeclaration,
|
|
29
|
+
messageId: 'usePipeTransformInterface',
|
|
30
|
+
fix: (fixer) => {
|
|
31
|
+
const { implementsNodeReplace, implementsTextReplace } = utils_1.RuleFixes.getImplementsSchemaFixer(classDeclaration, PIPE_TRANSFORM);
|
|
32
|
+
return [
|
|
33
|
+
utils_1.RuleFixes.getImportAddFix({
|
|
34
|
+
compatibleWithTypeOnlyImport: true,
|
|
35
|
+
fixer,
|
|
36
|
+
importName: PIPE_TRANSFORM,
|
|
37
|
+
moduleName: '@angular/core',
|
|
38
|
+
node: classDeclaration,
|
|
39
|
+
}),
|
|
40
|
+
fixer.insertTextAfter(implementsNodeReplace, implementsTextReplace),
|
|
41
|
+
].filter(utils_1.isNotNullOrUndefined);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createESLintRule = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
/**
|
|
6
|
+
* We need to patch the RuleCreator in order to preserve the defaultOptions
|
|
7
|
+
* to use as part of documentation generation.
|
|
8
|
+
*/
|
|
9
|
+
const patchedRuleCreator = (urlCreator) => {
|
|
10
|
+
return function createRule({ name, meta, defaultOptions, create }) {
|
|
11
|
+
return {
|
|
12
|
+
meta: Object.assign(Object.assign({}, meta), {
|
|
13
|
+
docs: Object.assign(Object.assign({}, meta.docs), {
|
|
14
|
+
url: urlCreator(name),
|
|
15
|
+
}),
|
|
16
|
+
}),
|
|
17
|
+
defaultOptions,
|
|
18
|
+
create(context) {
|
|
19
|
+
const optionsWithDefault = utils_1.ESLintUtils.applyDefault(defaultOptions, context.options);
|
|
20
|
+
return create(context, optionsWithDefault);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
patchedRuleCreator.withoutDocs = utils_1.ESLintUtils.RuleCreator.withoutDocs;
|
|
26
|
+
exports.createESLintRule = patchedRuleCreator((ruleName) => `https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/${ruleName}.md`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-eslint/eslint-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.0-alpha.1",
|
|
4
4
|
"description": "ESLint plugin for Angular applications, following angular.io/styleguide",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@angular-eslint/utils": "
|
|
21
|
-
"@typescript-eslint/utils": "5.
|
|
20
|
+
"@angular-eslint/utils": "16.0.0-alpha.1",
|
|
21
|
+
"@typescript-eslint/utils": "5.59.2"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"eslint": "^7.20.0 || ^8.0.0",
|
|
25
25
|
"typescript": "*"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "ffcbae10749bc12760a9d442d4293179c6c19042"
|
|
28
28
|
}
|
package/dist/configs/base.json
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"plugins": ["eslint-plugin-jsdoc"],
|
|
3
|
-
"rules": {
|
|
4
|
-
"arrow-body-style": "error",
|
|
5
|
-
"arrow-parens": "off",
|
|
6
|
-
"comma-dangle": "off",
|
|
7
|
-
"curly": "error",
|
|
8
|
-
"eol-last": "error",
|
|
9
|
-
"jsdoc/check-alignment": "error",
|
|
10
|
-
"max-len": [
|
|
11
|
-
"error",
|
|
12
|
-
{
|
|
13
|
-
"code": 140
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"new-parens": "error",
|
|
17
|
-
"no-multiple-empty-lines": "off",
|
|
18
|
-
"no-trailing-spaces": "error",
|
|
19
|
-
"quote-props": ["error", "as-needed"],
|
|
20
|
-
"space-before-function-paren": [
|
|
21
|
-
"error",
|
|
22
|
-
{
|
|
23
|
-
"anonymous": "never",
|
|
24
|
-
"asyncArrow": "always",
|
|
25
|
-
"named": "never"
|
|
26
|
-
}
|
|
27
|
-
],
|
|
28
|
-
|
|
29
|
-
"@typescript-eslint/member-delimiter-style": [
|
|
30
|
-
"error",
|
|
31
|
-
{
|
|
32
|
-
"multiline": {
|
|
33
|
-
"delimiter": "semi",
|
|
34
|
-
"requireLast": true
|
|
35
|
-
},
|
|
36
|
-
"singleline": {
|
|
37
|
-
"delimiter": "semi",
|
|
38
|
-
"requireLast": false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"quotes": "off",
|
|
43
|
-
"@typescript-eslint/quotes": [
|
|
44
|
-
"error",
|
|
45
|
-
"single",
|
|
46
|
-
{ "allowTemplateLiterals": true }
|
|
47
|
-
],
|
|
48
|
-
"@typescript-eslint/semi": ["error", "always"],
|
|
49
|
-
"@typescript-eslint/type-annotation-spacing": "error"
|
|
50
|
-
}
|
|
51
|
-
}
|