@cdktn/hcl2cdk 0.24.0-pre.45 → 0.24.0-pre.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +355 -0
- package/README.md +1 -1
- package/build/__tests__/expressions.test.js +10 -19
- package/build/__tests__/functions.test.js +8 -18
- package/build/__tests__/testHelpers.js +3 -2
- package/build/coerceType.js +11 -21
- package/build/dynamic-blocks.js +3 -3
- package/build/expressions.js +13 -22
- package/build/function-bindings/functions.generated.js +2 -2
- package/build/generation.js +24 -34
- package/build/index.js +15 -25
- package/build/iteration.js +7 -6
- package/build/jsii-rosetta-workarounds.js +6 -5
- package/build/partialCode.js +11 -20
- package/build/provider.js +4 -3
- package/build/references.js +6 -5
- package/build/schema.js +8 -18
- package/build/terraformSchema.js +4 -4
- package/build/utils.js +3 -3
- package/build/variables.js +12 -21
- package/package.json +20 -17
- package/package.sh +1 -1
- package/src/__tests__/coerceType.test.ts +207 -0
- package/src/__tests__/expressionToTs.test.ts +1167 -0
- package/src/__tests__/expressions.test.ts +541 -0
- package/src/__tests__/findExpressionType.test.ts +112 -0
- package/src/__tests__/functions.test.ts +768 -0
- package/src/__tests__/generation.test.ts +72 -0
- package/src/__tests__/jsii-rosetta-workarounds.test.ts +145 -0
- package/src/__tests__/partialCode.test.ts +432 -0
- package/src/__tests__/terraformSchema.test.ts +107 -0
- package/src/__tests__/testHelpers.ts +11 -0
- package/src/coerceType.ts +261 -0
- package/src/dynamic-blocks.ts +61 -0
- package/src/expressions.ts +968 -0
- package/src/function-bindings/functions.generated.ts +1139 -0
- package/src/function-bindings/functions.ts +104 -0
- package/src/generation.ts +1189 -0
- package/src/index.ts +584 -0
- package/src/iteration.ts +156 -0
- package/src/jsii-rosetta-workarounds.ts +145 -0
- package/src/partialCode.ts +132 -0
- package/src/provider.ts +60 -0
- package/src/references.ts +193 -0
- package/src/schema.ts +74 -0
- package/src/terraformSchema.ts +182 -0
- package/src/types.ts +58 -0
- package/src/utils.ts +19 -0
- package/src/variables.ts +214 -0
- package/test/__snapshots__/backends.test.ts.snap +70 -0
- package/test/__snapshots__/externals.test.ts.snap +37 -0
- package/test/__snapshots__/granular-imports.test.ts.snap +180 -0
- package/test/__snapshots__/imports.test.ts.snap +159 -0
- package/test/__snapshots__/iteration.test.ts.snap +532 -0
- package/test/__snapshots__/jsiiLanguage.test.ts.snap +347 -0
- package/test/__snapshots__/locals.test.ts.snap +55 -0
- package/test/__snapshots__/modules.test.ts.snap +127 -0
- package/test/__snapshots__/outputs.test.ts.snap +77 -0
- package/test/__snapshots__/partialCode.test.ts.snap +120 -0
- package/test/__snapshots__/provider.test.ts.snap +128 -0
- package/test/__snapshots__/references.test.ts.snap +376 -0
- package/test/__snapshots__/resource-meta-properties.test.ts.snap +342 -0
- package/test/__snapshots__/resources.test.ts.snap +613 -0
- package/test/__snapshots__/tfExpressions.test.ts.snap +537 -0
- package/test/__snapshots__/typeCoercion.test.ts.snap +253 -0
- package/test/__snapshots__/variables.test.ts.snap +150 -0
- package/test/backends.test.ts +75 -0
- package/test/convertProject.test.ts +257 -0
- package/test/externals.test.ts +35 -0
- package/test/globalSetup.ts +224 -0
- package/test/globalTeardown.ts +11 -0
- package/test/granular-imports.test.ts +161 -0
- package/test/hcl2cdk.test.ts +88 -0
- package/test/helpers/convert.ts +543 -0
- package/test/helpers/tmp.ts +25 -0
- package/test/imports.test.ts +141 -0
- package/test/iteration.test.ts +342 -0
- package/test/jsiiLanguage.test.ts +73 -0
- package/test/locals.test.ts +47 -0
- package/test/modules.test.ts +143 -0
- package/test/outputs.test.ts +69 -0
- package/test/partialCode.test.ts +25 -0
- package/test/provider.test.ts +106 -0
- package/test/references.test.ts +287 -0
- package/test/resource-meta-properties.test.ts +288 -0
- package/test/resources.test.ts +551 -0
- package/test/tfExpressions.test.ts +300 -0
- package/test/typeCoercion.test.ts +154 -0
- package/test/variables.test.ts +96 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import * as t from "@babel/types";
|
|
4
|
+
import template from "@babel/template";
|
|
5
|
+
import { camelCase, fileBugCommentText, logger } from "./utils";
|
|
6
|
+
import {
|
|
7
|
+
IteratorVariableReference,
|
|
8
|
+
ProgramScope,
|
|
9
|
+
ResourceScope,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { getReferencesInExpression, getExpressionAst } from "@cdktn/hcl2json";
|
|
12
|
+
import {
|
|
13
|
+
TFExpressionSyntaxTree as tex,
|
|
14
|
+
wrapTerraformExpression,
|
|
15
|
+
} from "@cdktn/hcl2json";
|
|
16
|
+
import { functionsMap } from "./function-bindings/functions";
|
|
17
|
+
import { coerceType, findExpressionType } from "./coerceType";
|
|
18
|
+
import { AttributeType } from "@cdktn/commons";
|
|
19
|
+
import { getTypeAtPath } from "./terraformSchema";
|
|
20
|
+
import { containsReference } from "./references";
|
|
21
|
+
import { variableName } from "./variables";
|
|
22
|
+
|
|
23
|
+
const tfBinaryOperatorsToCdktf = {
|
|
24
|
+
logicalOr: "or",
|
|
25
|
+
logicalAnd: "and",
|
|
26
|
+
greaterThan: "gt",
|
|
27
|
+
greaterThanOrEqual: "gte",
|
|
28
|
+
lessThan: "lt",
|
|
29
|
+
lessThanOrEqual: "lte",
|
|
30
|
+
equal: "eq",
|
|
31
|
+
notEqual: "neq",
|
|
32
|
+
add: "add",
|
|
33
|
+
subtract: "sub",
|
|
34
|
+
multiply: "mul",
|
|
35
|
+
divide: "div",
|
|
36
|
+
modulo: "mod",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const tfUnaryOperatorsToCdktf = {
|
|
40
|
+
logicalNot: "not",
|
|
41
|
+
negate: "negate",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type supportedBinaryOperators = keyof typeof tfBinaryOperatorsToCdktf;
|
|
45
|
+
type supportedUnaryOperators = keyof typeof tfUnaryOperatorsToCdktf;
|
|
46
|
+
|
|
47
|
+
function traversalPartsToString(
|
|
48
|
+
traversals: tex.TerraformTraversalPart[],
|
|
49
|
+
asSuffix = false,
|
|
50
|
+
) {
|
|
51
|
+
let seed = "";
|
|
52
|
+
if (asSuffix && tex.isNameTraversalPart(traversals[0])) {
|
|
53
|
+
seed = ".";
|
|
54
|
+
}
|
|
55
|
+
return traversals.reduce((acc, part) => {
|
|
56
|
+
if (part.type === "nameTraversal") {
|
|
57
|
+
if (acc === seed) {
|
|
58
|
+
return `${acc}${part.segment}`;
|
|
59
|
+
}
|
|
60
|
+
return `${acc}.${part.segment}`;
|
|
61
|
+
}
|
|
62
|
+
return `${acc}[${part.segment}]`;
|
|
63
|
+
}, seed);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function canUseFqn(expression: tex.ExpressionType) {
|
|
67
|
+
if (!tex.isScopeTraversalExpression(expression)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rootSegment = expression.meta.traversal[0].segment;
|
|
72
|
+
|
|
73
|
+
return !["var", "local"].includes(rootSegment);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function traversalToVariableName(
|
|
77
|
+
scope: ProgramScope,
|
|
78
|
+
node: tex.ExpressionType,
|
|
79
|
+
) {
|
|
80
|
+
if (!tex.isScopeTraversalExpression(node)) {
|
|
81
|
+
logger.error(
|
|
82
|
+
`Unexpected expression type ${node.type} with value ${node.meta.value} passed to convert to a variable. ${fileBugCommentText}`,
|
|
83
|
+
);
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const segments = node.meta.traversal;
|
|
88
|
+
if (segments.length === 1) {
|
|
89
|
+
return node.meta.fullAccessor;
|
|
90
|
+
}
|
|
91
|
+
const rootSegment = segments[0].segment;
|
|
92
|
+
const resource =
|
|
93
|
+
rootSegment === "data"
|
|
94
|
+
? `${segments[0].segment}.${segments[1].segment}`
|
|
95
|
+
: rootSegment;
|
|
96
|
+
const name =
|
|
97
|
+
rootSegment === "data" ? segments[2].segment : segments[1].segment;
|
|
98
|
+
|
|
99
|
+
return variableName(scope, resource, name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function expressionForSerialStringConcatenation(
|
|
103
|
+
scope: ResourceScope,
|
|
104
|
+
nodes: t.Expression[],
|
|
105
|
+
) {
|
|
106
|
+
const reducedNodes = nodes.reduce((acc, node) => {
|
|
107
|
+
const prev = acc[acc.length - 1];
|
|
108
|
+
if (!prev) return [node];
|
|
109
|
+
|
|
110
|
+
if (t.isStringLiteral(prev) && t.isStringLiteral(node)) {
|
|
111
|
+
acc.pop();
|
|
112
|
+
acc.push(t.stringLiteral(prev.value + node.value));
|
|
113
|
+
return acc;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
acc.push(node);
|
|
117
|
+
return acc;
|
|
118
|
+
}, [] as t.Expression[]);
|
|
119
|
+
|
|
120
|
+
return reducedNodes.reduce(
|
|
121
|
+
(acc: t.Expression | undefined, node: t.Expression) => {
|
|
122
|
+
if (!acc) {
|
|
123
|
+
return node;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// wrap access to dynamic blocks in Token.asString() as they return a Lazy
|
|
127
|
+
// for .key and .value which can't be concatenated in languages like Python
|
|
128
|
+
// because JSII currently has no support for the toString() method in
|
|
129
|
+
// languages other than TypeScript: https://github.com/aws/jsii/issues/380
|
|
130
|
+
// example: dynamic_iterator0.key / dynamic_iterator0.value
|
|
131
|
+
if (
|
|
132
|
+
t.isMemberExpression(node) &&
|
|
133
|
+
t.isIdentifier(node.object) &&
|
|
134
|
+
Object.values(scope.scopedVariables || {}).includes(node.object.name) &&
|
|
135
|
+
t.isIdentifier(node.property) &&
|
|
136
|
+
["key", "value"].includes(node.property.name)
|
|
137
|
+
) {
|
|
138
|
+
node = coerceType(scope, node, "dynamic", "string");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return t.binaryExpression("+", acc as t.Expression, node);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getTfResourcePathFromNode(node: tex.ScopeTraversalExpression) {
|
|
147
|
+
const segments = node.meta.traversal;
|
|
148
|
+
let resource = segments[0].segment;
|
|
149
|
+
let result = [];
|
|
150
|
+
let attributes = [];
|
|
151
|
+
|
|
152
|
+
if (segments[0].segment === "data") {
|
|
153
|
+
result.push(segments[0].segment);
|
|
154
|
+
resource = segments[1].segment;
|
|
155
|
+
attributes = segments.slice(3); // we want to skip the variable name
|
|
156
|
+
} else {
|
|
157
|
+
attributes = segments.slice(2); // we want to skip the variable name
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const [provider, ...resourceNameFragments] = resource.split("_");
|
|
161
|
+
|
|
162
|
+
// Hack: This happens in the case of `external` provider
|
|
163
|
+
// where the data source does not have a provider name prefix
|
|
164
|
+
if (resourceNameFragments.length === 0) {
|
|
165
|
+
resourceNameFragments.push(provider);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
result.push(provider);
|
|
169
|
+
result.push(resourceNameFragments.join("_"));
|
|
170
|
+
result = [
|
|
171
|
+
...result,
|
|
172
|
+
...attributes.map((seg) => {
|
|
173
|
+
if (tex.isIndexTraversalPart(seg)) {
|
|
174
|
+
return `[${seg.segment}]`;
|
|
175
|
+
}
|
|
176
|
+
return seg.segment;
|
|
177
|
+
}),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
return result.join(".");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function convertLiteralValueExpressionToTs(
|
|
184
|
+
_scope: ResourceScope,
|
|
185
|
+
node: tex.LiteralValueExpression,
|
|
186
|
+
) {
|
|
187
|
+
const literalType = node.meta.type;
|
|
188
|
+
if (literalType === "number") {
|
|
189
|
+
return t.numericLiteral(Number(node.meta.value));
|
|
190
|
+
}
|
|
191
|
+
if (literalType === "bool") {
|
|
192
|
+
return t.booleanLiteral(node.meta.value === "true" ? true : false);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return t.stringLiteral(node.meta.value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function convertScopeTraversalExpressionToTs(
|
|
199
|
+
scope: ResourceScope,
|
|
200
|
+
node: tex.ScopeTraversalExpression,
|
|
201
|
+
) {
|
|
202
|
+
const hasReference = containsReference(node);
|
|
203
|
+
|
|
204
|
+
const segments = node.meta.traversal;
|
|
205
|
+
|
|
206
|
+
if (segments[0].segment === "each" && scope.forEachIteratorName) {
|
|
207
|
+
return dynamicVariableToAst(scope, node, scope.forEachIteratorName);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (segments[0].segment === "count" && scope.countIteratorName) {
|
|
211
|
+
return dynamicVariableToAst(scope, node, scope.countIteratorName, "count");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (segments[0].segment === "self") {
|
|
215
|
+
scope.importables.push({
|
|
216
|
+
constructName: "TerraformSelf",
|
|
217
|
+
provider: "cdktn",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return t.callExpression(
|
|
221
|
+
t.memberExpression(t.identifier("TerraformSelf"), t.identifier("getAny")),
|
|
222
|
+
|
|
223
|
+
[t.stringLiteral(traversalPartsToString(segments.slice(1)))],
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// setting.value, setting.value[1].id
|
|
228
|
+
const dynamicBlock = scope.scopedVariables?.[segments[0].segment];
|
|
229
|
+
if (dynamicBlock) {
|
|
230
|
+
if (dynamicBlock === "dynamic-block") {
|
|
231
|
+
return dynamicVariableToAst(
|
|
232
|
+
scope,
|
|
233
|
+
node,
|
|
234
|
+
dynamicBlock,
|
|
235
|
+
traversalPartsToString(segments),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return dynamicVariableToAst(scope, node, dynamicBlock, segments[0].segment);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// This may be a variable reference that we don't understand yet, so we wrap it in a template string
|
|
242
|
+
// for Terraform to handle
|
|
243
|
+
let varIdentifier: t.Expression = t.stringLiteral(
|
|
244
|
+
`\${${node.meta.fullAccessor}}`,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (hasReference) {
|
|
248
|
+
varIdentifier = t.identifier(
|
|
249
|
+
camelCase(traversalToVariableName(scope, node)),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (["var", "local"].includes(segments[0].segment)) {
|
|
254
|
+
const variableAccessor =
|
|
255
|
+
segments[0].segment === "var"
|
|
256
|
+
? t.memberExpression(varIdentifier, t.identifier("value"))
|
|
257
|
+
: varIdentifier;
|
|
258
|
+
|
|
259
|
+
if (segments.length > 2) {
|
|
260
|
+
scope.importables.push({
|
|
261
|
+
constructName: "Fn",
|
|
262
|
+
provider: "cdktn",
|
|
263
|
+
});
|
|
264
|
+
const callee = t.memberExpression(
|
|
265
|
+
t.identifier("Fn"),
|
|
266
|
+
t.identifier("lookupNested"),
|
|
267
|
+
);
|
|
268
|
+
return t.callExpression(callee, [
|
|
269
|
+
variableAccessor,
|
|
270
|
+
t.arrayExpression(
|
|
271
|
+
segments.slice(2).map((s) => t.stringLiteral(s.segment)),
|
|
272
|
+
),
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return variableAccessor;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!hasReference || scope.withinOverrideExpression) {
|
|
280
|
+
return varIdentifier;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const rootSegment = segments[0].segment;
|
|
284
|
+
const attributeIndex = rootSegment === "data" ? 3 : 2;
|
|
285
|
+
const attributeSegments = segments.slice(attributeIndex);
|
|
286
|
+
const numericAccessorIndex = attributeSegments.findIndex((seg) =>
|
|
287
|
+
tex.isIndexTraversalPart(seg),
|
|
288
|
+
);
|
|
289
|
+
let minAccessorIndex = numericAccessorIndex;
|
|
290
|
+
let mapAccessorIndex = -1;
|
|
291
|
+
if (numericAccessorIndex === -1) {
|
|
292
|
+
// only do this if we have to, if we already have a
|
|
293
|
+
// numeric accessor, we don't have to do this additional work
|
|
294
|
+
const resourcePath = getTfResourcePathFromNode(node);
|
|
295
|
+
let usingSubPathType = false;
|
|
296
|
+
const parts = resourcePath.split(".").filter((p) => p !== "");
|
|
297
|
+
const minParts = attributeIndex; // we need to stop before data.aws.resource_name or aws.resource_name
|
|
298
|
+
const originalParts = parts.length;
|
|
299
|
+
let hasMapAccessor = false;
|
|
300
|
+
while (parts.length >= minParts) {
|
|
301
|
+
const type = getTypeAtPath(scope.providerSchema, parts.join("."));
|
|
302
|
+
if (type !== null) {
|
|
303
|
+
if (Array.isArray(type) && type[0] === "map" && usingSubPathType) {
|
|
304
|
+
hasMapAccessor = true;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
parts.pop();
|
|
309
|
+
usingSubPathType = true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (hasMapAccessor) {
|
|
313
|
+
mapAccessorIndex = originalParts - parts.length - 1;
|
|
314
|
+
minAccessorIndex = mapAccessorIndex;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const needsPropertyAccess = minAccessorIndex >= 0;
|
|
319
|
+
|
|
320
|
+
const refSegments = needsPropertyAccess
|
|
321
|
+
? attributeSegments.slice(0, minAccessorIndex)
|
|
322
|
+
: attributeSegments;
|
|
323
|
+
const nonRefSegments = needsPropertyAccess
|
|
324
|
+
? attributeSegments.slice(minAccessorIndex)
|
|
325
|
+
: [];
|
|
326
|
+
|
|
327
|
+
const ref = refSegments.reduce(
|
|
328
|
+
(acc: t.Expression, seg, index) =>
|
|
329
|
+
t.memberExpression(
|
|
330
|
+
acc,
|
|
331
|
+
t.identifier(
|
|
332
|
+
index === 0 && rootSegment === "module"
|
|
333
|
+
? camelCase(seg.segment + "Output")
|
|
334
|
+
: camelCase(seg.segment),
|
|
335
|
+
),
|
|
336
|
+
),
|
|
337
|
+
varIdentifier,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (nonRefSegments.length === 0) {
|
|
341
|
+
return ref;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
scope.importables.push({
|
|
345
|
+
constructName: "Fn",
|
|
346
|
+
provider: "cdktn",
|
|
347
|
+
});
|
|
348
|
+
const callee = t.memberExpression(
|
|
349
|
+
t.identifier("Fn"),
|
|
350
|
+
t.identifier("lookupNested"),
|
|
351
|
+
);
|
|
352
|
+
return t.callExpression(callee, [
|
|
353
|
+
ref,
|
|
354
|
+
t.arrayExpression(nonRefSegments.map((s) => t.stringLiteral(s.segment))),
|
|
355
|
+
]);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function convertUnaryOpExpressionToTs(
|
|
359
|
+
scope: ResourceScope,
|
|
360
|
+
node: tex.UnaryOpExpression,
|
|
361
|
+
) {
|
|
362
|
+
const operand = convertTFExpressionAstToTs(
|
|
363
|
+
scope,
|
|
364
|
+
tex.getChildWithValue(node, node.meta.valueExpression)!,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
let fnName = node.meta.operator;
|
|
368
|
+
if (tfUnaryOperatorsToCdktf[fnName as supportedUnaryOperators]) {
|
|
369
|
+
fnName = tfUnaryOperatorsToCdktf[fnName as supportedUnaryOperators];
|
|
370
|
+
} else {
|
|
371
|
+
throw new Error(`Cannot convert unknown operator ${node.meta.operator}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
scope.importables.push({
|
|
375
|
+
constructName: "Op",
|
|
376
|
+
provider: "cdktn",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const fn = t.memberExpression(t.identifier("Op"), t.identifier(fnName));
|
|
380
|
+
|
|
381
|
+
return t.callExpression(fn, [operand]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function convertBinaryOpExpressionToTs(
|
|
385
|
+
scope: ResourceScope,
|
|
386
|
+
node: tex.BinaryOpExpression,
|
|
387
|
+
) {
|
|
388
|
+
const left = convertTFExpressionAstToTs(
|
|
389
|
+
scope,
|
|
390
|
+
tex.getChildWithValue(node, node.meta.lhsExpression)!,
|
|
391
|
+
);
|
|
392
|
+
const right = convertTFExpressionAstToTs(
|
|
393
|
+
scope,
|
|
394
|
+
tex.getChildWithValue(node, node.meta.rhsExpression)!,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
let fnName = node.meta.operator;
|
|
398
|
+
if (tfBinaryOperatorsToCdktf[fnName as supportedBinaryOperators]) {
|
|
399
|
+
fnName = tfBinaryOperatorsToCdktf[fnName as supportedBinaryOperators];
|
|
400
|
+
} else {
|
|
401
|
+
throw new Error(`Cannot convert unknown operator ${node.meta.operator}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
scope.importables.push({
|
|
405
|
+
constructName: "Op",
|
|
406
|
+
provider: "cdktn",
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const fn = t.memberExpression(t.identifier("Op"), t.identifier(fnName));
|
|
410
|
+
return t.callExpression(fn, [left, right]);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function convertTemplateExpressionToTs(
|
|
414
|
+
scope: ResourceScope,
|
|
415
|
+
node: tex.TemplateExpression | tex.TemplateWrapExpression,
|
|
416
|
+
) {
|
|
417
|
+
const parts = node.children.map((child) => ({
|
|
418
|
+
node: child,
|
|
419
|
+
expr: convertTFExpressionAstToTs(scope, child),
|
|
420
|
+
}));
|
|
421
|
+
|
|
422
|
+
const lastPart = parts[parts.length - 1];
|
|
423
|
+
if (t.isStringLiteral(lastPart.expr) && lastPart.expr.value === "\n") {
|
|
424
|
+
// This is a bit of a hack, but the trailing newline we add due to
|
|
425
|
+
// heredocs looks ugly and unnecessary in the generated code, so we
|
|
426
|
+
// try to remove it
|
|
427
|
+
parts.pop();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (parts.length === 0) {
|
|
431
|
+
return t.stringLiteral(node.meta.value);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (parts.length === 1) {
|
|
435
|
+
return parts[0].expr;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let isScopedTraversal = false;
|
|
439
|
+
const expressions: t.Expression[] = [];
|
|
440
|
+
for (const { node, expr } of parts) {
|
|
441
|
+
if (
|
|
442
|
+
tex.isScopeTraversalExpression(node) &&
|
|
443
|
+
!t.isStringLiteral(expr) &&
|
|
444
|
+
!t.isCallExpression(expr)
|
|
445
|
+
) {
|
|
446
|
+
expressions.push(t.stringLiteral("${"));
|
|
447
|
+
isScopedTraversal = true;
|
|
448
|
+
} else if (
|
|
449
|
+
// we should ideally be doing type coercion more
|
|
450
|
+
// carefully here, because it may not always be needed
|
|
451
|
+
t.isCallExpression(expr)
|
|
452
|
+
) {
|
|
453
|
+
scope.importables.push({
|
|
454
|
+
constructName: "Token",
|
|
455
|
+
provider: "cdktn",
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
expressions.push(
|
|
459
|
+
template.expression(`Token.asString(%%expr%%)`)({
|
|
460
|
+
expr,
|
|
461
|
+
}) as t.Expression,
|
|
462
|
+
);
|
|
463
|
+
continue;
|
|
464
|
+
} else {
|
|
465
|
+
if (isScopedTraversal) {
|
|
466
|
+
expressions.push(t.stringLiteral("}"));
|
|
467
|
+
isScopedTraversal = false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
expressions.push(expr);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (isScopedTraversal) {
|
|
474
|
+
expressions.push(t.stringLiteral("}"));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return expressionForSerialStringConcatenation(scope, expressions);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function convertObjectExpressionToTs(
|
|
481
|
+
scope: ResourceScope,
|
|
482
|
+
node: tex.ObjectExpression,
|
|
483
|
+
) {
|
|
484
|
+
return t.objectExpression(
|
|
485
|
+
Object.entries(node.meta.items)
|
|
486
|
+
.map(([key, value]) => {
|
|
487
|
+
const valueChild = tex.getChildWithValue(node, value);
|
|
488
|
+
if (!valueChild) {
|
|
489
|
+
logger.error(`Unable to value for object key '${key}': ${value}`);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return t.objectProperty(
|
|
494
|
+
t.identifier(key),
|
|
495
|
+
convertTFExpressionAstToTs(scope, valueChild),
|
|
496
|
+
);
|
|
497
|
+
})
|
|
498
|
+
.filter((s) => s !== null) as t.ObjectProperty[],
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function convertFunctionCallExpressionToTs(
|
|
503
|
+
scope: ResourceScope,
|
|
504
|
+
node: tex.FunctionCallExpression,
|
|
505
|
+
) {
|
|
506
|
+
const functionName = node.meta.name;
|
|
507
|
+
const mapping = functionsMap[functionName];
|
|
508
|
+
|
|
509
|
+
if (!mapping) {
|
|
510
|
+
logger.error(
|
|
511
|
+
`Unknown function ${functionName} encountered. ${fileBugCommentText}`,
|
|
512
|
+
);
|
|
513
|
+
const argumentExpressions = node.children.map((child) =>
|
|
514
|
+
convertTFExpressionAstToTs(scope, child),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return t.callExpression(t.identifier(functionName), argumentExpressions);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const transformedNode: tex.FunctionCallExpression = mapping.transformer
|
|
521
|
+
? mapping.transformer(node)
|
|
522
|
+
: node;
|
|
523
|
+
|
|
524
|
+
const argumentExpressions = transformedNode.children.map((child) =>
|
|
525
|
+
convertTFExpressionAstToTs(scope, child),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
scope.importables.push({
|
|
529
|
+
constructName: "Fn",
|
|
530
|
+
provider: "cdktn",
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const callee = t.memberExpression(
|
|
534
|
+
t.identifier("Fn"),
|
|
535
|
+
t.identifier(mapping.name),
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
if (
|
|
539
|
+
mapping.parameters.length > 0 &&
|
|
540
|
+
mapping.parameters[mapping.parameters.length - 1].variadic
|
|
541
|
+
) {
|
|
542
|
+
const lastParameterType =
|
|
543
|
+
mapping.parameters[mapping.parameters.length - 1].type;
|
|
544
|
+
const nonVariadicArguments = argumentExpressions.slice(
|
|
545
|
+
0,
|
|
546
|
+
mapping.parameters.length - 1,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const fnCallArguments = [
|
|
550
|
+
...nonVariadicArguments.map((argExpr, index) =>
|
|
551
|
+
coerceType(
|
|
552
|
+
scope,
|
|
553
|
+
argExpr,
|
|
554
|
+
findExpressionType(scope, argExpr),
|
|
555
|
+
mapping.parameters[index].type,
|
|
556
|
+
),
|
|
557
|
+
),
|
|
558
|
+
|
|
559
|
+
t.arrayExpression(
|
|
560
|
+
argumentExpressions
|
|
561
|
+
.slice(mapping.parameters.length - 1)
|
|
562
|
+
.map((argExpr) =>
|
|
563
|
+
coerceType(
|
|
564
|
+
scope,
|
|
565
|
+
argExpr,
|
|
566
|
+
findExpressionType(scope, argExpr),
|
|
567
|
+
lastParameterType,
|
|
568
|
+
),
|
|
569
|
+
),
|
|
570
|
+
),
|
|
571
|
+
];
|
|
572
|
+
|
|
573
|
+
return t.callExpression(callee, fnCallArguments);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return t.callExpression(
|
|
577
|
+
callee,
|
|
578
|
+
argumentExpressions.map((argExpr, index) =>
|
|
579
|
+
coerceType(
|
|
580
|
+
scope,
|
|
581
|
+
argExpr,
|
|
582
|
+
findExpressionType(scope, argExpr),
|
|
583
|
+
mapping.parameters[index].type,
|
|
584
|
+
),
|
|
585
|
+
),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function convertIndexExpressionToTs(
|
|
590
|
+
scope: ResourceScope,
|
|
591
|
+
node: tex.IndexExpression,
|
|
592
|
+
) {
|
|
593
|
+
const collectionExpressionChild = tex.getChildWithValue(
|
|
594
|
+
node,
|
|
595
|
+
node.meta.collectionExpression,
|
|
596
|
+
);
|
|
597
|
+
const keyExpressionChild = tex.getChildWithValue(
|
|
598
|
+
node,
|
|
599
|
+
node.meta.keyExpression,
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
const collectionExpression = convertTFExpressionAstToTs(
|
|
603
|
+
scope,
|
|
604
|
+
collectionExpressionChild!,
|
|
605
|
+
);
|
|
606
|
+
const keyExpression = convertTFExpressionAstToTs(scope, keyExpressionChild!);
|
|
607
|
+
|
|
608
|
+
scope.importables.push({
|
|
609
|
+
constructName: "Fn",
|
|
610
|
+
provider: "cdktn",
|
|
611
|
+
});
|
|
612
|
+
const callee = t.memberExpression(
|
|
613
|
+
t.identifier("Fn"),
|
|
614
|
+
t.identifier("lookupNested"),
|
|
615
|
+
);
|
|
616
|
+
return t.callExpression(callee, [
|
|
617
|
+
collectionExpression,
|
|
618
|
+
t.arrayExpression([keyExpression]),
|
|
619
|
+
]);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function convertSplatExpressionToTs(
|
|
623
|
+
scope: ResourceScope,
|
|
624
|
+
node: tex.SplatExpression,
|
|
625
|
+
) {
|
|
626
|
+
const sourceExpressionChild = tex.getChildWithValue(
|
|
627
|
+
node,
|
|
628
|
+
node.meta.sourceExpression,
|
|
629
|
+
)!;
|
|
630
|
+
const sourceExpression = convertTFExpressionAstToTs(
|
|
631
|
+
scope,
|
|
632
|
+
sourceExpressionChild,
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
// We don't convert the relative expression because everything after the splat is going to be
|
|
636
|
+
// a string
|
|
637
|
+
const relativeExpression = node.meta.eachExpression.startsWith(
|
|
638
|
+
node.meta.anonSymbolExpression,
|
|
639
|
+
)
|
|
640
|
+
? node.meta.eachExpression.slice(node.meta.anonSymbolExpression.length)
|
|
641
|
+
: node.meta.eachExpression;
|
|
642
|
+
|
|
643
|
+
const segments = relativeExpression.split(/\.|\[|\]/).filter((s) => s);
|
|
644
|
+
scope.importables.push({
|
|
645
|
+
constructName: "Fn",
|
|
646
|
+
provider: "cdktn",
|
|
647
|
+
});
|
|
648
|
+
const callee = t.memberExpression(
|
|
649
|
+
t.identifier("Fn"),
|
|
650
|
+
t.identifier("lookupNested"),
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
return t.callExpression(callee, [
|
|
654
|
+
sourceExpression,
|
|
655
|
+
t.arrayExpression([
|
|
656
|
+
// we don't need to use the anonSymbolExpression here because
|
|
657
|
+
// it only changes between .* and [*] which we don't care about
|
|
658
|
+
t.stringLiteral("*"),
|
|
659
|
+
...segments.map(t.stringLiteral),
|
|
660
|
+
]),
|
|
661
|
+
]);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function convertConditionalExpressionToTs(
|
|
665
|
+
scope: ResourceScope,
|
|
666
|
+
node: tex.ConditionalExpression,
|
|
667
|
+
) {
|
|
668
|
+
const conditionChild = tex.getChildWithValue(
|
|
669
|
+
node,
|
|
670
|
+
node.meta.conditionExpression,
|
|
671
|
+
)!;
|
|
672
|
+
let condition = convertTFExpressionAstToTs(scope, conditionChild);
|
|
673
|
+
if (t.isIdentifier(condition) && canUseFqn(conditionChild)) {
|
|
674
|
+
// We have a resource or data source here, which we would need to
|
|
675
|
+
// reference using fqn
|
|
676
|
+
condition = t.memberExpression(condition, t.identifier("fqn"));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const trueExpression = convertTFExpressionAstToTs(
|
|
680
|
+
scope,
|
|
681
|
+
tex.getChildWithValue(node, node.meta.trueExpression)!,
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const falseExpression = convertTFExpressionAstToTs(
|
|
685
|
+
scope,
|
|
686
|
+
tex.getChildWithValue(node, node.meta.falseExpression)!,
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
scope.importables.push({
|
|
690
|
+
constructName: "conditional",
|
|
691
|
+
provider: "cdktn",
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
return t.callExpression(t.identifier("conditional"), [
|
|
695
|
+
condition,
|
|
696
|
+
trueExpression,
|
|
697
|
+
falseExpression,
|
|
698
|
+
]);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function convertTupleExpressionToTs(
|
|
702
|
+
scope: ResourceScope,
|
|
703
|
+
node: tex.TupleExpression,
|
|
704
|
+
) {
|
|
705
|
+
const expressions = node.children.map((child) =>
|
|
706
|
+
convertTFExpressionAstToTs(scope, child),
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
return t.arrayExpression(expressions);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function convertRelativeTraversalExpressionToTs(
|
|
713
|
+
scope: ResourceScope,
|
|
714
|
+
node: tex.RelativeTraversalExpression,
|
|
715
|
+
) {
|
|
716
|
+
const segments = node.meta.traversal;
|
|
717
|
+
|
|
718
|
+
// The left hand side / source of a relative traversal is not a proper
|
|
719
|
+
// object / resource / data thing that is being referenced
|
|
720
|
+
const source = convertTFExpressionAstToTs(
|
|
721
|
+
scope,
|
|
722
|
+
tex.getChildWithValue(node, node.meta.sourceExpression)!,
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
scope.importables.push({
|
|
726
|
+
constructName: "Fn",
|
|
727
|
+
provider: "cdktn",
|
|
728
|
+
});
|
|
729
|
+
const callee = t.memberExpression(
|
|
730
|
+
t.identifier("Fn"),
|
|
731
|
+
t.identifier("lookupNested"),
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
return t.callExpression(callee, [
|
|
735
|
+
source,
|
|
736
|
+
t.arrayExpression(segments.map((s) => t.stringLiteral(s.segment))),
|
|
737
|
+
]);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function convertForExpressionToTs(
|
|
741
|
+
scope: ResourceScope,
|
|
742
|
+
node: tex.ForExpression,
|
|
743
|
+
) {
|
|
744
|
+
const collectionChild = tex.getChildWithValue(
|
|
745
|
+
node,
|
|
746
|
+
node.meta.collectionExpression,
|
|
747
|
+
)!;
|
|
748
|
+
|
|
749
|
+
let collectionExpression = convertTFExpressionAstToTs(scope, collectionChild);
|
|
750
|
+
|
|
751
|
+
if (t.isIdentifier(collectionExpression) && canUseFqn(collectionChild)) {
|
|
752
|
+
// We have a resource or data source here, which we would need to
|
|
753
|
+
// reference using fqn
|
|
754
|
+
collectionExpression = t.memberExpression(
|
|
755
|
+
collectionExpression,
|
|
756
|
+
t.identifier("fqn"),
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const collectionRequiresWrapping = !t.isStringLiteral(collectionExpression);
|
|
761
|
+
const expressions = [];
|
|
762
|
+
const conditionBody = node.meta.keyVar
|
|
763
|
+
? `${node.meta.keyVar}, ${node.meta.valVar}`
|
|
764
|
+
: node.meta.valVar;
|
|
765
|
+
|
|
766
|
+
const openBrace = node.meta.openRangeValue;
|
|
767
|
+
const closeBrace = node.meta.closeRangeValue;
|
|
768
|
+
const grouped = node.meta.groupedValue ? "..." : "";
|
|
769
|
+
const valueExpression = `${node.meta.valueExpression}${grouped}`;
|
|
770
|
+
|
|
771
|
+
const prefix = `\${${openBrace} for ${conditionBody} in `;
|
|
772
|
+
const keyValue = node.meta.keyExpression
|
|
773
|
+
? ` : ${node.meta.keyExpression} => ${valueExpression}`
|
|
774
|
+
: ` : ${valueExpression}`;
|
|
775
|
+
const conditional = node.meta.conditionalExpression;
|
|
776
|
+
const suffix = `${keyValue}${
|
|
777
|
+
conditional ? ` if ${conditional}` : ""
|
|
778
|
+
}${closeBrace}}`;
|
|
779
|
+
|
|
780
|
+
expressions.push(t.stringLiteral(prefix));
|
|
781
|
+
if (collectionRequiresWrapping) {
|
|
782
|
+
expressions.push(t.stringLiteral("${"));
|
|
783
|
+
}
|
|
784
|
+
expressions.push(collectionExpression);
|
|
785
|
+
if (collectionRequiresWrapping) {
|
|
786
|
+
expressions.push(t.stringLiteral("}"));
|
|
787
|
+
}
|
|
788
|
+
expressions.push(t.stringLiteral(suffix));
|
|
789
|
+
|
|
790
|
+
return expressionForSerialStringConcatenation(scope, expressions);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function convertTFExpressionAstToTs(
|
|
794
|
+
scope: ResourceScope,
|
|
795
|
+
node: tex.ExpressionType,
|
|
796
|
+
): t.Expression {
|
|
797
|
+
if (tex.isLiteralValueExpression(node)) {
|
|
798
|
+
return convertLiteralValueExpressionToTs(scope, node);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (tex.isScopeTraversalExpression(node)) {
|
|
802
|
+
return convertScopeTraversalExpressionToTs(scope, node);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (tex.isUnaryOpExpression(node)) {
|
|
806
|
+
return convertUnaryOpExpressionToTs(scope, node);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (tex.isBinaryOpExpression(node)) {
|
|
810
|
+
return convertBinaryOpExpressionToTs(scope, node);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (tex.isTemplateExpression(node) || tex.isTemplateWrapExpression(node)) {
|
|
814
|
+
return convertTemplateExpressionToTs(scope, node);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (tex.isObjectExpression(node)) {
|
|
818
|
+
return convertObjectExpressionToTs(scope, node);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (tex.isFunctionCallExpression(node)) {
|
|
822
|
+
return convertFunctionCallExpressionToTs(scope, node);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (tex.isIndexExpression(node)) {
|
|
826
|
+
return convertIndexExpressionToTs(scope, node);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (tex.isSplatExpression(node)) {
|
|
830
|
+
return convertSplatExpressionToTs(scope, node);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (tex.isConditionalExpression(node)) {
|
|
834
|
+
return convertConditionalExpressionToTs(scope, node);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (tex.isTupleExpression(node)) {
|
|
838
|
+
return convertTupleExpressionToTs(scope, node);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (tex.isRelativeTraversalExpression(node)) {
|
|
842
|
+
return convertRelativeTraversalExpressionToTs(scope, node);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (tex.isForExpression(node)) {
|
|
846
|
+
return convertForExpressionToTs(scope, node);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return t.stringLiteral("");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export async function expressionAst(
|
|
853
|
+
input: string,
|
|
854
|
+
): Promise<tex.ExpressionType> {
|
|
855
|
+
const { wrap, wrapOffset } = wrapTerraformExpression(input);
|
|
856
|
+
const ast = await getExpressionAst("main.tf", wrap);
|
|
857
|
+
|
|
858
|
+
if (!ast) {
|
|
859
|
+
throw new Error(`Unable to parse terraform expression: ${input}`);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (wrapOffset != 0 && tex.isTemplateWrapExpression(ast)) {
|
|
863
|
+
return ast.children[0];
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return ast;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export async function convertTerraformExpressionToTs(
|
|
870
|
+
scope: ResourceScope,
|
|
871
|
+
input: string,
|
|
872
|
+
targetType: () => AttributeType,
|
|
873
|
+
): Promise<t.Expression> {
|
|
874
|
+
logger.debug(`convertTerraformExpressionToTs(${input})`);
|
|
875
|
+
const tsExpression = convertTFExpressionAstToTs(
|
|
876
|
+
scope,
|
|
877
|
+
await expressionAst(input),
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
return coerceType(
|
|
881
|
+
scope,
|
|
882
|
+
tsExpression,
|
|
883
|
+
findExpressionType(scope, tsExpression),
|
|
884
|
+
targetType(),
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export async function extractIteratorVariablesFromExpression(
|
|
889
|
+
input: string,
|
|
890
|
+
): Promise<IteratorVariableReference[]> {
|
|
891
|
+
const possibleVariableSpots = await getReferencesInExpression(
|
|
892
|
+
"main.tf",
|
|
893
|
+
input,
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
return possibleVariableSpots
|
|
897
|
+
.filter((spot) => spot.value.startsWith("each."))
|
|
898
|
+
.map((spot) => ({
|
|
899
|
+
start: spot.startPosition,
|
|
900
|
+
end: spot.endPosition,
|
|
901
|
+
value: spot.value,
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export function dynamicVariableToAst(
|
|
906
|
+
scope: ProgramScope,
|
|
907
|
+
node: tex.ScopeTraversalExpression,
|
|
908
|
+
iteratorName: string,
|
|
909
|
+
block: string = "each",
|
|
910
|
+
): t.Expression {
|
|
911
|
+
if (iteratorName === "dynamic-block") {
|
|
912
|
+
return expressionForSerialStringConcatenation(scope, [
|
|
913
|
+
t.stringLiteral("${"),
|
|
914
|
+
t.stringLiteral(block),
|
|
915
|
+
t.stringLiteral("}"),
|
|
916
|
+
]);
|
|
917
|
+
}
|
|
918
|
+
if (node.meta.value === `${block}.key`) {
|
|
919
|
+
return t.memberExpression(t.identifier(iteratorName), t.identifier("key"));
|
|
920
|
+
}
|
|
921
|
+
if (node.meta.value === `${block}.value`) {
|
|
922
|
+
return t.memberExpression(
|
|
923
|
+
t.identifier(iteratorName),
|
|
924
|
+
t.identifier("value"),
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (block === "count" && node.meta.value === `${block}.index`) {
|
|
929
|
+
return t.memberExpression(
|
|
930
|
+
t.identifier(iteratorName),
|
|
931
|
+
t.identifier("index"),
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const segments = node.meta.traversal;
|
|
936
|
+
|
|
937
|
+
if (
|
|
938
|
+
segments.length > 2 &&
|
|
939
|
+
segments[0].segment === block &&
|
|
940
|
+
segments[1].segment === "value"
|
|
941
|
+
) {
|
|
942
|
+
const segmentsAfterEachValue = segments.slice(2);
|
|
943
|
+
scope.importables.push({
|
|
944
|
+
constructName: "Fn",
|
|
945
|
+
provider: "cdktn",
|
|
946
|
+
});
|
|
947
|
+
const callee = t.memberExpression(
|
|
948
|
+
t.identifier("Fn"),
|
|
949
|
+
t.identifier("lookupNested"),
|
|
950
|
+
);
|
|
951
|
+
return t.callExpression(callee, [
|
|
952
|
+
t.memberExpression(t.identifier(iteratorName), t.identifier("value")),
|
|
953
|
+
t.arrayExpression(
|
|
954
|
+
segmentsAfterEachValue.map((part) => {
|
|
955
|
+
if (part.type === "nameTraversal") {
|
|
956
|
+
return t.stringLiteral(part.segment);
|
|
957
|
+
} else {
|
|
958
|
+
return t.stringLiteral(`[${part.segment}]`);
|
|
959
|
+
}
|
|
960
|
+
}),
|
|
961
|
+
),
|
|
962
|
+
]);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
throw new Error(
|
|
966
|
+
`Can not create AST for iterator variable of '${node.meta.value}'`,
|
|
967
|
+
);
|
|
968
|
+
}
|