Skip to content

Commit e28f675

Browse files
committed
feat: add way to customize Destroy service name
1 parent 2c1b80e commit e28f675

File tree

7 files changed

+645
-519
lines changed

7 files changed

+645
-519
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This is a repository containing custom ESLint rules for Angular projects
1010

1111
## Usage
1212

13+
### `destroy-service-provider`
14+
1315
Edit your eslint config file as follow
1416

1517
```json

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@types/estree": "0.0.51",
3434
"@types/node": "16.11.33",
3535
"@typescript-eslint/parser": "5.21.0",
36+
"@typescript-eslint/utils": "5.31.0",
3637
"eslint": "8.15.0",
3738
"husky": "7.0.4",
3839
"jest": "28.1.0",

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import destroyServiceProvider from "./rules/destroy-service-provider";
1+
import * as destroyServiceProviderRule from "./rules/destroy-service-provider";
22

33
export = {
44
rules: {
5-
"destroy-service-provider": destroyServiceProvider,
5+
[destroyServiceProviderRule.ruleName]: destroyServiceProviderRule.rule,
66
},
77
};

src/rules/destroy-service-provider.test.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { RuleTester } from "eslint";
1+
import { ESLintUtils } from "@typescript-eslint/utils";
2+
import { rule } from "./destroy-service-provider";
23

3-
import rule from "./destroy-service-provider";
4-
5-
const tester = new RuleTester({
6-
parser: require.resolve("@typescript-eslint/parser"),
7-
parserOptions: { ecmaVersion: 2015 },
4+
const tester = new ESLintUtils.RuleTester({
5+
parser: "@typescript-eslint/parser",
86
});
97

108
tester.run("destroy-service-provider", rule, {
@@ -88,8 +86,10 @@ tester.run("destroy-service-provider", rule, {
8886
}`,
8987
errors: [
9088
{
91-
message:
92-
"Please provide DestroyService in Component class providers.",
89+
messageId: "missing",
90+
data: {
91+
className: "Component",
92+
},
9393
},
9494
],
9595
},
@@ -105,8 +105,30 @@ tester.run("destroy-service-provider", rule, {
105105
}`,
106106
errors: [
107107
{
108-
message:
109-
"Please provide DestroyService in Directive class providers.",
108+
messageId: "missing",
109+
data: {
110+
className: "Directive",
111+
},
112+
},
113+
],
114+
},
115+
{
116+
code: `
117+
@Directive({
118+
selector: 'my-directive',
119+
})
120+
export class MyDirective implements OnInit {
121+
constructor(
122+
private destroy$: Destroy,
123+
) {}
124+
}`,
125+
options: [{ destroyServiceName: "Destroy" }],
126+
errors: [
127+
{
128+
messageId: "missing",
129+
data: {
130+
className: "Directive",
131+
},
110132
},
111133
],
112134
},

src/rules/destroy-service-provider.ts

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
1-
import { Rule } from "eslint";
1+
import {
2+
CallExpression,
3+
ClassDeclaration,
4+
Decorator,
5+
MethodDefinition,
6+
ObjectExpression,
7+
Property,
8+
} from "@typescript-eslint/types/dist/generated/ast-spec";
9+
import { ESLintUtils } from "@typescript-eslint/utils";
10+
import { repositoryUrl } from "../utils";
211

3-
const rule: Rule.RuleModule = {
4-
create: context => {
12+
export const ruleName = "destroy-service-provider";
13+
14+
type MessageIds = "missing";
15+
16+
type Options = [
17+
{
18+
/**
19+
* @default DestroyService
20+
*/
21+
destroyServiceName?: string;
22+
},
23+
];
24+
25+
const createRule = ESLintUtils.RuleCreator(name => `${repositoryUrl}#${name}`);
26+
27+
export const rule = createRule<Options, MessageIds>({
28+
create(context, [options]) {
529
return {
630
"ClassDeclaration > Decorator[expression.callee.name=/^(Component|Directive)$/]":
7-
(node: any) => {
31+
(node: Decorator) => {
32+
const nodeExpression = node.expression as CallExpression;
833
const isDecoratorEmpty =
9-
!node.expression.arguments.length ||
10-
!node.expression.arguments[0].properties.length;
34+
!nodeExpression.arguments.length ||
35+
!(nodeExpression.arguments[0] as ObjectExpression).properties
36+
.length;
1137

1238
if (isDecoratorEmpty) {
1339
return;
1440
}
1541

1642
// whether component has providers decorator property
17-
const providersProperty =
18-
node.expression.arguments[0].properties.find((property: any) => {
19-
return (
20-
property.key.type === "Identifier" &&
21-
property.key.name === "providers"
22-
);
23-
});
43+
const providersProperty = (
44+
nodeExpression.arguments[0] as ObjectExpression
45+
).properties.find((property: any) => {
46+
return (
47+
property.key.type === "Identifier" &&
48+
property.key.name === "providers"
49+
);
50+
}) as Property;
2451

2552
let providerValuesHasDestroyService: boolean = false;
2653

@@ -31,7 +58,10 @@ const rule: Rule.RuleModule = {
3158
) {
3259
providerValuesHasDestroyService =
3360
!!providersProperty.value.elements.find((e: any) => {
34-
return e.type === "Identifier" && e.name === "DestroyService";
61+
return (
62+
e.type === "Identifier" &&
63+
e.name === options.destroyServiceName
64+
);
3565
});
3666
}
3767

@@ -40,17 +70,18 @@ const rule: Rule.RuleModule = {
4070
(providersProperty && !providerValuesHasDestroyService)
4171
) {
4272
// get constructor
43-
const classDeclaration = node.parent;
73+
const classDeclaration = (node as any).parent as ClassDeclaration;
4474
const classElements = classDeclaration.body.body;
45-
const classConstructor = classElements.find((e: any) => {
75+
const classConstructor = classElements.find(e => {
4676
return e.type === "MethodDefinition" && e.kind === "constructor";
4777
});
4878

4979
let hasDestroy;
5080

5181
if (classConstructor) {
5282
// find DestroyService
53-
const params: any[] = classConstructor.value.params;
83+
const params: any[] = (classConstructor as MethodDefinition).value
84+
.params;
5485
hasDestroy = params.find(param => {
5586
return (
5687
param.type === "TSParameterProperty" &&
@@ -60,21 +91,48 @@ const rule: Rule.RuleModule = {
6091
param.parameter.typeAnnotation.typeAnnotation.typeName
6192
.type === "Identifier" &&
6293
param.parameter.typeAnnotation.typeAnnotation.typeName
63-
.name === "DestroyService"
94+
.name === options.destroyServiceName
6495
);
6596
});
6697
}
6798

6899
if (hasDestroy) {
69100
context.report({
70101
loc: hasDestroy.loc,
71-
message: `Please provide DestroyService in ${node.expression.callee.name} class providers.`,
102+
messageId: "missing",
103+
data: {
104+
className: (nodeExpression as any).callee.name,
105+
},
72106
});
73107
}
74108
}
75109
},
76110
};
77111
},
78-
};
79-
80-
export = rule;
112+
name: ruleName,
113+
meta: {
114+
type: "problem",
115+
docs: {
116+
description:
117+
"Destroy service should be provided in Component/Directive providers/viewProviders array.",
118+
recommended: "error",
119+
},
120+
messages: {
121+
missing:
122+
"Please provide DestroyService in {{className}} class providers.",
123+
},
124+
schema: [
125+
{
126+
type: "object",
127+
properties: {
128+
destroyServiceName: {
129+
type: "string",
130+
default: "DestroyService",
131+
},
132+
},
133+
additionalProperties: false,
134+
},
135+
],
136+
},
137+
defaultOptions: [{ destroyServiceName: "DestroyService" }],
138+
});

src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const packageJson = require('../package.json');
2+
3+
export const repositoryUrl = packageJson.repository;

0 commit comments

Comments
 (0)