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.
@@ -15,10 +15,9 @@ type Options = [
15
15
  }
16
16
  ];
17
17
 
18
- type Context = TSESLint.RuleContext<"noParentNameConstructIdMatch", Options>;
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
- noParentNameConstructIdMatch:
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: "noParentNameConstructIdMatch",
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: "noParentNameConstructIdMatch",
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<"noVariableConstructId", []>;
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
- noVariableConstructId: "Shouldn't use a parameter as a Construct ID.",
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: "noVariableConstructId",
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<"pascalCaseConstructId", []>;
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
- pascalCaseConstructId: "Construct ID must be PascalCase.",
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: "pascalCaseConstructId",
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<"requirePassingThis", Options>;
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
- requirePassingThis: "Require passing `this` in a constructor.",
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: "requirePassingThis",
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: "requirePassingThis",
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
- };