eslint-cdk-plugin 1.0.1 → 1.0.3

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.
Files changed (58) hide show
  1. package/README.md +29 -2
  2. package/dist/index.cjs +783 -0
  3. package/dist/index.d.ts +51 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.mjs +755 -39
  6. package/package.json +22 -9
  7. package/src/index.ts +69 -0
  8. package/src/rules/no-class-in-interface.ts +56 -0
  9. package/src/rules/no-construct-stack-suffix.ts +86 -0
  10. package/src/rules/no-import-private.ts +60 -0
  11. package/src/rules/no-mutable-props-interface.ts +58 -0
  12. package/src/rules/no-mutable-public-fields.ts +75 -0
  13. package/src/rules/no-parent-name-construct-id-match.ts +312 -0
  14. package/src/rules/no-public-class-fields.ts +148 -0
  15. package/src/rules/no-variable-construct-id.ts +101 -0
  16. package/src/rules/pascal-case-construct-id.ts +95 -0
  17. package/src/rules/require-passing-this.ts +51 -0
  18. package/src/types/symbolFlags.ts +6 -0
  19. package/src/utils/convertString.ts +20 -0
  20. package/src/utils/typeCheck.ts +57 -0
  21. package/dist/index.d.mts +0 -31
  22. package/dist/index.mjs.map +0 -1
  23. package/dist/no-class-in-interface-props.d.mts +0 -2
  24. package/dist/no-class-in-interface-props.mjs +0 -45
  25. package/dist/no-class-in-interface-props.mjs.map +0 -1
  26. package/dist/no-construct-stack-suffix.d.mts +0 -2
  27. package/dist/no-construct-stack-suffix.mjs +0 -64
  28. package/dist/no-construct-stack-suffix.mjs.map +0 -1
  29. package/dist/no-import-private.d.mts +0 -2
  30. package/dist/no-import-private.mjs +0 -37
  31. package/dist/no-import-private.mjs.map +0 -1
  32. package/dist/no-mutable-props-interface.d.mts +0 -2
  33. package/dist/no-mutable-props-interface.mjs +0 -44
  34. package/dist/no-mutable-props-interface.mjs.map +0 -1
  35. package/dist/no-mutable-public-fields.d.mts +0 -2
  36. package/dist/no-mutable-public-fields.mjs +0 -57
  37. package/dist/no-mutable-public-fields.mjs.map +0 -1
  38. package/dist/no-parent-name-construct-id-match.d.mts +0 -2
  39. package/dist/no-parent-name-construct-id-match.mjs +0 -218
  40. package/dist/no-parent-name-construct-id-match.mjs.map +0 -1
  41. package/dist/no-public-class-fields.d.mts +0 -2
  42. package/dist/no-public-class-fields.mjs +0 -105
  43. package/dist/no-public-class-fields.mjs.map +0 -1
  44. package/dist/no-variable-construct-id.d.mts +0 -2
  45. package/dist/no-variable-construct-id.mjs +0 -63
  46. package/dist/no-variable-construct-id.mjs.map +0 -1
  47. package/dist/pascal-case-construct-id.d.mts +0 -2
  48. package/dist/pascal-case-construct-id.mjs +0 -62
  49. package/dist/pascal-case-construct-id.mjs.map +0 -1
  50. package/dist/require-passing-this.d.mts +0 -2
  51. package/dist/require-passing-this.mjs +0 -39
  52. package/dist/require-passing-this.mjs.map +0 -1
  53. package/dist/utils/convertString.d.mts +0 -1
  54. package/dist/utils/convertString.mjs +0 -13
  55. package/dist/utils/convertString.mjs.map +0 -1
  56. package/dist/utils/typeCheck.d.mts +0 -3
  57. package/dist/utils/typeCheck.mjs +0 -16
  58. package/dist/utils/typeCheck.mjs.map +0 -1
