eslint-cdk-plugin 1.0.5 → 1.1.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
@@ -194402,6 +194402,118 @@ const validateConstructId = (node, context) => {
194402
194402
  });
194403
194403
  };
194404
194404
 
194405
+ const propsNameConvention = utils.ESLintUtils.RuleCreator.withoutDocs({
194406
+ meta: {
194407
+ type: "problem",
194408
+ docs: {
194409
+ description: "Enforce props interface name to follow ${ConstructName}Props format"
194410
+ },
194411
+ schema: [],
194412
+ messages: {
194413
+ invalidPropsName: "Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'."
194414
+ }
194415
+ },
194416
+ defaultOptions: [],
194417
+ create(context) {
194418
+ const parserServices = utils.ESLintUtils.getParserServices(context);
194419
+ return {
194420
+ ClassDeclaration(node) {
194421
+ if (!node.id || !node.superClass) return;
194422
+ const type = parserServices.getTypeAtLocation(node.superClass);
194423
+ if (!isConstructType(type)) return;
194424
+ const constructor = node.body.body.find(
194425
+ (member) => member.type === utils.AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
194426
+ );
194427
+ const propsParam = constructor?.value.params?.[2];
194428
+ if (propsParam?.type !== utils.AST_NODE_TYPES.Identifier) return;
194429
+ const typeAnnotation = propsParam.typeAnnotation;
194430
+ if (typeAnnotation?.type !== utils.AST_NODE_TYPES.TSTypeAnnotation) return;
194431
+ const typeNode = typeAnnotation.typeAnnotation;
194432
+ if (typeNode.type !== utils.AST_NODE_TYPES.TSTypeReference) return;
194433
+ const propsTypeName = typeNode.typeName;
194434
+ if (propsTypeName.type !== utils.AST_NODE_TYPES.Identifier) return;
194435
+ const constructName = node.id.name;
194436
+ const expectedPropsName = `${constructName}Props`;
194437
+ if (propsTypeName.name !== expectedPropsName) {
194438
+ context.report({
194439
+ node: propsTypeName,
194440
+ messageId: "invalidPropsName",
194441
+ data: {
194442
+ interfaceName: propsTypeName.name,
194443
+ expectedName: expectedPropsName
194444
+ }
194445
+ });
194446
+ }
194447
+ }
194448
+ };
194449
+ }
194450
+ });
194451
+
194452
+ const requireJSDoc = utils.ESLintUtils.RuleCreator.withoutDocs({
194453
+ meta: {
194454
+ type: "problem",
194455
+ docs: {
194456
+ description: "Require JSDoc comments for interface properties and public properties in Construct classes"
194457
+ },
194458
+ messages: {
194459
+ missingJSDoc: "Property '{{ propertyName }}' should have a JSDoc comment."
194460
+ },
194461
+ schema: []
194462
+ },
194463
+ defaultOptions: [],
194464
+ create(context) {
194465
+ const parserServices = utils.ESLintUtils.getParserServices(context);
194466
+ return {
194467
+ TSPropertySignature(node) {
194468
+ if (node.key.type !== utils.AST_NODE_TYPES.Identifier || node.parent?.type !== utils.AST_NODE_TYPES.TSInterfaceBody) {
194469
+ return;
194470
+ }
194471
+ const sourceCode = context.sourceCode;
194472
+ const comments = sourceCode.getCommentsBefore(node);
194473
+ const hasJSDoc = comments.some(
194474
+ ({ type, value }) => type === utils.AST_TOKEN_TYPES.Block && value.startsWith("*")
194475
+ );
194476
+ if (!hasJSDoc) {
194477
+ context.report({
194478
+ node,
194479
+ messageId: "missingJSDoc",
194480
+ data: {
194481
+ propertyName: node.key.name
194482
+ }
194483
+ });
194484
+ }
194485
+ },
194486
+ PropertyDefinition(node) {
194487
+ if (node.key.type !== utils.AST_NODE_TYPES.Identifier || node.parent.type !== utils.AST_NODE_TYPES.ClassBody) {
194488
+ return;
194489
+ }
194490
+ const classDeclaration = node.parent.parent;
194491
+ if (classDeclaration.type !== utils.AST_NODE_TYPES.ClassDeclaration || !classDeclaration.superClass) {
194492
+ return;
194493
+ }
194494
+ const classType = parserServices.getTypeAtLocation(classDeclaration);
194495
+ if (!isConstructType(classType) || node.accessibility !== "public") {
194496
+ return;
194497
+ }
194498
+ const sourceCode = context.sourceCode;
194499
+ const comments = sourceCode.getCommentsBefore(node);
194500
+ const hasJSDoc = comments.some(
194501
+ ({ type, value }) => type === utils.AST_TOKEN_TYPES.Block && value.startsWith("*")
194502
+ );
194503
+ if (!hasJSDoc) {
194504
+ context.report({
194505
+ node,
194506
+ messageId: "missingJSDoc",
194507
+ data: {
194508
+ propertyName: node.key.name
194509
+ }
194510
+ });
194511
+ }
194512
+ }
194513
+ };
194514
+ }
194515
+ });
194516
+
194405
194517
  const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
