eslint-cdk-plugin 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -26,7 +26,7 @@ function _interopNamespaceDefault(e) {
26
26
  var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
27
27
 
28
28
  var name = "eslint-cdk-plugin";
29
- var version = "2.0.0";
29
+ var version = "2.2.0";
30
30
 
31
31
  const isConstructOrStackType = (type, ignoredClasses = ["App", "Stage"]) => {
32
32
  if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
@@ -406,12 +406,33 @@ const noParentNameConstructIdMatch = utils.ESLintUtils.RuleCreator.withoutDocs(
406
406
  messages: {
407
407
  noParentNameConstructIdMatch: "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier."
408
408
  },
409
- schema: []
409
+ schema: [
410
+ {
411
+ type: "object",
412
+ properties: {
413
+ disallowContainingParentName: {
414
+ type: "boolean",
415
+ default: false
416
+ }
417
+ },
418
+ additionalProperties: false
419
+ }
420
+ ]
410
421
  },
411
- defaultOptions: [],
422
+ defaultOptions: [
423
+ {
424
+ disallowContainingParentName: false
425
+ }
426
+ ],
412
427
  create(context) {
428
+ const option = context.options[0] || {
429
+ disallowContainingParentName: false
430
+ };
431
+ const parserServices = utils.ESLintUtils.getParserServices(context);
413
432
  return {
414
433
  ClassBody(node) {
434
+ const type = parserServices.getTypeAtLocation(node);
435
+ if (!isConstructOrStackType(type)) return;
415
436
  const parent = node.parent;
416
437
  if (parent?.type !== utils.AST_NODE_TYPES.ClassDeclaration) return;
417
438
  const parentClassName = parent.id?.name;
@@ -424,7 +445,9 @@ const noParentNameConstructIdMatch = utils.ESLintUtils.RuleCreator.withoutDocs(
424
445
  node,
425
446
  expression: body.value,
426
447
  parentClassName,
427
- context
448
+ context,
449
+ parserServices,
450
+ option
428
451
  });
429
452
  }
430
453
  }
@@ -436,7 +459,9 @@ const validateConstructorBody = ({
436
459
  node,
437
460
  expression,
438
461
  parentClassName,
439
- context
462
+ context,
463
+ parserServices,
464
+ option
440
465
  }) => {
441
466
  for (const statement of expression.body.body) {
442
467
  switch (statement.type) {
@@ -447,7 +472,9 @@ const validateConstructorBody = ({
447
472
  node,
448
473
  context,
449
474
  expression: newExpression,
450
- parentClassName
475
+ parentClassName,
476
+ parserServices,
477
+ option
451
478
  });
452
479
  break;
453
480
  }
@@ -457,7 +484,9 @@ const validateConstructorBody = ({
457
484
  node,
458
485
  statement,
459
486
  parentClassName,
460
- context
487
+ context,
488
+ parserServices,
489
+ option
461
490
  });
462
491
  break;
463
492
  }
@@ -466,7 +495,9 @@ const validateConstructorBody = ({
466
495
  node,
467
496
  context,
468
497
  parentClassName,
469
- statement: statement.consequent
498
+ statement: statement.consequent,
499
+ parserServices,
500
+ option
470
501
  });
471
502
  break;
472
503
  }
@@ -477,7 +508,9 @@ const validateConstructorBody = ({
477
508
  node,
478
509
  context,
479
510
  parentClassName,
480
- statement: statement2
511
+ statement: statement2,
512
+ parserServices,
513
+ option
481
514
  });
482
515
  }
483
516
  }
@@ -490,7 +523,9 @@ const traverseStatements = ({
490
523
  node,
491
524
  statement,
492
525
  parentClassName,
493
- context
526
+ context,
527
+ parserServices,
528
+ option
494
529
  }) => {
495
530
  switch (statement.type) {
496
531
  case utils.AST_NODE_TYPES.BlockStatement: {
@@ -499,7 +534,9 @@ const traverseStatements = ({
499
534
  node,
500
535
  statement: body,
501
536
  parentClassName,
502
- context
537
+ context,
538
+ parserServices,
539
+ option
503
540
  });
504
541
  }
505
542
  break;
@@ -511,7 +548,9 @@ const traverseStatements = ({
511
548
  node,
512
549
  statement,
513
550
  parentClassName,
514
- context
551
+ context,
552
+ parserServices,
553
+ option
515
554
  });
516
555
  break;
517
556
  }
@@ -522,7 +561,9 @@ const traverseStatements = ({
522
561
  node,
523
562
  context,
524
563
  expression: newExpression,
525
- parentClassName
564
+ parentClassName,
565
+ parserServices,
566
+ option
526
567
  });
527
568
  break;
528
569
  }
