@cdktn/hcl2cdk 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 (83) hide show
  1. package/.spec.swcrc +22 -0
  2. package/LICENSE +355 -0
  3. package/README.md +1 -1
  4. package/build/__tests__/jsii-rosetta-workarounds.test.js +2 -2
  5. package/build/expressions.js +3 -4
  6. package/build/function-bindings/functions.generated.js +2 -2
  7. package/build/generation.js +2 -2
  8. package/build/index.d.ts +20 -20
  9. package/build/index.js +4 -4
  10. package/build/references.js +2 -3
  11. package/build/schema.d.ts +60 -60
  12. package/build/utils.d.ts +1 -1
  13. package/build/utils.js +3 -3
  14. package/jest.config.js +18 -11
  15. package/package.json +37 -25
  16. package/package.sh +1 -1
  17. package/src/__tests__/coerceType.test.ts +207 -0
  18. package/src/__tests__/expressionToTs.test.ts +1167 -0
  19. package/src/__tests__/expressions.test.ts +541 -0
  20. package/src/__tests__/findExpressionType.test.ts +112 -0
  21. package/src/__tests__/functions.test.ts +768 -0
  22. package/src/__tests__/generation.test.ts +72 -0
  23. package/src/__tests__/jsii-rosetta-workarounds.test.ts +145 -0
  24. package/src/__tests__/partialCode.test.ts +432 -0
  25. package/src/__tests__/terraformSchema.test.ts +107 -0
  26. package/src/__tests__/testHelpers.ts +11 -0
  27. package/src/coerceType.ts +261 -0
  28. package/src/dynamic-blocks.ts +61 -0
  29. package/src/expressions.ts +968 -0
  30. package/src/function-bindings/functions.generated.ts +1139 -0
  31. package/src/function-bindings/functions.ts +104 -0
  32. package/src/generation.ts +1189 -0
  33. package/src/index.ts +584 -0
  34. package/src/iteration.ts +156 -0
  35. package/src/jsii-rosetta-workarounds.ts +145 -0
  36. package/src/partialCode.ts +132 -0
  37. package/src/provider.ts +60 -0
  38. package/src/references.ts +193 -0
  39. package/src/schema.ts +74 -0
  40. package/src/terraformSchema.ts +182 -0
  41. package/src/types.ts +58 -0
  42. package/src/utils.ts +19 -0
  43. package/src/variables.ts +214 -0
  44. package/test/__snapshots__/backends.test.ts.snap +70 -0
  45. package/test/__snapshots__/externals.test.ts.snap +37 -0
  46. package/test/__snapshots__/granular-imports.test.ts.snap +180 -0
  47. package/test/__snapshots__/imports.test.ts.snap +159 -0
  48. package/test/__snapshots__/iteration.test.ts.snap +532 -0
  49. package/test/__snapshots__/jsiiLanguage.test.ts.snap +347 -0
  50. package/test/__snapshots__/locals.test.ts.snap +55 -0
  51. package/test/__snapshots__/modules.test.ts.snap +127 -0
  52. package/test/__snapshots__/outputs.test.ts.snap +77 -0
  53. package/test/__snapshots__/partialCode.test.ts.snap +120 -0
  54. package/test/__snapshots__/provider.test.ts.snap +128 -0
  55. package/test/__snapshots__/references.test.ts.snap +376 -0
  56. package/test/__snapshots__/resource-meta-properties.test.ts.snap +342 -0
  57. package/test/__snapshots__/resources.test.ts.snap +613 -0
  58. package/test/__snapshots__/tfExpressions.test.ts.snap +537 -0
  59. package/test/__snapshots__/typeCoercion.test.ts.snap +253 -0
  60. package/test/__snapshots__/variables.test.ts.snap +150 -0
  61. package/test/backends.test.ts +75 -0
  62. package/test/convertProject.test.ts +257 -0
  63. package/test/externals.test.ts +35 -0
  64. package/test/globalSetup.ts +224 -0
  65. package/test/globalTeardown.ts +11 -0
  66. package/test/granular-imports.test.ts +161 -0
  67. package/test/hcl2cdk.test.ts +88 -0
  68. package/test/helpers/convert.ts +543 -0
  69. package/test/helpers/tmp.ts +25 -0
  70. package/test/imports.test.ts +141 -0
  71. package/test/iteration.test.ts +342 -0
  72. package/test/jsiiLanguage.test.ts +73 -0
  73. package/test/locals.test.ts +47 -0
  74. package/test/modules.test.ts +143 -0
  75. package/test/outputs.test.ts +69 -0
  76. package/test/partialCode.test.ts +25 -0
  77. package/test/provider.test.ts +106 -0
  78. package/test/references.test.ts +287 -0
  79. package/test/resource-meta-properties.test.ts +288 -0
  80. package/test/resources.test.ts +551 -0
  81. package/test/tfExpressions.test.ts +300 -0
  82. package/test/typeCoercion.test.ts +154 -0
  83. package/test/variables.test.ts +96 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import { ProviderSchema } from "@cdktn/commons";
