eslint-cdk-plugin 2.2.0 → 3.0.2
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/index.cjs +149 -169
- package/dist/index.d.ts +39 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +149 -169
- package/package.json +12 -11
- package/src/index.ts +29 -28
- package/src/rules/construct-constructor-property.ts +4 -4
- package/src/rules/{no-class-in-interface.ts → no-construct-in-interface.ts} +8 -7
- package/src/rules/no-construct-in-public-property-of-construct.ts +155 -0
- package/src/rules/no-construct-stack-suffix.ts +6 -6
- package/src/rules/no-import-private.ts +2 -2
- package/src/rules/no-mutable-property-of-props-interface.ts +60 -0
- package/src/rules/no-mutable-public-property-of-construct.ts +76 -0
- package/src/rules/no-parent-name-construct-id-match.ts +6 -28
- package/src/rules/no-variable-construct-id.ts +4 -4
- package/src/rules/pascal-case-construct-id.ts +4 -4
- package/src/rules/require-passing-this.ts +6 -6
- package/src/rules/no-mutable-props-interface.ts +0 -58
- package/src/rules/no-mutable-public-fields.ts +0 -75
- package/src/rules/no-public-class-fields.ts +0 -154
|
@@ -15,10 +15,9 @@ type Options = [
|
|
|
15
15
|
}
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
type Context = TSESLint.RuleContext<"
|
|
18
|
+
type Context = TSESLint.RuleContext<"invalidConstructId", Options>;
|
|
19
19
|
|
|
20
20
|
type ValidateStatementArgs<T extends TSESTree.Statement> = {
|
|
21
|
-
node: TSESTree.ClassBody;
|
|
22
21
|
statement: T;
|
|
23
22
|
parentClassName: string;
|
|
24
23
|
context: Context;
|
|
@@ -27,7 +26,6 @@ type ValidateStatementArgs<T extends TSESTree.Statement> = {
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
type ValidateExpressionArgs<T extends TSESTree.Expression> = {
|
|
30
|
-
node: TSESTree.ClassBody;
|
|
31
29
|
expression: T;
|
|
32
30
|
parentClassName: string;
|
|
33
31
|
context: Context;
|
|
@@ -50,7 +48,7 @@ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
|
|
|
50
48
|
"Enforce that construct IDs does not match the parent construct name.",
|
|
51
49
|
},
|
|
52
50
|
messages: {
|
|
53
|
-
|
|
51
|
+
invalidConstructId:
|
|
54
52
|
"Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier.",
|
|
55
53
|
},
|
|
56
54
|
schema: [
|
|
@@ -99,7 +97,6 @@ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
|
|
|
99
97
|
continue;
|
|
100
98
|
}
|
|
101
99
|
validateConstructorBody({
|
|
102
|
-
node,
|
|
103
100
|
expression: body.value,
|
|
104
101
|
parentClassName,
|
|
105
102
|
context,
|
|
@@ -118,7 +115,6 @@ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
|
|
|
118
115
|
* - validate each statement in the constructor body
|
|
119
116
|
*/
|
|
120
117
|
const validateConstructorBody = ({
|
|
121
|
-
node,
|
|
122
118
|
expression,
|
|
123
119
|
parentClassName,
|
|
124
120
|
context,
|
|
@@ -131,7 +127,6 @@ const validateConstructorBody = ({
|
|
|
131
127
|
const newExpression = statement.declarations[0].init;
|
|
132
128
|
if (newExpression?.type !== AST_NODE_TYPES.NewExpression) continue;
|
|
133
129
|
validateConstructId({
|
|
134
|
-
node,
|
|
135
130
|
context,
|
|
136
131
|
expression: newExpression,
|
|
137
132
|
parentClassName,
|
|
@@ -143,7 +138,6 @@ const validateConstructorBody = ({
|
|
|
143
138
|
case AST_NODE_TYPES.ExpressionStatement: {
|
|
144
139
|
if (statement.expression?.type !== AST_NODE_TYPES.NewExpression) break;
|
|
145
140
|
validateStatement({
|
|
146
|
-
node,
|
|
147
141
|
statement,
|
|
148
142
|
parentClassName,
|
|
149
143
|
context,
|
|
@@ -154,7 +148,6 @@ const validateConstructorBody = ({
|
|
|
154
148
|
}
|
|
155
149
|
case AST_NODE_TYPES.IfStatement: {
|
|
156
150
|
traverseStatements({
|
|
157
|
-
node,
|
|
158
151
|
context,
|
|
159
152
|
parentClassName,
|
|
160
153
|
statement: statement.consequent,
|
|
@@ -167,7 +160,6 @@ const validateConstructorBody = ({
|
|
|
167
160
|
for (const switchCase of statement.cases) {
|
|
168
161
|
for (const statement of switchCase.consequent) {
|
|
169
162
|
traverseStatements({
|
|
170
|
-
node,
|
|
171
163
|
context,
|
|
172
164
|
parentClassName,
|
|
173
165
|
statement,
|
|
@@ -188,7 +180,6 @@ const validateConstructorBody = ({
|
|
|
188
180
|
* - Validates construct IDs against parent class name
|
|
189
181
|
*/
|
|
190
182
|
const traverseStatements = ({
|
|
191
|
-
node,
|
|
192
183
|
statement,
|
|
193
184
|
parentClassName,
|
|
194
185
|
context,
|
|
@@ -199,7 +190,6 @@ const traverseStatements = ({
|
|
|
199
190
|
case AST_NODE_TYPES.BlockStatement: {
|
|
200
191
|
for (const body of statement.body) {
|
|
201
192
|
validateStatement({
|
|
202
|
-
node,
|
|
203
193
|
statement: body,
|
|
204
194
|
parentClassName,
|
|
205
195
|
context,
|
|
@@ -213,7 +203,6 @@ const traverseStatements = ({
|
|
|
213
203
|
const newExpression = statement.expression;
|
|
214
204
|
if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
|
|
215
205
|
validateStatement({
|
|
216
|
-
node,
|
|
217
206
|
statement,
|
|
218
207
|
parentClassName,
|
|
219
208
|
context,
|
|
@@ -226,7 +215,6 @@ const traverseStatements = ({
|
|
|
226
215
|
const newExpression = statement.declarations[0].init;
|
|
227
216
|
if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
|
|
228
217
|
validateConstructId({
|
|
229
|
-
node,
|
|
230
218
|
context,
|
|
231
219
|
expression: newExpression,
|
|
232
220
|
parentClassName,
|
|
@@ -244,7 +232,6 @@ const traverseStatements = ({
|
|
|
244
232
|
* - Extracts and validates construct IDs from new expressions
|
|
245
233
|
*/
|
|
246
234
|
const validateStatement = ({
|
|
247
|
-
node,
|
|
248
235
|
statement,
|
|
249
236
|
parentClassName,
|
|
250
237
|
context,
|
|
@@ -256,7 +243,6 @@ const validateStatement = ({
|
|
|
256
243
|
const newExpression = statement.declarations[0].init;
|
|
257
244
|
if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
|
|
258
245
|
validateConstructId({
|
|
259
|
-
node,
|
|
260
246
|
context,
|
|
261
247
|
expression: newExpression,
|
|
262
248
|
parentClassName,
|
|
@@ -269,7 +255,6 @@ const validateStatement = ({
|
|
|
269
255
|
const newExpression = statement.expression;
|
|
270
256
|
if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
|
|
271
257
|
validateConstructId({
|
|
272
|
-
node,
|
|
273
258
|
context,
|
|
274
259
|
expression: newExpression,
|
|
275
260
|
parentClassName,
|
|
@@ -280,7 +265,6 @@ const validateStatement = ({
|
|
|
280
265
|
}
|
|
281
266
|
case AST_NODE_TYPES.IfStatement: {
|
|
282
267
|
validateIfStatement({
|
|
283
|
-
node,
|
|
284
268
|
statement,
|
|
285
269
|
parentClassName,
|
|
286
270
|
context,
|
|
@@ -291,7 +275,6 @@ const validateStatement = ({
|
|
|
291
275
|
}
|
|
292
276
|
case AST_NODE_TYPES.SwitchStatement: {
|
|
293
277
|
validateSwitchStatement({
|
|
294
|
-
node,
|
|
295
278
|
statement,
|
|
296
279
|
parentClassName,
|
|
297
280
|
context,
|
|
@@ -308,7 +291,6 @@ const validateStatement = ({
|
|
|
308
291
|
* - Validate recursively if `if` statements are nested
|
|
309
292
|
*/
|
|
310
293
|
const validateIfStatement = ({
|
|
311
|
-
node,
|
|
312
294
|
statement,
|
|
313
295
|
parentClassName,
|
|
314
296
|
context,
|
|
@@ -316,7 +298,6 @@ const validateIfStatement = ({
|
|
|
316
298
|
option,
|
|
317
299
|
}: ValidateStatementArgs<TSESTree.IfStatement>): void => {
|
|
318
300
|
traverseStatements({
|
|
319
|
-
node,
|
|
320
301
|
context,
|
|
321
302
|
parentClassName,
|
|
322
303
|
statement: statement.consequent,
|
|
@@ -330,7 +311,6 @@ const validateIfStatement = ({
|
|
|
330
311
|
* - Validate recursively if `switch` statements are nested
|
|
331
312
|
*/
|
|
332
313
|
const validateSwitchStatement = ({
|
|
333
|
-
node,
|
|
334
314
|
statement,
|
|
335
315
|
parentClassName,
|
|
336
316
|
context,
|
|
@@ -340,7 +320,6 @@ const validateSwitchStatement = ({
|
|
|
340
320
|
for (const caseStatement of statement.cases) {
|
|
341
321
|
for (const _consequent of caseStatement.consequent) {
|
|
342
322
|
traverseStatements({
|
|
343
|
-
node,
|
|
344
323
|
context,
|
|
345
324
|
parentClassName,
|
|
346
325
|
statement: _consequent,
|
|
@@ -355,7 +334,6 @@ const validateSwitchStatement = ({
|
|
|
355
334
|
* Validate that parent construct name and child id do not match
|
|
356
335
|
*/
|
|
357
336
|
const validateConstructId = ({
|
|
358
|
-
node,
|
|
359
337
|
context,
|
|
360
338
|
expression,
|
|
361
339
|
parentClassName,
|
|
@@ -385,8 +363,8 @@ const validateConstructId = ({
|
|
|
385
363
|
formattedConstructId.includes(formattedParentClassName)
|
|
386
364
|
) {
|
|
387
365
|
context.report({
|
|
388
|
-
node,
|
|
389
|
-
messageId: "
|
|
366
|
+
node: secondArg,
|
|
367
|
+
messageId: "invalidConstructId",
|
|
390
368
|
data: {
|
|
391
369
|
constructId: secondArg.value,
|
|
392
370
|
parentConstructName: parentClassName,
|
|
@@ -396,8 +374,8 @@ const validateConstructId = ({
|
|
|
396
374
|
}
|
|
397
375
|
if (formattedParentClassName === formattedConstructId) {
|
|
398
376
|
context.report({
|
|
399
|
-
node,
|
|
400
|
-
messageId: "
|
|
377
|
+
node: secondArg,
|
|
378
|
+
messageId: "invalidConstructId",
|
|
401
379
|
data: {
|
|
402
380
|
constructId: secondArg.value,
|
|
403
381
|
parentConstructName: parentClassName,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { getConstructorPropertyNames } from "../utils/parseType";
|
|
9
9
|
import { isConstructType } from "../utils/typeCheck";
|
|
10
10
|
|
|
11
|
-
type Context = TSESLint.RuleContext<"
|
|
11
|
+
type Context = TSESLint.RuleContext<"invalidConstructId", []>;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Enforce using literal strings for Construct ID.
|
|
@@ -22,7 +22,7 @@ export const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
22
22
|
description: `Enforce using literal strings for Construct ID.`,
|
|
23
23
|
},
|
|
24
24
|
messages: {
|
|
25
|
-
|
|
25
|
+
invalidConstructId: "Shouldn't use a parameter as a Construct ID.",
|
|
26
26
|
},
|
|
27
27
|
schema: [],
|
|
28
28
|
},
|
|
@@ -73,8 +73,8 @@ const validateConstructId = (
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
context.report({
|
|
76
|
-
node,
|
|
77
|
-
messageId: "
|
|
76
|
+
node: secondArg,
|
|
77
|
+
messageId: "invalidConstructId",
|
|
78
78
|
});
|
|
79
79
|
};
|
|
80
80
|
|
|
@@ -16,7 +16,7 @@ const QUOTE_TYPE = {
|
|
|
16
16
|
|
|
17
17
|
type QuoteType = (typeof QUOTE_TYPE)[keyof typeof QUOTE_TYPE];
|
|
18
18
|
|
|
19
|
-
type Context = TSESLint.RuleContext<"
|
|
19
|
+
type Context = TSESLint.RuleContext<"invalidConstructId", []>;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
/**
|
|
@@ -32,7 +32,7 @@ export const pascalCaseConstructId = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
32
32
|
description: "Enforce PascalCase for Construct ID.",
|
|
33
33
|
},
|
|
34
34
|
messages: {
|
|
35
|
-
|
|
35
|
+
invalidConstructId: "Construct ID must be PascalCase.",
|
|
36
36
|
},
|
|
37
37
|
schema: [],
|
|
38
38
|
fixable: "code",
|
|
@@ -90,8 +90,8 @@ const validateConstructId = (
|
|
|
90
90
|
if (isPascalCase(secondArg.value)) return;
|
|
91
91
|
|
|
92
92
|
context.report({
|
|
93
|
-
node,
|
|
94
|
-
messageId: "
|
|
93
|
+
node: secondArg,
|
|
94
|
+
messageId: "invalidConstructId",
|
|
95
95
|
fix: (fixer) => {
|
|
96
96
|
const pascalCaseValue = toPascalCase(secondArg.value);
|
|
97
97
|
return fixer.replaceText(secondArg, `${quote}${pascalCaseValue}${quote}`);
|
|
@@ -13,7 +13,7 @@ type Options = [
|
|
|
13
13
|
}
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
type Context = TSESLint.RuleContext<"
|
|
16
|
+
type Context = TSESLint.RuleContext<"missingPassingThis", Options>;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Enforces that `this` is passed to the constructor
|
|
@@ -28,7 +28,7 @@ export const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
28
28
|
description: "Require passing `this` in a constructor.",
|
|
29
29
|
},
|
|
30
30
|
messages: {
|
|
31
|
-
|
|
31
|
+
missingPassingThis: "Require passing `this` in a constructor.",
|
|
32
32
|
},
|
|
33
33
|
schema: [
|
|
34
34
|
{
|
|
@@ -72,8 +72,8 @@ export const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
72
72
|
// NOTE: If `allowNonThisAndDisallowScope` is false, require `this` for all cases
|
|
73
73
|
if (!options.allowNonThisAndDisallowScope) {
|
|
74
74
|
context.report({
|
|
75
|
-
node,
|
|
76
|
-
messageId: "
|
|
75
|
+
node: argument,
|
|
76
|
+
messageId: "missingPassingThis",
|
|
77
77
|
fix: (fixer) => {
|
|
78
78
|
return fixer.replaceText(argument, "this");
|
|
79
79
|
},
|
|
@@ -87,8 +87,8 @@ export const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
87
87
|
argument.name === "scope"
|
|
88
88
|
) {
|
|
89
89
|
context.report({
|
|
90
|
-
node,
|
|
91
|
-
messageId: "
|
|
90
|
+
node: argument,
|
|
91
|
+
messageId: "missingPassingThis",
|
|
92
92
|
fix: (fixer) => {
|
|
93
93
|
return fixer.replaceText(argument, "this");
|
|
94
94
|
},
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Disallow mutable properties in Props interfaces
|
|
5
|
-
* @param context - The rule context provided by ESLint
|
|
6
|
-
* @returns An object containing the AST visitor functions
|
|
7
|
-
* @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-props-interface} - Documentation
|
|
8
|
-
*/
|
|
9
|
-
export const noMutablePropsInterface = ESLintUtils.RuleCreator.withoutDocs({
|
|
10
|
-
meta: {
|
|
11
|
-
type: "problem",
|
|
12
|
-
docs: {
|
|
13
|
-
description: "Disallow mutable properties in Props interfaces",
|
|
14
|
-
},
|
|
15
|
-
fixable: "code",
|
|
16
|
-
messages: {
|
|
17
|
-
noMutablePropsInterface:
|
|
18
|
-
"Property '{{ propertyName }}' in Props interface should be readonly.",
|
|
19
|
-
},
|
|
20
|
-
schema: [],
|
|
21
|
-
},
|
|
22
|
-
defaultOptions: [],
|
|
23
|
-
create(context) {
|
|
24
|
-
return {
|
|
25
|
-
TSInterfaceDeclaration(node) {
|
|
26
|
-
const sourceCode = context.sourceCode;
|
|
27
|
-
|
|
28
|
-
// NOTE: Interface name check for "Props"
|
|
29
|
-
if (!node.id.name.endsWith("Props")) return;
|
|
30
|
-
|
|
31
|
-
for (const property of node.body.body) {
|
|
32
|
-
// NOTE: check property signature
|
|
33
|
-
if (
|
|
34
|
-
property.type !== AST_NODE_TYPES.TSPropertySignature ||
|
|
35
|
-
property.key.type !== AST_NODE_TYPES.Identifier
|
|
36
|
-
) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// NOTE: Skip if already readonly
|
|
41
|
-
if (property.readonly) continue;
|
|
42
|
-
|
|
43
|
-
context.report({
|
|
44
|
-
node: property,
|
|
45
|
-
messageId: "noMutablePropsInterface",
|
|
46
|
-
data: {
|
|
47
|
-
propertyName: property.key.name,
|
|
48
|
-
},
|
|
49
|
-
fix: (fixer) => {
|
|
50
|
-
const propertyText = sourceCode.getText(property);
|
|
51
|
-
return fixer.replaceText(property, `readonly ${propertyText}`);
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
|
|
3
|
-
import { isConstructOrStackType } from "../utils/typeCheck";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Disallow mutable public class fields
|
|
7
|
-
* @param context - The rule context provided by ESLint
|
|
8
|
-
* @returns An object containing the AST visitor functions
|
|
9
|
-
* @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-public-fields} - Documentation
|
|
10
|
-
*/
|
|
11
|
-
export const noMutablePublicFields = ESLintUtils.RuleCreator.withoutDocs({
|
|
12
|
-
meta: {
|
|
13
|
-
type: "problem",
|
|
14
|
-
docs: {
|
|
15
|
-
description: "Disallow mutable public class fields",
|
|
16
|
-
},
|
|
17
|
-
fixable: "code",
|
|
18
|
-
messages: {
|
|
19
|
-
noMutablePublicFields:
|
|
20
|
-
"Public field '{{ propertyName }}' should be readonly. Consider adding the 'readonly' modifier.",
|
|
21
|
-
},
|
|
22
|
-
schema: [],
|
|
23
|
-
},
|
|
24
|
-
defaultOptions: [],
|
|
25
|
-
create(context) {
|
|
26
|
-
const parserServices = ESLintUtils.getParserServices(context);
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
ClassDeclaration(node) {
|
|
30
|
-
const sourceCode = context.sourceCode;
|
|
31
|
-
const type = parserServices.getTypeAtLocation(node);
|
|
32
|
-
if (!isConstructOrStackType(type)) return;
|
|
33
|
-
|
|
34
|
-
for (const member of node.body.body) {
|
|
35
|
-
// NOTE: check property definition
|
|
36
|
-
if (
|
|
37
|
-
member.type !== AST_NODE_TYPES.PropertyDefinition ||
|
|
38
|
-
member.key.type !== AST_NODE_TYPES.Identifier
|
|
39
|
-
) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// NOTE: Skip private and protected fields
|
|
44
|
-
if (["private", "protected"].includes(member.accessibility ?? "")) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// NOTE: Skip if readonly is present
|
|
49
|
-
if (member.readonly) continue;
|
|
50
|
-
|
|
51
|
-
context.report({
|
|
52
|
-
node: member,
|
|
53
|
-
messageId: "noMutablePublicFields",
|
|
54
|
-
data: {
|
|
55
|
-
propertyName: member.key.name,
|
|
56
|
-
},
|
|
57
|
-
fix: (fixer) => {
|
|
58
|
-
const accessibility = member.accessibility ? "public " : "";
|
|
59
|
-
const paramText = sourceCode.getText(member);
|
|
60
|
-
const [key, value] = paramText.split(":");
|
|
61
|
-
const replacedKey = key.startsWith("public ")
|
|
62
|
-
? key.replace("public ", "")
|
|
63
|
-
: key;
|
|
64
|
-
|
|
65
|
-
return fixer.replaceText(
|
|
66
|
-
member,
|
|
67
|
-
`${accessibility}readonly ${replacedKey}:${value}`
|
|
68
|
-
);
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
},
|
|
75
|
-
});
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AST_NODE_TYPES,
|
|
3
|
-
ESLintUtils,
|
|
4
|
-
ParserServicesWithTypeInformation,
|
|
5
|
-
TSESLint,
|
|
6
|
-
TSESTree,
|
|
7
|
-
} from "@typescript-eslint/utils";
|
|
8
|
-
|
|
9
|
-
import { SYMBOL_FLAGS } from "../constants/tsInternalFlags";
|
|
10
|
-
import { isConstructOrStackType } from "../utils/typeCheck";
|
|
11
|
-
|
|
12
|
-
type Context = TSESLint.RuleContext<"noPublicClassFields", []>;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Disallow class types in public class fields
|
|
16
|
-
* @param context - The rule context provided by ESLint
|
|
17
|
-
* @returns An object containing the AST visitor functions
|
|
18
|
-
* @see {@link https://eslint-cdk-plugin.dev/rules/no-public-class-fields} - Documentation
|
|
19
|
-
*/
|
|
20
|
-
export const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
|
|
21
|
-
meta: {
|
|
22
|
-
type: "problem",
|
|
23
|
-
docs: {
|
|
24
|
-
description: "Disallow class types in public class fields",
|
|
25
|
-
},
|
|
26
|
-
messages: {
|
|
27
|
-
noPublicClassFields:
|
|
28
|
-
"Public field '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead.",
|
|
29
|
-
},
|
|
30
|
-
schema: [],
|
|
31
|
-
},
|
|
32
|
-
defaultOptions: [],
|
|
33
|
-
create(context) {
|
|
34
|
-
const parserServices = ESLintUtils.getParserServices(context);
|
|
35
|
-
return {
|
|
36
|
-
ClassDeclaration(node) {
|
|
37
|
-
const type = parserServices.getTypeAtLocation(node);
|
|
38
|
-
if (!isConstructOrStackType(type)) return;
|
|
39
|
-
|
|
40
|
-
// NOTE: Check class members
|
|
41
|
-
validateClassMember(node, context, parserServices);
|
|
42
|
-
|
|
43
|
-
// NOTE: Check constructor parameter properties
|
|
44
|
-
const constructor = node.body.body.find(
|
|
45
|
-
(member): member is TSESTree.MethodDefinition =>
|
|
46
|
-
member.type === AST_NODE_TYPES.MethodDefinition &&
|
|
47
|
-
member.kind === "constructor"
|
|
48
|
-
);
|
|
49
|
-
if (
|
|
50
|
-
!constructor ||
|
|
51
|
-
constructor.value.type !== AST_NODE_TYPES.FunctionExpression
|
|
52
|
-
) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
validateConstructorParameterProperty(
|
|
57
|
-
constructor,
|
|
58
|
-
context,
|
|
59
|
-
parserServices
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* check the public variable of the class
|
|
68
|
-
* - if it is a class type, report an error
|
|
69
|
-
*/
|
|
70
|
-
const validateClassMember = (
|
|
71
|
-
node: TSESTree.ClassDeclaration,
|
|
72
|
-
context: Context,
|
|
73
|
-
parserServices: ParserServicesWithTypeInformation
|
|
74
|
-
) => {
|
|
75
|
-
for (const member of node.body.body) {
|
|
76
|
-
if (
|
|
77
|
-
member.type !== AST_NODE_TYPES.PropertyDefinition ||
|
|
78
|
-
member.key.type !== AST_NODE_TYPES.Identifier
|
|
79
|
-
) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// NOTE: Skip private and protected fields
|
|
84
|
-
if (["private", "protected"].includes(member.accessibility ?? "")) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// NOTE: Skip fields without type annotation
|
|
89
|
-
if (!member.typeAnnotation) continue;
|
|
90
|
-
|
|
91
|
-
const type = parserServices.getTypeAtLocation(member);
|
|
92
|
-
if (!type.symbol) continue;
|
|
93
|
-
|
|
94
|
-
// NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
|
|
95
|
-
// Therefore, the type information structures do not match.
|
|
96
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
97
|
-
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
98
|
-
if (!isClass) continue;
|
|
99
|
-
|
|
100
|
-
context.report({
|
|
101
|
-
node: member,
|
|
102
|
-
messageId: "noPublicClassFields",
|
|
103
|
-
data: {
|
|
104
|
-
propertyName: member.key.name,
|
|
105
|
-
typeName: type.symbol.name,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* check the constructor parameter property
|
|
113
|
-
* - if it is a class type, report an error
|
|
114
|
-
*/
|
|
115
|
-
const validateConstructorParameterProperty = (
|
|
116
|
-
constructor: TSESTree.MethodDefinition,
|
|
117
|
-
context: Context,
|
|
118
|
-
parserServices: ParserServicesWithTypeInformation
|
|
119
|
-
) => {
|
|
120
|
-
for (const param of constructor.value.params) {
|
|
121
|
-
if (
|
|
122
|
-
param.type !== AST_NODE_TYPES.TSParameterProperty ||
|
|
123
|
-
param.parameter.type !== AST_NODE_TYPES.Identifier
|
|
124
|
-
) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// NOTE: Skip private and protected parameters
|
|
129
|
-
if (["private", "protected"].includes(param.accessibility ?? "")) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// NOTE: Skip parameters without type annotation
|
|
134
|
-
if (!param.parameter.typeAnnotation) continue;
|
|
135
|
-
|
|
136
|
-
const type = parserServices.getTypeAtLocation(param);
|
|
137
|
-
if (!type.symbol) continue;
|
|
138
|
-
|
|
139
|
-
// NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
|
|
140
|
-
// Therefore, the type information structures do not match.
|
|
141
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
142
|
-
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
143
|
-
if (!isClass) continue;
|
|
144
|
-
|
|
145
|
-
context.report({
|
|
146
|
-
node: param,
|
|
147
|
-
messageId: "noPublicClassFields",
|
|
148
|
-
data: {
|
|
149
|
-
propertyName: param.parameter.name,
|
|
150
|
-
typeName: type.symbol.name,
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
};
|