@@ -0,0 +1,312 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ TSESLint,
5
+ TSESTree,
6
+ } from "@typescript-eslint/utils";
7
+
8
+ import { toPascalCase } from "../utils/convertString";
9
+
10
+ type Context = TSESLint.RuleContext<"noParentNameConstructIdMatch", []>;
11
+
12
+ type ValidateStatementArgs<T extends TSESTree.Statement> = {
13
+ node: TSESTree.ClassBody;
14
+ statement: T;
15
+ parentClassName: string;
16
+ context: Context;
17
+ };
18
+
19
+ type ValidateExpressionArgs<T extends TSESTree.Expression> = {
20
+ node: TSESTree.ClassBody;
21
+ expression: T;
22
+ parentClassName: string;
23
+ context: Context;
24
+ };
25
+
26
+ /**
27
+ * Enforce that construct IDs does not match the parent construct name.
28
+ * @param context - The rule context provided by ESLint
29
+ * @returns An object containing the AST visitor functions
30
+ * @see {@link https://eslint-cdk-plugin.dev/rules/no-parent-name-construct-id-match} - Documentation
31
+ */
32
+ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
33
+ {
34
+ meta: {
35
+ type: "problem",
36
+ docs: {
37
+ description:
38
+ "Enforce that construct IDs does not match the parent construct name.",
39
+ },
40
+ messages: {
41
+ noParentNameConstructIdMatch:
42
+ "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier.",
43
+ },
44
+ schema: [],
45
+ },
46
+ defaultOptions: [],
47
+ create(context) {
48
+ return {
49
+ ClassBody(node) {
50
+ const parent = node.parent;
51
+ if (parent?.type !== AST_NODE_TYPES.ClassDeclaration) return;
52
+
53
+ const parentClassName = parent.id?.name;
54
+ if (!parentClassName) return;
55
+
56
+ for (const body of node.body) {
57
+ // NOTE: Ignore if neither method nor constructor.
58
+ if (
59
+ body.type !== AST_NODE_TYPES.MethodDefinition ||
60
+ !["method", "constructor"].includes(body.kind) ||
61
+ body.value.type !== AST_NODE_TYPES.FunctionExpression
62
+ ) {
63
+ continue;
64
+ }
65
+ validateConstructorBody({
66
+ node,
67
+ expression: body.value,
68
+ parentClassName,
69
+ context,
70
+ });
71
+ }
72
+ },
73
+ };
74
+ },
75
+ }
76
+ );
77
+
78
+ /**
79
+ * Validate the constructor body for the parent class
80
+ * - validate each statement in the constructor body
81
+ */
82
+ const validateConstructorBody = ({
83
+ node,
84
+ expression,
85
+ parentClassName,
86
+ context,
87
+ }: ValidateExpressionArgs<TSESTree.FunctionExpression>): void => {
88
+ for (const statement of expression.body.body) {
89
+ switch (statement.type) {
90
+ case AST_NODE_TYPES.VariableDeclaration: {
91
+ const newExpression = statement.declarations[0].init;
92
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) continue;
93
+ validateConstructId({
94
+ node,
95
+ context,
96
+ expression: newExpression,
97
+ parentClassName,
98
+ });
99
+ break;
100
+ }
101
+ case AST_NODE_TYPES.ExpressionStatement: {
102
+ if (statement.expression?.type !== AST_NODE_TYPES.NewExpression) break;
103
+ validateStatement({
104
+ node,
105
+ statement,
106
+ parentClassName,
107
+ context,
108
+ });
109
+ break;
110
+ }
111
+ case AST_NODE_TYPES.IfStatement: {
112
+ traverseStatements({
113
+ node,
114
+ context,
115
+ parentClassName,
116
+ statement: statement.consequent,
117
+ });
118
+ break;
119
+ }
120
+ case AST_NODE_TYPES.SwitchStatement: {
121
+ for (const switchCase of statement.cases) {
122
+ for (const statement of switchCase.consequent) {
123
+ traverseStatements({
124
+ node,
125
+ context,
126
+ parentClassName,
127
+ statement,
128
+ });
129
+ }
130
+ }
131
+ break;
132
+ }
133
+ }
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Recursively traverse and validate statements in the AST
139
+ * - Handles BlockStatement, ExpressionStatement, and VariableDeclaration
140
+ * - Validates construct IDs against parent class name
141
+ */
142
+ const traverseStatements = ({
143
+ node,
144
+ statement,
145
+ parentClassName,
146
+ context,
147
+ }: ValidateStatementArgs<TSESTree.Statement>) => {
148
+ switch (statement.type) {
149
+ case AST_NODE_TYPES.BlockStatement: {
150
+ for (const body of statement.body) {
151
+ validateStatement({
152
+ node,
153
+ statement: body,
154
+ parentClassName,
155
+ context,
156
+ });
157
+ }
158
+ break;
159
+ }
160
+ case AST_NODE_TYPES.ExpressionStatement: {
161
+ const newExpression = statement.expression;
162
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
163
+ validateStatement({
164
+ node,
165
+ statement,
166
+ parentClassName,
167
+ context,
168
+ });
169
+ break;
170
+ }
171
+ case AST_NODE_TYPES.VariableDeclaration: {
172
+ const newExpression = statement.declarations[0].init;
173
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
174
+ validateConstructId({
175
+ node,
176
+ context,
177
+ expression: newExpression,
178
+ parentClassName,
179
+ });
180
+ break;
181
+ }
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Validate a single statement in the AST
187
+ * - Handles different types of statements (Variable, Expression, If, Switch)
188
+ * - Extracts and validates construct IDs from new expressions
189
+ */
190
+ const validateStatement = ({
191
+ node,
192
+ statement,
193
+ parentClassName,
194
+ context,
195
+ }: ValidateStatementArgs<TSESTree.Statement>): void => {
196
+ switch (statement.type) {
197
+ case AST_NODE_TYPES.VariableDeclaration: {
198
+ const newExpression = statement.declarations[0].init;
199
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
200
+ validateConstructId({
201
+ node,
202
+ context,
203
+ expression: newExpression,
204
+ parentClassName,
205
+ });
206
+ break;
207
+ }
208
+ case AST_NODE_TYPES.ExpressionStatement: {
209
+ const newExpression = statement.expression;
210
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
211
+ validateConstructId({
212
+ node,
213
+ context,
214
+ expression: newExpression,
215
+ parentClassName,
216
+ });
217
+ break;
218
+ }
219
+ case AST_NODE_TYPES.IfStatement: {
220
+ validateIfStatement({
221
+ node,
222
+ statement,
223
+ parentClassName,
224
+ context,
225
+ });
226
+ break;
227
+ }
228
+ case AST_NODE_TYPES.SwitchStatement: {
229
+ validateSwitchStatement({
230
+ node,
231
+ statement,
232
+ parentClassName,
233
+ context,
234
+ });
235
+ break;
236
+ }
237
+ }
238
+ };
239
+
240
+ /**
241
+ * Validate the `if` statement
242
+ * - Validate recursively if `if` statements are nested
243
+ */
244
+ const validateIfStatement = ({
245
+ node,
246
+ statement,
247
+ parentClassName,
248
+ context,
249
+ }: ValidateStatementArgs<TSESTree.IfStatement>): void => {
250
+ traverseStatements({
251
+ node,
252
+ context,
253
+ parentClassName,
254
+ statement: statement.consequent,
255
+ });
256
+ };
257
+
258
+ /**
259
+ * Validate the `switch` statement
260
+ * - Validate recursively if `switch` statements are nested
261
+ */
262
+ const validateSwitchStatement = ({
263
+ node,
264
+ statement,
265
+ parentClassName,
266
+ context,
267
+ }: ValidateStatementArgs<TSESTree.SwitchStatement>): void => {
268
+ for (const caseStatement of statement.cases) {
269
+ for (const _consequent of caseStatement.consequent) {
270
+ traverseStatements({
271
+ node,
272
+ context,
273
+ parentClassName,
274
+ statement: _consequent,
275
+ });
276
+ }
277
+ }
278
+ };
279
+
280
+ /**
281
+ * Validate that parent construct name and child id do not match
282
+ */
283
+ const validateConstructId = ({
284
+ node,
285
+ context,
286
+ expression,
287
+ parentClassName,
288
+ }: ValidateExpressionArgs<TSESTree.NewExpression>): void => {
289
+ if (expression.arguments.length < 2) return;
290
+
291
+ // NOTE: Treat the second argument as ID
292
+ const secondArg = expression.arguments[1];
293
+ if (
294
+ secondArg.type !== AST_NODE_TYPES.Literal ||
295
+ typeof secondArg.value !== "string"
296
+ ) {
297
+ return;
298
+ }
299
+
300
+ const formattedConstructId = toPascalCase(secondArg.value as string);
301
+ const formattedParentClassName = toPascalCase(parentClassName);
302
+ if (formattedParentClassName !== formattedConstructId) return;
303
+
304
+ context.report({
305
+ node,
306
+ messageId: "noParentNameConstructIdMatch",
307
+ data: {
308
+ constructId: secondArg.value,
309
+ parentConstructName: parentClassName,
310
+ },
311
+ });
312
+ };
@@ -0,0 +1,148 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ ParserServicesWithTypeInformation,
5
+ TSESLint,
6
+ TSESTree,
7
+ } from "@typescript-eslint/utils";
8
+
9
+ import { SymbolFlags } from "../types/symbolFlags";
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
+ const isClass = type.symbol.flags === SymbolFlags.Class;
95
+ if (!isClass) continue;
96
+
97
+ context.report({
98
+ node: member,
99
+ messageId: "noPublicClassFields",
100
+ data: {
101
+ propertyName: member.key.name,
102
+ typeName: type.symbol.name,
103
+ },
104
+ });
105
+ }
106
+ };
107
+
108
+ /**
109
+ * check the constructor parameter property
110
+ * - if it is a class type, report an error
111
+ */
112
+ const validateConstructorParameterProperty = (
113
+ constructor: TSESTree.MethodDefinition,
114
+ context: Context,
115
+ parserServices: ParserServicesWithTypeInformation
116
+ ) => {
117
+ for (const param of constructor.value.params) {
118
+ if (
119
+ param.type !== AST_NODE_TYPES.TSParameterProperty ||
120
+ param.parameter.type !== AST_NODE_TYPES.Identifier
121
+ ) {
122
+ continue;
123
+ }
124
+
125
+ // NOTE: Skip private and protected parameters
126
+ if (["private", "protected"].includes(param.accessibility ?? "")) {
127
+ continue;
128
+ }
129
+
130
+ // NOTE: Skip parameters without type annotation
131
+ if (!param.parameter.typeAnnotation) continue;
132
+
133
+ const type = parserServices.getTypeAtLocation(param);
134
+ if (!type.symbol) continue;
135
+
136
+ const isClass = type.symbol.flags === SymbolFlags.Class;
137
+ if (!isClass) continue;
138
+
139
+ context.report({
140
+ node: param,
141
+ messageId: "noPublicClassFields",
142
+ data: {
143
+ propertyName: param.parameter.name,
144
+ typeName: type.symbol.name,
145
+ },
146
+ });
147
+ }
148
+ };
@@ -0,0 +1,101 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ TSESLint,
5
+ TSESTree,
6
+ } from "@typescript-eslint/utils";
7
+
8
+ import { isConstructType, isStackType } from "../utils/typeCheck";
9
+
10
+ type Context = TSESLint.RuleContext<"noVariableConstructId", []>;
11
+
12
+ /**
13
+ * Enforce using literal strings for Construct ID.
14
+ * @param context - The rule context provided by ESLint
15
+ * @returns An object containing the AST visitor functions
16
+ */
17
+ export const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ description: `Enforce using literal strings for Construct ID.`,
22
+ },
23
+ messages: {
24
+ noVariableConstructId: "Shouldn't use a parameter as a Construct ID.",
25
+ },
26
+ schema: [],
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ const parserServices = ESLintUtils.getParserServices(context);
31
+ return {
32
+ NewExpression(node) {
33
+ const type = parserServices.getTypeAtLocation(node);
34
+
35
+ if (
36
+ !isConstructType(type) ||
37
+ isStackType(type) ||
38
+ node.arguments.length < 2
39
+ ) {
40
+ return;
41
+ }
42
+
43
+ validateConstructId(node, context);
44
+ },
45
+ };
46
+ },
47
+ });
48
+
49
+ /**
50
+ * Check if the construct ID is a literal string
51
+ */
52
+ const validateConstructId = (
53
+ node: TSESTree.NewExpression,
54
+ context: Context
55
+ ) => {
56
+ if (node.arguments.length < 2 || isInsideLoop(node)) return;
57
+
58
+ // NOTE: Treat the second argument as ID
59
+ const secondArg = node.arguments[1];
60
+
61
+ // NOTE: When id is string literal, it's OK
62
+ if (
63
+ secondArg.type === AST_NODE_TYPES.Literal &&
64
+ typeof secondArg.value === "string"
65
+ ) {
66
+ return;
67
+ }
68
+
69
+ // NOTE: When id is template literal, only those without expressions are OK
70
+ if (
71
+ secondArg.type === AST_NODE_TYPES.TemplateLiteral &&
72
+ !secondArg.expressions.length
73
+ ) {
74
+ return;
75
+ }
76
+
77
+ context.report({
78
+ node,
79
+ messageId: "noVariableConstructId",
80
+ });
81
+ };
82
+
83
+ /**
84
+ * Check if the node is inside a loop statement
85
+ */
86
+ const isInsideLoop = (node: TSESTree.Node): boolean => {
87
+ let current = node.parent;
88
+ while (current) {
89
+ if (
90
+ current.type === AST_NODE_TYPES.ForStatement ||
91
+ current.type === AST_NODE_TYPES.ForInStatement ||
92
+ current.type === AST_NODE_TYPES.ForOfStatement ||
93
+ current.type === AST_NODE_TYPES.WhileStatement ||
94
+ current.type === AST_NODE_TYPES.DoWhileStatement
95
+ ) {
96
+ return true;
97
+ }
98
+ current = current.parent;
99
+ }
100
+ return false;
101
+ };
@@ -0,0 +1,95 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ TSESLint,
5
+ TSESTree,
6
+ } from "@typescript-eslint/utils";
7
+
8
+ import { toPascalCase } from "../utils/convertString";
9
+ import { isConstructOrStackType } from "../utils/typeCheck";
10
+
11
+ const QUOTE_TYPE = {
12
+ SINGLE: "'",
13
+ DOUBLE: '"',
14
+ } as const;
15
+
16
+ type QuoteType = (typeof QUOTE_TYPE)[keyof typeof QUOTE_TYPE];
17
+
18
+ type Context = TSESLint.RuleContext<"pascalCaseConstructId", []>;
19
+
20
+ /**
21
+ /**
22
+ * Enforce PascalCase for Construct ID.
23
+ * @param context - The rule context provided by ESLint
24
+ * @returns An object containing the AST visitor functions
25
+ * @see {@link https://eslint-cdk-plugin.dev/rules/pascal-case-construct-id} - Documentation
26
+ */
27
+ export const pascalCaseConstructId = ESLintUtils.RuleCreator.withoutDocs({
28
+ meta: {
29
+ type: "problem",
30
+ docs: {
31
+ description: "Enforce PascalCase for Construct ID.",
32
+ },
33
+ messages: {
34
+ pascalCaseConstructId: "Construct ID must be PascalCase.",
35
+ },
36
+ schema: [],
37
+ fixable: "code",
38
+ },
39
+ defaultOptions: [],
40
+ create(context) {
41
+ const parserServices = ESLintUtils.getParserServices(context);
42
+ return {
43
+ NewExpression(node) {
44
+ const type = parserServices.getTypeAtLocation(node);
45
+ if (!isConstructOrStackType(type) || node.arguments.length < 2) {
46
+ return;
47
+ }
48
+ validateConstructId(node, context);
49
+ },
50
+ };
51
+ },
52
+ });
53
+
54
+ /**
55
+ * check if the string is PascalCase
56
+ * @param str - The string to check
57
+ * @returns true if the string is PascalCase, false otherwise
58
+ */
59
+ const isPascalCase = (str: string) => {
60
+ return /^[A-Z][a-zA-Z0-9]*$/.test(str);
61
+ };
62
+
63
+ /**
64
+ * Check the construct ID is PascalCase
65
+ */
66
+ const validateConstructId = (
67
+ node: TSESTree.NewExpression,
68
+ context: Context
69
+ ) => {
70
+ if (node.arguments.length < 2) return;
71
+
72
+ // NOTE: Treat the second argument as ID
73
+ const secondArg = node.arguments[1];
74
+ if (
75
+ secondArg.type !== AST_NODE_TYPES.Literal ||
76
+ typeof secondArg.value !== "string"
77
+ ) {
78
+ return;
79
+ }
80
+
81
+ const quote: QuoteType = secondArg.raw?.startsWith('"')
82
+ ? QUOTE_TYPE.DOUBLE
83
+ : QUOTE_TYPE.SINGLE;
84
+
85
+ if (isPascalCase(secondArg.value)) return;
86
+
87
+ context.report({
88
+ node,
89
+ messageId: "pascalCaseConstructId",
90
+ fix: (fixer) => {
91
+ const pascalCaseValue = toPascalCase(secondArg.value as string);
92
+ return fixer.replaceText(secondArg, `${quote}${pascalCaseValue}${quote}`);
93
+ },
94
+ });
95
+ };
@@ -0,0 +1,51 @@
1
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ import { isConstructType, isStackType } from "../utils/typeCheck";
4
+
5
+ /**
6
+ * Enforces that `this` is passed to the constructor
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/require-passing-this} - Documentation
10
+ */
11
+ export const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description: "Require passing `this` in a constructor.",
16
+ },
17
+ messages: {
18
+ requirePassingThis: "Require passing `this` in a constructor.",
19
+ },
20
+ schema: [],
21
+ fixable: "code",
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ const parserServices = ESLintUtils.getParserServices(context);
26
+ return {
27
+ NewExpression(node) {
28
+ const type = parserServices.getTypeAtLocation(node);
29
+
30
+ if (
31
+ !isConstructType(type) ||
32
+ isStackType(type) ||
33
+ !node.arguments.length
34
+ ) {
35
+ return;
36
+ }
37
+
38
+ const argument = node.arguments[0];
39
+ if (argument.type === AST_NODE_TYPES.ThisExpression) return;
40
+
41
+ context.report({
42
+ node,
43
+ messageId: "requirePassingThis",
44
+ fix: (fixer) => {
45
+ return fixer.replaceText(argument, "this");
46
+ },
47
+ });
48
+ },
49
+ };
50
+ },
51
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Implementing `SymbolFlags` defined in typescript on your own, in order not to include TypeScript in dependencies
3
+ */
4
+ export enum SymbolFlags {
5
+ Class = 32,
6
+ }