194406
194518
  meta: {
194407
194519
  type: "problem",
@@ -194437,6 +194549,48 @@ const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
194437
194549
  }
194438
194550
  });
194439
194551
 
194552
+ const requirePropsDefaultDoc = utils.ESLintUtils.RuleCreator.withoutDocs({
194553
+ meta: {
194554
+ type: "problem",
194555
+ docs: {
194556
+ description: "Require @default JSDoc for optional properties in interfaces ending with 'Props'"
194557
+ },
194558
+ schema: [],
194559
+ messages: {
194560
+ missingDefaultDoc: "Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation"
194561
+ }
194562
+ },
194563
+ defaultOptions: [],
194564
+ create(context) {
194565
+ return {
194566
+ TSPropertySignature(node) {
194567
+ if (!node.optional) return;
194568
+ const parent = node.parent?.parent;
194569
+ if (parent?.type !== utils.AST_NODE_TYPES.TSInterfaceDeclaration) {
194570
+ return;
194571
+ }
194572
+ if (parent.id.type !== utils.AST_NODE_TYPES.Identifier || !parent.id.name.endsWith("Props")) {
194573
+ return;
194574
+ }
194575
+ const sourceCode = context.sourceCode;
194576
+ const comments = sourceCode.getCommentsBefore(node);
194577
+ const hasDefaultDoc = comments.some(
194578
+ (comment) => comment.type === utils.AST_TOKEN_TYPES.Block && comment.value.includes("*") && comment.value.includes("@default")
194579
+ );
194580
+ if (!hasDefaultDoc) {
194581
+ context.report({
194582
+ node,
194583
+ messageId: "missingDefaultDoc",
194584
+ data: {
194585
+ propertyName: node.key.type === utils.AST_NODE_TYPES.Identifier ? node.key.name : "unknown"
194586
+ }
194587
+ });
194588
+ }
194589
+ }
194590
+ };
194591
+ }
194592
+ });
194593
+
194440
194594
  const rules = {
194441
194595
  "no-class-in-interface": noClassInInterface,
194442
194596
  "no-construct-stack-suffix": noConstructStackSuffix,
@@ -194447,6 +194601,9 @@ const rules = {
194447
194601
  "no-mutable-props-interface": noMutablePropsInterface,
194448
194602
  "require-passing-this": requirePassingThis,
194449
194603
  "no-variable-construct-id": noVariableConstructId,
194604
+ "require-jsdoc": requireJSDoc,
194605
+ "require-props-default-doc": requirePropsDefaultDoc,
194606
+ "props-name-convention": propsNameConvention,
194450
194607
  "no-import-private": noImportPrivate
194451
194608
  };
194452
194609
  const configs = {
@@ -194461,7 +194618,8 @@ const configs = {
194461
194618
  "cdk/require-passing-this": "error",
194462
194619
  "cdk/no-variable-construct-id": "error",
194463
194620
  "cdk/no-mutable-public-fields": "warn",
194464
- "cdk/no-mutable-props-interface": "warn"
194621
+ "cdk/no-mutable-props-interface": "warn",
194622
+ "cdk/props-name-convention": "warn"
194465
194623
  }
194466
194624
  },
194467
194625
  strict: {
@@ -194476,7 +194634,10 @@ const configs = {
194476
194634
  "cdk/no-variable-construct-id": "error",
194477
194635
  "cdk/no-mutable-public-fields": "error",
194478
194636
  "cdk/no-mutable-props-interface": "error",
194479
- "cdk/no-import-private": "error"
194637
+ "cdk/no-import-private": "error",
194638
+ "cdk/require-props-default-doc": "error",
194639
+ "cdk/props-name-convention": "error",
194640
+ "cdk/require-jsdoc": "error"
194480
194641
  }
194481
194642
  }
194482
194643
  };
