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
package/dist/index.mjs CHANGED
@@ -1,42 +1,758 @@
1
- import { noClassInInterfaceProps } from "./no-class-in-interface-props.mjs";
2
- import { noConstructStackSuffix } from "./no-construct-stack-suffix.mjs";
3
- import { noImportPrivate } from "./no-import-private.mjs";
4
- import { noMutablePropsInterface } from "./no-mutable-props-interface.mjs";
5
- import { noMutablePublicFields } from "./no-mutable-public-fields.mjs";
6
- import { noParentNameConstructIdMatch } from "./no-parent-name-construct-id-match.mjs";
7
- import { noPublicClassFields } from "./no-public-class-fields.mjs";
8
- import { noVariableConstructId } from "./no-variable-construct-id.mjs";
9
- import { pascalCaseConstructId } from "./pascal-case-construct-id.mjs";
10
- import { requirePassingThis } from "./require-passing-this.mjs";
11
- const plugin = {
12
- rules: {
13
- "no-class-in-interface": noClassInInterfaceProps,
14
- "no-construct-stack-suffix": noConstructStackSuffix,
15
- "no-import-private": noImportPrivate,
16
- "no-parent-name-construct-id-match": noParentNameConstructIdMatch,
17
- "no-public-class-fields": noPublicClassFields,
18
- "pascal-case-construct-id": pascalCaseConstructId,
19
- "no-mutable-public-fields": noMutablePublicFields,
20
- "no-mutable-props-interface": noMutablePropsInterface,
21
- "require-passing-this": requirePassingThis,
22
- "no-variable-construct-id": noVariableConstructId,
23
- },
24
- configs: {
25
- recommended: {
26
- plugins: ["cdk"],
27
- rules: {
28
- "cdk/no-class-in-interface": "error",
29
- "cdk/no-construct-stack-suffix": "error",
30
- "cdk/no-parent-name-construct-id-match": "error",
31
- "cdk/no-public-class-fields": "error",
32
- "cdk/pascal-case-construct-id": "error",
33
- "cdk/require-passing-this": "error",
34
- "cdk/no-variable-construct-id": "error",
35
- "cdk/no-mutable-public-fields": "warn",
36
- "cdk/no-mutable-props-interface": "warn",
1
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import * as path from 'path';
3
+
4
+ var SymbolFlags = /* @__PURE__ */ ((SymbolFlags2) => {
5
+ SymbolFlags2[SymbolFlags2["Class"] = 32] = "Class";
6
+ return SymbolFlags2;
7
+ })(SymbolFlags || {});
8
+
9
+ const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description: "Disallow class types in interface properties"
14
+ },
15
+ messages: {
16
+ noClassInInterfaceProps: "Interface property '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead."
17
+ },
18
+ schema: []
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const parserServices = ESLintUtils.getParserServices(context);
23
+ return {
24
+ TSInterfaceDeclaration(node) {
25
+ for (const property of node.body.body) {
26
+ if (property.type !== AST_NODE_TYPES.TSPropertySignature || property.key.type !== AST_NODE_TYPES.Identifier) {
27
+ continue;
28
+ }
29
+ const type = parserServices.getTypeAtLocation(property);
30
+ if (!type.symbol) continue;
31
+ const isClass = type.symbol.flags === SymbolFlags.Class;
32
+ if (!isClass) continue;
33
+ context.report({
34
+ node: property,
35
+ messageId: "noClassInInterfaceProps",
36
+ data: {
37
+ propertyName: property.key.name,
38
+ typeName: type.symbol.name
39
+ }
40
+ });
41
+ }
42
+ }
43
+ };
44
+ }
45
+ });
46
+
47
+ const toPascalCase = (str) => {
48
+ return str.split(/[-_\s]/).map((word) => {
49
+ return word.replace(/([A-Z])/g, " $1").split(/\s+/).map(
50
+ (part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
51
+ ).join("");
52
+ }).join("");
53
+ };
54
+
55
+ const isConstructOrStackType = (type) => {
56
+ return isTargetSuperClassType(
57
+ type,
58
+ ["Construct", "Stack"],
59
+ isConstructOrStackType
60
+ );
61
+ };
62
+ const isConstructType = (type) => {
63
+ return isTargetSuperClassType(type, ["Construct"], isConstructType);
64
+ };
65
+ const isStackType = (type) => {
66
+ return isTargetSuperClassType(type, ["Stack"], isStackType);
67
+ };
68
+ const isTargetSuperClassType = (type, targetSuperClasses, typeCheckFunction) => {
69
+ if (!type.symbol) return false;
70
+ if (targetSuperClasses.some((suffix) => type.symbol.name.endsWith(suffix))) {
71
+ return true;
72
+ }
73
+ const baseTypes = type.getBaseTypes() || [];
74
+ return baseTypes.some((baseType) => typeCheckFunction(baseType));
75
+ };
76
+
77
+ const noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
78
+ meta: {
79
+ type: "problem",
80
+ docs: {
81
+ description: "Effort to avoid using 'Construct' and 'Stack' suffix in construct id."
82
+ },
83
+ messages: {
84
+ noConstructStackSuffix: "{{ classType }} ID '{{ id }}' should not include {{ suffix }} suffix."
85
+ },
86
+ schema: []
87
+ },
88
+ defaultOptions: [],
89
+ create(context) {
90
+ const parserServices = ESLintUtils.getParserServices(context);
91
+ return {
92
+ NewExpression(node) {
93
+ const type = parserServices.getTypeAtLocation(node);
94
+ if (!isConstructOrStackType(type) || node.arguments.length < 2) {
95
+ return;
96
+ }
97
+ validateConstructId$3(node, context);
98
+ }
99
+ };
100
+ }
101
+ });
102
+ const validateConstructId$3 = (node, context) => {
103
+ const secondArg = node.arguments[1];
104
+ if (secondArg.type !== AST_NODE_TYPES.Literal || typeof secondArg.value !== "string") {
105
+ return;
106
+ }
107
+ const formattedConstructId = toPascalCase(secondArg.value);
108
+ if (formattedConstructId.endsWith("Construct")) {
109
+ context.report({
110
+ node,
111
+ messageId: "noConstructStackSuffix",
112
+ data: {
113
+ classType: "Construct",
114
+ id: secondArg.value,
115
+ suffix: "Construct"
116
+ }
117
+ });
118
+ } else if (formattedConstructId.endsWith("Stack")) {
119
+ context.report({
120
+ node,
121
+ messageId: "noConstructStackSuffix",
122
+ data: {
123
+ classType: "Stack",
124
+ id: secondArg.value,
125
+ suffix: "Stack"
126
+ }
127
+ });
128
+ }
129
+ };
130
+
131
+ const noImportPrivate = {
132
+ meta: {
133
+ type: "problem",
134
+ docs: {
135
+ description: "Cannot import modules from private dir at different levels of the hierarchy."
136
+ },
137
+ messages: {
138
+ noImportPrivate: "Cannot import modules from private dir at different levels of the hierarchy."
139
+ },
140
+ schema: []
141
+ },
142
+ create(context) {
143
+ return {
144
+ ImportDeclaration(node) {
145
+ const importPath = node.source.value?.toString() ?? "";
146
+ const currentFilePath = context.filename;
147
+ const currentDirPath = path.dirname(currentFilePath);
148
+ if (!importPath.includes("/private")) return;
149
+ const absoluteCurrentDirPath = path.resolve(currentDirPath);
150
+ const absoluteImportPath = path.resolve(currentDirPath, importPath);
151
+ const importDirBeforePrivate = absoluteImportPath.split("/private")[0];
152
+ const currentDirSegments = getDirSegments(absoluteCurrentDirPath);
153
+ const importDirSegments = getDirSegments(importDirBeforePrivate);
154
+ if (currentDirSegments.length !== importDirSegments.length || currentDirSegments.some(
155
+ (segment, index) => segment !== importDirSegments[index]
156
+ )) {
157
+ context.report({ node, messageId: "noImportPrivate" });
158
+ }
159
+ }
160
+ };
161
+ }
162
+ };
163
+ const getDirSegments = (dirPath) => {
164
+ return dirPath.split(path.sep).filter((segment) => segment !== "");
165
+ };
166
+
167
+ const noMutablePropsInterface = ESLintUtils.RuleCreator.withoutDocs({
168
+ meta: {
169
+ type: "problem",
170
+ docs: {
171
+ description: "Disallow mutable properties in Props interfaces"
172
+ },
173
+ fixable: "code",
174
+ messages: {
175
+ noMutablePropsInterface: "Property '{{ propertyName }}' in Props interface should be readonly."
176
+ },
177
+ schema: []
178
+ },
179
+ defaultOptions: [],
180
+ create(context) {
181
+ return {
182
+ TSInterfaceDeclaration(node) {
183
+ const sourceCode = context.sourceCode;
184
+ if (!node.id.name.endsWith("Props")) return;
185
+ for (const property of node.body.body) {
186
+ if (property.type !== AST_NODE_TYPES.TSPropertySignature || property.key.type !== AST_NODE_TYPES.Identifier) {
187
+ continue;
188
+ }
189
+ if (property.readonly) continue;
190
+ context.report({
191
+ node: property,
192
+ messageId: "noMutablePropsInterface",
193
+ data: {
194
+ propertyName: property.key.name
37
195
  },
38
- },
196
+ fix: (fixer) => {
197
+ const propertyText = sourceCode.getText(property);
198
+ return fixer.replaceText(property, `readonly ${propertyText}`);
199
+ }
200
+ });
201
+ }
202
+ }
203
+ };
204
+ }
205
+ });
206
+
207
+ const noMutablePublicFields = ESLintUtils.RuleCreator.withoutDocs({
208
+ meta: {
209
+ type: "problem",
210
+ docs: {
211
+ description: "Disallow mutable public class fields"
212
+ },
213
+ fixable: "code",
214
+ messages: {
215
+ noMutablePublicFields: "Public field '{{ propertyName }}' should be readonly. Consider adding the 'readonly' modifier."
39
216
  },
217
+ schema: []
218
+ },
219
+ defaultOptions: [],
220
+ create(context) {
221
+ const parserServices = ESLintUtils.getParserServices(context);
222
+ return {
223
+ ClassDeclaration(node) {
224
+ const sourceCode = context.sourceCode;
225
+ const type = parserServices.getTypeAtLocation(node);
226
+ if (!isConstructOrStackType(type)) return;
227
+ for (const member of node.body.body) {
228
+ if (member.type !== AST_NODE_TYPES.PropertyDefinition || member.key.type !== AST_NODE_TYPES.Identifier) {
229
+ continue;
230
+ }
231
+ if (["private", "protected"].includes(member.accessibility ?? "")) {
232
+ continue;
233
+ }
234
+ if (member.readonly) continue;
235
+ context.report({
236
+ node: member,
237
+ messageId: "noMutablePublicFields",
238
+ data: {
239
+ propertyName: member.key.name
240
+ },
241
+ fix: (fixer) => {
242
+ const accessibility = member.accessibility ? "public " : "";
243
+ const paramText = sourceCode.getText(member);
244
+ const [key, value] = paramText.split(":");
245
+ const replacedKey = key.startsWith("public ") ? key.replace("public ", "") : key;
246
+ return fixer.replaceText(
247
+ member,
248
+ `${accessibility}readonly ${replacedKey}:${value}`
249
+ );
250
+ }
251
+ });
252
+ }
253
+ }
254
+ };
255
+ }
256
+ });
257
+
258
+ const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
259
+ {
260
+ meta: {
261
+ type: "problem",
262
+ docs: {
263
+ description: "Enforce that construct IDs does not match the parent construct name."
264
+ },
265
+ messages: {
266
+ noParentNameConstructIdMatch: "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier."
267
+ },
268
+ schema: []
269
+ },
270
+ defaultOptions: [],
271
+ create(context) {
272
+ return {
273
+ ClassBody(node) {
274
+ const parent = node.parent;
275
+ if (parent?.type !== AST_NODE_TYPES.ClassDeclaration) return;
276
+ const parentClassName = parent.id?.name;
277
+ if (!parentClassName) return;
278
+ for (const body of node.body) {
279
+ if (body.type !== AST_NODE_TYPES.MethodDefinition || !["method", "constructor"].includes(body.kind) || body.value.type !== AST_NODE_TYPES.FunctionExpression) {
280
+ continue;
281
+ }
282
+ validateConstructorBody({
283
+ node,
284
+ expression: body.value,
285
+ parentClassName,
286
+ context
287
+ });
288
+ }
289
+ }
290
+ };
291
+ }
292
+ }
293
+ );
294
+ const validateConstructorBody = ({
295
+ node,
296
+ expression,
297
+ parentClassName,
298
+ context
299
+ }) => {
300
+ for (const statement of expression.body.body) {
301
+ switch (statement.type) {
302
+ case AST_NODE_TYPES.VariableDeclaration: {
303
+ const newExpression = statement.declarations[0].init;
304
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) continue;
305
+ validateConstructId$2({
306
+ node,
307
+ context,
308
+ expression: newExpression,
309
+ parentClassName
310
+ });
311
+ break;
312
+ }
313
+ case AST_NODE_TYPES.ExpressionStatement: {
314
+ if (statement.expression?.type !== AST_NODE_TYPES.NewExpression) break;
315
+ validateStatement({
316
+ node,
317
+ statement,
318
+ parentClassName,
319
+ context
320
+ });
321
+ break;
322
+ }
323
+ case AST_NODE_TYPES.IfStatement: {
324
+ traverseStatements({
325
+ node,
326
+ context,
327
+ parentClassName,
328
+ statement: statement.consequent
329
+ });
330
+ break;
331
+ }
332
+ case AST_NODE_TYPES.SwitchStatement: {
333
+ for (const switchCase of statement.cases) {
334
+ for (const statement2 of switchCase.consequent) {
335
+ traverseStatements({
336
+ node,
337
+ context,
338
+ parentClassName,
339
+ statement: statement2
340
+ });
341
+ }
342
+ }
343
+ break;
344
+ }
345
+ }
346
+ }
347
+ };
348
+ const traverseStatements = ({
349
+ node,
350
+ statement,
351
+ parentClassName,
352
+ context
353
+ }) => {
354
+ switch (statement.type) {
355
+ case AST_NODE_TYPES.BlockStatement: {
356
+ for (const body of statement.body) {
357
+ validateStatement({
358
+ node,
359
+ statement: body,
360
+ parentClassName,
361
+ context
362
+ });
363
+ }
364
+ break;
365
+ }
366
+ case AST_NODE_TYPES.ExpressionStatement: {
367
+ const newExpression = statement.expression;
368
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
369
+ validateStatement({
370
+ node,
371
+ statement,
372
+ parentClassName,
373
+ context
374
+ });
375
+ break;
376
+ }
377
+ case AST_NODE_TYPES.VariableDeclaration: {
378
+ const newExpression = statement.declarations[0].init;
379
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
380
+ validateConstructId$2({
381
+ node,
382
+ context,
383
+ expression: newExpression,
384
+ parentClassName
385
+ });
386
+ break;
387
+ }
388
+ }
389
+ };
390
+ const validateStatement = ({
391
+ node,
392
+ statement,
393
+ parentClassName,
394
+ context
395
+ }) => {
396
+ switch (statement.type) {
397
+ case AST_NODE_TYPES.VariableDeclaration: {
398
+ const newExpression = statement.declarations[0].init;
399
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
400
+ validateConstructId$2({
401
+ node,
402
+ context,
403
+ expression: newExpression,
404
+ parentClassName
405
+ });
406
+ break;
407
+ }
408
+ case AST_NODE_TYPES.ExpressionStatement: {
409
+ const newExpression = statement.expression;
410
+ if (newExpression?.type !== AST_NODE_TYPES.NewExpression) break;
411
+ validateConstructId$2({
412
+ node,
413
+ context,
414
+ expression: newExpression,
415
+ parentClassName
416
+ });
417
+ break;
418
+ }
419
+ case AST_NODE_TYPES.IfStatement: {
420
+ validateIfStatement({
421
+ node,
422
+ statement,
423
+ parentClassName,
424
+ context
425
+ });
426
+ break;
427
+ }
428
+ case AST_NODE_TYPES.SwitchStatement: {
429
+ validateSwitchStatement({
430
+ node,
431
+ statement,
432
+ parentClassName,
433
+ context
434
+ });
435
+ break;
436
+ }
437
+ }
438
+ };
439
+ const validateIfStatement = ({
440
+ node,
441
+ statement,
442
+ parentClassName,
443
+ context
444
+ }) => {
445
+ traverseStatements({
446
+ node,
447
+ context,
448
+ parentClassName,
449
+ statement: statement.consequent
450
+ });
451
+ };
452
+ const validateSwitchStatement = ({
453
+ node,
454
+ statement,
455
+ parentClassName,
456
+ context
457
+ }) => {
458
+ for (const caseStatement of statement.cases) {
459
+ for (const _consequent of caseStatement.consequent) {
460
+ traverseStatements({
461
+ node,
462
+ context,
463
+ parentClassName,
464
+ statement: _consequent
465
+ });
466
+ }
467
+ }
468
+ };
469
+ const validateConstructId$2 = ({
470
+ node,
471
+ context,
472
+ expression,
473
+ parentClassName
474
+ }) => {
475
+ if (expression.arguments.length < 2) return;
476
+ const secondArg = expression.arguments[1];
477
+ if (secondArg.type !== AST_NODE_TYPES.Literal || typeof secondArg.value !== "string") {
478
+ return;
479
+ }
480
+ const formattedConstructId = toPascalCase(secondArg.value);
481
+ const formattedParentClassName = toPascalCase(parentClassName);
482
+ if (formattedParentClassName !== formattedConstructId) return;
483
+ context.report({
484
+ node,
485
+ messageId: "noParentNameConstructIdMatch",
486
+ data: {
487
+ constructId: secondArg.value,
488
+ parentConstructName: parentClassName
489
+ }
490
+ });
491
+ };
492
+
493
+ const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
494
+ meta: {
495
+ type: "problem",
496
+ docs: {
497
+ description: "Disallow class types in public class fields"
498
+ },
499
+ messages: {
500
+ noPublicClassFields: "Public field '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead."
501
+ },
502
+ schema: []
503
+ },
504
+ defaultOptions: [],
505
+ create(context) {
506
+ const parserServices = ESLintUtils.getParserServices(context);
507
+ return {
508
+ ClassDeclaration(node) {
509
+ const type = parserServices.getTypeAtLocation(node);
510
+ if (!isConstructOrStackType(type)) return;
511
+ validateClassMember(node, context, parserServices);
512
+ const constructor = node.body.body.find(
513
+ (member) => member.type === AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
514
+ );
515
+ if (!constructor || constructor.value.type !== AST_NODE_TYPES.FunctionExpression) {
516
+ return;
517
+ }
518
+ validateConstructorParameterProperty(
519
+ constructor,
520
+ context,
521
+ parserServices
522
+ );
523
+ }
524
+ };
525
+ }
526
+ });
527
+ const validateClassMember = (node, context, parserServices) => {
528
+ for (const member of node.body.body) {
529
+ if (member.type !== AST_NODE_TYPES.PropertyDefinition || member.key.type !== AST_NODE_TYPES.Identifier) {
530
+ continue;
531
+ }
532
+ if (["private", "protected"].includes(member.accessibility ?? "")) {
533
+ continue;
534
+ }
535
+ if (!member.typeAnnotation) continue;
536
+ const type = parserServices.getTypeAtLocation(member);
537
+ if (!type.symbol) continue;
538
+ const isClass = type.symbol.flags === SymbolFlags.Class;
539
+ if (!isClass) continue;
540
+ context.report({
541
+ node: member,
542
+ messageId: "noPublicClassFields",
543
+ data: {
544
+ propertyName: member.key.name,
545
+ typeName: type.symbol.name
546
+ }
547
+ });
548
+ }
549
+ };
550
+ const validateConstructorParameterProperty = (constructor, context, parserServices) => {
551
+ for (const param of constructor.value.params) {
552
+ if (param.type !== AST_NODE_TYPES.TSParameterProperty || param.parameter.type !== AST_NODE_TYPES.Identifier) {
553
+ continue;
554
+ }
555
+ if (["private", "protected"].includes(param.accessibility ?? "")) {
556
+ continue;
557
+ }
558
+ if (!param.parameter.typeAnnotation) continue;
559
+ const type = parserServices.getTypeAtLocation(param);
560
+ if (!type.symbol) continue;
561
+ const isClass = type.symbol.flags === SymbolFlags.Class;
562
+ if (!isClass) continue;
563
+ context.report({
564
+ node: param,
565
+ messageId: "noPublicClassFields",
566
+ data: {
567
+ propertyName: param.parameter.name,
568
+ typeName: type.symbol.name
569
+ }
570
+ });
571
+ }
572
+ };
573
+
574
+ const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
575
+ meta: {
576
+ type: "problem",
577
+ docs: {
578
+ description: `Enforce using literal strings for Construct ID.`
579
+ },
580
+ messages: {
581
+ noVariableConstructId: "Shouldn't use a parameter as a Construct ID."
582
+ },
583
+ schema: []
584
+ },
585
+ defaultOptions: [],
586
+ create(context) {
587
+ const parserServices = ESLintUtils.getParserServices(context);
588
+ return {
589
+ NewExpression(node) {
590
+ const type = parserServices.getTypeAtLocation(node);
591
+ if (!isConstructType(type) || isStackType(type) || node.arguments.length < 2) {
592
+ return;
593
+ }
594
+ validateConstructId$1(node, context);
595
+ }
596
+ };
597
+ }
598
+ });
599
+ const validateConstructId$1 = (node, context) => {
600
+ if (node.arguments.length < 2 || isInsideLoop(node)) return;
601
+ const secondArg = node.arguments[1];
602
+ if (secondArg.type === AST_NODE_TYPES.Literal && typeof secondArg.value === "string") {
603
+ return;
604
+ }
605
+ if (secondArg.type === AST_NODE_TYPES.TemplateLiteral && !secondArg.expressions.length) {
606
+ return;
607
+ }
608
+ context.report({
609
+ node,
610
+ messageId: "noVariableConstructId"
611
+ });
612
+ };
613
+ const isInsideLoop = (node) => {
614
+ let current = node.parent;
615
+ while (current) {
616
+ if (current.type === AST_NODE_TYPES.ForStatement || current.type === AST_NODE_TYPES.ForInStatement || current.type === AST_NODE_TYPES.ForOfStatement || current.type === AST_NODE_TYPES.WhileStatement || current.type === AST_NODE_TYPES.DoWhileStatement) {
617
+ return true;
618
+ }
619
+ current = current.parent;
620
+ }
621
+ return false;
622
+ };
623
+
624
+ const QUOTE_TYPE = {
625
+ SINGLE: "'",
626
+ DOUBLE: '"'
627
+ };
628
+ const pascalCaseConstructId = ESLintUtils.RuleCreator.withoutDocs({
629
+ meta: {
630
+ type: "problem",
631
+ docs: {
632
+ description: "Enforce PascalCase for Construct ID."
633
+ },
634
+ messages: {
635
+ pascalCaseConstructId: "Construct ID must be PascalCase."
636
+ },
637
+ schema: [],
638
+ fixable: "code"
639
+ },
640
+ defaultOptions: [],
641
+ create(context) {
642
+ const parserServices = ESLintUtils.getParserServices(context);
643
+ return {
644
+ NewExpression(node) {
645
+ const type = parserServices.getTypeAtLocation(node);
646
+ if (!isConstructOrStackType(type) || node.arguments.length < 2) {
647
+ return;
648
+ }
649
+ validateConstructId(node, context);
650
+ }
651
+ };
652
+ }
653
+ });
654
+ const isPascalCase = (str) => {
655
+ return /^[A-Z][a-zA-Z0-9]*$/.test(str);
656
+ };
657
+ const validateConstructId = (node, context) => {
658
+ if (node.arguments.length < 2) return;
659
+ const secondArg = node.arguments[1];
660
+ if (secondArg.type !== AST_NODE_TYPES.Literal || typeof secondArg.value !== "string") {
661
+ return;
662
+ }
663
+ const quote = secondArg.raw?.startsWith('"') ? QUOTE_TYPE.DOUBLE : QUOTE_TYPE.SINGLE;
664
+ if (isPascalCase(secondArg.value)) return;
665
+ context.report({
666
+ node,
667
+ messageId: "pascalCaseConstructId",
668
+ fix: (fixer) => {
669
+ const pascalCaseValue = toPascalCase(secondArg.value);
670
+ return fixer.replaceText(secondArg, `${quote}${pascalCaseValue}${quote}`);
671
+ }
672
+ });
673
+ };
674
+
675
+ const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
676
+ meta: {
677
+ type: "problem",
678
+ docs: {
679
+ description: "Require passing `this` in a constructor."
680
+ },
681
+ messages: {
682
+ requirePassingThis: "Require passing `this` in a constructor."
683
+ },
684
+ schema: [],
685
+ fixable: "code"
686
+ },
687
+ defaultOptions: [],
688
+ create(context) {
689
+ const parserServices = ESLintUtils.getParserServices(context);
690
+ return {
691
+ NewExpression(node) {
692
+ const type = parserServices.getTypeAtLocation(node);
693
+ if (!isConstructType(type) || isStackType(type) || !node.arguments.length) {
694
+ return;
695
+ }
696
+ const argument = node.arguments[0];
697
+ if (argument.type === AST_NODE_TYPES.ThisExpression) return;
698
+ context.report({
699
+ node,
700
+ messageId: "requirePassingThis",
701
+ fix: (fixer) => {
702
+ return fixer.replaceText(argument, "this");
703
+ }
704
+ });
705
+ }
706
+ };
707
+ }
708
+ });
709
+
710
+ const rules = {
711
+ "no-class-in-interface": noClassInInterface,
712
+ "no-construct-stack-suffix": noConstructStackSuffix,
713
+ "no-parent-name-construct-id-match": noParentNameConstructIdMatch,
714
+ "no-public-class-fields": noPublicClassFields,
715
+ "pascal-case-construct-id": pascalCaseConstructId,
716
+ "no-mutable-public-fields": noMutablePublicFields,
717
+ "no-mutable-props-interface": noMutablePropsInterface,
718
+ "require-passing-this": requirePassingThis,
719
+ "no-variable-construct-id": noVariableConstructId,
720
+ "no-import-private": noImportPrivate
721
+ };
722
+ const configs = {
723
+ recommended: {
724
+ plugins: ["cdk"],
725
+ rules: {
726
+ "cdk/no-class-in-interface": "error",
727
+ "cdk/no-construct-stack-suffix": "error",
728
+ "cdk/no-parent-name-construct-id-match": "error",
729
+ "cdk/no-public-class-fields": "error",
730
+ "cdk/pascal-case-construct-id": "error",
731
+ "cdk/require-passing-this": "error",
732
+ "cdk/no-variable-construct-id": "error",
733
+ "cdk/no-mutable-public-fields": "warn",
734
+ "cdk/no-mutable-props-interface": "warn"
735
+ }
736
+ },
737
+ strict: {
738
+ plugins: ["cdk"],
739
+ rules: {
740
+ "cdk/no-class-in-interface": "error",
741
+ "cdk/no-construct-stack-suffix": "error",
742
+ "cdk/no-parent-name-construct-id-match": "error",
743
+ "cdk/no-public-class-fields": "error",
744
+ "cdk/pascal-case-construct-id": "error",
745
+ "cdk/require-passing-this": "error",
746
+ "cdk/no-variable-construct-id": "error",
747
+ "cdk/no-mutable-public-fields": "error",
748
+ "cdk/no-mutable-props-interface": "error",
749
+ "cdk/no-import-private": "error"
750
+ }
751
+ }
752
+ };
753
+ const eslintCdkPlugin = {
754
+ rules,
755
+ configs
40
756
  };
41
- export default plugin;
42
- //# sourceMappingURL=index.mjs.map
757
+
758
+ export { configs, eslintCdkPlugin as default, rules };