@cdktn/provider-generator 0.24.0-pre.5 → 0.24.0-pre.50

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 (67) hide show
  1. package/.spec.swcrc +22 -0
  2. package/LICENSE +355 -0
  3. package/README.md +1 -1
  4. package/build/__tests__/edge-provider-schema/cli.js +8 -3
  5. package/build/get/__tests__/generator/import-style.test.d.ts +2 -0
  6. package/build/get/__tests__/generator/import-style.test.js +101 -0
  7. package/build/get/__tests__/generator/module-generator.test.js +12 -12
  8. package/build/get/constructs-maker.d.ts +5 -1
  9. package/build/get/constructs-maker.js +14 -6
  10. package/build/get/generator/emitter/struct-emitter.d.ts +2 -1
  11. package/build/get/generator/emitter/struct-emitter.js +14 -11
  12. package/build/get/generator/module-generator.js +3 -3
  13. package/build/get/generator/provider-generator.d.ts +9 -1
  14. package/build/get/generator/provider-generator.js +9 -6
  15. package/jest.config.js +16 -9
  16. package/package.json +24 -23
  17. package/package.sh +1 -1
  18. package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +8 -8
  19. package/src/__tests__/__snapshots__/provider.test.ts.snap +2951 -2951
  20. package/src/__tests__/edge-provider-schema/builder.ts +185 -0
  21. package/src/__tests__/edge-provider-schema/cli.ts +51 -0
  22. package/src/__tests__/edge-provider-schema/index.ts +161 -0
  23. package/src/__tests__/edge-provider-schema.test.ts +24 -0
  24. package/src/__tests__/provider.test.ts +180 -0
  25. package/src/get/__tests__/constructs-maker.test.ts +118 -0
  26. package/src/get/__tests__/generator/__snapshots__/complex-computed-types.test.ts.snap +5 -5
  27. package/src/get/__tests__/generator/__snapshots__/export-sharding.test.ts.snap +3310 -3310
  28. package/src/get/__tests__/generator/__snapshots__/module-generator.test.ts.snap +355 -355
  29. package/src/get/__tests__/generator/__snapshots__/nested-types.test.ts.snap +8 -8
  30. package/src/get/__tests__/generator/__snapshots__/provider.test.ts.snap +8 -8
  31. package/src/get/__tests__/generator/__snapshots__/resource-types.test.ts.snap +126 -126
  32. package/src/get/__tests__/generator/__snapshots__/skipped-attributes.test.ts.snap +17 -17
  33. package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +65 -65
  34. package/src/get/__tests__/generator/complex-computed-types.test.ts +28 -0
  35. package/src/get/__tests__/generator/deep-nested-attributes.test.ts +56 -0
  36. package/src/get/__tests__/generator/description-escaping.test.ts +84 -0
  37. package/src/get/__tests__/generator/empty-provider-resources.test.ts +26 -0
  38. package/src/get/__tests__/generator/export-sharding.test.ts +169 -0
  39. package/src/get/__tests__/generator/import-style.test.ts +129 -0
  40. package/src/get/__tests__/generator/module-generator.test.ts +528 -0
  41. package/src/get/__tests__/generator/nested-types.test.ts +54 -0
  42. package/src/get/__tests__/generator/provider.test.ts +51 -0
  43. package/src/get/__tests__/generator/resource-types.test.ts +118 -0
  44. package/src/get/__tests__/generator/skipped-attributes.test.ts +72 -0
  45. package/src/get/__tests__/generator/types.test.ts +611 -0
  46. package/src/get/__tests__/generator/versions-file.test.ts +72 -0
  47. package/src/get/__tests__/util.ts +75 -0
  48. package/src/get/constructs-maker.ts +822 -0
  49. package/src/get/generator/custom-defaults.ts +493 -0
  50. package/src/get/generator/emitter/attributes-emitter.ts +225 -0
  51. package/src/get/generator/emitter/index.ts +5 -0
  52. package/src/get/generator/emitter/resource-emitter.ts +226 -0
  53. package/src/get/generator/emitter/struct-emitter.ts +683 -0
  54. package/src/get/generator/loop-detection.ts +81 -0
  55. package/src/get/generator/models/attribute-model.ts +216 -0
  56. package/src/get/generator/models/attribute-type-model.ts +448 -0
  57. package/src/get/generator/models/index.ts +7 -0
  58. package/src/get/generator/models/resource-model.ts +161 -0
  59. package/src/get/generator/models/scope.ts +54 -0
  60. package/src/get/generator/models/struct.ts +116 -0
  61. package/src/get/generator/module-generator.ts +234 -0
  62. package/src/get/generator/provider-generator.ts +355 -0
  63. package/src/get/generator/resource-parser.ts +762 -0
  64. package/src/get/generator/sanitized-comments.ts +49 -0
  65. package/src/get/generator/skipped-attributes.ts +27 -0
  66. package/src/index.ts +40 -0
  67. package/src/util.ts +26 -0