package/dist/index.d.ts CHANGED
@@ -8,6 +8,9 @@ declare const rules: {
8
8
  "no-mutable-props-interface": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noMutablePropsInterface", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
9
9
  "require-passing-this": import("@typescript-eslint/utils/ts-eslint").RuleModule<"requirePassingThis", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
10
  "no-variable-construct-id": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noVariableConstructId", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
+ "require-jsdoc": import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingJSDoc", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
12
+ "require-props-default-doc": import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingDefaultDoc", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
13
+ "props-name-convention": import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalidPropsName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
14
  "no-import-private": import("eslint").Rule.RuleModule;
12
15
  };
13
16
  declare const configs: {
@@ -23,6 +26,7 @@ declare const configs: {
23
26
  "cdk/no-variable-construct-id": string;
24
27
  "cdk/no-mutable-public-fields": string;
25
28
  "cdk/no-mutable-props-interface": string;
29
+ "cdk/props-name-convention": string;
26
30
  };
27
31
  };
28
32
  strict: {
@@ -38,6 +42,9 @@ declare const configs: {
38
42
  "cdk/no-mutable-public-fields": string;
39
43
  "cdk/no-mutable-props-interface": string;
40
44
  "cdk/no-import-private": string;
45
+ "cdk/require-props-default-doc": string;
46
+ "cdk/props-name-convention": string;
47
+ "cdk/require-jsdoc": string;
41
48
  };
42
49
  };
43
50
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,KAAK;;;;;;;;;;;CAWV,CAAC;AAEF,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BZ,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":"AAcA,QAAA,MAAM,KAAK;;;;;;;;;;;;;;CAcV,CAAC;AAEF,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCZ,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
@@ -1,4 +1,4 @@
1
- import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
1
+ import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
2
2
  import { createRequire } from 'module';
3
3
  import require$$1 from 'fs';
4
4
  import * as path from 'path';
@@ -194379,6 +194379,118 @@ const validateConstructId = (node, context) => {
194379
194379
  });
194380
194380
  };
194381
194381
 
