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