7
+ import { getTypeAtPath } from "../terraformSchema";
8
+
9
+ describe("getTypeAtPath", () => {
10
+ const schema: ProviderSchema = {
11
+ provider_schemas: {
12
+ test: {
13
+ provider: {} as any,
14
+ data_source_schemas: {},
15
+ resource_schemas: {
16
+ test_resource: {
17
+ version: 1,
18
+ block: {
19
+ attributes: {
20
+ stringAttribute: {
21
+ type: "string",
22
+ },
23
+ numberAttribute: {
24
+ type: "number",
25
+ },
26
+ booleanAttribute: {
27
+ type: "bool",
28
+ },
29
+ stringListAttribute: {
30
+ type: ["list", "string"],
31
+ },
32
+ stringSetAttribute: {
33
+ type: ["set", "string"],
34
+ },
35
+ stringMapAttribute: {
36
+ type: ["map", "string"],
37
+ },
38
+ stringListListAttribute: {
39
+ type: ["list", ["list", "string"]],
40
+ },
41
+ objectAttribute: {
42
+ type: [
43
+ "object",
44
+ {
45
+ stringAttribute: "string",
46
+ },
47
+ ],
48
+ },
49
+ },
50
+ block_types: {
51
+ listMode: {
52
+ nesting_mode: "list",
53
+ block: {
54
+ attributes: {
55
+ stringAttribute: {
56
+ type: "string",
57
+ },
58
+ },
59
+ block_types: {
60
+ listMode: {
61
+ nesting_mode: "list",
62
+ block: {
63
+ attributes: {
64
+ stringAttribute: {
65
+ type: "string",
66
+ },
67
+ },
68
+ block_types: {},
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ };
81
+
82
+ it("should return null if it can not find the resource", () => {
83
+ expect(getTypeAtPath(schema, "test.nonExistantResource")).toBeNull();
84
+ });
85
+ it("should return null if it can not find the attribute on the resource", () => {
86
+ expect(getTypeAtPath(schema, "test.resource.ponyfoo")).toBeNull();
87
+ });
88
+
89
+ describe("known types", () => {
90
+ it.each([
91
+ ["stringAttribute", "string"],
92
+ ["numberAttribute", "number"],
93
+ ["booleanAttribute", "bool"],
94
+ ["stringListAttribute", ["list", "string"]],
95
+ ["stringSetAttribute", ["set", "string"]],
96
+ ["stringMapAttribute", ["map", "string"]],
97
+ ["stringMapAttribute.whateverkey", "string"],
98
+ ["stringListListAttribute", ["list", ["list", "string"]]],
99
+ ["stringListListAttribute.[]", ["list", "string"]],
100
+ ["stringListListAttribute.[].[]", "string"],
101
+ ["listMode.[].listMode.[].stringAttribute", "string"],
102
+ ["objectAttribute.stringAttribute", "string"],
103
+ ])(`access to %s returns %s`, (attr, res) => {
104
+ expect(getTypeAtPath(schema, `test.resource.${attr}`)).toEqual(res);
105
+ });
106
+ });
107
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import generate from "@babel/generator";
7
+ import * as t from "@babel/types";
8
+
9
+ export function astToCode(e: t.Expression): string {
10
+ return generate(e as any).code;
11
+ }
@@ -0,0 +1,261 @@
1
+ // Copyright (c) HashiCorp, Inc
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import template from "@babel/template";
4
+ import * as t from "@babel/types";
5
+ import { logger, AttributeType } from "@cdktn/commons";
6
+ import { ProgramScope } from "./types";
7
+ import deepEqual from "deep-equal";
8
+ import { tsFunctionsMap } from "./function-bindings/functions";
9
+ import { getTypeAtPath } from "./terraformSchema";
10
+ import { toSnakeCase } from "codemaker";
11
+
12
+ function changeValueAccessor(
13
+ ast: t.MemberExpression,
14
+ newAccessor: string,
15
+ ): t.MemberExpression {
16
+ const propertyIdentifier: t.Identifier = {
17
+ ...(ast.property as t.Identifier),
18
+ name: newAccessor,
19
+ };
20
+ return {
21
+ ...ast,
22
+ property: propertyIdentifier,
23
+ };
24
+ }
25
+
26
+ export function typeForCallExpression(ast: t.CallExpression): AttributeType {
27
+ // Find all cdktf.Fn.* || cdktn.Fn.* calls
28
+ if (
29
+ t.isMemberExpression(ast.callee) &&
30
+ t.isMemberExpression(ast.callee.object) &&
31
+ t.isIdentifier(ast.callee.object.object) &&
32
+ (ast.callee.object.object.name === "cdktf" ||
33
+ ast.callee.object.object.name === "cdktn") &&
34
+ t.isIdentifier(ast.callee.object.property) &&
35
+ ast.callee.object.property.name === "Fn" &&
36
+ t.isIdentifier(ast.callee.property)
37
+ ) {
38
+ const meta = tsFunctionsMap[ast.callee.property.name];
39
+ if (meta) {
40
+ return meta.returnType;
41
+ } else {
42
+ return "dynamic";
43
+ }
44
+ }
45
+
46
+ // cdktf.conditional, cdktf.propertyAccess, cdktf.Op.* are all dynamic
47
+ // By default we assume dynamic
48
+ return "dynamic";
49
+ }
50
+
51
+ export const coerceType = (
52
+ scope: ProgramScope,
53
+ ast: t.Expression,
54
+ from: AttributeType,
55
+ to: AttributeType | undefined,
56
+ ): t.Expression => {
57
+ if (to === undefined) {
58
+ return ast;
59
+ }
60
+
61
+ if (deepEqual(to, from)) {
62
+ return ast;
63
+ }
64
+
65
+ const isTerraformVariableOrLocal =
66
+ ast.type === "MemberExpression" &&
67
+ ast.property.type === "Identifier" &&
68
+ ast.property.name === "value" &&
69
+ ast.object.type === "Identifier" &&
70
+ Object.values(scope.variables).some(
71
+ (knownVars) =>
72
+ knownVars.variableName === (ast.object as t.Identifier).name &&
73
+ ["var", "local"].includes(knownVars.resource),
74
+ );
75
+
76
+ const addTokenToImports = () =>
77
+ scope.importables.push({
78
+ constructName: "Token",
79
+ provider: "cdktn",
80
+ });
81
+
82
+ if (Array.isArray(to)) {
83
+ if (to[0] === "list" || to[0] === "set") {
84
+ switch (to[1]) {
85
+ case "string":
86
+ if (isTerraformVariableOrLocal) {
87
+ return changeValueAccessor(ast as t.MemberExpression, "listValue");
88
+ }
89
+
90
+ addTokenToImports();
91
+ return template.expression(`Token.asList(%%ast%%)`)({
92
+ ast: ast,
93
+ }) as t.Expression;
94
+ case "number":
95
+ addTokenToImports();
96
+ return template.expression(`Token.asNumberList(%%ast%%)`)({
97
+ ast: ast,
98
+ }) as t.Expression;
99
+ case "bool":
100
+ addTokenToImports();
101
+ return template.expression(`Token.asAny(%%ast%%)`)({
102
+ ast: ast,
103
+ }) as t.Expression;
104
+ default:
105
+ addTokenToImports();
106
+ return template.expression(`Token.asAny(%%ast%%)`)({
107
+ ast: ast,
108
+ }) as t.Expression;
109
+ }
110
+ }
111
+
112
+ if (to[0] === "map") {
113
+ switch (to[1]) {
114
+ case "string":
115
+ addTokenToImports();
116
+ return template.expression(`Token.asStringMap(%%ast%%)`)({
117
+ ast: ast,
118
+ }) as t.Expression;
119
+ case "number":
120
+ addTokenToImports();
121
+ return template.expression(`Token.asNumberMap(%%ast%%)`)({
122
+ ast: ast,
123
+ }) as t.Expression;
124
+ case "bool":
125
+ addTokenToImports();
126
+ return template.expression(`Token.asBooleanMap(%%ast%%)`)({
127
+ ast: ast,
128
+ }) as t.Expression;
129
+ default:
130
+ addTokenToImports();
131
+ return template.expression(`Token.asAnyMap(%%ast%%)`)({
132
+ ast: ast,
133
+ }) as t.Expression;
134
+ }
135
+ }
136
+ }
137
+
138
+ switch (to) {
139
+ case "number":
140
+ if (isTerraformVariableOrLocal) {
141
+ return changeValueAccessor(ast as t.MemberExpression, "numberValue");
142
+ }
143
+ addTokenToImports();
144
+ return template.expression(`Token.asNumber(%%ast%%)`)({
145
+ ast: ast,
146
+ }) as t.Expression;
147
+ case "string":
148
+ if (isTerraformVariableOrLocal) {
149
+ return changeValueAccessor(ast as t.MemberExpression, "stringValue");
150
+ }
151
+ addTokenToImports();
152
+ return template.expression(`Token.asString(%%ast%%)`)({
153
+ ast: ast,
154
+ }) as t.Expression;
155
+ case "bool":
156
+ if (isTerraformVariableOrLocal) {
157
+ return changeValueAccessor(ast as t.MemberExpression, "booleanValue");
158
+ }
159
+ addTokenToImports();
160
+ return template.expression(`Token.asBoolean(%%ast%%)`)({
161
+ ast: ast,
162
+ }) as t.Expression;
163
+ }
164
+
165
+ logger.debug(`Could not coerce from ${from} to ${to} for ${ast}`);
166
+ return ast;
167
+ };
168
+
169
+ export function findExpressionType(
170
+ scope: ProgramScope,
171
+ ast: t.Expression,
172
+ ): AttributeType {
173
+ const isReferenceWithoutTemplateString =
174
+ ast.type === "MemberExpression" && ast.object.type === "Identifier";
175
+
176
+ // If we have a property to cdktf.propertyAccess call it's dynamic
177
+ if (ast.type === "CallExpression") {
178
+ return typeForCallExpression(ast);
179
+ }
180
+
181
+ if (ast.type === "StringLiteral") {
182
+ return "string";
183
+ }
184
+ if (ast.type === "NumericLiteral") {
185
+ return "number";
186
+ }
187
+ if (ast.type === "BooleanLiteral") {
188
+ return "bool";
189
+ }
190
+
191
+ // If we only have one reference this is a
192
+ if (isReferenceWithoutTemplateString) {
193
+ const destructuredAst = destructureAst(ast);
194
+ if (!destructuredAst) {
195
+ logger.debug(
196
+ `Could not destructure ast: ${JSON.stringify(ast, null, 2)}`,
197
+ );
198
+ return "dynamic";
199
+ }
200
+
201
+ const [astVariableName, ...attributes] = destructuredAst;
202
+ const variable = Object.values(scope.variables).find(
203
+ (x) => x.variableName === astVariableName,
204
+ );
205
+
206
+ if (!variable) {
207
+ logger.debug(
208
+ `Could not find variable ${astVariableName} given scope: ${JSON.stringify(
209
+ scope.variables,
210
+ null,
211
+ 2,
212
+ )}`,
213
+ );
214
+ // We don't know, this should not happen, but if it does we assume the worst case and make it dynamic
215
+ return "dynamic";
216
+ }
217
+
218
+ if (variable.resource === "var") {
219
+ return "dynamic";
220
+ }
221
+
222
+ const { resource: resourceType } = variable;
223
+ const [provider, ...resourceNameFragments] = resourceType.split("_");
224
+ const tfResourcePath = `${provider}.${resourceNameFragments.join(
225
+ "_",
226
+ )}.${attributes.map((x) => toSnakeCase(x)).join(".")}`;
227
+ const type = getTypeAtPath(scope.providerSchema, tfResourcePath);
228
+
229
+ // If this is an attribute type we can return it
230
+ if (typeof type === "string" || Array.isArray(type)) {
231
+ return type;
232
+ }
233
+
234
+ // Either nothing is found or it's a block type
235
+ return "dynamic";
236
+ }
237
+
238
+ return "string";
239
+ }
240
+
241
+ /*
242
+ * Transforms a babel AST into a list of string accessors
243
+ * e.g. foo.bar.baz -> ["foo", "bar", "baz"]
244
+ */
245
+ function destructureAst(ast: t.Expression): string[] | undefined {
246
+ switch (ast.type) {
247
+ case "Identifier":
248
+ return [ast.name];
249
+ case "MemberExpression": {
250
+ const object = destructureAst(ast.object);
251
+ const property = destructureAst(ast.property as t.Expression);
252
+ if (object && property) {
253
+ return [...object, ...property];
254
+ } else {
255
+ return undefined;
256
+ }
257
+ }
258
+ default:
259
+ return undefined;
260
+ }
261
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Copyright (c) HashiCorp, Inc.
3
+ * SPDX-License-Identifier: MPL-2.0
4
+ */
5
+
6
+ import { DynamicBlock, TerraformResourceBlock } from "./types";
7
+
8
+ export function isNestedDynamicBlock(
9
+ dynBlocks: DynamicBlock[],
10
+ block: DynamicBlock,
11
+ ): boolean {
12
+ return dynBlocks.some(
13
+ (dyn) => dyn.path !== block.path && block.path.startsWith(dyn.path),
14
+ );
15
+ }
16
+
17
+ export const extractDynamicBlocks = (
18
+ config: TerraformResourceBlock,
19
+ path = "",
20
+ ): DynamicBlock[] => {
21
+ if (typeof config !== "object") {
22
+ return [];
23
+ }
24
+
25
+ if (!config) {
26
+ return [];
27
+ }
28
+
29
+ if (Array.isArray(config)) {
30
+ return config.reduce(
31
+ (carry, item, index) => [
32
+ ...carry,
33
+ ...extractDynamicBlocks(item, `${path}.${index}`),
34
+ ],
35
+ [],
36
+ );
37
+ }
38
+
39
+ if ("dynamic" in config) {
40
+ const dynamic = (config as any).dynamic;
41
+ const scopedVar = Object.keys(dynamic)[0];
42
+ const { for_each, content } = dynamic[scopedVar][0];
43
+
44
+ return [
45
+ {
46
+ path: `${path}.dynamic.${scopedVar}`,
47
+ for_each,
48
+ content,
49
+ scopedVar,
50
+ },
51
+ ...extractDynamicBlocks(
52
+ content,
53
+ `${path}.dynamic.${scopedVar}.0.content`,
54
+ ),
55
+ ];
56
+ }
57
+
58
+ return Object.entries(config).reduce((carry, [key, value]) => {
59
+ return [...carry, ...extractDynamicBlocks(value as any, `${path}.${key}`)];
60
+ }, [] as DynamicBlock[]);
61
+ };