194382
+ const propsNameConvention = ESLintUtils.RuleCreator.withoutDocs({
194383
+ meta: {
194384
+ type: "problem",
194385
+ docs: {
194386
+ description: "Enforce props interface name to follow ${ConstructName}Props format"
194387
+ },
194388
+ schema: [],
194389
+ messages: {
194390
+ invalidPropsName: "Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'."
194391
+ }
194392
+ },
194393
+ defaultOptions: [],
194394
+ create(context) {
194395
+ const parserServices = ESLintUtils.getParserServices(context);
194396
+ return {
194397
+ ClassDeclaration(node) {
194398
+ if (!node.id || !node.superClass) return;
194399
+ const type = parserServices.getTypeAtLocation(node.superClass);
194400
+ if (!isConstructType(type)) return;
194401
+ const constructor = node.body.body.find(
194402
+ (member) => member.type === AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
194403
+ );
194404
+ const propsParam = constructor?.value.params?.[2];
194405
+ if (propsParam?.type !== AST_NODE_TYPES.Identifier) return;
194406
+ const typeAnnotation = propsParam.typeAnnotation;
194407
+ if (typeAnnotation?.type !== AST_NODE_TYPES.TSTypeAnnotation) return;
194408
+ const typeNode = typeAnnotation.typeAnnotation;
194409
+ if (typeNode.type !== AST_NODE_TYPES.TSTypeReference) return;
194410
+ const propsTypeName = typeNode.typeName;
194411
+ if (propsTypeName.type !== AST_NODE_TYPES.Identifier) return;
194412
+ const constructName = node.id.name;
194413
+ const expectedPropsName = `${constructName}Props`;
194414
+ if (propsTypeName.name !== expectedPropsName) {
194415
+ context.report({
194416
+ node: propsTypeName,
194417
+ messageId: "invalidPropsName",
194418
+ data: {
194419
+ interfaceName: propsTypeName.name,
194420
+ expectedName: expectedPropsName
194421
+ }
194422
+ });
194423
+ }
194424
+ }
194425
+ };
194426
+ }
194427
+ });
194428
+
194429
+ const requireJSDoc = ESLintUtils.RuleCreator.withoutDocs({
194430
+ meta: {
194431
+ type: "problem",
194432
+ docs: {
194433
+ description: "Require JSDoc comments for interface properties and public properties in Construct classes"
194434
+ },
194435
+ messages: {
194436
+ missingJSDoc: "Property '{{ propertyName }}' should have a JSDoc comment."
194437
+ },
194438
+ schema: []
194439
+ },
194440
+ defaultOptions: [],
194441
+ create(context) {
194442
+ const parserServices = ESLintUtils.getParserServices(context);
194443
+ return {
194444
+ TSPropertySignature(node) {
194445
+ if (node.key.type !== AST_NODE_TYPES.Identifier || node.parent?.type !== AST_NODE_TYPES.TSInterfaceBody) {
194446
+ return;
194447
+ }
194448
+ const sourceCode = context.sourceCode;
194449
+ const comments = sourceCode.getCommentsBefore(node);
194450
+ const hasJSDoc = comments.some(
194451
+ ({ type, value }) => type === AST_TOKEN_TYPES.Block && value.startsWith("*")
194452
+ );
194453
+ if (!hasJSDoc) {
194454
+ context.report({
194455
+ node,
194456
+ messageId: "missingJSDoc",
194457
+ data: {
194458
+ propertyName: node.key.name
194459
+ }
194460
+ });
194461
+ }
194462
+ },
194463
+ PropertyDefinition(node) {
194464
+ if (node.key.type !== AST_NODE_TYPES.Identifier || node.parent.type !== AST_NODE_TYPES.ClassBody) {
194465
+ return;
194466
+ }
194467
+ const classDeclaration = node.parent.parent;
194468
+ if (classDeclaration.type !== AST_NODE_TYPES.ClassDeclaration || !classDeclaration.superClass) {
194469
+ return;
194470
+ }
194471
+ const classType = parserServices.getTypeAtLocation(classDeclaration);
194472
+ if (!isConstructType(classType) || node.accessibility !== "public") {
194473
+ return;
194474
+ }
194475
+ const sourceCode = context.sourceCode;
194476
+ const comments = sourceCode.getCommentsBefore(node);
194477
+ const hasJSDoc = comments.some(
194478
+ ({ type, value }) => type === AST_TOKEN_TYPES.Block && value.startsWith("*")
194479
+ );
194480
+ if (!hasJSDoc) {
194481
+ context.report({
194482
+ node,
194483
+ messageId: "missingJSDoc",
194484
+ data: {
194485
+ propertyName: node.key.name
194486
+ }
194487
+ });
194488
+ }
194489
+ }
194490
+ };
194491
+ }
194492
+ });
194493
+
194382
194494
  const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
194383
194495
  meta: {
194384
194496
  type: "problem",
@@ -194414,6 +194526,48 @@ const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
194414
194526
  }
194415
194527
  });
194416
194528
 
