@cdktn/provider-generator 0.24.0-pre.9 → 0.24.0-pre.91

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 (124) 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/builder.d.ts +9 -3
  5. package/build/__tests__/edge-provider-schema/builder.js +6 -3
  6. package/build/__tests__/edge-provider-schema/cli.js +8 -3
  7. package/build/__tests__/edge-provider-schema/index.js +119 -1
  8. package/build/__tests__/provider.test.js +4 -1
  9. package/build/get/__tests__/generator/ephemeral-resources.test.d.ts +2 -0
  10. package/build/get/__tests__/generator/ephemeral-resources.test.js +49 -0
  11. package/build/get/__tests__/generator/module-generator.test.js +12 -12
  12. package/build/get/__tests__/generator/provider-functions.test.d.ts +2 -0
  13. package/build/get/__tests__/generator/provider-functions.test.js +176 -0
  14. package/build/get/__tests__/generator/provider-source-matching.test.d.ts +2 -0
  15. package/build/get/__tests__/generator/provider-source-matching.test.js +45 -0
  16. package/build/get/__tests__/generator/provider.test.js +9 -1
  17. package/build/get/__tests__/generator/supported-stored-classes.test.d.ts +2 -0
  18. package/build/get/__tests__/generator/supported-stored-classes.test.js +137 -0
  19. package/build/get/__tests__/generator/types.test.js +44 -1
  20. package/build/get/__tests__/generator/write-only.test.d.ts +2 -0
  21. package/build/get/__tests__/generator/write-only.test.js +65 -0
  22. package/build/get/__tests__/target-versions.test.d.ts +2 -0
  23. package/build/get/__tests__/target-versions.test.js +171 -0
  24. package/build/get/constructs-maker.d.ts +8 -1
  25. package/build/get/constructs-maker.js +46 -10
  26. package/build/get/generator/emitter/attributes-emitter.d.ts +13 -2
  27. package/build/get/generator/emitter/attributes-emitter.js +71 -5
  28. package/build/get/generator/emitter/index.d.ts +1 -0
  29. package/build/get/generator/emitter/index.js +2 -1
  30. package/build/get/generator/emitter/provider-functions-emitter.d.ts +11 -0
  31. package/build/get/generator/emitter/provider-functions-emitter.js +94 -0
  32. package/build/get/generator/emitter/resource-emitter.d.ts +5 -1
  33. package/build/get/generator/emitter/resource-emitter.js +83 -7
  34. package/build/get/generator/emitter/struct-emitter.js +10 -10
  35. package/build/get/generator/models/attribute-model.d.ts +4 -0
  36. package/build/get/generator/models/attribute-model.js +50 -4
  37. package/build/get/generator/models/attribute-type-model.d.ts +24 -0
  38. package/build/get/generator/models/attribute-type-model.js +91 -5
  39. package/build/get/generator/models/index.d.ts +1 -0
  40. package/build/get/generator/models/index.js +2 -1
  41. package/build/get/generator/models/provider-function-model.d.ts +102 -0
  42. package/build/get/generator/models/provider-function-model.js +667 -0
  43. package/build/get/generator/models/resource-model.d.ts +3 -0
  44. package/build/get/generator/models/resource-model.js +13 -4
  45. package/build/get/generator/models/struct.d.ts +2 -0
  46. package/build/get/generator/models/struct.js +6 -2
  47. package/build/get/generator/models/supported-stored-classes.d.ts +5 -0
  48. package/build/get/generator/models/supported-stored-classes.js +86 -0
  49. package/build/get/generator/module-generator.js +3 -3
  50. package/build/get/generator/provider-generator.d.ts +2 -0
  51. package/build/get/generator/provider-generator.js +41 -6
  52. package/build/get/generator/resource-parser.js +3 -1
  53. package/jest.config.js +16 -9
  54. package/package.json +28 -27
  55. package/package.sh +1 -1
  56. package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +4 -0
  57. package/src/__tests__/__snapshots__/provider.test.ts.snap +2828 -2828
  58. package/src/__tests__/edge-provider-schema/builder.ts +201 -0
  59. package/src/__tests__/edge-provider-schema/cli.ts +51 -0
  60. package/src/__tests__/edge-provider-schema/index.ts +284 -0
  61. package/src/__tests__/edge-provider-schema.test.ts +24 -0
  62. package/src/__tests__/provider.test.ts +183 -0
  63. package/src/get/__tests__/constructs-maker.test.ts +118 -0
  64. package/src/get/__tests__/generator/__snapshots__/complex-computed-types.test.ts.snap +5 -5
  65. package/src/get/__tests__/generator/__snapshots__/ephemeral-resources.test.ts.snap +524 -0
  66. package/src/get/__tests__/generator/__snapshots__/export-sharding.test.ts.snap +3310 -3310
  67. package/src/get/__tests__/generator/__snapshots__/module-generator.test.ts.snap +355 -355
  68. package/src/get/__tests__/generator/__snapshots__/nested-types.test.ts.snap +8 -8
  69. package/src/get/__tests__/generator/__snapshots__/provider-functions.test.ts.snap +444 -0
  70. package/src/get/__tests__/generator/__snapshots__/provider.test.ts.snap +8 -8
  71. package/src/get/__tests__/generator/__snapshots__/resource-types.test.ts.snap +126 -126
  72. package/src/get/__tests__/generator/__snapshots__/skipped-attributes.test.ts.snap +17 -17
  73. package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +201 -52
  74. package/src/get/__tests__/generator/__snapshots__/write-only.test.ts.snap +262 -0
  75. package/src/get/__tests__/generator/complex-computed-types.test.ts +28 -0
  76. package/src/get/__tests__/generator/deep-nested-attributes.test.ts +56 -0
  77. package/src/get/__tests__/generator/description-escaping.test.ts +84 -0
  78. package/src/get/__tests__/generator/empty-provider-resources.test.ts +26 -0
  79. package/src/get/__tests__/generator/ephemeral-resources.test.ts +46 -0
  80. package/src/get/__tests__/generator/export-sharding.test.ts +169 -0
  81. package/src/get/__tests__/generator/fixtures/deeply-nested-collections.test.fixture.json +129 -0
  82. package/src/get/__tests__/generator/fixtures/ephemeral-resources.test.fixture.json +160 -0
  83. package/src/get/__tests__/generator/fixtures/provider-functions-collision.test.fixture.json +31 -0
  84. package/src/get/__tests__/generator/fixtures/provider-functions-synthetic.test.fixture.json +192 -0
  85. package/src/get/__tests__/generator/fixtures/provider-functions.test.fixture.json +170 -0
  86. package/src/get/__tests__/generator/fixtures/write-only.test.fixture.json +58 -0
  87. package/src/get/__tests__/generator/import-style.test.ts +129 -0
  88. package/src/get/__tests__/generator/module-generator.test.ts +528 -0
  89. package/src/get/__tests__/generator/nested-types.test.ts +54 -0
  90. package/src/get/__tests__/generator/provider-functions.test.ts +262 -0
  91. package/src/get/__tests__/generator/provider-source-matching.test.ts +84 -0
  92. package/src/get/__tests__/generator/provider.test.ts +59 -0
  93. package/src/get/__tests__/generator/resource-types.test.ts +118 -0
  94. package/src/get/__tests__/generator/skipped-attributes.test.ts +72 -0
  95. package/src/get/__tests__/generator/supported-stored-classes.test.ts +147 -0
  96. package/src/get/__tests__/generator/types.test.ts +686 -0
  97. package/src/get/__tests__/generator/versions-file.test.ts +72 -0
  98. package/src/get/__tests__/generator/write-only.test.ts +71 -0
  99. package/src/get/__tests__/target-versions.test.ts +206 -0
  100. package/src/get/__tests__/util.ts +75 -0
  101. package/src/get/constructs-maker.ts +885 -0
  102. package/src/get/generator/custom-defaults.ts +493 -0
  103. package/src/get/generator/emitter/attributes-emitter.ts +316 -0
  104. package/src/get/generator/emitter/index.ts +6 -0
  105. package/src/get/generator/emitter/provider-functions-emitter.ts +117 -0
  106. package/src/get/generator/emitter/resource-emitter.ts +330 -0
  107. package/src/get/generator/emitter/struct-emitter.ts +683 -0
  108. package/src/get/generator/loop-detection.ts +81 -0
  109. package/src/get/generator/models/attribute-model.ts +273 -0
  110. package/src/get/generator/models/attribute-type-model.ts +560 -0
  111. package/src/get/generator/models/index.ts +8 -0
  112. package/src/get/generator/models/provider-function-model.ts +872 -0
  113. package/src/get/generator/models/resource-model.ts +183 -0
  114. package/src/get/generator/models/scope.ts +54 -0
  115. package/src/get/generator/models/struct.ts +124 -0
  116. package/src/get/generator/models/supported-stored-classes.ts +88 -0
  117. package/src/get/generator/module-generator.ts +234 -0
  118. package/src/get/generator/provider-generator.ts +428 -0
  119. package/src/get/generator/resource-parser.ts +764 -0
  120. package/src/get/generator/sanitized-comments.ts +49 -0
  121. package/src/get/generator/skipped-attributes.ts +27 -0
  122. package/src/index.ts +40 -0
  123. package/src/util.ts +26 -0
  124. package/tsconfig.json +1 -2
