eslint-cdk-plugin 2.2.0 → 3.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-cdk-plugin",
3
- "version": "2.2.0",
3
+ "version": "3.0.2",
4
4
  "description": "eslint plugin for AWS CDK projects",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -30,23 +30,24 @@
30
30
  "docs:preview": "cd ./docs && pnpm install && pnpm run preview"
31
31
  },
32
32
  "devDependencies": {
33
- "@eslint/js": "^9.22.0",
34
- "@types/node": "^22.13.10",
35
- "@typescript-eslint/rule-tester": "^8.26.0",
33
+ "@eslint/js": "^9.26.0",
34
+ "@types/node": "^22.15.0",
35
+ "@typescript-eslint/rule-tester": "^8.32.1",
36
36
  "eslint": "9.22.0",
37
37
  "eslint-plugin-import": "^2.31.0",
38
- "pkgroll": "^2.11.2",
38
+ "pkgroll": "^2.12.2",
39
39
  "standard-version": "^9.5.0",
40
- "typescript": "^5.8.2",
41
- "typescript-eslint": "^8.26.0",
42
- "vitest": "^3.0.8"
40
+ "typescript": "^5.8.3",
41
+ "typescript-eslint": "^8.32.1",
42
+ "vitepress-plugin-llms": "^1.1.4",
43
+ "vitest": "^3.1.3"
43
44
  },
44
45
  "dependencies": {
45
- "@typescript-eslint/parser": "^8.26.0",
46
- "@typescript-eslint/utils": "^8.26.0"
46
+ "@typescript-eslint/parser": "^8.32.1",
47
+ "@typescript-eslint/utils": "^8.32.1"
47
48
  },
48
49
  "volta": {
49
- "node": "22.14.0"
50
+ "node": "22.15.0"
50
51
  },