194529
+ const requirePropsDefaultDoc = ESLintUtils.RuleCreator.withoutDocs({
194530
+ meta: {
194531
+ type: "problem",
194532
+ docs: {
194533
+ description: "Require @default JSDoc for optional properties in interfaces ending with 'Props'"
194534
+ },
194535
+ schema: [],
194536
+ messages: {
194537
+ missingDefaultDoc: "Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation"
194538
+ }
194539
+ },
194540
+ defaultOptions: [],
194541
+ create(context) {
194542
+ return {
194543
+ TSPropertySignature(node) {
194544
+ if (!node.optional) return;
194545
+ const parent = node.parent?.parent;
194546
+ if (parent?.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
194547
+ return;
194548
+ }
194549
+ if (parent.id.type !== AST_NODE_TYPES.Identifier || !parent.id.name.endsWith("Props")) {
194550
+ return;
194551
+ }
194552
+ const sourceCode = context.sourceCode;
194553
+ const comments = sourceCode.getCommentsBefore(node);
194554
+ const hasDefaultDoc = comments.some(
194555
+ (comment) => comment.type === AST_TOKEN_TYPES.Block && comment.value.includes("*") && comment.value.includes("@default")
194556
+ );
194557
+ if (!hasDefaultDoc) {
194558
+ context.report({
194559
+ node,
194560
+ messageId: "missingDefaultDoc",
194561
+ data: {
194562
+ propertyName: node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : "unknown"
194563
+ }
194564
+ });
194565
+ }
194566
+ }
194567
+ };
194568
+ }
194569
+ });
194570
+
194417
194571
  const rules = {
194418
194572
  "no-class-in-interface": noClassInInterface,
194419
194573
  "no-construct-stack-suffix": noConstructStackSuffix,
@@ -194424,6 +194578,9 @@ const rules = {
194424
194578
  "no-mutable-props-interface": noMutablePropsInterface,
194425
194579
  "require-passing-this": requirePassingThis,
194426
194580
  "no-variable-construct-id": noVariableConstructId,
194581
+ "require-jsdoc": requireJSDoc,
194582
+ "require-props-default-doc": requirePropsDefaultDoc,
194583
+ "props-name-convention": propsNameConvention,
194427
194584
  "no-import-private": noImportPrivate
194428
194585
  };
194429
194586
  const configs = {
@@ -194438,7 +194595,8 @@ const configs = {
194438
194595
  "cdk/require-passing-this": "error",
194439
194596
  "cdk/no-variable-construct-id": "error",
194440
194597
  "cdk/no-mutable-public-fields": "warn",
194441
- "cdk/no-mutable-props-interface": "warn"
194598
+ "cdk/no-mutable-props-interface": "warn",
194599
+ "cdk/props-name-convention": "warn"
194442
194600
  }
194443
194601
  },
194444
194602
  strict: {
@@ -194453,7 +194611,10 @@ const configs = {
194453
194611
  "cdk/no-variable-construct-id": "error",
194454
194612
  "cdk/no-mutable-public-fields": "error",
194455
194613
  "cdk/no-mutable-props-interface": "error",
194456
- "cdk/no-import-private": "error"
194614
+ "cdk/no-import-private": "error",
194615
+ "cdk/require-props-default-doc": "error",
194616
+ "cdk/props-name-convention": "error",
194617
+ "cdk/require-jsdoc": "error"
194457
194618
  }
194458
194619
  }
194459
194620
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-cdk-plugin",
3
- "version": "1.0.5",
3
+ "version": "1.1.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
@@ -7,7 +7,10 @@ import { noParentNameConstructIdMatch } from "./rules/no-parent-name-construct-i
7
7
  import { noPublicClassFields } from "./rules/no-public-class-fields";
8
8
  import { noVariableConstructId } from "./rules/no-variable-construct-id";
9
9
  import { pascalCaseConstructId } from "./rules/pascal-case-construct-id";
10
+ import { propsNameConvention } from "./rules/props-name-convention";
11
+ import { requireJSDoc } from "./rules/require-jsdoc";
10
12
  import { requirePassingThis } from "./rules/require-passing-this";
13
+ import { requirePropsDefaultDoc } from "./rules/require-props-default-doc";
11
14
 
12
15
  const rules = {
13
16
  "no-class-in-interface": noClassInInterface,
@@ -19,6 +22,9 @@ const rules = {
19
22
  "no-mutable-props-interface": noMutablePropsInterface,
20
23
  "require-passing-this": requirePassingThis,
21
24
  "no-variable-construct-id": noVariableConstructId,
25
+ "require-jsdoc": requireJSDoc,
26
+ "require-props-default-doc": requirePropsDefaultDoc,
27
+ "props-name-convention": propsNameConvention,
22
28
  "no-import-private": noImportPrivate,
23
29
  };
24
30
 
@@ -35,6 +41,7 @@ const configs = {
35
41
  "cdk/no-variable-construct-id": "error",
36
42
  "cdk/no-mutable-public-fields": "warn",
37
43
  "cdk/no-mutable-props-interface": "warn",
44
+ "cdk/props-name-convention": "warn",
38
45
  },
39
46
  },
40
47
  strict: {
@@ -50,6 +57,9 @@ const configs = {
50
57
  "cdk/no-mutable-public-fields": "error",
51
58
  "cdk/no-mutable-props-interface": "error",
52
59
  "cdk/no-import-private": "error",
60
+ "cdk/require-props-default-doc": "error",
61
+ "cdk/props-name-convention": "error",
62
+ "cdk/require-jsdoc": "error",
53
63
  },
54
64
  },
55
65
  };