@@ -532,7 +573,9 @@ const validateStatement = ({
532
573
  node,
533
574
  statement,
534
575
  parentClassName,
535
- context
576
+ context,
577
+ parserServices,
578
+ option
536
579
  }) => {
537
580
  switch (statement.type) {
538
581
  case utils.AST_NODE_TYPES.VariableDeclaration: {
@@ -542,7 +585,9 @@ const validateStatement = ({
542
585
  node,
543
586
  context,
544
587
  expression: newExpression,
545
- parentClassName
588
+ parentClassName,
589
+ parserServices,
590
+ option
546
591
  });
547
592
  break;
548
593
  }
@@ -553,7 +598,9 @@ const validateStatement = ({
553
598
  node,
554
599
  context,
555
600
  expression: newExpression,
556
- parentClassName
601
+ parentClassName,
602
+ parserServices,
603
+ option
557
604
  });
558
605
  break;
559
606
  }
@@ -562,7 +609,9 @@ const validateStatement = ({
562
609
  node,
563
610
  statement,
564
611
  parentClassName,
565
- context
612
+ context,
613
+ parserServices,
614
+ option
566
615
  });
567
616
  break;
568
617
  }
@@ -571,7 +620,9 @@ const validateStatement = ({
571
620
  node,
572
621
  statement,
573
622
  parentClassName,
574
- context
623
+ context,
624
+ parserServices,
625
+ option
575
626
  });
576
627
  break;
577
628
  }
@@ -581,20 +632,26 @@ const validateIfStatement = ({
581
632
  node,
582
633
  statement,
583
634
  parentClassName,
584
- context
635
+ context,
636
+ parserServices,
637
+ option
585
638
  }) => {
586
639
  traverseStatements({
587
640
  node,
588
641
  context,
589
642
  parentClassName,
590
- statement: statement.consequent
643
+ statement: statement.consequent,
644
+ parserServices,
645
+ option
591
646
  });
592
647
  };