@@ -0,0 +1,872 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { toCamelCase, toPascalCase } from "codemaker";
4
+ import {
5
+ AttributeType,
6
+ FunctionParameter,
7
+ FunctionSignature,
8
+ } from "@cdktn/commons";
9
+
10
+ /**
11
+ * A provider-defined function parameter (positional or the trailing
12
+ * variadic one), mapped to the jsii-safe TypeScript type used in the
13
+ * generated method signature.
14
+ */
15
+ export interface ProviderFunctionParameterModel {
16
+ readonly terraformName: string;
17
+ readonly name: string;
18
+ readonly tsType: string;
19
+ /**
20
+ * The JSDoc type expression emitted inside the `@param {...}` braces.
21
+ * A pure type expression only - prose (Terraform list/set semantics,
22
+ * `cdktn.Token.nullValue()` guidance) belongs in `docstringNote`.
23
+ */
24
+ readonly docstringType: string;
25
+ /**
26
+ * Prose appended after the parameter name (and `description`, if any) in
27
+ * the emitted `@param` line - e.g. set-ordering semantics or how to pass
28
+ * an explicit Terraform `null`. Official JSDoc places only the type
29
+ * expression inside the braces, so this must never be folded into
30
+ * `docstringType`.
31
+ */
32
+ readonly docstringNote?: string;
33
+ readonly description?: string;
34
+ /**
35
+ * True when this (fixed, non-variadic) parameter is emitted as a
36
+ * jsii-optional TypeScript parameter (`name?: T`) - see the
37
+ * is_nullable/trailing-compatibility handling in `applyNullability`.
38
+ * Never set on the variadic parameter: an array parameter can't be `?`.
39
+ */
40
+ readonly optional?: boolean;
41
+ }
42
+
43
+ /**
44
+ * `ProviderFunctionParameterModel` plus the one bit of information that's
45
+ * only needed while building the model (see `mapParameterType`/
46
+ * `applyNullability`), never by the emitter: whether `tsType` already
47
+ * accepts an arbitrary `cdktn.IResolvable` in this position (a bare
48
+ * boolean/collection-of-boolean token, `any`, or an explicit
49
+ * `| cdktn.IResolvable` union already present). Nullability handling uses
50
+ * this to decide whether it needs to append another `| cdktn.IResolvable`
51
+ * so a caller can pass `cdktn.Token.nullValue()` - without resorting to
52
+ * re-deriving that answer with a string search over `tsType`.
53
+ */
54
+ interface InternalParameterModel extends ProviderFunctionParameterModel {
55
+ readonly acceptsResolvableAlready: boolean;
56
+ }
57
+
58
+ /**
59
+ * A single provider-defined function, mapped to a static method on the
60
+ * generated `<Provider>ProviderFunctions` class.
61
+ */
62
+ export interface ProviderFunctionModel {
63
+ readonly terraformName: string;
64
+ readonly methodName: string;
65
+ readonly description?: string;
66
+ readonly summary?: string;
67
+ /**
68
+ * Terraform (>=1.8) deprecation message for the function, surfaced as an
69
+ * `@deprecated` JSDoc tag. OpenTofu never emits this field - see
70
+ * `FunctionSignature.deprecation_message`.
71
+ */
72
+ readonly deprecationMessage?: string;
73
+ readonly returnTsType: string;
74
+ /**
75
+ * The `@returns {...}` JSDoc type, describing the conceptual/Terraform
76
+ * shape of the return value - see `mapReturnType`. This deliberately can
77
+ * differ from `returnTsType` (e.g. a `bool` return is declared
78
+ * `cdktn.IResolvable` but documented as `boolean | IResolvable`), the same
79
+ * way a parameter's `docstringType` can differ from its `tsType`.
80
+ */
81
+ readonly returnDocstringType: string;
82
+ /**
83
+ * Prose appended after the `@returns {...}` braces (e.g. Terraform
84
+ * list/set semantics) - the return-side counterpart of a parameter's
85
+ * `docstringNote`.
86
+ */
87
+ readonly returnDocstringNote?: string;
88
+ /**
89
+ * Wraps the `cdktn.TerraformProviderFunction.invoke(...)` call expression
90
+ * into the method's `return` statement (e.g. wrapping it in
91
+ * `cdktn.Token.asString(...)`, or returning it unwrapped for `bool`).
92
+ */
93
+ readonly wrapReturn: (invokeExpression: string) => string;
94
+ readonly parameters: ProviderFunctionParameterModel[];
95
+ readonly variadicParameter?: ProviderFunctionParameterModel;
96
+ }
97
+
98
+ /**
99
+ * All provider-defined functions of a single provider, mapped to a single
100
+ * generated `providers/<provider>/provider-functions/index.ts` file.
101
+ */
102
+ export interface ProviderFunctionsModel {
103
+ readonly providerName: string;
104
+ readonly className: string;
105
+ readonly functions: ProviderFunctionModel[];
106
+ }
107
+
108
+ // Parameter names that collide with a reserved word/identifier in one of the
109
+ // jsii target languages. This is the sibling table for provider-defined
110
+ // functions: tools/generate-function-bindings/scripts/generate.ts holds the
111
+ // canonical (and separately maintained) table for built-in `Fn.*` functions.
112
+ // The two are intentionally not shared - that script lives outside the
113
+ // packages/ build and generates a different artifact - but any reserved name
114
+ // added there should be considered here too.
115
+ const RESERVED_PARAMETER_NAMES: { [name: string]: string } = {
116
+ default: "defaultValue", // reserved word in TypeScript
117
+ string: "str", // causes issues in Go
118
+ };
119
+
120
+ function sanitizeParameterName(name: string): string {
121
+ const camelCased = toCamelCase(name);
122
+ return RESERVED_PARAMETER_NAMES[camelCased] ?? camelCased;
123
+ }
124
+
125
+ function sanitizeMethodName(name: string): string {
126
+ if (name === "length") return "lengthOf"; // reserved on jsii-generated classes
127
+ return toCamelCase(name);
128
+ }
129
+
130
+ // Members that `ProviderFunctionsEmitter` (see `emitter/provider-functions-emitter.ts`)
131
+ // itself puts on every generated `<Provider>ProviderFunctions` wrapper class,
132
+ // regardless of which functions a provider declares:
133
+ // - "constructor": the class's own constructor, which takes the provider's
134
+ // `providerLocalName`. A provider-defined function named "constructor"
135
+ // would sanitize to a *second* `constructor` member on the class, which
136
+ // is invalid TypeScript.
137
+ // - "providerLocalName": the `private readonly providerLocalName: string`
138
+ // field the constructor assigns (see the emitter's `emitClassHeader`/
139
+ // constructor emission). A provider-defined function sanitizing to this
140
+ // name would collide with that field.
141
+ // If `ProviderFunctionsEmitter` ever grows more members on the wrapper class,
142
+ // add them here too.
143
+ const RESERVED_WRAPPER_MEMBER_NAMES = new Set([
144
+ "constructor",
145
+ "providerLocalName",
146
+ ]);
147
+
148
+ /**
149
+ * Result of mapping a single Terraform attribute type into a jsii-safe
150
+ * parameter shape (see `mapParameterType`).
151
+ */
152
+ interface MappedParameterType {
153
+ readonly tsType: string;
154
+ readonly docstringType: string;
155
+ /** See `ProviderFunctionParameterModel.docstringNote`. */
156
+ readonly docstringNote?: string;
157
+ /**
158
+ * True when `tsType` already accepts an arbitrary `cdktn.IResolvable` in
159
+ * this position without needing an extra `| cdktn.IResolvable` union -
160
+ * see `InternalParameterModel`.
161
+ */
162
+ readonly acceptsResolvableAlready: boolean;
163
+ }
164
+
165
+ /**
166
+ * The bracket-composable ("T[]", no top-level union) TypeScript/docstring
167
+ * type for an element that can nest inside an array without ITSELF needing
168
+ * the whole-collection token union at that level: `string`, `number`, or a
169
+ * list/set of one of those, recursively (e.g. `list(list(string))` bottoms
170
+ * out at `string`). Returns `undefined` for anything else (`bool`, `map`,
171
+ * `object`, `dynamic`, or a nested collection that doesn't itself qualify),
172
+ * so the caller falls back to widening the whole parameter to
173
+ * `any[] | cdktn.IResolvable` instead.
174
+ */
175
+ function plainArrayElementType(
176
+ type: AttributeType,
177
+ ): { tsType: string; docstringType: string } | undefined {
178
+ if (type === "string") return { tsType: "string", docstringType: "string" };
179
+ if (type === "number") return { tsType: "number", docstringType: "number" };
180
+ if (Array.isArray(type) && (type[0] === "list" || type[0] === "set")) {
181
+ const inner = plainArrayElementType(type[1]);
182
+ if (!inner) return undefined;
183
+ return {
184
+ tsType: `${inner.tsType}[]`,
185
+ docstringType: `${type[0] === "set" ? "Set" : "Array"}<${inner.docstringType}>`,
186
+ };
187
+ }
188
+ return undefined;
189
+ }
190
+
191
+ /**
192
+ * Maps a `list`/`set` parameter to its jsii-safe shape - mirrors
193
+ * `ListAttributeTypeModel`/`SetAttributeTypeModel.inputTypeDefinition`
194
+ * (both identical) branch for branch, since jsii parameters, like resource
195
+ * inputs, are allowed to union in a whole-collection `cdktn.IResolvable`
196
+ * token. `kind` only affects the documentation: the docstring notation
197
+ * (`Array<...>` for a Terraform list, `Set<...>` for a Terraform set,
198
+ * recursively via `plainArrayElementType`/`mapParameterType`) plus, for
199
+ * sets, a prose note - the jsii type is an array either way, so the note
200
+ * keeps a reader from literally passing a JS `Set` and flags the
201
+ * ordering/duplicate-element semantics difference. The type-level handling
202
+ * is identical for both.
203
+ */
204
+ function mapListOrSetParameterType(
205
+ kind: "list" | "set",
206
+ element: AttributeType,
207
+ ): MappedParameterType {
208
+ const wrapper = kind === "set" ? "Set" : "Array";
209
+ const docstringNote =
210
+ kind === "set"
211
+ ? "Terraform set; ordering is not guaranteed and duplicate values are removed."
212
+ : undefined;
213
+
214
+ if (element === "string") {
215
+ // Mirrors the final `else` branch: encoded list tokens already satisfy
216
+ // `string[]`, so no union is needed.
217
+ return {
218
+ tsType: "string[]",
219
+ docstringType: `${wrapper}<string>`,
220
+ docstringNote,
221
+ acceptsResolvableAlready: false,
222
+ };
223
+ }
224
+ if (element === "number") {
225
+ // Same reasoning as `string` above.
226
+ return {
227
+ tsType: "number[]",
228
+ docstringType: `${wrapper}<number>`,
229
+ docstringNote,
230
+ acceptsResolvableAlready: false,
231
+ };
232
+ }
233
+ if (element === "bool") {
234
+ // Mirrors the `elementType.storedClassType === "boolean"` branch: bool
235
+ // has no lossless per-element token, but the WHOLE collection is still
236
+ // tokenizable, so the union is on the outside.
237
+ return {
238
+ tsType: "Array<boolean | cdktn.IResolvable> | cdktn.IResolvable",
239
+ docstringType: `${wrapper}<boolean | IResolvable>`,
240
+ docstringNote,
241
+ acceptsResolvableAlready: true,
242
+ };
243
+ }
244
+
245
+ const plainElement = plainArrayElementType(element);
246
+ if (plainElement) {
247
+ // Mirrors the `elementType.typeModelType !== "simple"` branch: a nested
248
+ // list/set that itself bottoms out at string/number composes into a
249
+ // proper nested array type (e.g. `list(list(string))` -> `string[][]`),
250
+ // with the whole-collection token additionally accepted alongside it.
251
+ return {
252
+ tsType: `${plainElement.tsType}[] | cdktn.IResolvable`,
253
+ docstringType: `${wrapper}<${plainElement.docstringType}>`,
254
+ docstringNote,
255
+ acceptsResolvableAlready: true,
256
+ };
257
+ }
258
+
259
+ // map/object/dynamic element, or a nested collection that doesn't itself
260
+ // reduce to string/number: no jsii-safe array-of-X type exists - widen
261
+ // the whole parameter to `any[]`. Unlike a bare `any` parameter, plain
262
+ // `any[]` does NOT structurally accept a whole-collection
263
+ // `cdktn.IResolvable` token, so the union is required here (this is
264
+ // stricter than `MapAttributeTypeModel`/`ListAttributeTypeModel` have to
265
+ // be, since those only ever fall back to `any[] | cdktn.IResolvable` via
266
+ // the same "not simple" branch above, which already grants it). Keep the
267
+ // docstring honest about the real element shape rather than just saying
268
+ // `any`.
269
+ const elementDescription = mapParameterType(element).docstringType;
270
+ return {
271
+ tsType: "any[] | cdktn.IResolvable",
272
+ docstringType: `${wrapper}<${elementDescription}>`,
273
+ docstringNote,
274
+ acceptsResolvableAlready: true,
275
+ };
276
+ }
277
+
278
+ /**
279
+ * Maps a `map` parameter to its jsii-safe shape - mirrors
280
+ * `MapAttributeTypeModel.inputTypeDefinition`. Only the `bool`-element case
281
+ * is reachable from real provider schemas today (`string`/`number` element
282
+ * maps stay a plain, non-union record; nested/complex map elements aren't
283
+ * emitted for provider functions - see the `object` fallback in
284
+ * `mapParameterType`), but the shape is kept branch-for-branch aligned with
285
+ * the model it mirrors rather than collapsed.
286
+ */
287
+ function mapMapParameterType(element: AttributeType): MappedParameterType {
288
+ if (element === "string") {
289
+ return {
290
+ tsType: "{ [key: string]: string }",
291
+ docstringType: "{ [key: string]: string }",
292
+ acceptsResolvableAlready: false,
293
+ };
294
+ }
295
+ if (element === "number") {
296
+ return {
297
+ tsType: "{ [key: string]: number }",
298
+ docstringType: "{ [key: string]: number }",
299
+ acceptsResolvableAlready: false,
300
+ };
301
+ }
302
+ if (element === "bool") {
303
+ // Mirrors the `elementType.storedClassType === "boolean"` branch: map
304
+ // of booleans has PER-ENTRY token support, but the whole map itself is
305
+ // not additionally unioned with `cdktn.IResolvable` (unlike the list/set
306
+ // case) - matching `MapAttributeTypeModel.inputTypeDefinition` exactly.
307
+ return {
308
+ tsType: "{ [key: string]: (boolean | cdktn.IResolvable) }",
309
+ docstringType: "{ [key: string]: (boolean | IResolvable) }",
310
+ acceptsResolvableAlready: false,
311
+ };
312
+ }
313
+ // map/object/dynamic element: no structural input helper exists here -
314
+ // stays `any`, which (being `any`) already accepts a whole-map
315
+ // `cdktn.IResolvable` token too.
316
+ return {
317
+ tsType: "any",
318
+ docstringType: "any",
319
+ acceptsResolvableAlready: true,
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Maps a provider function parameter's declared Terraform type to a
325
+ * jsii-safe TypeScript parameter type, recursively for collection types.
326
+ *
327
+ * Unlike return types, jsii allows unions in *parameter* position, so
328
+ * `bool` is typed `boolean | cdktn.IResolvable` (mirroring
329
+ * `SimpleAttributeTypeModel.inputTypeDefinition`'s input-side convention) -
330
+ * this both accepts a real boolean and still accepts a token in place of
331
+ * one. `dynamic` has no structural typing and stays `any` (which already
332
+ * accepts anything, including a token); `object` (structural input helpers
333
+ * are deferred to a follow-up issue) stays `any` too.
334
+ *
335
+ * `list`/`set` and `map` delegate to `mapListOrSetParameterType`/
336
+ * `mapMapParameterType`, which mirror `ListAttributeTypeModel`/
337
+ * `SetAttributeTypeModel`/`MapAttributeTypeModel.inputTypeDefinition`
338
+ * branch for branch (see `attribute-type-model.ts`) - the same
339
+ * whole-collection-token conventions already used for resource inputs.
340
+ */
341
+ function mapParameterType(type: AttributeType): MappedParameterType {
342
+ if (type === "string") {
343
+ return {
344
+ tsType: "string",
345
+ docstringType: "string",
346
+ acceptsResolvableAlready: false,
347
+ };
348
+ }
349
+ if (type === "number") {
350
+ return {
351
+ tsType: "number",
352
+ docstringType: "number",
353
+ acceptsResolvableAlready: false,
354
+ };
355
+ }
356
+ if (type === "bool") {
357
+ return {
358
+ tsType: "boolean | cdktn.IResolvable",
359
+ docstringType: "boolean | IResolvable",
360
+ acceptsResolvableAlready: true,
361
+ };
362
+ }
363
+ if (type === "dynamic") {
364
+ return {
365
+ tsType: "any",
366
+ docstringType: "any",
367
+ acceptsResolvableAlready: true,
368
+ };
369
+ }
370
+
371
+ if (Array.isArray(type) && (type[0] === "list" || type[0] === "set")) {
372
+ return mapListOrSetParameterType(type[0], type[1]);
373
+ }
374
+
375
+ if (Array.isArray(type) && type[0] === "map") {
376
+ return mapMapParameterType(type[1]);
377
+ }
378
+
379
+ // object: structural input helpers are deferred to a follow-up issue.
380
+ return {
381
+ tsType: "any",
382
+ docstringType: "object",
383
+ acceptsResolvableAlready: true,
384
+ };
385
+ }
386
+
387
+ function buildParameterModel(
388
+ parameter: FunctionParameter,
389
+ fallbackName: string,
390
+ ): InternalParameterModel {
391
+ const terraformName = parameter.name ?? fallbackName;
392
+ const { tsType, docstringType, docstringNote, acceptsResolvableAlready } =
393
+ mapParameterType(parameter.type);
394
+ return {
395
+ terraformName,
396
+ name: sanitizeParameterName(terraformName),
397
+ tsType,
398
+ docstringType,
399
+ docstringNote,
400
+ description: parameter.description,
401
+ acceptsResolvableAlready,
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Widens a mapped type's `tsType`/`docstringType` to also accept an
407
+ * explicit `cdktn.Token.nullValue()` in this position - unless
408
+ * `acceptsResolvableAlready` says it already does (e.g. a `bool` parameter
409
+ * is already `boolean | cdktn.IResolvable`, so nullability adds nothing to
410
+ * the type itself, only to the docstring note appended by the caller).
411
+ */
412
+ function withNullableResolvableUnion(
413
+ model: Pick<
414
+ InternalParameterModel,
415
+ "tsType" | "docstringType" | "acceptsResolvableAlready"
416
+ >,
417
+ ): { tsType: string; docstringType: string } {
418
+ if (model.acceptsResolvableAlready) {
419
+ return { tsType: model.tsType, docstringType: model.docstringType };
420
+ }
421
+ return {
422
+ tsType: `${model.tsType} | cdktn.IResolvable`,
423
+ docstringType: `${model.docstringType} | IResolvable`,
424
+ };
425
+ }
426
+
427
+ /**
428
+ * Applies `is_nullable` trailing-compatibility rules across a function's
429
+ * fixed (non-variadic) parameter list. This needs the whole list in view
430
+ * (see `buildFunctionModel`), since whether a nullable parameter can become
431
+ * jsii-optional depends on every parameter *after* it too:
432
+ *
433
+ * - A nullable parameter where it and every later fixed parameter are also
434
+ * nullable becomes jsii-optional (`name?: T`): omitting it in TypeScript
435
+ * leaves `undefined` in the invoke args array, which `FunctionCall`
436
+ * already renders as the Terraform `null` keyword. It additionally gains
437
+ * the `| cdktn.IResolvable` union (see `withNullableResolvableUnion`), so
438
+ * a caller who does supply the argument can pass
439
+ * `cdktn.Token.nullValue()` for an explicit Terraform `null` without
440
+ * omitting the parameter.
441
+ * - A nullable parameter followed by a required one keeps its position
442
+ * (jsii can't express "optional but not trailing"), and instead gains the
443
+ * same `| cdktn.IResolvable` union so a caller can still pass an explicit
444
+ * `cdktn.Token.nullValue()` (an `IResolvable` resolving to `null` - see
445
+ * `Token.nullValue()`/`Token.asAny()`) in that fixed position - keeping
446
+ * the parameter's real type rather than giving up all type safety by
447
+ * widening it to `any`.
448
+ *
449
+ * Both cases document the real mechanism in the docstring note rather than
450
+ * implying the plain TypeScript `null` literal is accepted: the trailing
451
+ * case can also just be omitted, the mid-position case can't be omitted at
452
+ * all (jsii can't express "optional but not trailing").
453
+ */
454
+ function applyNullability(
455
+ parameters: FunctionParameter[],
456
+ models: InternalParameterModel[],
457
+ ): InternalParameterModel[] {
458
+ const trailingCompatible = new Array(parameters.length).fill(false);
459
+ let allNullableSoFar = true;
460
+ for (let i = parameters.length - 1; i >= 0; i--) {
461
+ allNullableSoFar = allNullableSoFar && parameters[i].is_nullable === true;
462
+ trailingCompatible[i] = allNullableSoFar;
463
+ }
464
+
465
+ return models.map((model, index) => {
466
+ const parameter = parameters[index];
467
+ if (!parameter.is_nullable) return model;
468
+
469
+ const { tsType, docstringType } = withNullableResolvableUnion(model);
470
+ const nullabilityNote = trailingCompatible[index]
471
+ ? "Omit or pass cdktn.Token.nullValue() to render the Terraform null keyword."
472
+ : "Pass cdktn.Token.nullValue() for an explicit Terraform null.";
473
+
474
+ return {
475
+ ...model,
476
+ ...(trailingCompatible[index] ? { optional: true } : {}),
477
+ tsType,
478
+ docstringType,
479
+ docstringNote: [model.docstringNote, nullabilityNote]
480
+ .filter(Boolean)
481
+ .join(" "),
482
+ };
483
+ });
484
+ }
485
+
486
+ /**
487
+ * Maps a provider function's declared return type to the jsii-safe
488
+ * TypeScript return type, the `@returns` JSDoc type, and the expression
489
+ * that unwraps the `TerraformProviderFunction.invoke(...)` `IResolvable`
490
+ * into it.
491
+ *
492
+ * jsii return positions can't use the `T | cdktn.IResolvable` union that
493
+ * parameters can (see `mapParameterType`), so every shape that can't be
494
+ * losslessly unwrapped through an `asXxx` Token helper falls back to the
495
+ * plain `cdktn.IResolvable` produced by `invoke()` itself, unwrapped.
496
+ * Provider functions frequently return objects (e.g. every function in the
497
+ * `time` provider), so - unlike built-in `Fn.*` functions - the object case
498
+ * is a primary path, not an error: it is treated the same as
499
+ * `dynamic`/a `list`/`set` of anything other than `string`/`number` (no
500
+ * `asAnyList` Token helper exists), and returned as the RAW `invoke()`
501
+ * result. `map` returns, unlike those, DO have a full set of typed Token
502
+ * helpers (`asStringMap`/`asNumberMap`/`asBooleanMap`/`asAnyMap`), so every
503
+ * `map` shape gets an actual typed record return instead of falling back to
504
+ * `cdktn.IResolvable`.
505
+ *
506
+ * Whenever the declared return type IS `cdktn.IResolvable` (not `any`):
507
+ * unlike `helpers.ts` `asAny`, this must NOT go through
508
+ * `cdktn.Token.asString(...)` - `Token.asString()` produces an encoded
509
+ * string token that `Tokenization.isResolvable()` does not recognize, so
510
+ * generated struct setters (e.g. an `OutputReference` `internalValue`) treat
511
+ * it as a plain object with no known keys and the attribute silently
512
+ * vanishes from synth output. The raw `IResolvable` from `invoke()` IS
513
+ * recognized by `Tokenization.isResolvable()` and resolves correctly.
514
+ * Declaring it as `cdktn.IResolvable` instead of `any` also gives callers
515
+ * real type safety: `any` let a caller write `result.hours` on a token with
516
+ * no compile-time feedback, silently producing `undefined` at runtime;
517
+ * `IResolvable` has no such property and forces the caller through
518
+ * `cdktn.Token`/the provider's struct types instead. `invoke()` already
519
+ * returns `IResolvable`, so no cast is needed to return it as
520
+ * `cdktn.IResolvable`.
521
+ */
522
+ function mapReturnType(returnType: AttributeType): {
523
+ tsType: string;
524
+ docstringType: string;
525
+ docstringNote?: string;
526
+ wrapReturn: (invokeExpression: string) => string;
527
+ } {
528
+ if (returnType === "string") {
529
+ return {
530
+ tsType: "string",
531
+ docstringType: "string",
532
+ wrapReturn: (expr) => `cdktn.Token.asString(${expr})`,
533
+ };
534
+ }
535
+ if (returnType === "number") {
536
+ return {
537
+ tsType: "number",
538
+ docstringType: "number",
539
+ wrapReturn: (expr) => `cdktn.Token.asNumber(${expr})`,
540
+ };
541
+ }
542
+ if (returnType === "bool") {
543
+ // Booleans can't be represented as tokens (see helpers.ts asBoolean):
544
+ // return the IResolvable produced by invoke() unwrapped. The docstring
545
+ // still documents the real conceptual shape.
546
+ return {
547
+ tsType: "cdktn.IResolvable",
548
+ docstringType: "boolean | IResolvable",
549
+ wrapReturn: (expr) => expr,
550
+ };
551
+ }
552
+ if (returnType === "dynamic") {
553
+ return {
554
+ tsType: "cdktn.IResolvable",
555
+ docstringType: "any",
556
+ wrapReturn: (expr) => expr,
557
+ };
558
+ }
559
+ if (
560
+ Array.isArray(returnType) &&
561
+ (returnType[0] === "list" || returnType[0] === "set")
562
+ ) {
563
+ // Same documentation conventions as `mapListOrSetParameterType`: the
564
+ // notation (`Array<...>`/`Set<...>`) carries the Terraform collection
565
+ // structure recursively, and only sets get a prose note.
566
+ const wrapper = returnType[0] === "set" ? "Set" : "Array";
567
+ const docstringNote =
568
+ returnType[0] === "set"
569
+ ? "Terraform set; ordering is not guaranteed and duplicate values are removed."
570
+ : undefined;
571
+ const element = returnType[1];
572
+ if (element === "string") {
573
+ return {
574
+ tsType: "string[]",
575
+ docstringType: `${wrapper}<string>`,
576
+ docstringNote,
577
+ wrapReturn: (expr) => `cdktn.Token.asList(${expr})`,
578
+ };
579
+ }
580
+ if (element === "number") {
581
+ return {
582
+ tsType: "number[]",
583
+ docstringType: `${wrapper}<number>`,
584
+ docstringNote,
585
+ wrapReturn: (expr) => `cdktn.Token.asNumberList(${expr})`,
586
+ };
587
+ }
588
+ // list/set of anything else: there is no `asAnyList` Token helper -
589
+ // fall back to the raw IResolvable, same reasoning as dynamic/object
590
+ // below, but keep the docstring honest about the real element shape
591
+ // (reusing mapParameterType purely for its descriptive docstringType -
592
+ // its tsType/acceptsResolvableAlready are irrelevant here).
593
+ return {
594
+ tsType: "cdktn.IResolvable",
595
+ docstringType: `${wrapper}<${mapParameterType(element).docstringType}>`,
596
+ docstringNote,
597
+ wrapReturn: (expr) => expr,
598
+ };
599
+ }
600
+ if (Array.isArray(returnType) && returnType[0] === "map") {
601
+ // Unlike list/set and object/dynamic, every `map` shape has a matching
602
+ // typed Token helper (see token.ts), so it gets a real typed record
603
+ // return instead of falling back to the raw IResolvable.
604
+ const element = returnType[1];
605
+ if (element === "string") {
606
+ return {
607
+ tsType: "{ [key: string]: string }",
608
+ docstringType: "{ [key: string]: string }",
609
+ wrapReturn: (expr) => `cdktn.Token.asStringMap(${expr})`,
610
+ };
611
+ }
612
+ if (element === "number") {
613
+ return {
614
+ tsType: "{ [key: string]: number }",
615
+ docstringType: "{ [key: string]: number }",
616
+ wrapReturn: (expr) => `cdktn.Token.asNumberMap(${expr})`,
617
+ };
618
+ }
619
+ if (element === "bool") {
620
+ // Token.asBooleanMap's declared return type is `{ [key: string]:
621
+ // boolean }` (see token.ts) - used verbatim, not unioned with
622
+ // IResolvable: the map's individual entries are plain booleans once
623
+ // unwrapped by the helper.
624
+ return {
625
+ tsType: "{ [key: string]: boolean }",
626
+ docstringType: "{ [key: string]: boolean }",
627
+ wrapReturn: (expr) => `cdktn.Token.asBooleanMap(${expr})`,
628
+ };
629
+ }
630
+ // map of anything else (object/dynamic/nested collection): asAnyMap
631
+ // still gives a typed (if loosely-typed) record rather than falling
632
+ // back to the raw IResolvable.
633
+ return {
634
+ tsType: "{ [key: string]: any }",
635
+ docstringType: "{ [key: string]: any }",
636
+ wrapReturn: (expr) => `cdktn.Token.asAnyMap(${expr})`,
637
+ };
638
+ }
639
+ // object: no structural typing for arbitrary provider function results -
640
+ // declared as `cdktn.IResolvable`, and returned as the raw `IResolvable`
641
+ // from invoke() (NOT wrapped in `cdktn.Token.asString(...)`, which would
642
+ // make the result unrecognizable to `Tokenization.isResolvable()`
643
+ // downstream - see the mapReturnType docstring above).
644
+ return {
645
+ tsType: "cdktn.IResolvable",
646
+ docstringType: "object",
647
+ wrapReturn: (expr) => expr,
648
+ };
649
+ }
650
+
651
+ function buildFunctionModel(
652
+ terraformName: string,
653
+ signature: FunctionSignature,
654
+ ): ProviderFunctionModel {
655
+ const {
656
+ tsType: returnTsType,
657
+ docstringType: returnDocstringType,
658
+ docstringNote: returnDocstringNote,
659
+ wrapReturn,
660
+ } = mapReturnType(signature.return_type);
661
+
662
+ const rawParameters = signature.parameters ?? [];
663
+ const parameters = applyNullability(
664
+ rawParameters,
665
+ rawParameters.map((parameter, index) =>
666
+ buildParameterModel(parameter, `arg${index}`),
667
+ ),
668
+ );
669
+
670
+ let variadicParameter: InternalParameterModel | undefined;
671
+ if (signature.variadic_parameter) {
672
+ const elementModel = buildParameterModel(
673
+ signature.variadic_parameter,
674
+ "values",
675
+ );
676
+ if (signature.variadic_parameter.is_nullable) {
677
+ // A nullable variadic parameter: jsii can't express "each individual
678
+ // argument may independently be null" on the generated `values:
679
+ // Array<T>` array parameter (it's an ordinary array parameter, not a
680
+ // true rest/spread parameter - only the invoke() call site spreads
681
+ // it), so every element's type instead widens to also accept an
682
+ // explicit `cdktn.Token.nullValue()`, the same way a fixed nullable
683
+ // parameter does (see `withNullableResolvableUnion`); the docstring
684
+ // keeps the honest per-element type plus that guidance in its note.
685
+ const { tsType, docstringType } =
686
+ withNullableResolvableUnion(elementModel);
687
+ variadicParameter = {
688
+ ...elementModel,
689
+ tsType,
690
+ docstringType: `Array<${docstringType}>`,
691
+ docstringNote: [
692
+ elementModel.docstringNote,
693
+ "Pass cdktn.Token.nullValue() for an explicit Terraform null.",
694
+ ]
695
+ .filter(Boolean)
696
+ .join(" "),
697
+ };
698
+ } else {
699
+ // Non-nullable: the docstring wraps the element's docstring type in
700
+ // `Array<...>` here (rather than in the emitter), so every parameter
701
+ // - fixed or variadic - can share one `@param {docstringType}`
702
+ // emission path.
703
+ variadicParameter = {
704
+ ...elementModel,
705
+ docstringType: `Array<${elementModel.docstringType}>`,
706
+ };
707
+ }
708
+ }
709
+
710
+ return {
711
+ terraformName,
712
+ methodName: sanitizeMethodName(terraformName),
713
+ description: signature.description,
714
+ summary: signature.summary,
715
+ deprecationMessage: signature.deprecation_message,
716
+ returnTsType,
717
+ returnDocstringType,
718
+ returnDocstringNote,
719
+ wrapReturn,
720
+ parameters,
721
+ variadicParameter,
722
+ };
723
+ }
724
+
725
+ /**
726
+ * Throws if two functions in the same provider sanitize to the same
727
+ * generated method name. Terraform function names are unique, but
728
+ * `sanitizeMethodName` (camelCasing, `length` -> `lengthOf`) is not
729
+ * necessarily injective, so a real - if unlikely - provider schema could
730
+ * still produce a duplicate method. Generation is aborted rather than
731
+ * silently emitting one method that shadows the other.
732
+ */
733
+ function assertNoMethodNameCollisions(
734
+ providerName: string,
735
+ functions: ProviderFunctionModel[],
736
+ ): void {
737
+ const seenBy = new Map<string, string>(); // methodName -> terraformName
738
+
739
+ for (const fn of functions) {
740
+ const existingTerraformName = seenBy.get(fn.methodName);
741
+ if (existingTerraformName !== undefined) {
742
+ throw new Error(
743
+ `Provider "${providerName}" declares two provider-defined functions, ` +
744
+ `"${existingTerraformName}" and "${fn.terraformName}", that both ` +
745
+ `sanitize to the generated method name "${fn.methodName}". ` +
746
+ `Generation aborted to avoid silently overwriting one method with ` +
747
+ `the other - please report this as an issue.`,
748
+ );
749
+ }
750
+ seenBy.set(fn.methodName, fn.terraformName);
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Throws if a function's sanitized `methodName` lands on a member name
756
+ * `ProviderFunctionsEmitter` already puts on every wrapper class (see
757
+ * `RESERVED_WRAPPER_MEMBER_NAMES`). Unlike `assertNoMethodNameCollisions`,
758
+ * which only catches two *functions* colliding with each other, this catches
759
+ * a single function colliding with the wrapper class itself - e.g. a
760
+ * Terraform function literally named "constructor", or "provider_local_name"
761
+ * camelCasing to "providerLocalName".
762
+ *
763
+ * This intentionally does NOT also check sanitized *parameter* names against
764
+ * "providerLocalName": parameters are declared in the generated method's own
765
+ * scope, not the class's, so a parameter named `providerLocalName` shadows
766
+ * the class field within that method body without colliding with it (valid,
767
+ * if confusing, TypeScript) - there is nothing to abort generation over.
768
+ */
769
+ function assertNoReservedWrapperMemberCollisions(
770
+ providerName: string,
771
+ functions: ProviderFunctionModel[],
772
+ ): void {
773
+ for (const fn of functions) {
774
+ if (RESERVED_WRAPPER_MEMBER_NAMES.has(fn.methodName)) {
775
+ throw new Error(
776
+ `Provider "${providerName}" declares a provider-defined function ` +
777
+ `"${fn.terraformName}" that sanitizes to the generated method name ` +
778
+ `"${fn.methodName}", which collides with a member that ` +
779
+ `ProviderFunctionsEmitter already puts on every ` +
780
+ `"<Provider>ProviderFunctions" wrapper class (the class ` +
781
+ `constructor, or its "providerLocalName" field). Generation ` +
782
+ `aborted to avoid emitting invalid TypeScript or silently ` +
783
+ `shadowing that member - please report this as an issue.`,
784
+ );
785
+ }
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Throws if two parameters of the same function (including the variadic
791
+ * parameter) sanitize to the same generated parameter name, for the same
792
+ * reason as `assertNoMethodNameCollisions` above but at the parameter level.
793
+ */
794
+ function assertNoParameterNameCollisions(
795
+ providerName: string,
796
+ fn: ProviderFunctionModel,
797
+ ): void {
798
+ const seenBy = new Map<string, string>(); // sanitized name -> terraformName
799
+ const allParameters = fn.variadicParameter
800
+ ? [...fn.parameters, fn.variadicParameter]
801
+ : fn.parameters;
802
+
803
+ for (const param of allParameters) {
804
+ const existingTerraformName = seenBy.get(param.name);
805
+ if (existingTerraformName !== undefined) {
806
+ throw new Error(
807
+ `Provider "${providerName}" function "${fn.terraformName}" declares ` +
808
+ `two parameters, "${existingTerraformName}" and ` +
809
+ `"${param.terraformName}", that both sanitize to the generated ` +
810
+ `parameter name "${param.name}". Generation aborted to avoid ` +
811
+ `silently dropping one parameter - please report this as an issue.`,
812
+ );
813
+ }
814
+ seenBy.set(param.name, param.terraformName);
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Throws if the provider's own config schema declares an attribute whose
820
+ * generated property name (see `AttributeModel.name` /
821
+ * `escapeAttributeName`) is exactly "functions", while the provider also
822
+ * declares provider-defined functions. That combination would collide with
823
+ * the memoized `functions` getter `ResourceEmitter` emits on the provider
824
+ * class (see `resource-emitter.ts`'s `emitFunctionsGetter`) - one would
825
+ * silently shadow the other. Same loud-failure convention as
826
+ * `assertNoMethodNameCollisions`/`assertNoParameterNameCollisions` above:
827
+ * generation is aborted rather than silently picking a winner.
828
+ */
829
+ export function assertNoFunctionsGetterCollision(
830
+ providerName: string,
831
+ attributeNames: string[],
832
+ ): void {
833
+ if (attributeNames.includes("functions")) {
834
+ throw new Error(
835
+ `Provider "${providerName}" declares provider-defined functions and its ` +
836
+ `own config schema also has an attribute that generates the property ` +
837
+ `name "functions" on the provider class. This collides with the ` +
838
+ `generated "functions" getter used to invoke provider-defined ` +
839
+ `functions. Generation aborted to avoid silently shadowing one with ` +
840
+ `the other - please report this as an issue.`,
841
+ );
842
+ }
843
+ }
844
+
845
+ /**
846
+ * Builds the model for a provider's `provider-functions/index.ts` file from
847
+ * its provider schema `functions` map. Returns `undefined` when the provider
848
+ * declares no functions - callers should skip emitting the file entirely.
849
+ */
850
+ export function buildProviderFunctionsModel(
851
+ providerName: string,
852
+ functions: { [name: string]: FunctionSignature } | undefined,
853
+ ): ProviderFunctionsModel | undefined {
854
+ const entries = Object.entries(functions ?? {});
855
+ if (entries.length === 0) return undefined;
856
+
857
+ const mappedFunctions = entries
858
+ .sort(([a], [b]) => a.localeCompare(b))
859
+ .map(([name, signature]) => buildFunctionModel(name, signature));
860
+
861
+ assertNoMethodNameCollisions(providerName, mappedFunctions);
862
+ assertNoReservedWrapperMemberCollisions(providerName, mappedFunctions);
863
+ for (const fn of mappedFunctions) {
864
+ assertNoParameterNameCollisions(providerName, fn);
865
+ }
866
+
867
+ return {
868
+ providerName,
869
+ className: `${toPascalCase(providerName)}ProviderFunctions`,
870
+ functions: mappedFunctions,
871
+ };
872
+ }