@@ -0,0 +1,75 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ TSESTree,
5
+ } from "@typescript-eslint/utils";
6
+
7
+ import { isConstructType } from "../utils/typeCheck";
8
+
9
+ /**
10
+ * Enforces a naming convention for props interfaces in Construct classes
11
+ * @param context - The rule context provided by ESLint
12
+ * @returns An object containing the AST visitor functions
13
+ * @see {@link https://eslint-cdk-plugin.dev/rules/props-name-convention} - Documentation
14
+ */
15
+ export const propsNameConvention = ESLintUtils.RuleCreator.withoutDocs({
16
+ meta: {
17
+ type: "problem",
18
+ docs: {
19
+ description:
20
+ "Enforce props interface name to follow ${ConstructName}Props format",
21
+ },
22
+ schema: [],
23
+ messages: {
24
+ invalidPropsName:
25
+ "Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'.",
26
+ },
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ const parserServices = ESLintUtils.getParserServices(context);
31
+ return {
32
+ ClassDeclaration(node) {
33
+ if (!node.id || !node.superClass) return;
34
+
35
+ const type = parserServices.getTypeAtLocation(node.superClass);
36
+ if (!isConstructType(type)) return;
37
+
38
+ // NOTE: check constructor parameter
39
+ const constructor = node.body.body.find(
40
+ (member): member is TSESTree.MethodDefinition =>
41
+ member.type === AST_NODE_TYPES.MethodDefinition &&
42
+ member.kind === "constructor"
43
+ );
44
+
45
+ const propsParam = constructor?.value.params?.[2];
46
+ if (propsParam?.type !== AST_NODE_TYPES.Identifier) return;
47
+
48
+ const typeAnnotation = propsParam.typeAnnotation;
49
+ if (typeAnnotation?.type !== AST_NODE_TYPES.TSTypeAnnotation) return;
50
+
51
+ const typeNode = typeAnnotation.typeAnnotation;
52
+ if (typeNode.type !== AST_NODE_TYPES.TSTypeReference) return;
53
+
54
+ const propsTypeName = typeNode.typeName;
55
+ if (propsTypeName.type !== AST_NODE_TYPES.Identifier) return;
56
+
57
+ // NOTE: create valid props name
58
+ const constructName = node.id.name;
59
+ const expectedPropsName = `${constructName}Props`;
60
+
61
+ // NOTE: error when props name is not expected format
62
+ if (propsTypeName.name !== expectedPropsName) {
63
+ context.report({
64
+ node: propsTypeName,
65
+ messageId: "invalidPropsName",
66
+ data: {
67
+ interfaceName: propsTypeName.name,
68
+ expectedName: expectedPropsName,
69
+ },
70
+ });
71
+ }
72
+ },
73
+ };
74
+ },
75
+ });
@@ -0,0 +1,99 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ AST_TOKEN_TYPES,
4
+ ESLintUtils,
5
+ } from "@typescript-eslint/utils";
6
+
7
+ import { isConstructType } from "../utils/typeCheck";
8
+
9
+ /**
10
+ * Require JSDoc comments for interface properties and public properties in Construct classes
11
+ * @param context - The rule context provided by ESLint
12
+ * @returns An object containing the AST visitor functions
13
+ * @see {@link https://eslint-cdk-plugin.dev/rules/require-jsdoc} - Documentation
14
+ */
15
+ export const requireJSDoc = ESLintUtils.RuleCreator.withoutDocs({
16
+ meta: {
17
+ type: "problem",
18
+ docs: {
19
+ description:
20
+ "Require JSDoc comments for interface properties and public properties in Construct classes",
21
+ },
22
+ messages: {
23
+ missingJSDoc:
24
+ "Property '{{ propertyName }}' should have a JSDoc comment.",
25
+ },
26
+ schema: [],
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ const parserServices = ESLintUtils.getParserServices(context);
31
+ return {
32
+ TSPropertySignature(node) {
33
+ if (
34
+ node.key.type !== AST_NODE_TYPES.Identifier ||
35
+ node.parent?.type !== AST_NODE_TYPES.TSInterfaceBody
36
+ ) {
37
+ return;
38
+ }
39
+
40
+ const sourceCode = context.sourceCode;
41
+ const comments = sourceCode.getCommentsBefore(node);
42
+ const hasJSDoc = comments.some(
43
+ ({ type, value }) =>
44
+ type === AST_TOKEN_TYPES.Block && value.startsWith("*")
45
+ );
46
+
47
+ if (!hasJSDoc) {
48
+ context.report({
49
+ node,
50
+ messageId: "missingJSDoc",
51
+ data: {
52
+ propertyName: node.key.name,
53
+ },
54
+ });
55
+ }
56
+ },
57
+ PropertyDefinition(node) {
58
+ if (
59
+ node.key.type !== AST_NODE_TYPES.Identifier ||
60
+ node.parent.type !== AST_NODE_TYPES.ClassBody
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ // NOTE: Check if the class extends Construct
66
+ const classDeclaration = node.parent.parent;
67
+ if (
68
+ classDeclaration.type !== AST_NODE_TYPES.ClassDeclaration ||
69
+ !classDeclaration.superClass
70
+ ) {
71
+ return;
72
+ }
73
+
74
+ // NOTE: Check if the class extends Construct and the property is public
75
+ const classType = parserServices.getTypeAtLocation(classDeclaration);
76
+ if (!isConstructType(classType) || node.accessibility !== "public") {
77
+ return;
78
+ }
79
+
80
+ const sourceCode = context.sourceCode;
81
+ const comments = sourceCode.getCommentsBefore(node);
82
+ const hasJSDoc = comments.some(
83
+ ({ type, value }) =>
84
+ type === AST_TOKEN_TYPES.Block && value.startsWith("*")
85
+ );
86
+
87
+ if (!hasJSDoc) {
88
+ context.report({
89
+ node,
90
+ messageId: "missingJSDoc",
91
+ data: {
92
+ propertyName: node.key.name,
93
+ },
94
+ });
95
+ }
96
+ },
97
+ };
98
+ },
99
+ });
@@ -0,0 +1,72 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ AST_TOKEN_TYPES,
4
+ ESLintUtils,
5
+ } from "@typescript-eslint/utils";
6
+
7
+ /**
8
+ * Requires @default JSDoc documentation for optional properties in interfaces ending with 'Props'
9
+ * @param context - The rule context provided by ESLint
10
+ * @returns An object containing the AST visitor functions
11
+ * @see {@link https://eslint-cdk-plugin.dev/rules/require-props-default-doc} - Documentation
12
+ */
13
+ export const requirePropsDefaultDoc = ESLintUtils.RuleCreator.withoutDocs({
14
+ meta: {
15
+ type: "problem",
16
+ docs: {
17
+ description:
18
+ "Require @default JSDoc for optional properties in interfaces ending with 'Props'",
19
+ },
20
+ schema: [],
21
+ messages: {
22
+ missingDefaultDoc:
23
+ "Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation",
24
+ },
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ return {
29
+ TSPropertySignature(node) {
30
+ // NOTE: Check if the property is optional
31
+ if (!node.optional) return;
32
+
33
+ // NOTE: Check if the parent is an interface
34
+ const parent = node.parent?.parent;
35
+ if (parent?.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
36
+ return;
37
+ }
38
+
39
+ // NOTE: Check if the interface name ends with 'Props'
40
+ if (
41
+ parent.id.type !== AST_NODE_TYPES.Identifier ||
42
+ !parent.id.name.endsWith("Props")
43
+ ) {
44
+ return;
45
+ }
46
+
47
+ // NOTE: Get JSDoc comments
48
+ const sourceCode = context.sourceCode;
49
+ const comments = sourceCode.getCommentsBefore(node);
50
+ const hasDefaultDoc = comments.some(
51
+ (comment) =>
52
+ comment.type === AST_TOKEN_TYPES.Block &&
53
+ comment.value.includes("*") &&
54
+ comment.value.includes("@default")
55
+ );
56
+
57
+ if (!hasDefaultDoc) {
58
+ context.report({
59
+ node,
60
+ messageId: "missingDefaultDoc",
61
+ data: {
62
+ propertyName:
63
+ node.key.type === AST_NODE_TYPES.Identifier
64
+ ? node.key.name
65
+ : "unknown",
66
+ },
67
+ });
68
+ }
69
+ },
70
+ };
71
+ },
72
+ });