@@ -0,0 +1,683 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { CodeMaker } from "codemaker";
4
+ import { ResourceModel, Struct, ConfigStruct } from "../models";
5
+ import { AttributesEmitter } from "./attributes-emitter";
6
+ import { downcaseFirst } from "../../../util";
7
+ import * as path from "path";
8
+ import { STRUCT_SHARDING_THRESHOLD } from "../models/resource-model";
9
+ import { AttributeModel } from "../models/attribute-model";
10
+ import { sanitizedComment } from "../sanitized-comments";
11
+
12
+ export class StructEmitter {
13
+ attributesEmitter: AttributesEmitter;
14
+ private readonly importExtension: string;
15
+
16
+ constructor(
17
+ private readonly code: CodeMaker,
18
+ importExtension: string,
19
+ ) {
20
+ this.attributesEmitter = new AttributesEmitter(this.code);
21
+ this.importExtension = importExtension;
22
+ }
23
+
24
+ public emit(resource: ResourceModel) {
25
+ if (resource.structsRequireSharding) {
26
+ this.emitNamespacedStructs(resource);
27
+ } else {
28
+ this.emitStructs(resource);
29
+ }
30
+ }
31
+
32
+ // Due to https://github.com/hashicorp/terraform-plugin-sdk/commit/2387eb85e32c064b4a62718c9f5c80bf00dc7fb9 all
33
+ // resources from providers using the old SDK have the id field by default
34
+ // We have no way to distinguish them through the provider schema, so a word of warning for our users
35
+ private warnAboutIdField(att: AttributeModel) {
36
+ if (att.name === "id") {
37
+ this.code.line(`*`);
38
+ this.code.line(
39
+ `* Please be aware that the id field is automatically added to all resources in Terraform providers using a Terraform provider SDK version below 2.`,
40
+ );
41
+ this.code.line(
42
+ `* If you experience problems setting this value it might not be settable. Please take a look at the provider documentation to ensure it should be settable.`,
43
+ );
44
+ }
45
+ }
46
+
47
+ public emitInterface(
48
+ resource: ResourceModel,
49
+ struct: Struct,
50
+ name = struct.name,
51
+ ) {
52
+ if (resource.isProvider) {
53
+ this.code.openBlock(`export interface ${name}`);
54
+ } else {
55
+ this.code.openBlock(`export interface ${name}${struct.extends}`);
56
+ }
57
+
58
+ for (const att of struct.assignableAttributes) {
59
+ const comment = sanitizedComment(this.code);
60
+ if (att.description) {
61
+ comment.line(att.description);
62
+ comment.line(``);
63
+ comment.line(
64
+ `Docs at Terraform Registry: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
65
+ );
66
+ this.warnAboutIdField(att);
67
+ comment.end();
68
+ } else {
69
+ comment.line(
70
+ `Docs at Terraform Registry: {@link ${resource.linkToDocs}#${att.terraformName} ${resource.className}#${att.terraformName}}`,
71
+ );
72
+ this.warnAboutIdField(att);
73
+ comment.end();
74
+ }
75
+
76
+ this.code.line(`readonly ${att.typeDefinition};`);
77
+ }
78
+ this.code.closeBlock();
79
+
80
+ if (!(struct instanceof ConfigStruct)) {
81
+ this.emitToTerraformFunction(struct);
82
+ this.emitToHclTerraformFunction(struct);
83
+ }
84
+ }
85
+
86
+ private emitStructs(resource: ResourceModel) {
87
+ resource.structs.forEach((struct) => {
88
+ if (struct instanceof ConfigStruct) {
89
+ this.emitInterface(resource, struct);
90
+ } else {
91
+ // We use the interface here for the configuration / inputs of a resource / nested block
92
+ this.emitInterface(resource, struct);
93
+ // And we use the class for the attributes / outputs of a resource / nested block
94
+ if (!struct.isProvider) {
95
+ this.emitClass(struct);
96
+ }
97
+ }
98
+ });
99
+ }
100
+
101
+ private emitNamespacedStructs(resource: ResourceModel) {
102
+ // iterate over all structs in batches of 400 to avoid too many exports (> 1200)
103
+ const structImports: Record<string, string> = {};
104
+
105
+ // drop configStruct from resource.structs to avoid double import
106
+ const structsWithoutConfigStruct = resource.structs.slice(1);
107
+
108
+ const structSplits: Struct[][] = [[]];
109
+ const splitCounts: number[] = [0];
110
+ structsWithoutConfigStruct.forEach((struct) => {
111
+ if (
112
+ splitCounts[splitCounts.length - 1] + struct.exportCount <=
113
+ STRUCT_SHARDING_THRESHOLD
114
+ ) {
115
+ structSplits[structSplits.length - 1].push(struct);
116
+ splitCounts[splitCounts.length - 1] += struct.exportCount;
117
+ } else {
118
+ structSplits.push([struct]);
119
+ splitCounts.push(struct.exportCount);
120
+ }
121
+ });
122
+
123
+ // fill the struct imports mapping before code generation so that imports can
124
+ // point to not yet generated struct files
125
+ for (let i = 0; i < structSplits.length; i++) {
126
+ const structFilename = `structs${i * STRUCT_SHARDING_THRESHOLD}.ts`;
127
+ const structs = structSplits[i];
128
+
129
+ // associate current structs batch with the file it will be written to
130
+ // to find it in subsequent files for importing
131
+ structs.forEach(
132
+ (struct) => (structImports[struct.name] = structFilename),
133
+ );
134
+ }
135
+
136
+ const structPaths = [];
137
+ for (let i = 0; i < structSplits.length; i++) {
138
+ const structsToImport: Record<string, string[]> = {};
139
+ const structs = structSplits[i];
140
+ const structFilename = `structs${i * STRUCT_SHARDING_THRESHOLD}.ts`;
141
+ structPaths.push(structFilename);
142
+
143
+ // find all structs that need to be imported in this file
144
+ structs.forEach((struct) => {
145
+ struct.attributes.forEach((att) => {
146
+ const structTypeName = att.type.struct?.name;
147
+ const fileToImport = structImports[structTypeName ?? ""];
148
+
149
+ if (fileToImport && fileToImport !== structFilename) {
150
+ const attTypeStruct = att.type.struct;
151
+ if (!attTypeStruct)
152
+ throw new Error(`${structTypeName} is not a struct`);
153
+
154
+ structsToImport[fileToImport] ??= [];
155
+
156
+ const attReferences = att.getReferencedTypes(
157
+ struct instanceof ConfigStruct,
158
+ );
159
+ if (attReferences) {
160
+ structsToImport[fileToImport].push(...attReferences);
161
+ }
162
+ }
163
+ });
164
+ });
165
+
166
+ const namespacedFilePath = path.join(
167
+ resource.structsFolderPath,
168
+ structFilename,
169
+ );
170
+
171
+ this.code.openFile(namespacedFilePath);
172
+ // the structs only makes use of cdktn not constructs
173
+ this.code.line(`import * as cdktn from 'cdktn';`);
174
+ Object.entries(structsToImport).forEach(([fileToImport, structs]) => {
175
+ const target = path.basename(fileToImport, ".ts");
176
+ this.code.line(
177
+ `import { ${[...new Set(structs)].join(",\n")} } from './${target}${this.importExtension}';`,
178
+ );
179
+ });
180
+
181
+ structs.forEach((struct) => {
182
+ if (struct instanceof ConfigStruct) {
183
+ this.emitInterface(resource, struct);
184
+ } else {
185
+ // We use the interface here for the configuration / inputs of a resource / nested block
186
+ this.emitInterface(resource, struct);
187
+ // And we use the class for the attributes / outputs of a resource / nested block
188
+ if (!struct.isProvider) {
189
+ this.emitClass(struct);
190
+ }
191
+ }
192
+ });
193
+ this.code.closeFile(namespacedFilePath);
194
+ }
195
+
196
+ // emit the index file that exports all the struct files we've just generated
197
+ const indexFilePath = path.join(resource.structsFolderPath, "index.ts");
198
+
199
+ this.code.openFile(indexFilePath);
200
+ structPaths.forEach((structPath) => {
201
+ const target = path.basename(structPath, ".ts");
202
+ this.code.line(`export * from './${target}${this.importExtension}';`);
203
+ });
204
+ this.code.closeFile(indexFilePath);
205
+ }
206
+
207
+ private emitClass(struct: Struct) {
208
+ this.code.openBlock(
209
+ `export class ${struct.outputReferenceName} extends cdktn.ComplexObject`,
210
+ );
211
+
212
+ this.code.line("private isEmptyObject = false;");
213
+ if (!struct.isClass) {
214
+ this.code.line("private resolvableValue?: cdktn.IResolvable;");
215
+ }
216
+ this.code.line();
217
+
218
+ const comment = sanitizedComment(this.code);
219
+ comment.line(`@param terraformResource The parent resource`);
220
+ comment.line(
221
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
222
+ );
223
+ if (struct.isSingleItem) {
224
+ comment.end();
225
+ this.code.openBlock(
226
+ `public constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string)`,
227
+ );
228
+ this.code.line(`super(terraformResource, terraformAttribute, false, 0);`);
229
+ this.code.closeBlock();
230
+ } else if (
231
+ struct.nestingMode === "single" ||
232
+ struct.nestingMode === "object"
233
+ ) {
234
+ comment.end();
235
+ this.code.openBlock(
236
+ `public constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string)`,
237
+ );
238
+ this.code.line(`super(terraformResource, terraformAttribute, false);`);
239
+ this.code.closeBlock();
240
+ } else if (struct.nestingMode.startsWith("map")) {
241
+ this.code.line(
242
+ `* @param complexObjectKey the key of this item in the map`,
243
+ );
244
+ comment.end();
245
+ this.code.openBlock(
246
+ `public constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string, complexObjectKey: string)`,
247
+ );
248
+ this.code.line(
249
+ `super(terraformResource, terraformAttribute, false, complexObjectKey);`,
250
+ );
251
+ this.code.closeBlock();
252
+ } else {
253
+ comment.line(
254
+ `@param complexObjectIndex the index of this item in the list`,
255
+ );
256
+ comment.line(
257
+ `@param complexObjectIsFromSet whether the list is wrapping a set (will add tolist() to be able to access an item via an index)`,
258
+ );
259
+ comment.end();
260
+ this.code.openBlock(
261
+ `public constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string, complexObjectIndex: number, complexObjectIsFromSet: boolean)`,
262
+ );
263
+ this.code.line(
264
+ `super(terraformResource, terraformAttribute, complexObjectIsFromSet, complexObjectIndex);`,
265
+ );
266
+ this.code.closeBlock();
267
+ }
268
+
269
+ this.code.line();
270
+ this.emitInternalValueGetter(struct);
271
+ this.code.line();
272
+ this.emitInternalValueSetter(struct);
273
+
274
+ for (const att of struct.attributes) {
275
+ this.attributesEmitter.emit(
276
+ att,
277
+ this.attributesEmitter.needsResetEscape(att, struct.attributes),
278
+ this.attributesEmitter.needsInputEscape(att, struct.attributes),
279
+ );
280
+ }
281
+
282
+ this.code.closeBlock();
283
+
284
+ if (
285
+ !struct.isSingleItem &&
286
+ (struct.nestingMode === "list" || struct.nestingMode === "set")
287
+ ) {
288
+ this.emitComplexListClass(struct);
289
+ } else if (struct.nestingMode === "map") {
290
+ this.emitComplexMapClass(struct);
291
+ } else if (struct.nestingMode === "maplist") {
292
+ this.emitComplexMapListClasses(struct);
293
+ } else if (struct.nestingMode === "mapset") {
294
+ this.emitComplexMapListClasses(struct);
295
+ } else if (struct.nestingMode === "listmap") {
296
+ this.emitComplexListMapClasses(struct, false);
297
+ } else if (struct.nestingMode === "setmap") {
298
+ this.emitComplexListMapClasses(struct, true);
299
+ } else if (
300
+ struct.nestingMode === "listlist" ||
301
+ struct.nestingMode === "listset" ||
302
+ struct.nestingMode === "setlist" ||
303
+ struct.nestingMode === "setset"
304
+ ) {
305
+ this.emitComplexListListClasses(struct);
306
+ }
307
+ // other types of nested collections aren't supported
308
+ }
309
+
310
+ private emitComplexListClass(struct: Struct) {
311
+ this.code.line();
312
+ this.code.openBlock(
313
+ `export class ${struct.listName} extends cdktn.ComplexList`,
314
+ );
315
+
316
+ if (struct.assignable) {
317
+ this.code.line(
318
+ `public internalValue? : ${struct.name}[] | cdktn.IResolvable`,
319
+ );
320
+ }
321
+
322
+ this.code.line();
323
+ const constructorComment = sanitizedComment(this.code);
324
+ constructorComment.line(`@param terraformResource The parent resource`);
325
+ constructorComment.line(
326
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
327
+ );
328
+ constructorComment.line(
329
+ `@param wrapsSet whether the list is wrapping a set (will add tolist() to be able to access an item via an index)`,
330
+ );
331
+ constructorComment.end();
332
+ this.code.openBlock(
333
+ `constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string, wrapsSet: boolean)`,
334
+ );
335
+ this.code.line(`super(terraformResource, terraformAttribute, wrapsSet);`);
336
+ this.code.closeBlock();
337
+
338
+ this.code.line();
339
+ const getterComment = sanitizedComment(this.code);
340
+ getterComment.line(`@param index the index of the item to return`);
341
+ getterComment.end();
342
+ this.code.openBlock(
343
+ `public get(index: number): ${struct.outputReferenceName}`,
344
+ );
345
+ this.code.line(
346
+ `return new ${struct.outputReferenceName}(this.terraformResource, this.terraformAttribute, index, this.wrapsSet);`,
347
+ );
348
+ this.code.closeBlock();
349
+
350
+ this.code.closeBlock();
351
+ }
352
+
353
+ private emitComplexMapClass(struct: Struct) {
354
+ this.code.line();
355
+ this.code.openBlock(
356
+ `export class ${struct.mapName} extends cdktn.ComplexMap`,
357
+ );
358
+
359
+ if (struct.assignable) {
360
+ this.code.line(
361
+ `public internalValue? : { [key: string]: ${struct.name} } | cdktn.IResolvable`,
362
+ );
363
+ }
364
+
365
+ this.code.line();
366
+ const constructorComment = sanitizedComment(this.code);
367
+ constructorComment.line(`@param terraformResource The parent resource`);
368
+ constructorComment.line(
369
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
370
+ );
371
+ constructorComment.end();
372
+ this.code.openBlock(
373
+ `constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string)`,
374
+ );
375
+ this.code.line(`super(terraformResource, terraformAttribute);`);
376
+ this.code.closeBlock();
377
+
378
+ this.code.line();
379
+ const getterComment = sanitizedComment(this.code);
380
+ getterComment.line(`@param key the key of the item to return`);
381
+ getterComment.end();
382
+ this.code.openBlock(
383
+ `public get(key: string): ${struct.outputReferenceName}`,
384
+ );
385
+ this.code.line(
386
+ `return new ${struct.outputReferenceName}(this.terraformResource, this.terraformAttribute, key);`,
387
+ );
388
+ this.code.closeBlock();
389
+
390
+ this.code.closeBlock();
391
+ }
392
+
393
+ private emitComplexMapListClasses(struct: Struct) {
394
+ this.emitComplexMapListClass(struct);
395
+ this.emitComplexMapClass(struct);
396
+ }
397
+
398
+ private emitComplexMapListClass(struct: Struct) {
399
+ this.code.line();
400
+ this.code.openBlock(
401
+ `export class ${struct.mapListName} extends cdktn.MapList`,
402
+ );
403
+
404
+ if (struct.assignable) {
405
+ this.code.line(
406
+ `public internalValue? : ${struct.mapName}[] | cdktn.IResolvable`,
407
+ );
408
+ }
409
+
410
+ this.code.line();
411
+ const constructorComment = sanitizedComment(this.code);
412
+ constructorComment.line(`@param terraformResource The parent resource`);
413
+ constructorComment.line(
414
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
415
+ );
416
+ constructorComment.line(
417
+ `@param wrapsSet whether the list is wrapping a set (will add tolist() to be able to access an item via an index)`,
418
+ );
419
+ constructorComment.end();
420
+ this.code.openBlock(
421
+ `constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string, wrapsSet: boolean)`,
422
+ );
423
+ this.code.line(`super(terraformResource, terraformAttribute, wrapsSet);`);
424
+ this.code.closeBlock();
425
+
426
+ this.code.line();
427
+ const getterComment = sanitizedComment(this.code);
428
+ getterComment.line(`@param index the index of the item to return`);
429
+ getterComment.end();
430
+ this.code.openBlock(`public get(index: number): ${struct.mapName}`);
431
+ this.code.line(`return new ${struct.mapName}(this, \`[\${index}]\`);`);
432
+ this.code.closeBlock();
433
+
434
+ this.code.closeBlock();
435
+ }
436
+
437
+ private emitComplexListMapClasses(struct: Struct, isSet: boolean) {
438
+ this.emitComplexListMapClass(struct, isSet);
439
+ this.emitComplexListClass(struct);
440
+ }
441
+
442
+ private emitComplexListMapClass(struct: Struct, isSet: boolean) {
443
+ this.code.line();
444
+ this.code.openBlock(
445
+ `export class ${struct.listMapName} extends cdktn.ComplexMap`,
446
+ );
447
+
448
+ if (struct.assignable) {
449
+ this.code.line(
450
+ `public internalValue? : { [key: string]: ${struct.name}[] } | cdktn.IResolvable`,
451
+ );
452
+ }
453
+
454
+ this.code.line();
455
+ const constructorComment = sanitizedComment(this.code);
456
+ constructorComment.line(`@param terraformResource The parent resource`);
457
+ constructorComment.line(
458
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
459
+ );
460
+ constructorComment.end();
461
+ this.code.openBlock(
462
+ `constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string)`,
463
+ );
464
+ this.code.line(`super(terraformResource, terraformAttribute);`);
465
+ this.code.closeBlock();
466
+
467
+ this.code.line();
468
+ const getterComment = sanitizedComment(this.code);
469
+ getterComment.line(`@param key the key of the item to return`);
470
+ getterComment.end();
471
+ this.code.openBlock(`public get(key: string): ${struct.listName}`);
472
+ this.code.line(
473
+ `return new ${struct.listName}(this, \`[\${key}]\`, ${isSet});`,
474
+ );
475
+ this.code.closeBlock();
476
+
477
+ this.code.closeBlock();
478
+ }
479
+
480
+ private emitComplexListListClasses(struct: Struct) {
481
+ this.emitComplexListListClass(struct);
482
+ this.emitComplexListClass(struct);
483
+ }
484
+
485
+ private emitComplexListListClass(struct: Struct) {
486
+ this.code.line();
487
+ this.code.openBlock(
488
+ `export class ${struct.listListName} extends cdktn.MapList`, // despite name, need the same behavior
489
+ );
490
+
491
+ if (struct.assignable) {
492
+ this.code.line(
493
+ `public internalValue? : ${struct.name}[][] | cdktn.IResolvable`,
494
+ );
495
+ }
496
+
497
+ this.code.line();
498
+ const constructorComment = sanitizedComment(this.code);
499
+ constructorComment.line(`@param terraformResource The parent resource`);
500
+ constructorComment.line(
501
+ `@param terraformAttribute The attribute on the parent resource this class is referencing`,
502
+ );
503
+ constructorComment.line(
504
+ `@param wrapsSet whether the list is wrapping a set (will add tolist() to be able to access an item via an index)`,
505
+ );
506
+ constructorComment.end();
507
+ this.code.openBlock(
508
+ `constructor(terraformResource: cdktn.IInterpolatingParent, terraformAttribute: string, wrapsSet: boolean)`,
509
+ );
510
+ this.code.line(`super(terraformResource, terraformAttribute, wrapsSet);`);
511
+ this.code.closeBlock();
512
+
513
+ this.code.line();
514
+ const getterComment = sanitizedComment(this.code);
515
+ getterComment.line(`@param index the index of the item to return`);
516
+ getterComment.end();
517
+ this.code.openBlock(`public get(index: number): ${struct.listName}`);
518
+ this.code.line(
519
+ `return new ${struct.listName}(this, \`[\${index}]\`, ${
520
+ struct.nestingMode === "setlist" || struct.nestingMode === "setset"
521
+ });`,
522
+ );
523
+ this.code.closeBlock();
524
+
525
+ this.code.closeBlock();
526
+ }
527
+
528
+ private emitInternalValueGetter(struct: Struct) {
529
+ this.code.openBlock(
530
+ `public get internalValue(): ${struct.name}${
531
+ !struct.isClass ? " | cdktn.IResolvable" : ""
532
+ } | undefined`,
533
+ );
534
+
535
+ if (!struct.isClass) {
536
+ this.code.openBlock("if (this.resolvableValue)");
537
+ this.code.line("return this.resolvableValue;");
538
+ this.code.closeBlock();
539
+ }
540
+
541
+ this.code.line("let hasAnyValues = this.isEmptyObject;");
542
+ this.code.line("const internalValueResult: any = {};");
543
+ for (const att of struct.attributes) {
544
+ if (att.isStored) {
545
+ if (att.getterType._type === "stored_class") {
546
+ this.code.openBlock(
547
+ `if (this.${att.storageName}?.internalValue !== undefined)`,
548
+ );
549
+ } else {
550
+ this.code.openBlock(`if (this.${att.storageName} !== undefined)`);
551
+ }
552
+ this.code.line("hasAnyValues = true;");
553
+ if (att.getterType._type === "stored_class") {
554
+ this.code.line(
555
+ `internalValueResult.${att.name} = this.${att.storageName}?.internalValue;`,
556
+ );
557
+ } else {
558
+ this.code.line(
559
+ `internalValueResult.${att.name} = this.${att.storageName};`,
560
+ );
561
+ }
562
+ this.code.closeBlock();
563
+ }
564
+ }
565
+ this.code.line("return hasAnyValues ? internalValueResult : undefined;");
566
+ this.code.closeBlock();
567
+ }
568
+
569
+ private emitInternalValueSetter(struct: Struct) {
570
+ this.code.openBlock(
571
+ `public set internalValue(value: ${struct.name}${
572
+ !struct.isClass ? " | cdktn.IResolvable" : ""
573
+ } | undefined)`,
574
+ );
575
+
576
+ this.code.openBlock("if (value === undefined)");
577
+ this.code.line("this.isEmptyObject = false;");
578
+ if (!struct.isClass) {
579
+ this.code.line("this.resolvableValue = undefined;");
580
+ }
581
+ for (const att of struct.attributes) {
582
+ if (att.isStored) {
583
+ if (att.setterType._type === "stored_class") {
584
+ this.code.line(`this.${att.storageName}.internalValue = undefined;`);
585
+ } else if (att.setterType._type !== "none") {
586
+ this.code.line(`this.${att.storageName} = undefined;`);
587
+ }
588
+ }
589
+ }
590
+ this.code.closeBlock();
591
+ if (!struct.isClass) {
592
+ this.code.openBlock("else if (cdktn.Tokenization.isResolvable(value))");
593
+ this.code.line("this.isEmptyObject = false;");
594
+ this.code.line("this.resolvableValue = value;");
595
+ this.code.closeBlock();
596
+ }
597
+ this.code.openBlock("else");
598
+ this.code.line("this.isEmptyObject = Object.keys(value).length === 0;");
599
+ if (!struct.isClass) {
600
+ this.code.line("this.resolvableValue = undefined;");
601
+ }
602
+ for (const att of struct.attributes) {
603
+ if (att.isStored) {
604
+ if (att.setterType._type === "stored_class") {
605
+ this.code.line(
606
+ `this.${att.storageName}.internalValue = value.${att.name};`,
607
+ );
608
+ } else if (att.setterType._type !== "none") {
609
+ this.code.line(`this.${att.storageName} = value.${att.name};`);
610
+ }
611
+ }
612
+ }
613
+ this.code.closeBlock();
614
+ this.code.closeBlock();
615
+ }
616
+
617
+ private emitToHclTerraformFunction(struct: Struct) {
618
+ this.code.line();
619
+ this.code.openBlock(
620
+ `export function ${downcaseFirst(struct.name)}ToHclTerraform(struct?: ${
621
+ struct.isSingleItem && !struct.isProvider
622
+ ? `${struct.name}OutputReference | `
623
+ : ""
624
+ }${struct.name}${!struct.isClass ? " | cdktn.IResolvable" : ""}): any`,
625
+ );
626
+ this.code.line(
627
+ `if (!cdktn.canInspect(struct) || cdktn.Tokenization.isResolvable(struct)) { return struct; }`,
628
+ );
629
+ this.code.openBlock(`if (cdktn.isComplexElement(struct))`);
630
+ this.code.line(
631
+ `throw new Error("A complex element was used as configuration, this is not supported: https://cdktn.io/docs/concepts/resources#references");`,
632
+ );
633
+ this.code.closeBlock();
634
+
635
+ this.code.open(`const attrs = {`);
636
+
637
+ for (const att of struct.assignableAttributes) {
638
+ this.attributesEmitter.emitToHclTerraform(att, true);
639
+ }
640
+
641
+ this.code.close(`};`);
642
+
643
+ if (struct.assignableAttributes.length > 0) {
644
+ this.code.line();
645
+ this.code.line(`// remove undefined attributes`);
646
+ this.code.line(
647
+ `return Object.fromEntries(Object.entries(attrs).filter(([_, value]) => value !== undefined && value.value !== undefined));`,
648
+ );
649
+ } else {
650
+ this.code.line(`return attrs;`);
651
+ }
652
+
653
+ this.code.closeBlock();
654
+ this.code.line();
655
+ }
656
+
657
+ private emitToTerraformFunction(struct: Struct) {
658
+ this.code.line();
659
+ this.code.openBlock(
660
+ `export function ${downcaseFirst(struct.name)}ToTerraform(struct?: ${
661
+ struct.isSingleItem && !struct.isProvider
662
+ ? `${struct.name}OutputReference | `
663
+ : ""
664
+ }${struct.name}${!struct.isClass ? " | cdktn.IResolvable" : ""}): any`,
665
+ );
666
+ this.code.line(
667
+ `if (!cdktn.canInspect(struct) || cdktn.Tokenization.isResolvable(struct)) { return struct; }`,
668
+ );
669
+ this.code.openBlock(`if (cdktn.isComplexElement(struct))`);
670
+ this.code.line(
671
+ `throw new Error("A complex element was used as configuration, this is not supported: https://cdktn.io/docs/concepts/resources#references");`,
672
+ );
673
+ this.code.closeBlock();
674
+
675
+ this.code.openBlock("return");
676
+ for (const att of struct.assignableAttributes) {
677
+ this.attributesEmitter.emitToTerraform(att, true);
678
+ }
679
+ this.code.closeBlock(";");
680
+ this.code.closeBlock();
681
+ this.code.line();
682
+ }
683
+ }