593
648
  const validateSwitchStatement = ({
594
649
  node,
595
650
  statement,
596
651
  parentClassName,
597
- context
652
+ context,
653
+ parserServices,
654
+ option
598
655
  }) => {
599
656
  for (const caseStatement of statement.cases) {
600
657
  for (const _consequent of caseStatement.consequent) {
@@ -602,7 +659,9 @@ const validateSwitchStatement = ({
602
659
  node,
603
660
  context,
604
661
  parentClassName,
605
- statement: _consequent
662
+ statement: _consequent,
663
+ parserServices,
664
+ option
606
665
  });
607
666
  }
608
667
  }
@@ -611,8 +670,11 @@ const validateConstructId$2 = ({
611
670
  node,
612
671
  context,
613
672
  expression,
614
- parentClassName
673
+ parentClassName,
674
+ parserServices,
675
+ option
615
676
  }) => {
677
+ const type = parserServices.getTypeAtLocation(expression);
616
678
  if (expression.arguments.length < 2) return;
617
679
  const secondArg = expression.arguments[1];
618
680
  if (secondArg.type !== utils.AST_NODE_TYPES.Literal || typeof secondArg.value !== "string") {
@@ -620,15 +682,28 @@ const validateConstructId$2 = ({
620
682
  }
621
683
  const formattedConstructId = toPascalCase(secondArg.value);
622
684
  const formattedParentClassName = toPascalCase(parentClassName);
623
- if (formattedParentClassName !== formattedConstructId) return;
624
- context.report({
625
- node,
626
- messageId: "noParentNameConstructIdMatch",
627
- data: {
628
- constructId: secondArg.value,
629
- parentConstructName: parentClassName
630
- }
631
- });
685
+ if (!isConstructType(type)) return;
686
+ if (option.disallowContainingParentName && formattedConstructId.includes(formattedParentClassName)) {
687
+ context.report({
688
+ node,
689
+ messageId: "noParentNameConstructIdMatch",
690
+ data: {
691
+ constructId: secondArg.value,
692
+ parentConstructName: parentClassName
693
+ }
694
+ });
695
+ return;
696
+ }
697
+ if (formattedParentClassName === formattedConstructId) {
698
+ context.report({
699
+ node,
700
+ messageId: "noParentNameConstructIdMatch",
701
+ data: {
702
+ constructId: secondArg.value,
703
+ parentConstructName: parentClassName
704
+ }
705
+ });
706
+ }
632
707
  };
633
708
 
634
709
  const noPublicClassFields = utils.ESLintUtils.RuleCreator.withoutDocs({
@@ -1082,7 +1157,10 @@ const createFlatConfig = (rules2) => {
1082
1157
  const recommended = createFlatConfig({
1083
1158
  "cdk/no-class-in-interface": "error",
1084
1159
  "cdk/no-construct-stack-suffix": "error",
1085
- "cdk/no-parent-name-construct-id-match": "error",
1160
+ "cdk/no-parent-name-construct-id-match": [
1161
+ "error",
1162
+ { disallowContainingParentName: false }
1163
+ ],
1086
1164
  "cdk/no-public-class-fields": "error",
1087
1165
  "cdk/pascal-case-construct-id": "error",
1088
1166
  "cdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
@@ -1094,7 +1172,10 @@ const recommended = createFlatConfig({
1094
1172
  const strict = createFlatConfig({
1095
1173
  "cdk/no-class-in-interface": "error",
1096
1174
  "cdk/no-construct-stack-suffix": "error",
1097
- "cdk/no-parent-name-construct-id-match": "error",
1175
+ "cdk/no-parent-name-construct-id-match": [
1176
+ "error",
1177
+ { disallowContainingParentName: true }
1178
+ ],
1098
1179
  "cdk/no-public-class-fields": "error",
1099
1180
  "cdk/pascal-case-construct-id": "error",
1100
1181
  "cdk/require-passing-this": "error",
package/dist/index.d.ts CHANGED
@@ -4,7 +4,9 @@ declare const rules: {
4
4
  "no-construct-stack-suffix": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noConstructStackSuffix", [{
5
5
  disallowedSuffixes: ("Construct" | "Stack")[];
6
6
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
- "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
+ "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [{
8
+ disallowContainingParentName?: boolean;
9
+ }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
8
10
  "no-public-class-fields": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noPublicClassFields", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
9
11
  "pascal-case-construct-id": import("@typescript-eslint/utils/ts-eslint").RuleModule<"pascalCaseConstructId", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
12
  "require-passing-this": import("@typescript-eslint/utils/ts-eslint").RuleModule<"requirePassingThis", [{
@@ -38,7 +40,9 @@ declare const configs: {
38
40
  "no-construct-stack-suffix": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noConstructStackSuffix", [{
39
41
  disallowedSuffixes: ("Construct" | "Stack")[];
40
42
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
41
- "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
43
+ "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [{
44
+ disallowContainingParentName?: boolean;
45
+ }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
42
46
  "no-public-class-fields": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noPublicClassFields", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
43
47
  "pascal-case-construct-id": import("@typescript-eslint/utils/ts-eslint").RuleModule<"pascalCaseConstructId", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
44
48
  "require-passing-this": import("@typescript-eslint/utils/ts-eslint").RuleModule<"requirePassingThis", [{
@@ -75,7 +79,9 @@ declare const configs: {
75
79
  "no-construct-stack-suffix": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noConstructStackSuffix", [{
76
80
  disallowedSuffixes: ("Construct" | "Stack")[];
77
81
  }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
78
- "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
82
+ "no-parent-name-construct-id-match": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noParentNameConstructIdMatch", [{
83
+ disallowContainingParentName?: boolean;
84
+ }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
79
85
  "no-public-class-fields": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noPublicClassFields", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
80
86
  "pascal-case-construct-id": import("@typescript-eslint/utils/ts-eslint").RuleModule<"pascalCaseConstructId", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
81
87
  "require-passing-this": import("@typescript-eslint/utils/ts-eslint").RuleModule<"requirePassingThis", [{
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AAmBjD,QAAA,MAAM,KAAK;;;;;;;;;;;;;;;;;;;CAeV,CAAC;AAoDF,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGZ,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,OAAO,EAAE,OAAO,OAAO,CAAC;CACzB;AAED,QAAA,MAAM,eAAe,EAAE,eAGtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AAmBjD,QAAA,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;CAeV,CAAC;AA0DF,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGZ,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,OAAO,EAAE,OAAO,OAAO,CAAC;CACzB;AAED,QAAA,MAAM,eAAe,EAAE,eAGtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint
3
3
  import * as path from 'path';
4
4
 
5
5
  var name = "eslint-cdk-plugin";
6
- var version = "2.0.0";
6
+ var version = "2.2.0";
7
7
 
8
8
  const isConstructOrStackType = (type, ignoredClasses = ["App", "Stage"]) => {
9
9
  if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
@@ -383,12 +383,33 @@ const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
383
383
  messages: {
384
384
  noParentNameConstructIdMatch: "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier."
385
385
  },
386
- schema: []
386
+ schema: [
387
+ {
388
+ type: "object",
389
+ properties: {
390
+ disallowContainingParentName: {
391
+ type: "boolean",
392
+ default: false
393
+ }
394
+ },
395
+ additionalProperties: false
396
+ }
397
+ ]
387
398
  },
388
- defaultOptions: [],
399
+ defaultOptions: [
400
+ {
401
+ disallowContainingParentName: false
402
+ }
403
+ ],
389
404
  create(context) {
405
+ const option = context.options[0] || {
406
+ disallowContainingParentName: false
407
+ };
408
+ const parserServices = ESLintUtils.getParserServices(context);
390
409
  return {
391
410
  ClassBody(node) {
411
+ const type = parserServices.getTypeAtLocation(node);
412
+ if (!isConstructOrStackType(type)) return;
392
413
  const parent = node.parent;
393
414
  if (parent?.type !== AST_NODE_TYPES.ClassDeclaration) return;
394
415
  const parentClassName = parent.id?.name;
@@ -401,7 +422,9 @@ const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
401
422
  node,
402
423
  expression: body.value,
403
424
  parentClassName,
404
- context
425
+ context,
426
+ parserServices,
427
+ option
405
428
  });
406
429
  }
407
430
  }
@@ -413,7 +436,9 @@ const validateConstructorBody = ({
413
436
  node,
414
437
  expression,
415
438
  parentClassName,
416
- context
439
+ context,
440
+ parserServices,
441
+ option
417
442
  }) => {
418
443
  for (const statement of expression.body.body) {
419
444
  switch (statement.type) {
@@ -424,7 +449,9 @@ const validateConstructorBody = ({
424
449
  node,
425
450
  context,
426
451
  expression: newExpression,
427
- parentClassName
452
+ parentClassName,
453
+ parserServices,
454
+ option
428
455
  });
429
456
  break;
430
457
  }
@@ -434,7 +461,9 @@ const validateConstructorBody = ({
434
461
  node,
435
462
  statement,
436
463
  parentClassName,
437
- context
464
+ context,
465
+ parserServices,
466
+ option
438
467
  });
439
468
  break;
440
469
  }
@@ -443,7 +472,9 @@ const validateConstructorBody = ({
443
472
  node,
444
473
  context,
445
474
  parentClassName,
446
- statement: statement.consequent
475
+ statement: statement.consequent,
476
+ parserServices,
477
+ option
447
478
  });
448
479
  break;
449
480
  }
@@ -454,7 +485,9 @@ const validateConstructorBody = ({
454
485
  node,
455
486
  context,
456
487
  parentClassName,
457
- statement: statement2
488
+ statement: statement2,
489
+ parserServices,
490
+ option
458
491
  });
459
492
  }
460
493
  }
@@ -467,7 +500,9 @@ const traverseStatements = ({
467
500
  node,
468
501
  statement,
469
502
  parentClassName,
470
- context
503
+ context,
504
+ parserServices,
505
+ option
471
506
  }) => {
472
507
  switch (statement.type) {
473
508
  case AST_NODE_TYPES.BlockStatement: {
@@ -476,7 +511,9 @@ const traverseStatements = ({
476
511
  node,
477
512
  statement: body,
478
513
  parentClassName,
479
- context
514
+ context,
515
+ parserServices,
516
+ option
480
517
  });
481
518
  }
482
519
  break;
@@ -488,7 +525,9 @@ const traverseStatements = ({
488
525
  node,
489
526
  statement,
490
527
  parentClassName,
491
- context
528
+ context,
529
+ parserServices,
530
+ option
492
531
  });
493
532
  break;
494
533
  }
@@ -499,7 +538,9 @@ const traverseStatements = ({
499
538
  node,
500
539
  context,
501
540
  expression: newExpression,
502
- parentClassName
541
+ parentClassName,
542
+ parserServices,
543
+ option
503
544
  });
504
545
  break;
505
546
  }
@@ -509,7 +550,9 @@ const validateStatement = ({
509
550
  node,
510
551
  statement,
511
552
  parentClassName,
512
- context
553
+ context,
554
+ parserServices,
555
+ option
513
556
  }) => {
514
557
  switch (statement.type) {
515
558
  case AST_NODE_TYPES.VariableDeclaration: {
@@ -519,7 +562,9 @@ const validateStatement = ({
519
562
  node,
520
563
  context,
521
564
  expression: newExpression,
522
- parentClassName
565
+ parentClassName,
566
+ parserServices,
567
+ option
523
568
  });
524
569
  break;
525
570
  }
@@ -530,7 +575,9 @@ const validateStatement = ({
530
575
  node,
531
576
  context,
532
577
  expression: newExpression,
533
- parentClassName
578
+ parentClassName,
579
+ parserServices,
580
+ option
534
581
  });
535
582
  break;
536
583
  }
@@ -539,7 +586,9 @@ const validateStatement = ({
539
586
  node,
540
587
  statement,
541
588
  parentClassName,
542
- context
589
+ context,
590
+ parserServices,
591
+ option
543
592
  });
544
593
  break;
545
594
  }
@@ -548,7 +597,9 @@ const validateStatement = ({
548
597
  node,
549
598
  statement,
550
599
  parentClassName,
551
- context
600
+ context,
601
+ parserServices,
602
+ option
552
603
  });
553
604
  break;
554
605
  }
@@ -558,20 +609,26 @@ const validateIfStatement = ({
558
609
  node,
559
610
  statement,
560
611
  parentClassName,
561
- context
612
+ context,
613
+ parserServices,
614
+ option
562
615
  }) => {
563
616
  traverseStatements({
564
617
  node,
565
618
  context,
566
619
  parentClassName,
567
- statement: statement.consequent
620
+ statement: statement.consequent,
621
+ parserServices,
622
+ option
568
623
  });
569
624
  };
570
625
  const validateSwitchStatement = ({
571
626
  node,
572
627
  statement,
573
628
  parentClassName,
574
- context
629
+ context,
630
+ parserServices,
631
+ option
575
632
  }) => {
576
633
  for (const caseStatement of statement.cases) {
577
634
  for (const _consequent of caseStatement.consequent) {
@@ -579,7 +636,9 @@ const validateSwitchStatement = ({
579
636
  node,
580
637
  context,
581
638
  parentClassName,
582
- statement: _consequent
639
+ statement: _consequent,
640
+ parserServices,
641
+ option
583
642
  });
584
643
  }
585
644
  }
@@ -588,8 +647,11 @@ const validateConstructId$2 = ({
588
647
  node,
589
648
  context,
590
649
  expression,
591
- parentClassName
650
+ parentClassName,
651
+ parserServices,
652
+ option
592
653
  }) => {
654
+ const type = parserServices.getTypeAtLocation(expression);
593
655
  if (expression.arguments.length < 2) return;
594
656
  const secondArg = expression.arguments[1];
595
657
  if (secondArg.type !== AST_NODE_TYPES.Literal || typeof secondArg.value !== "string") {
@@ -597,15 +659,28 @@ const validateConstructId$2 = ({
597
659
  }
598
660
  const formattedConstructId = toPascalCase(secondArg.value);
599
661
  const formattedParentClassName = toPascalCase(parentClassName);
600
- if (formattedParentClassName !== formattedConstructId) return;
601
- context.report({
602
- node,
603
- messageId: "noParentNameConstructIdMatch",
604
- data: {
605
- constructId: secondArg.value,
606
- parentConstructName: parentClassName
607
- }
608
- });
662
+ if (!isConstructType(type)) return;
663
+ if (option.disallowContainingParentName && formattedConstructId.includes(formattedParentClassName)) {
664
+ context.report({
665
+ node,
666
+ messageId: "noParentNameConstructIdMatch",
667
+ data: {
668
+ constructId: secondArg.value,
669
+ parentConstructName: parentClassName
670
+ }
671
+ });
672
+ return;
673
+ }
674
+ if (formattedParentClassName === formattedConstructId) {
675
+ context.report({
676
+ node,
677
+ messageId: "noParentNameConstructIdMatch",
678
+ data: {
679
+ constructId: secondArg.value,
680
+ parentConstructName: parentClassName
681
+ }
682
+ });
683
+ }
609
684
  };
610
685
 
611
686
  const noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
@@ -1059,7 +1134,10 @@ const createFlatConfig = (rules2) => {
1059
1134
  const recommended = createFlatConfig({
1060
1135
  "cdk/no-class-in-interface": "error",
1061
1136
  "cdk/no-construct-stack-suffix": "error",
1062
- "cdk/no-parent-name-construct-id-match": "error",
1137
+ "cdk/no-parent-name-construct-id-match": [
1138
+ "error",
1139
+ { disallowContainingParentName: false }
1140
+ ],
1063
1141
  "cdk/no-public-class-fields": "error",
1064
1142
  "cdk/pascal-case-construct-id": "error",
1065
1143
  "cdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
@@ -1071,7 +1149,10 @@ const recommended = createFlatConfig({
1071
1149
  const strict = createFlatConfig({
1072
1150
  "cdk/no-class-in-interface": "error",
1073
1151
  "cdk/no-construct-stack-suffix": "error",
1074
- "cdk/no-parent-name-construct-id-match": "error",
1152
+ "cdk/no-parent-name-construct-id-match": [
1153
+ "error",
1154
+ { disallowContainingParentName: true }
1155
+ ],
1075
1156
  "cdk/no-public-class-fields": "error",
1076
1157
  "cdk/pascal-case-construct-id": "error",
1077
1158
  "cdk/require-passing-this": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-cdk-plugin",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "eslint plugin for AWS CDK projects",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -57,7 +57,10 @@ const createFlatConfig = (rules: Record<string, unknown>) => {
57
57
  const recommended = createFlatConfig({
58
58
  "cdk/no-class-in-interface": "error",
59
59
  "cdk/no-construct-stack-suffix": "error",
60
- "cdk/no-parent-name-construct-id-match": "error",
60
+ "cdk/no-parent-name-construct-id-match": [
61
+ "error",
62
+ { disallowContainingParentName: false },
63
+ ],
61
64
  "cdk/no-public-class-fields": "error",
62
65
  "cdk/pascal-case-construct-id": "error",
63
66
  "cdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
@@ -70,7 +73,10 @@ const recommended = createFlatConfig({
70
73
  const strict = createFlatConfig({
71
74
  "cdk/no-class-in-interface": "error",
72
75
  "cdk/no-construct-stack-suffix": "error",
73
- "cdk/no-parent-name-construct-id-match": "error",
76
+ "cdk/no-parent-name-construct-id-match": [
77
+ "error",
78
+ { disallowContainingParentName: true },
79
+ ],
74
80
  "cdk/no-public-class-fields": "error",
75
81
  "cdk/pascal-case-construct-id": "error",
76
82
  "cdk/require-passing-this": "error",
@@ -1,19 +1,29 @@
1
1
  import {
2
2
  AST_NODE_TYPES,
3
3
  ESLintUtils,
4
+ ParserServicesWithTypeInformation,
4
5
  TSESLint,
5
6
  TSESTree,
6
7
  } from "@typescript-eslint/utils";
7
8
 
8
9
  import { toPascalCase } from "../utils/convertString";
10
+ import { isConstructOrStackType, isConstructType } from "../utils/typeCheck";
9
11
 
10
- type Context = TSESLint.RuleContext<"noParentNameConstructIdMatch", []>;
12
+ type Options = [
13
+ {
14
+ disallowContainingParentName?: boolean;
15
+ }
16
+ ];
17
+
18
+ type Context = TSESLint.RuleContext<"noParentNameConstructIdMatch", Options>;
11
19
 
12
20
  type ValidateStatementArgs<T extends TSESTree.Statement> = {
13
21
  node: TSESTree.ClassBody;
14
22
  statement: T;
15
23
  parentClassName: string;
16
24
  context: Context;
25
+ parserServices: ParserServicesWithTypeInformation;
26
+ option: Options[0];
17
27
  };
18
28
 
19
29
  type ValidateExpressionArgs<T extends TSESTree.Expression> = {
@@ -21,6 +31,8 @@ type ValidateExpressionArgs<T extends TSESTree.Expression> = {
21
31
  expression: T;
22
32
  parentClassName: string;
23
33
  context: Context;
34
+ parserServices: ParserServicesWithTypeInformation;
35
+ option: Options[0];
24
36
  };
25
37
 
26
38
  /**
@@ -41,12 +53,36 @@ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
41
53
  noParentNameConstructIdMatch:
42
54
  "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier.",
43
55
  },
44
- schema: [],
56
+ schema: [
57
+ {
58
+ type: "object",
59
+ properties: {
60
+ disallowContainingParentName: {
61
+ type: "boolean",
62
+ default: false,
63
+ },
64
+ },
65
+ additionalProperties: false,
66
+ },
67
+ ],
45
68
  },
46
- defaultOptions: [],
47
- create(context) {
69
+ defaultOptions: [
70
+ {
71
+ disallowContainingParentName: false,
72
+ },
73
+ ],
74
+
75
+ create(context: Context) {
76
+ const option = context.options[0] || {
77
+ disallowContainingParentName: false,
78
+ };
79
+ const parserServices = ESLintUtils.getParserServices(context);
48
80
  return {
49
81
  ClassBody(node) {
82
+ const type = parserServices.getTypeAtLocation(node);
83
+
84
+ if (!isConstructOrStackType(type)) return;
85
+
50
86
  const parent = node.parent;
51
87
  if (parent?.type !== AST_NODE_TYPES.ClassDeclaration) return;
52
88
 
@@ -67,6 +103,8 @@ export const noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs(
67
103
  expression: body.value,
68
104
  parentClassName,
69
105
  context,
106
+ parserServices,
107
+ option,
70
108
  });
71
109
  }
72
110
  },
@@ -84,6 +122,8 @@ const validateConstructorBody = ({
84
122
  expression,
85
123
  parentClassName,
86
124
  context,
125
+ parserServices,
126
+ option,
87
127
  }: ValidateExpressionArgs<TSESTree.FunctionExpression>): void => {
88
128
  for (const statement of expression.body.body) {
89
129
  switch (statement.type) {
@@ -95,6 +135,8 @@ const validateConstructorBody = ({
95
135
  context,
96
136
  expression: newExpression,
97
137
  parentClassName,
138
+ parserServices,
139
+ option,
98
140
  });
99
141
  break;
100
142
  }
@@ -105,6 +147,8 @@ const validateConstructorBody = ({
105
147
  statement,
106
148
  parentClassName,
107
149
  context,
150
+ parserServices,
151
+ option,
108
152
  });
109
153
  break;
110
154
  }
@@ -114,6 +158,8 @@ const validateConstructorBody = ({
114
158
  context,
115
159
  parentClassName,
116
160
  statement: statement.consequent,
161
+ parserServices,
162
+ option,
117
163
  });
118
164
  break;
119
165
  }
@@ -125,6 +171,8 @@ const validateConstructorBody = ({
125
171
  context,
126
172
  parentClassName,
127
173
  statement,
174
+ parserServices,
175
+ option,
128
176
  });
129
177
  }
130
178
  }
@@ -144,6 +192,8 @@ const traverseStatements = ({
144
192
  statement,
145
193
  parentClassName,
146
194
  context,
195
+ parserServices,
196
+ option,
147
197
  }: ValidateStatementArgs<TSESTree.Statement>) => {
148
198
  switch (statement.type) {
149
199
  case AST_NODE_TYPES.BlockStatement: {
@@ -153,6 +203,8 @@ const traverseStatements = ({
153
203
  statement: body,
154
204
  parentClassName,
155
205
  context,
206
+ parserServices,
207
+ option,
156
208
  });
157
209
  }
158
210
  break;
@@ -165,6 +217,8 @@ const traverseStatements = ({
165
217
  statement,
166
218
  parentClassName,
167
219
  context,
220
+ parserServices,
221
+ option,
168
222
  });
169
223
  break;
170
224
  }
@@ -176,6 +230,8 @@ const traverseStatements = ({
176
230
  context,
177
231
  expression: newExpression,
178
232
  parentClassName,
233
+ parserServices,
234
+ option,
179
235
  });
180
236
  break;
181
237
  }
@@ -192,6 +248,8 @@ const validateStatement = ({
192
248
  statement,
193
249
  parentClassName,
194
250
  context,
251
+ parserServices,
252
+ option,
195
253
  }: ValidateStatementArgs<TSESTree.Statement>): void => {
196
254
  switch (statement.type) {
197
255
  case AST_NODE_TYPES.VariableDeclaration: {
@@ -202,6 +260,8 @@ const validateStatement = ({
202
260
  context,
203
261
  expression: newExpression,
204
262
  parentClassName,
263
+ parserServices,
264
+ option,
205
265
  });
206
266
  break;
207
267
  }
@@ -213,6 +273,8 @@ const validateStatement = ({
213
273
  context,
214
274
  expression: newExpression,
215
275
  parentClassName,
276
+ parserServices,
277
+ option,
216
278
  });
217
279
  break;
218
280
  }
@@ -222,6 +284,8 @@ const validateStatement = ({
222
284
  statement,
223
285
  parentClassName,
224
286
  context,
287
+ parserServices,
288
+ option,
225
289
  });
226
290
  break;
227
291
  }
@@ -231,6 +295,8 @@ const validateStatement = ({
231
295
  statement,
232
296
  parentClassName,
233
297
  context,
298
+ parserServices,
299
+ option,
234
300
  });
235
301
  break;
236
302
  }
@@ -246,12 +312,16 @@ const validateIfStatement = ({
246
312
  statement,
247
313
  parentClassName,
248
314
  context,
315
+ parserServices,
316
+ option,
249
317
  }: ValidateStatementArgs<TSESTree.IfStatement>): void => {
250
318
  traverseStatements({
251
319
  node,
252
320
  context,
253
321
  parentClassName,
254
322
  statement: statement.consequent,
323
+ parserServices,
324
+ option,
255
325
  });
256
326
  };
257
327
 
@@ -264,6 +334,8 @@ const validateSwitchStatement = ({
264
334
  statement,
265
335
  parentClassName,
266
336
  context,
337
+ parserServices,
338
+ option,
267
339
  }: ValidateStatementArgs<TSESTree.SwitchStatement>): void => {
268
340
  for (const caseStatement of statement.cases) {
269
341
  for (const _consequent of caseStatement.consequent) {
@@ -272,6 +344,8 @@ const validateSwitchStatement = ({
272
344
  context,
273
345
  parentClassName,
274
346
  statement: _consequent,
347
+ parserServices,
348
+ option,
275
349
  });
276
350
  }
277
351
  }
@@ -285,7 +359,11 @@ const validateConstructId = ({
285
359
  context,
286
360
  expression,
287
361
  parentClassName,
362
+ parserServices,
363
+ option,
288
364
  }: ValidateExpressionArgs<TSESTree.NewExpression>): void => {
365
+ const type = parserServices.getTypeAtLocation(expression);
366
+
289
367
  if (expression.arguments.length < 2) return;
290
368
 
291
369
  // NOTE: Treat the second argument as ID
@@ -299,14 +377,31 @@ const validateConstructId = ({
299
377
 
300
378
  const formattedConstructId = toPascalCase(secondArg.value);
301
379
  const formattedParentClassName = toPascalCase(parentClassName);
302
- if (formattedParentClassName !== formattedConstructId) return;
303
380
 
304
- context.report({
305
- node,
306
- messageId: "noParentNameConstructIdMatch",
307
- data: {
308
- constructId: secondArg.value,
309
- parentConstructName: parentClassName,
310
- },
311
- });
381
+ if (!isConstructType(type)) return;
382
+
383
+ if (
384
+ option.disallowContainingParentName &&
385
+ formattedConstructId.includes(formattedParentClassName)
386
+ ) {
387
+ context.report({
388
+ node,
389
+ messageId: "noParentNameConstructIdMatch",
390
+ data: {
391
+ constructId: secondArg.value,
392
+ parentConstructName: parentClassName,
393
+ },
394
+ });
395
+ return;
396
+ }
397
+ if (formattedParentClassName === formattedConstructId) {
398
+ context.report({
399
+ node,
400
+ messageId: "noParentNameConstructIdMatch",
401
+ data: {
402
+ constructId: secondArg.value,
403
+ parentConstructName: parentClassName,
404
+ },
405
+ });
406
+ }
312
407
  };