51
52
  "files": [
52
53
  "dist",
package/src/index.ts CHANGED
@@ -3,13 +3,13 @@ import tsParser from "@typescript-eslint/parser";
3
3
  import { name, version } from "../package.json";
4
4
 
5
5
  import { constructConstructorProperty } from "./rules/construct-constructor-property";
6
- import { noClassInInterface } from "./rules/no-class-in-interface";
6
+ import { noConstructInInterface } from "./rules/no-construct-in-interface";
7
+ import { noConstructInPublicPropertyOfConstruct } from "./rules/no-construct-in-public-property-of-construct";
7
8
  import { noConstructStackSuffix } from "./rules/no-construct-stack-suffix";
8
9
  import { noImportPrivate } from "./rules/no-import-private";
9
- import { noMutablePropsInterface } from "./rules/no-mutable-props-interface";
10
- import { noMutablePublicFields } from "./rules/no-mutable-public-fields";
10
+ import { noMutablePropertyOfPropsInterface } from "./rules/no-mutable-property-of-props-interface";
11
+ import { noMutablePublicPropertyOfConstruct } from "./rules/no-mutable-public-property-of-construct";
11
12
  import { noParentNameConstructIdMatch } from "./rules/no-parent-name-construct-id-match";
12
- import { noPublicClassFields } from "./rules/no-public-class-fields";
13
13
  import { noVariableConstructId } from "./rules/no-variable-construct-id";
14
14
  import { pascalCaseConstructId } from "./rules/pascal-case-construct-id";
15
15
  import { propsNameConvention } from "./rules/props-name-convention";
@@ -18,20 +18,21 @@ import { requirePassingThis } from "./rules/require-passing-this";
18
18
  import { requirePropsDefaultDoc } from "./rules/require-props-default-doc";
19
19
 
20
20
  const rules = {
21
- "no-class-in-interface": noClassInInterface,
21
+ "construct-constructor-property": constructConstructorProperty,
22
+ "no-construct-in-interface": noConstructInInterface,
23
+ "no-construct-in-public-property-of-construct":
24
+ noConstructInPublicPropertyOfConstruct,
22
25
  "no-construct-stack-suffix": noConstructStackSuffix,
26
+ "no-import-private": noImportPrivate,
27
+ "no-mutable-property-of-props-interface": noMutablePropertyOfPropsInterface,
28
+ "no-mutable-public-property-of-construct": noMutablePublicPropertyOfConstruct,
23
29
  "no-parent-name-construct-id-match": noParentNameConstructIdMatch,
24
- "no-public-class-fields": noPublicClassFields,
25
- "pascal-case-construct-id": pascalCaseConstructId,
26
- "require-passing-this": requirePassingThis,
27
30
  "no-variable-construct-id": noVariableConstructId,
28
- "no-mutable-public-fields": noMutablePublicFields,
29
- "no-mutable-props-interface": noMutablePropsInterface,
30
- "construct-constructor-property": constructConstructorProperty,
31
+ "pascal-case-construct-id": pascalCaseConstructId,
32
+ "props-name-convention": propsNameConvention,
31
33
  "require-jsdoc": requireJSDoc,
34
+ "require-passing-this": requirePassingThis,
32
35
  "require-props-default-doc": requirePropsDefaultDoc,
33
- "props-name-convention": propsNameConvention,
34
- "no-import-private": noImportPrivate,
35
36
  };
36
37
 
37
38
  const cdkPlugin = {
@@ -55,39 +56,39 @@ const createFlatConfig = (rules: Record<string, unknown>) => {
55
56
  };
56
57
 
57
58
  const recommended = createFlatConfig({
58
- "cdk/no-class-in-interface": "error",
59
+ "cdk/construct-constructor-property": "error",
60
+ "cdk/no-construct-in-interface": "error",
61
+ "cdk/no-construct-in-public-property-of-construct": "error",
59
62
  "cdk/no-construct-stack-suffix": "error",
63
+ "cdk/no-mutable-property-of-props-interface": "warn",
64
+ "cdk/no-mutable-public-property-of-construct": "warn",
60
65
  "cdk/no-parent-name-construct-id-match": [
61
66
  "error",
62
67
  { disallowContainingParentName: false },
63
68
  ],
64
- "cdk/no-public-class-fields": "error",
69
+ "cdk/no-variable-construct-id": "error",
65
70
  "cdk/pascal-case-construct-id": "error",
66
71
  "cdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
67
- "cdk/no-variable-construct-id": "error",
68
- "cdk/no-mutable-public-fields": "warn",
69
- "cdk/no-mutable-props-interface": "warn",
70
- "cdk/construct-constructor-property": "error",
71
72
  });
72
73
 
73
74
  const strict = createFlatConfig({
74
- "cdk/no-class-in-interface": "error",
75
+ "cdk/construct-constructor-property": "error",
76
+ "cdk/no-construct-in-interface": "error",
77
+ "cdk/no-construct-in-public-property-of-construct": "error",
75
78
  "cdk/no-construct-stack-suffix": "error",
79
+ "cdk/no-import-private": "error",
80
+ "cdk/no-mutable-property-of-props-interface": "error",
81
+ "cdk/no-mutable-public-property-of-construct": "error",
76
82
  "cdk/no-parent-name-construct-id-match": [
77
83
  "error",
78
84
  { disallowContainingParentName: true },
79
85
  ],
80
- "cdk/no-public-class-fields": "error",
81
- "cdk/pascal-case-construct-id": "error",
82
- "cdk/require-passing-this": "error",
83
86
  "cdk/no-variable-construct-id": "error",
84
- "cdk/no-mutable-public-fields": "error",
85
- "cdk/no-mutable-props-interface": "error",
86
- "cdk/construct-constructor-property": "error",
87
+ "cdk/pascal-case-construct-id": "error",
88
+ "cdk/props-name-convention": "error",
87
89
  "cdk/require-jsdoc": "error",
90
+ "cdk/require-passing-this": "error",
88
91
  "cdk/require-props-default-doc": "error",
89
- "cdk/props-name-convention": "error",
90
- "cdk/no-import-private": "error",
91
92
  });
92
93
 
93
94
  const configs = {
@@ -67,7 +67,7 @@ const validateConstructorProperty = (
67
67
  // NOTE: Check if the constructor has at least 2 parameters
68
68
  if (params.length < 2) {
69
69
  context.report({
70
- node: constructor,
70
+ node: constructor.value,
71
71
  messageId: "invalidConstructorProperty",
72
72
  });
73
73
  return;
@@ -80,7 +80,7 @@ const validateConstructorProperty = (
80
80
  firstParam.name !== "scope"
81
81
  ) {
82
82
  context.report({
83
- node: constructor,
83
+ node: firstParam,
84
84
  messageId: "invalidConstructorProperty",
85
85
  });
86
86
  return;
@@ -93,7 +93,7 @@ const validateConstructorProperty = (
93
93
  secondParam.name !== "id"
94
94
  ) {
95
95
  context.report({
96
- node: constructor,
96
+ node: secondParam,
97
97
  messageId: "invalidConstructorProperty",
98
98
  });
99
99
  return;
@@ -109,7 +109,7 @@ const validateConstructorProperty = (
109
109
  thirdParam.name !== "props"
110
110
  ) {
111
111
  context.report({
112
- node: constructor,
112
+ node: thirdParam,
113
113
  messageId: "invalidConstructorProperty",
114
114
  });
115
115
  return;
@@ -1,22 +1,23 @@
1
1
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
2
 
3
3
  import { SYMBOL_FLAGS } from "../constants/tsInternalFlags";
4
+ import { isConstructOrStackType } from "../utils/typeCheck";
4
5
 
5
6
  /**
6
- * Enforces the use of interface types instead of class in interface properties
7
+ * Enforces the use of interface types instead of CDK Construct types in interface properties
7
8
  * @param context - The rule context provided by ESLint
8
9
  * @returns An object containing the AST visitor functions
9
10
  * @see {@link https://eslint-cdk-plugin.dev/rules/no-class-in-interface} - Documentation
10
11
  */
11
- export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
12
+ export const noConstructInInterface = ESLintUtils.RuleCreator.withoutDocs({
12
13
  meta: {
13
14
  type: "problem",
14
15
  docs: {
15
- description: "Disallow class types in interface properties",
16
+ description: "Disallow CDK Construct types in interface properties",
16
17
  },
17
18
  messages: {
18
- noClassInInterfaceProps:
19
- "Interface property '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead.",
19
+ invalidInterfaceProperty:
20
+ "Interface property '{{ propertyName }}' should not use CDK Construct type '{{ typeName }}'. Consider using an interface or type alias instead.",
20
21
  },
21
22
  schema: [],
22
23
  },
@@ -35,7 +36,7 @@ export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
35
36
  }
36
37
 
37
38
  const type = parserServices.getTypeAtLocation(property);
38
- if (!type.symbol) continue;
39
+ if (!isConstructOrStackType(type)) continue;
39
40
 
40
41
  // NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
41
42
  // Therefore, the type information structures do not match.
@@ -45,7 +46,7 @@ export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
45
46
 
46
47
  context.report({
47
48
  node: property,
48
- messageId: "noClassInInterfaceProps",
49
+ messageId: "invalidInterfaceProperty",
49
50
  data: {
50
51
  propertyName: property.key.name,
51
52
  typeName: type.symbol.name,
@@ -0,0 +1,155 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ESLintUtils,
4
+ ParserServicesWithTypeInformation,
5
+ TSESLint,
6
+ TSESTree,
7
+ } from "@typescript-eslint/utils";
8
+
9
+ import { SYMBOL_FLAGS } from "../constants/tsInternalFlags";
10
+ import { isConstructOrStackType } from "../utils/typeCheck";
11
+
12
+ type Context = TSESLint.RuleContext<"invalidPublicPropertyOfConstruct", []>;
13
+
14
+ /**
15
+ * Disallow Construct types in public property of Construct
16
+ * @param context - The rule context provided by ESLint
17
+ * @returns An object containing the AST visitor functions
18
+ * @see {@link https://eslint-cdk-plugin.dev/rules/no-construct-in-public-property-of-construct} - Documentation
19
+ */
20
+ export const noConstructInPublicPropertyOfConstruct =
21
+ ESLintUtils.RuleCreator.withoutDocs({
22
+ meta: {
23
+ type: "problem",
24
+ docs: {
25
+ description: "Disallow Construct types in public property of Construct",
26
+ },
27
+ messages: {
28
+ invalidPublicPropertyOfConstruct:
29
+ "Public property '{{ propertyName }}' of Construct should not use Construct type '{{ typeName }}'. Consider using an interface or type alias instead.",
30
+ },
31
+ schema: [],
32
+ },
33
+ defaultOptions: [],
34
+ create(context) {
35
+ const parserServices = ESLintUtils.getParserServices(context);
36
+ return {
37
+ ClassDeclaration(node) {
38
+ const type = parserServices.getTypeAtLocation(node);
39
+ if (!isConstructOrStackType(type)) return;
40
+
41
+ // NOTE: Check class members
42
+ validatePublicPropertyOfConstruct(node, context, parserServices);
43
+
44
+ // NOTE: Check constructor parameter properties
45
+ const constructor = node.body.body.find(
46
+ (member): member is TSESTree.MethodDefinition =>
47
+ member.type === AST_NODE_TYPES.MethodDefinition &&
48
+ member.kind === "constructor"
49
+ );
50
+ if (
51
+ !constructor ||
52
+ constructor.value.type !== AST_NODE_TYPES.FunctionExpression
53
+ ) {
54
+ return;
55
+ }
56
+
57
+ validateConstructorParameterProperty(
58
+ constructor,
59
+ context,
60
+ parserServices
61
+ );
62
+ },
63
+ };
64
+ },
65
+ });
66
+
67
+ /**
68
+ * check the public property of Construct
69
+ * - if it is a Construct type, report an error
70
+ */
71
+ const validatePublicPropertyOfConstruct = (
72
+ node: TSESTree.ClassDeclaration,
73
+ context: Context,
74
+ parserServices: ParserServicesWithTypeInformation
75
+ ) => {
76
+ for (const property of node.body.body) {
77
+ if (
78
+ property.type !== AST_NODE_TYPES.PropertyDefinition ||
79
+ property.key.type !== AST_NODE_TYPES.Identifier
80
+ ) {
81
+ continue;
82
+ }
83
+
84
+ // NOTE: Skip private and protected fields
85
+ if (["private", "protected"].includes(property.accessibility ?? "")) {
86
+ continue;
87
+ }
88
+
89
+ // NOTE: Skip fields without type annotation
90
+ if (!property.typeAnnotation) continue;
91
+
92
+ const type = parserServices.getTypeAtLocation(property);
93
+ if (!isConstructOrStackType(type)) continue;
94
+
95
+ // NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
96
+ // Therefore, the type information structures do not match.
97
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
98
+ const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
99
+ if (!isClass) continue;
100
+
101
+ context.report({
102
+ node: property,
103
+ messageId: "invalidPublicPropertyOfConstruct",
104
+ data: {
105
+ propertyName: property.key.name,
106
+ typeName: type.symbol.name,
107
+ },
108
+ });
109
+ }
110
+ };
111
+
112
+ /**
113
+ * check the constructor parameter property
114
+ * - if it is a Construct type, report an error
115
+ */
116
+ const validateConstructorParameterProperty = (
117
+ constructor: TSESTree.MethodDefinition,
118
+ context: Context,
119
+ parserServices: ParserServicesWithTypeInformation
120
+ ) => {
121
+ for (const param of constructor.value.params) {
122
+ if (
123
+ param.type !== AST_NODE_TYPES.TSParameterProperty ||
124
+ param.parameter.type !== AST_NODE_TYPES.Identifier
125
+ ) {
126
+ continue;
127
+ }
128
+
129
+ // NOTE: Skip private and protected parameters
130
+ if (["private", "protected"].includes(param.accessibility ?? "")) {
131
+ continue;
132
+ }
133
+
134
+ // NOTE: Skip parameters without type annotation
135
+ if (!param.parameter.typeAnnotation) continue;
136
+
137
+ const type = parserServices.getTypeAtLocation(param);
138
+ if (!isConstructOrStackType(type)) continue;
139
+
140
+ // NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
141
+ // Therefore, the type information structures do not match.
142
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
143
+ const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
144
+ if (!isClass) continue;
145
+
146
+ context.report({
147
+ node: param,
148
+ messageId: "invalidPublicPropertyOfConstruct",
149
+ data: {
150
+ propertyName: param.parameter.name,
151
+ typeName: type.symbol.name,
152
+ },
153
+ });
154
+ }
155
+ };
@@ -22,7 +22,7 @@ type Options = [
22
22
  }
23
23
  ];
24
24
 
25
- type Context = TSESLint.RuleContext<"noConstructStackSuffix", Options>;
25
+ type Context = TSESLint.RuleContext<"invalidConstructId", Options>;
26
26
 
27
27
  /**
28
28
  * Enforces that Construct IDs do not end with 'Construct' or 'Stack' suffix
@@ -38,7 +38,7 @@ export const noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
38
38
  "Effort to avoid using 'Construct' and 'Stack' suffix in construct id.",
39
39
  },
40
40
  messages: {
41
- noConstructStackSuffix:
41
+ invalidConstructId:
42
42
  "{{ classType }} ID '{{ id }}' should not include {{ suffix }} suffix.",
43
43
  },
44
44
  schema: [
@@ -110,8 +110,8 @@ const validateConstructId = (
110
110
  formattedConstructId.endsWith(SUFFIX_TYPE.CONSTRUCT)
111
111
  ) {
112
112
  context.report({
113
- node,
114
- messageId: "noConstructStackSuffix",
113
+ node: secondArg,
114
+ messageId: "invalidConstructId",
115
115
  data: {
116
116
  classType: "Construct",
117
117
  id: secondArg.value,
@@ -123,8 +123,8 @@ const validateConstructId = (
123
123
  formattedConstructId.endsWith(SUFFIX_TYPE.STACK)
124
124
  ) {
125
125
  context.report({
126
- node,
127
- messageId: "noConstructStackSuffix",
126
+ node: secondArg,
127
+ messageId: "invalidConstructId",
128
128
  data: {
129
129
  classType: "Stack",
130
130
  id: secondArg.value,
@@ -16,7 +16,7 @@ export const noImportPrivate: Rule.RuleModule = {
16
16
  "Cannot import modules from private dir at different levels of the hierarchy.",
17
17
  },
18
18
  messages: {
19
- noImportPrivate:
19
+ invalidImportPath:
20
20
  "Cannot import modules from private dir at different levels of the hierarchy.",
21
21
  },
22
22
  schema: [],
@@ -43,7 +43,7 @@ export const noImportPrivate: Rule.RuleModule = {
43
43
  (segment, index) => segment !== importDirSegments[index]
44
44
  )
45
45
  ) {
46
- context.report({ node, messageId: "noImportPrivate" });
46
+ context.report({ node, messageId: "invalidImportPath" });
47
47
  }
48
48
  },
49
49
  };
@@ -0,0 +1,60 @@
1
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ /**
4
+ * Disallow mutable properties of Construct Props (interface)
5
+ * @param context - The rule context provided by ESLint
6
+ * @returns An object containing the AST visitor functions
7
+ * @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-property-of-props-interface} - Documentation
8
+ */
9
+ export const noMutablePropertyOfPropsInterface =
10
+ ESLintUtils.RuleCreator.withoutDocs({
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description:
15
+ "Disallow mutable properties of Construct Props (interface)",
16
+ },
17
+ fixable: "code",
18
+ messages: {
19
+ invalidPropertyOfPropsInterface:
20
+ "Property '{{ propertyName }}' of Construct Props should be readonly.",
21
+ },
22
+ schema: [],
23
+ },
24
+ defaultOptions: [],
25
+ create(context) {
26
+ return {
27
+ TSInterfaceDeclaration(node) {
28
+ const sourceCode = context.sourceCode;
29
+
30
+ // NOTE: Interface name check for "Props"
31
+ if (!node.id.name.endsWith("Props")) return;
32
+
33
+ for (const property of node.body.body) {
34
+ // NOTE: check property signature
35
+ if (
36
+ property.type !== AST_NODE_TYPES.TSPropertySignature ||
37
+ property.key.type !== AST_NODE_TYPES.Identifier
38
+ ) {
39
+ continue;
40
+ }
41
+
42
+ // NOTE: Skip if already readonly
43
+ if (property.readonly) continue;
44
+
45
+ context.report({
46
+ node: property,
47
+ messageId: "invalidPropertyOfPropsInterface",
48
+ data: {
49
+ propertyName: property.key.name,
50
+ },
51
+ fix: (fixer) => {
52
+ const propertyText = sourceCode.getText(property);
53
+ return fixer.replaceText(property, `readonly ${propertyText}`);
54
+ },
55
+ });
56
+ }
57
+ },
58
+ };
59
+ },
60
+ });
@@ -0,0 +1,76 @@
1
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ import { isConstructOrStackType } from "../utils/typeCheck";
4
+
5
+ /**
6
+ * Disallow mutable public properties of Construct
7
+ * @param context - The rule context provided by ESLint
8
+ * @returns An object containing the AST visitor functions
9
+ * @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-public-property-of-construct} - Documentation
10
+ */
11
+ export const noMutablePublicPropertyOfConstruct =
12
+ ESLintUtils.RuleCreator.withoutDocs({
13
+ meta: {
14
+ type: "problem",
15
+ docs: {
16
+ description: "Disallow mutable public properties of Construct",
17
+ },
18
+ fixable: "code",
19
+ messages: {
20
+ invalidPublicPropertyOfConstruct:
21
+ "Public property '{{ propertyName }}' should be readonly. Consider adding the 'readonly' modifier.",
22
+ },
23
+ schema: [],
24
+ },
25
+ defaultOptions: [],
26
+ create(context) {
27
+ const parserServices = ESLintUtils.getParserServices(context);
28
+
29
+ return {
30
+ ClassDeclaration(node) {
31
+ const sourceCode = context.sourceCode;
32
+ const type = parserServices.getTypeAtLocation(node);
33
+ if (!isConstructOrStackType(type)) return;
34
+
35
+ for (const member of node.body.body) {
36
+ // NOTE: check property definition
37
+ if (
38
+ member.type !== AST_NODE_TYPES.PropertyDefinition ||
39
+ member.key.type !== AST_NODE_TYPES.Identifier
40
+ ) {
41
+ continue;
42
+ }
43
+
44
+ // NOTE: Skip private and protected fields
45
+ if (["private", "protected"].includes(member.accessibility ?? "")) {
46
+ continue;
47
+ }
48
+
49
+ // NOTE: Skip if readonly is present
50
+ if (member.readonly) continue;
51
+
52
+ context.report({
53
+ node: member,
54
+ messageId: "invalidPublicPropertyOfConstruct",
55
+ data: {
56
+ propertyName: member.key.name,
57
+ },
58
+ fix: (fixer) => {
59
+ const accessibility = member.accessibility ? "public " : "";
60
+ const paramText = sourceCode.getText(member);
61
+ const [key, value] = paramText.split(":");
62
+ const replacedKey = key.startsWith("public ")
63
+ ? key.replace("public ", "")
64
+ : key;
65
+
66
+ return fixer.replaceText(
67
+ member,
68
+ `${accessibility}readonly ${replacedKey}:${value}`
69
+ );
70
+ },
71
+ });
72
+ }
73
+ },
74
+ };
75
+ },
76
+ });