2024-09-09 12:05:45 +00:00
|
|
|
// Copyright 2020-2024, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-04-03 06:27:05 +00:00
|
|
|
package nodejs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-12-12 12:19:42 +00:00
|
|
|
"errors"
|
2020-04-07 02:43:16 +00:00
|
|
|
"fmt"
|
2020-04-03 06:27:05 +00:00
|
|
|
"io"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
2024-03-29 14:24:29 +00:00
|
|
|
"unicode"
|
2020-04-03 06:27:05 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2022-04-18 09:03:42 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
|
|
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
2021-09-30 03:11:56 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
|
2022-04-18 09:03:42 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
2020-04-03 06:27:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type nameInfo int
|
|
|
|
|
2020-04-25 05:04:24 +00:00
|
|
|
func (nameInfo) Format(name string) string {
|
2020-05-19 08:18:38 +00:00
|
|
|
return makeValidIdentifier(name)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
2022-04-18 09:03:42 +00:00
|
|
|
func (g *generator) lowerExpression(expr model.Expression, typ model.Type) model.Expression {
|
2020-04-03 06:27:05 +00:00
|
|
|
// TODO(pdg): diagnostics
|
2020-05-11 17:21:56 +00:00
|
|
|
if g.asyncMain {
|
|
|
|
expr = g.awaitInvokes(expr)
|
|
|
|
}
|
2021-09-30 03:11:56 +00:00
|
|
|
expr = pcl.RewritePropertyReferences(expr)
|
[program-gen] Emit Output-returning JSON serialization methods without rewriting applies (#15371)
### Description
A while ago we started implementing [specialized JSON serialization
methods](https://github.com/pulumi/pulumi/issues/12519) for Pulumi
programs which can accept nested outputs without having to rewrite and
combine applies.
- `Output.SerializeJson` in .NET
- `pulumi.jsonStringify` in nodejs
- `pulumi.Output.json_dumps` in Python
This PR extends program-gen for TypeScript, C# and Python to start
emitting these JSON serialization functions (when necessary). The PR
special-cases the `toJSON` PCL function when rewriting applies so that
nested outputs aren't rewritted.
Example PCL program and generated results:
> Also check out the downstream codegen tests to see improved generated
examples
```
resource vpc "aws:ec2:Vpc" {
cidrBlock = "10.100.0.0/16"
instanceTenancy = "default"
}
resource policy "aws:iam/policy:Policy" {
description = "test"
policy = toJSON({
"Version" = "2012-10-17"
"Interpolated" = "arn:${vpc.arn}:value"
"Value" = vpc.id
})
}
```
### Generated TypeScript Before
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("vpc", {
cidrBlock: "10.100.0.0/16",
instanceTenancy: "default",
});
const policy = new aws.iam.Policy("policy", {
description: "test",
policy: pulumi.all([vpc.arn, vpc.id]).apply(([arn, id]) => JSON.stringify({
Version: "2012-10-17",
Interpolated: `arn:${arn}:value`,
Value: id,
})),
});
```
### Generated TypeScript After
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("vpc", {
cidrBlock: "10.100.0.0/16",
instanceTenancy: "default",
});
const policy = new aws.iam.Policy("policy", {
description: "test",
policy: pulumi.jsonStringify({
Version: "2012-10-17",
Interpolated: pulumi.interpolate`arn:${vpc.arn}:value`,
Value: vpc.id,
}),
});
```
### Generated Python Before
```python
import pulumi
import json
import pulumi_aws as aws
vpc = aws.ec2.Vpc("vpc",
cidr_block="10.100.0.0/16",
instance_tenancy="default")
policy = aws.iam.Policy("policy",
description="test",
policy=pulumi.Output.all(vpc.arn, vpc.id).apply(lambda arn, id: json.dumps({
"Version": "2012-10-17",
"Interpolated": f"arn:{arn}:value",
"Value": id,
})))
```
### Generated Python After
```python
import pulumi
import json
import pulumi_aws as aws
vpc = aws.ec2.Vpc("vpc",
cidr_block="10.100.0.0/16",
instance_tenancy="default")
policy = aws.iam.Policy("policy",
description="test",
policy=pulumi.Output.json_dumps({
"Version": "2012-10-17",
"Interpolated": vpc.arn.apply(lambda arn: f"arn:{arn}:value"),
"Value": vpc.id,
}))
```
### Generated C# Before
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var vpc = new Aws.Ec2.Vpc("vpc", new()
{
CidrBlock = "10.100.0.0/16",
InstanceTenancy = "default",
});
var policy = new Aws.Iam.Policy("policy", new()
{
Description = "test",
PolicyDocument = Output.Tuple(vpc.Arn, vpc.Id).Apply(values =>
{
var arn = values.Item1;
var id = values.Item2;
return JsonSerializer.Serialize(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Interpolated"] = $"arn:{arn}:value",
["Value"] = id,
});
}),
});
});
```
### Generated C# After
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var vpc = new Aws.Ec2.Vpc("vpc", new()
{
CidrBlock = "10.100.0.0/16",
InstanceTenancy = "default",
});
var policy = new Aws.Iam.Policy("policy", new()
{
Description = "test",
PolicyDocument = Output.JsonSerialize(Output.Create(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Interpolated"] = vpc.Arn.Apply(arn => $"arn:{arn}:value"),
["Value"] = vpc.Id,
})),
});
});
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-02-20 15:48:46 +00:00
|
|
|
skipToJSONWhenRewritingApplies := true
|
|
|
|
expr, diags := pcl.RewriteAppliesWithSkipToJSON(expr, nameInfo(0), !g.asyncMain, skipToJSONWhenRewritingApplies)
|
2022-04-18 09:03:42 +00:00
|
|
|
if typ != nil {
|
2022-07-07 20:15:47 +00:00
|
|
|
var convertDiags hcl.Diagnostics
|
|
|
|
expr, convertDiags = pcl.RewriteConversions(expr, typ)
|
|
|
|
diags = diags.Extend(convertDiags)
|
2022-04-18 09:03:42 +00:00
|
|
|
}
|
2022-07-07 20:15:47 +00:00
|
|
|
expr, lowerProxyDiags := g.lowerProxyApplies(expr)
|
|
|
|
diags = diags.Extend(lowerProxyDiags)
|
2022-07-13 21:29:34 +00:00
|
|
|
g.diagnostics = g.diagnostics.Extend(diags)
|
2020-04-16 23:44:34 +00:00
|
|
|
return expr
|
|
|
|
}
|
2020-04-03 06:27:05 +00:00
|
|
|
|
2020-04-16 23:44:34 +00:00
|
|
|
func (g *generator) GetPrecedence(expr model.Expression) int {
|
|
|
|
// Precedence is derived from
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence.
|
|
|
|
switch expr := expr.(type) {
|
|
|
|
case *model.ConditionalExpression:
|
|
|
|
return 4
|
|
|
|
case *model.BinaryOpExpression:
|
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpLogicalOr:
|
|
|
|
return 5
|
|
|
|
case hclsyntax.OpLogicalAnd:
|
|
|
|
return 6
|
|
|
|
case hclsyntax.OpEqual, hclsyntax.OpNotEqual:
|
|
|
|
return 11
|
|
|
|
case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan,
|
|
|
|
hclsyntax.OpLessThanOrEqual:
|
|
|
|
return 12
|
|
|
|
case hclsyntax.OpAdd, hclsyntax.OpSubtract:
|
|
|
|
return 14
|
|
|
|
case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
|
|
|
|
return 15
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected binary expression %v", expr)
|
|
|
|
}
|
|
|
|
case *model.UnaryOpExpression:
|
|
|
|
return 17
|
|
|
|
case *model.FunctionCallExpression:
|
|
|
|
switch expr.Name {
|
|
|
|
case intrinsicAwait:
|
|
|
|
return 17
|
|
|
|
case intrinsicInterpolate:
|
|
|
|
return 22
|
|
|
|
default:
|
|
|
|
return 20
|
|
|
|
}
|
|
|
|
case *model.ForExpression, *model.IndexExpression, *model.RelativeTraversalExpression, *model.SplatExpression,
|
|
|
|
*model.TemplateJoinExpression:
|
|
|
|
return 20
|
|
|
|
case *model.AnonymousFunctionExpression, *model.LiteralValueExpression, *model.ObjectConsExpression,
|
|
|
|
*model.ScopeTraversalExpression, *model.TemplateExpression, *model.TupleConsExpression:
|
|
|
|
return 22
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected expression %v of type %T", expr, expr)
|
|
|
|
}
|
|
|
|
return 0
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) {
|
|
|
|
switch len(expr.Signature.Parameters) {
|
|
|
|
case 0:
|
|
|
|
g.Fgen(w, "()")
|
|
|
|
case 1:
|
|
|
|
g.Fgenf(w, "%s", expr.Signature.Parameters[0].Name)
|
|
|
|
default:
|
|
|
|
g.Fgen(w, "([")
|
|
|
|
for i, p := range expr.Signature.Parameters {
|
|
|
|
if i > 0 {
|
|
|
|
g.Fgen(w, ", ")
|
|
|
|
}
|
|
|
|
g.Fgenf(w, "%s", p.Name)
|
|
|
|
}
|
|
|
|
g.Fgen(w, "])")
|
|
|
|
}
|
|
|
|
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, " => %.v", expr.Body)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
opstr, precedence := "", g.GetPrecedence(expr)
|
2020-04-03 06:27:05 +00:00
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpAdd:
|
|
|
|
opstr = "+"
|
|
|
|
case hclsyntax.OpDivide:
|
|
|
|
opstr = "/"
|
|
|
|
case hclsyntax.OpEqual:
|
|
|
|
opstr = "=="
|
|
|
|
case hclsyntax.OpGreaterThan:
|
|
|
|
opstr = ">"
|
|
|
|
case hclsyntax.OpGreaterThanOrEqual:
|
|
|
|
opstr = ">="
|
|
|
|
case hclsyntax.OpLessThan:
|
|
|
|
opstr = "<"
|
|
|
|
case hclsyntax.OpLessThanOrEqual:
|
|
|
|
opstr = "<="
|
|
|
|
case hclsyntax.OpLogicalAnd:
|
|
|
|
opstr = "&&"
|
|
|
|
case hclsyntax.OpLogicalOr:
|
|
|
|
opstr = "||"
|
|
|
|
case hclsyntax.OpModulo:
|
|
|
|
opstr = "%"
|
|
|
|
case hclsyntax.OpMultiply:
|
|
|
|
opstr = "*"
|
|
|
|
case hclsyntax.OpNotEqual:
|
|
|
|
opstr = "!="
|
|
|
|
case hclsyntax.OpSubtract:
|
|
|
|
opstr = "-"
|
2020-04-16 23:44:34 +00:00
|
|
|
default:
|
|
|
|
opstr, precedence = ",", 1
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.4v ? %.4v : %.4v", expr.Condition, expr.TrueResult, expr.FalseResult)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) {
|
|
|
|
switch expr.Collection.Type().(type) {
|
|
|
|
case *model.ListType, *model.TupleType:
|
2020-04-07 02:43:16 +00:00
|
|
|
if expr.KeyVariable == nil {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v", expr.Collection)
|
2020-04-07 02:43:16 +00:00
|
|
|
} else {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v.map((v, k) => [k, v])", expr.Collection)
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2020-04-03 06:27:05 +00:00
|
|
|
case *model.MapType, *model.ObjectType:
|
2020-04-07 02:43:16 +00:00
|
|
|
if expr.KeyVariable == nil {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "Object.values(%.v)", expr.Collection)
|
2020-04-07 02:43:16 +00:00
|
|
|
} else {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "Object.entries(%.v)", expr.Collection)
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
fnParams, reduceParams := expr.ValueVariable.Name, expr.ValueVariable.Name
|
2020-04-03 06:27:05 +00:00
|
|
|
if expr.KeyVariable != nil {
|
2020-04-16 23:44:34 +00:00
|
|
|
reduceParams = fmt.Sprintf("[%.v, %.v]", expr.KeyVariable.Name, expr.ValueVariable.Name)
|
2020-04-07 02:43:16 +00:00
|
|
|
fnParams = fmt.Sprintf("(%v)", reduceParams)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if expr.Condition != nil {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, ".filter(%s => %.v)", fnParams, expr.Condition)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if expr.Key != nil {
|
|
|
|
// TODO(pdg): grouping
|
2023-04-26 18:00:32 +00:00
|
|
|
g.Fgenf(w, ".reduce((__obj, %s) => ({ ...__obj, [%.v]: %.v }))", reduceParams, expr.Key, expr.Value)
|
2020-04-03 06:27:05 +00:00
|
|
|
} else {
|
2023-04-05 11:04:46 +00:00
|
|
|
g.Fgenf(w, ".map(%s => (%.v))", fnParams, expr.Value)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) {
|
|
|
|
// Extract the list of outputs and the continuation expression from the `__apply` arguments.
|
2021-09-30 03:11:56 +00:00
|
|
|
applyArgs, then := pcl.ParseApplyCall(expr)
|
2020-04-07 02:43:16 +00:00
|
|
|
|
|
|
|
// If all of the arguments are promises, use promise methods. If any argument is an output, convert all other args
|
|
|
|
// to outputs and use output methods.
|
2020-04-07 16:25:50 +00:00
|
|
|
anyOutputs := false
|
2020-05-06 17:09:20 +00:00
|
|
|
for _, arg := range applyArgs {
|
|
|
|
if isOutputType(arg.Type()) {
|
|
|
|
anyOutputs = true
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 16:25:50 +00:00
|
|
|
apply, all := "then", "Promise.all"
|
|
|
|
if anyOutputs {
|
|
|
|
apply, all = "apply", "pulumi.all"
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2020-04-03 06:27:05 +00:00
|
|
|
|
|
|
|
if len(applyArgs) == 1 {
|
2020-04-07 02:43:16 +00:00
|
|
|
// If we only have a single output, just generate a normal `.apply` or `.then`.
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v.%v(%.v)", applyArgs[0], apply, then)
|
2020-04-03 06:27:05 +00:00
|
|
|
} else {
|
|
|
|
// Otherwise, generate a call to `pulumi.all([]).apply()`.
|
2020-05-04 22:04:35 +00:00
|
|
|
g.Fgenf(w, "%v([", all)
|
2020-04-03 06:27:05 +00:00
|
|
|
for i, o := range applyArgs {
|
|
|
|
if i > 0 {
|
|
|
|
g.Fgen(w, ", ")
|
|
|
|
}
|
2020-05-06 17:09:20 +00:00
|
|
|
g.Fgenf(w, "%v", o)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "]).%v(%.v)", apply, then)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
// functionName computes the NodeJS package, module, and name for the given function token.
|
|
|
|
func functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) {
|
|
|
|
token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
|
|
|
|
tokenRange := tokenArg.SyntaxNode().Range()
|
|
|
|
|
|
|
|
// Compute the resource type from the Pulumi type token.
|
2021-09-30 03:11:56 +00:00
|
|
|
pkg, module, member, diagnostics := pcl.DecomposeToken(token, tokenRange)
|
2023-02-23 20:51:11 +00:00
|
|
|
return pkg, strings.ReplaceAll(module, "/", "."), member, diagnostics
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) genRange(w io.Writer, call *model.FunctionCallExpression, entries bool) {
|
|
|
|
var from, to model.Expression
|
|
|
|
switch len(call.Args) {
|
|
|
|
case 1:
|
|
|
|
from, to = &model.LiteralValueExpression{Value: cty.NumberIntVal(0)}, call.Args[0]
|
|
|
|
case 2:
|
|
|
|
from, to = call.Args[0], call.Args[1]
|
|
|
|
default:
|
|
|
|
contract.Failf("expected range() to have exactly 1 or 2 args; got %v", len(call.Args))
|
|
|
|
}
|
|
|
|
|
|
|
|
genPrefix := func() { g.Fprint(w, "((from, to) => (new Array(to - from))") }
|
|
|
|
mapValue := "from + i"
|
2020-04-16 23:44:34 +00:00
|
|
|
genSuffix := func() { g.Fgenf(w, ")(%.v, %.v)", from, to) }
|
2020-04-07 02:43:16 +00:00
|
|
|
|
|
|
|
if litFrom, ok := from.(*model.LiteralValueExpression); ok {
|
|
|
|
fromV, err := convert.Convert(litFrom.Value, cty.Number)
|
2023-02-23 20:46:42 +00:00
|
|
|
contract.AssertNoErrorf(err, "conversion of %v to number failed", litFrom.Value.Type())
|
2020-04-07 02:43:16 +00:00
|
|
|
|
|
|
|
from, _ := fromV.AsBigFloat().Int64()
|
|
|
|
if litTo, ok := to.(*model.LiteralValueExpression); ok {
|
|
|
|
toV, err := convert.Convert(litTo.Value, cty.Number)
|
2023-02-23 20:46:42 +00:00
|
|
|
contract.AssertNoErrorf(err, "conversion of %v to number failed", litTo.Value.Type())
|
2020-04-07 02:43:16 +00:00
|
|
|
|
|
|
|
to, _ := toV.AsBigFloat().Int64()
|
|
|
|
if from == 0 {
|
|
|
|
mapValue = "i"
|
|
|
|
} else {
|
|
|
|
mapValue = fmt.Sprintf("%d + i", from)
|
|
|
|
}
|
|
|
|
genPrefix = func() { g.Fprintf(w, "(new Array(%d))", to-from) }
|
|
|
|
genSuffix = func() {}
|
|
|
|
} else if from == 0 {
|
2020-04-16 23:44:34 +00:00
|
|
|
genPrefix = func() { g.Fgenf(w, "(new Array(%.v))", to) }
|
2020-04-07 02:43:16 +00:00
|
|
|
mapValue = "i"
|
|
|
|
genSuffix = func() {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if entries {
|
|
|
|
mapValue = fmt.Sprintf("{key: %[1]s, value: %[1]s}", mapValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
genPrefix()
|
|
|
|
g.Fprintf(w, ".map((_, i) => %v)", mapValue)
|
|
|
|
genSuffix()
|
|
|
|
}
|
|
|
|
|
2022-01-21 14:03:25 +00:00
|
|
|
var functionImports = map[string][]string{
|
|
|
|
intrinsicInterpolate: {"@pulumi/pulumi"},
|
|
|
|
"fileArchive": {"@pulumi/pulumi"},
|
2022-04-25 19:59:30 +00:00
|
|
|
"remoteArchive": {"@pulumi/pulumi"},
|
|
|
|
"assetArchive": {"@pulumi/pulumi"},
|
2022-01-21 14:03:25 +00:00
|
|
|
"fileAsset": {"@pulumi/pulumi"},
|
2022-04-25 19:59:30 +00:00
|
|
|
"stringAsset": {"@pulumi/pulumi"},
|
|
|
|
"remoteAsset": {"@pulumi/pulumi"},
|
2022-01-21 14:03:25 +00:00
|
|
|
"filebase64": {"fs"},
|
|
|
|
"filebase64sha256": {"fs", "crypto"},
|
|
|
|
"readFile": {"fs"},
|
|
|
|
"readDir": {"fs"},
|
|
|
|
"sha1": {"crypto"},
|
2020-04-21 17:24:42 +00:00
|
|
|
}
|
|
|
|
|
2022-01-21 14:03:25 +00:00
|
|
|
func (g *generator) getFunctionImports(x *model.FunctionCallExpression) []string {
|
2021-09-30 03:11:56 +00:00
|
|
|
if x.Name != pcl.Invoke {
|
2020-04-21 17:24:42 +00:00
|
|
|
return functionImports[x.Name]
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg, _, _, diags := functionName(x.Args[0])
|
2023-02-23 20:46:42 +00:00
|
|
|
contract.Assertf(len(diags) == 0, "unexpected diagnostics: %v", diags)
|
2022-01-21 14:03:25 +00:00
|
|
|
return []string{"@pulumi/" + pkg}
|
2020-04-21 17:24:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-18 09:03:42 +00:00
|
|
|
func enumName(enum *model.EnumType) (string, error) {
|
|
|
|
e, ok := pcl.GetSchemaForType(enum)
|
|
|
|
if !ok {
|
2023-12-12 12:19:42 +00:00
|
|
|
return "", errors.New("Could not get associated enum")
|
2022-04-18 09:03:42 +00:00
|
|
|
}
|
2023-07-20 15:14:56 +00:00
|
|
|
pkgRef := e.(*schema.EnumType).PackageReference
|
|
|
|
return enumNameWithPackage(enum.Token, pkgRef)
|
|
|
|
}
|
|
|
|
|
|
|
|
func enumNameWithPackage(enumToken string, pkgRef schema.PackageReference) (string, error) {
|
|
|
|
components := strings.Split(enumToken, ":")
|
|
|
|
contract.Assertf(len(components) == 3, "malformed token %v", enumToken)
|
|
|
|
name := tokenToName(enumToken)
|
|
|
|
pkg := makeValidIdentifier(components[0])
|
2022-04-29 16:56:02 +00:00
|
|
|
if mod := components[1]; mod != "" && mod != "index" {
|
[program-gen] Fix enum resolution from types of the form Union[string, Enum] and emit fully qualified enum cases (#15696)
# Description
This PR improves enum type resolution from strings. When we try to
resolve `Union[string, Enum]` for a string expression, we choose
`string` because it is the more general type since not every string is
assignable to `Enum`. However, here we spacial case strings that are
actually part of that `Enum`.
The result is that `pcl.LowerConversion` will choose `Enum` from
`Union[string, Enum]` when the value of the input string is compatible
with the enum. This greatly improves program-gen for all of typescript,
python, csharp and go which now will emit the fully qualified enum cases
instead of emitting strings.
Closes https://github.com/pulumi/pulumi-dotnet/issues/41 which is
supposed to be a duplicate of
https://github.com/pulumi/pulumi-azure-native/issues/2616 but that is
not the case (the former is about unions of objects, the latter is
unions of enums and strings)
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-15 17:49:12 +00:00
|
|
|
// if the token has the format {pkg}:{mod}/{name}:{Name}
|
|
|
|
// then we simplify into {pkg}:{mod}:{Name}
|
|
|
|
modParts := strings.Split(mod, "/")
|
|
|
|
if len(modParts) == 2 && strings.EqualFold(modParts[1], components[2]) {
|
|
|
|
mod = modParts[0]
|
|
|
|
}
|
2023-07-20 15:14:56 +00:00
|
|
|
if pkgRef != nil {
|
|
|
|
mod = moduleName(mod, pkgRef)
|
2022-04-29 16:56:02 +00:00
|
|
|
}
|
|
|
|
pkg += "." + mod
|
2022-04-18 09:03:42 +00:00
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s.%s", pkg, name), nil
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:00:32 +00:00
|
|
|
func (g *generator) genEntries(w io.Writer, expr *model.FunctionCallExpression) {
|
|
|
|
entriesArg := expr.Args[0]
|
|
|
|
entriesArgType := pcl.UnwrapOption(model.ResolveOutputs(entriesArg.Type()))
|
|
|
|
switch entriesArgType.(type) {
|
|
|
|
case *model.ListType, *model.TupleType:
|
|
|
|
if call, ok := expr.Args[0].(*model.FunctionCallExpression); ok && call.Name == "range" {
|
|
|
|
g.genRange(w, call, true)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Mapping over a list with a tuple receiver accepts (value, index).
|
|
|
|
g.Fgenf(w, "%.20v.map((v, k)", expr.Args[0])
|
|
|
|
case *model.MapType, *model.ObjectType:
|
|
|
|
g.Fgenf(w, "Object.entries(%.v).map(([k, v])", expr.Args[0])
|
2023-05-16 18:11:57 +00:00
|
|
|
case *model.OpaqueType:
|
|
|
|
if entriesArgType.Equals(model.DynamicType) {
|
|
|
|
g.Fgenf(w, "Object.entries(%.v).map(([k, v])", expr.Args[0])
|
|
|
|
}
|
2023-04-26 18:00:32 +00:00
|
|
|
}
|
|
|
|
g.Fgenf(w, " => ({key: k, value: v}))")
|
|
|
|
}
|
|
|
|
|
2020-04-03 06:27:05 +00:00
|
|
|
func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) {
|
|
|
|
switch expr.Name {
|
2022-04-18 09:03:42 +00:00
|
|
|
case pcl.IntrinsicConvert:
|
|
|
|
from := expr.Args[0]
|
|
|
|
to := pcl.LowerConversion(from, expr.Signature.ReturnType)
|
|
|
|
output, isOutput := to.(*model.OutputType)
|
|
|
|
if isOutput {
|
|
|
|
to = output.ElementType
|
|
|
|
}
|
|
|
|
switch to := to.(type) {
|
|
|
|
case *model.EnumType:
|
|
|
|
if enum, err := enumName(to); err == nil {
|
|
|
|
if isOutput {
|
|
|
|
g.Fgenf(w, "%.v.apply((x) => %s[x])", from, enum)
|
|
|
|
} else {
|
2022-12-01 22:41:24 +00:00
|
|
|
diag := pcl.GenEnum(to, from, func(member *schema.Enum) {
|
2022-04-18 09:03:42 +00:00
|
|
|
memberTag, err := enumMemberName(tokenToName(to.Token), member)
|
|
|
|
contract.AssertNoErrorf(err, "Failed to get member name on enum '%s'", enum)
|
|
|
|
g.Fgenf(w, "%s.%s", enum, memberTag)
|
|
|
|
}, func(from model.Expression) {
|
|
|
|
g.Fgenf(w, "%s[%.v]", enum, from)
|
|
|
|
})
|
2022-12-01 22:41:24 +00:00
|
|
|
if diag != nil {
|
|
|
|
g.diagnostics = append(g.diagnostics, diag)
|
|
|
|
}
|
2022-04-18 09:03:42 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
g.Fgenf(w, "%v", from)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
g.Fgenf(w, "%v", from)
|
|
|
|
}
|
2021-09-30 03:11:56 +00:00
|
|
|
case pcl.IntrinsicApply:
|
2020-04-03 06:27:05 +00:00
|
|
|
g.genApply(w, expr)
|
2020-05-04 22:04:35 +00:00
|
|
|
case intrinsicAwait:
|
|
|
|
g.Fgenf(w, "await %.17v", expr.Args[0])
|
2020-04-03 06:27:05 +00:00
|
|
|
case intrinsicInterpolate:
|
|
|
|
g.Fgen(w, "pulumi.interpolate`")
|
|
|
|
for _, part := range expr.Args {
|
2021-06-24 16:17:55 +00:00
|
|
|
if lit, ok := part.(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
|
2020-04-03 06:27:05 +00:00
|
|
|
g.Fgen(w, lit.Value.AsString())
|
|
|
|
} else {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "${%.v}", part)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
g.Fgen(w, "`")
|
2020-04-16 23:44:34 +00:00
|
|
|
case "element":
|
|
|
|
g.Fgenf(w, "%.20v[%.v]", expr.Args[0], expr.Args[1])
|
2020-04-07 02:43:16 +00:00
|
|
|
case "entries":
|
2023-04-26 18:00:32 +00:00
|
|
|
g.genEntries(w, expr)
|
2020-04-03 06:27:05 +00:00
|
|
|
case "fileArchive":
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "new pulumi.asset.FileArchive(%.v)", expr.Args[0])
|
2022-04-25 19:59:30 +00:00
|
|
|
case "remoteArchive":
|
|
|
|
g.Fgenf(w, "new pulumi.asset.RemoteArchive(%.v)", expr.Args[0])
|
|
|
|
case "assetArchive":
|
|
|
|
g.Fgenf(w, "new pulumi.asset.AssetArchive(%.v)", expr.Args[0])
|
2020-04-03 06:27:05 +00:00
|
|
|
case "fileAsset":
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "new pulumi.asset.FileAsset(%.v)", expr.Args[0])
|
2022-04-25 19:59:30 +00:00
|
|
|
case "stringAsset":
|
|
|
|
g.Fgenf(w, "new pulumi.asset.StringAsset(%.v)", expr.Args[0])
|
|
|
|
case "remoteAsset":
|
|
|
|
g.Fgenf(w, "new pulumi.asset.RemoteAsset(%.v)", expr.Args[0])
|
2021-09-10 22:09:28 +00:00
|
|
|
case "filebase64":
|
2024-01-25 18:14:38 +00:00
|
|
|
g.Fgenf(w, "fs.readFileSync(%v, { encoding: \"base64\" })", expr.Args[0])
|
2022-01-21 14:03:25 +00:00
|
|
|
case "filebase64sha256":
|
|
|
|
// Assuming the existence of the following helper method
|
|
|
|
g.Fgenf(w, "computeFilebase64sha256(%v)", expr.Args[0])
|
2023-03-10 11:14:28 +00:00
|
|
|
case "notImplemented":
|
|
|
|
g.Fgenf(w, "notImplemented(%v)", expr.Args[0])
|
2023-05-25 20:12:13 +00:00
|
|
|
case "singleOrNone":
|
|
|
|
g.Fgenf(w, "singleOrNone(%v)", expr.Args[0])
|
[program-gen] Fix generated utility functions for filebase64, filebase64sha256, sha1 and mimeType (#14857)
# Description
While writing program tests for generated helper utility functions
`filebase64`, `filebase64sha256`, `sha1` and `mimeType` with the idea to
increase code coverage, it turned out that those are completely broken
in all of the languages containing syntax errors, missing imports and
wrong indentation. This PR fixes them and extends the `functions`
program to show how they now look like and to show that they compile.
Also adding example usage of `stack()`, `project()` and `cwd()` in the
test program.
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-12-15 11:26:00 +00:00
|
|
|
case "mimeType":
|
|
|
|
g.Fgenf(w, "mimeType(%v)", expr.Args[0])
|
2021-09-30 03:11:56 +00:00
|
|
|
case pcl.Invoke:
|
2020-04-07 02:43:16 +00:00
|
|
|
pkg, module, fn, diags := functionName(expr.Args[0])
|
2023-02-23 20:46:42 +00:00
|
|
|
contract.Assertf(len(diags) == 0, "unexpected diagnostics: %v", diags)
|
2020-04-07 02:43:16 +00:00
|
|
|
if module != "" {
|
|
|
|
module = "." + module
|
|
|
|
}
|
2021-11-17 20:27:50 +00:00
|
|
|
isOut := pcl.IsOutputVersionInvokeCall(expr)
|
2020-09-17 16:23:54 +00:00
|
|
|
name := fmt.Sprintf("%s%s.%s", makeValidIdentifier(pkg), module, fn)
|
2021-11-17 20:27:50 +00:00
|
|
|
if isOut {
|
2023-12-12 12:19:42 +00:00
|
|
|
name = name + "Output"
|
2021-11-17 20:27:50 +00:00
|
|
|
}
|
2021-07-29 03:41:23 +00:00
|
|
|
g.Fprintf(w, "%s(", name)
|
|
|
|
if len(expr.Args) >= 2 {
|
2023-01-11 22:17:14 +00:00
|
|
|
if expr.Signature.MultiArgumentInputs {
|
|
|
|
var invokeArgs *model.ObjectConsExpression
|
|
|
|
// extract invoke args in case we have the form invoke("token", __convert(args))
|
|
|
|
if converted, objectArgs, _ := pcl.RecognizeTypedObjectCons(expr.Args[1]); converted {
|
|
|
|
invokeArgs = objectArgs
|
|
|
|
} else {
|
|
|
|
// otherwise, we have the form invoke("token", args)
|
|
|
|
invokeArgs = expr.Args[1].(*model.ObjectConsExpression)
|
|
|
|
}
|
|
|
|
|
|
|
|
pcl.GenerateMultiArguments(g.Formatter, w, "undefined", invokeArgs, pcl.SortedFunctionParameters(expr))
|
|
|
|
} else {
|
|
|
|
g.Fgenf(w, "%.v", expr.Args[1])
|
|
|
|
}
|
2021-07-29 03:41:23 +00:00
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
if len(expr.Args) == 3 {
|
2021-07-29 03:41:23 +00:00
|
|
|
g.Fgenf(w, ", %.v", expr.Args[2])
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
2021-07-29 03:41:23 +00:00
|
|
|
g.Fprint(w, ")")
|
|
|
|
case "join":
|
|
|
|
g.Fgenf(w, "%.20v.join(%v)", expr.Args[1], expr.Args[0])
|
2020-04-16 23:44:34 +00:00
|
|
|
case "length":
|
|
|
|
g.Fgenf(w, "%.20v.length", expr.Args[0])
|
|
|
|
case "lookup":
|
|
|
|
g.Fgenf(w, "%v[%v]", expr.Args[0], expr.Args[1])
|
|
|
|
if len(expr.Args) == 3 {
|
|
|
|
g.Fgenf(w, " || %v", expr.Args[2])
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
case "range":
|
|
|
|
g.genRange(w, expr, false)
|
2020-04-16 23:44:34 +00:00
|
|
|
case "readFile":
|
2023-11-23 00:26:41 +00:00
|
|
|
g.Fgenf(w, "fs.readFileSync(%v, \"utf8\")", expr.Args[0])
|
2020-04-16 23:44:34 +00:00
|
|
|
case "readDir":
|
2022-11-28 19:26:32 +00:00
|
|
|
g.Fgenf(w, "fs.readdirSync(%v)", expr.Args[0])
|
2020-06-30 18:35:24 +00:00
|
|
|
case "secret":
|
|
|
|
g.Fgenf(w, "pulumi.secret(%v)", expr.Args[0])
|
2023-01-31 12:31:00 +00:00
|
|
|
case "unsecret":
|
|
|
|
g.Fgenf(w, "pulumi.unsecret(%v)", expr.Args[0])
|
2020-04-16 23:44:34 +00:00
|
|
|
case "split":
|
|
|
|
g.Fgenf(w, "%.20v.split(%v)", expr.Args[1], expr.Args[0])
|
2021-07-29 03:41:23 +00:00
|
|
|
case "toBase64":
|
|
|
|
g.Fgenf(w, "Buffer.from(%v).toString(\"base64\")", expr.Args[0])
|
2022-09-16 23:12:29 +00:00
|
|
|
case "fromBase64":
|
|
|
|
g.Fgenf(w, "Buffer.from(%v, \"base64\").toString(\"utf8\")", expr.Args[0])
|
2020-04-03 06:27:05 +00:00
|
|
|
case "toJSON":
|
[program-gen] Emit Output-returning JSON serialization methods without rewriting applies (#15371)
### Description
A while ago we started implementing [specialized JSON serialization
methods](https://github.com/pulumi/pulumi/issues/12519) for Pulumi
programs which can accept nested outputs without having to rewrite and
combine applies.
- `Output.SerializeJson` in .NET
- `pulumi.jsonStringify` in nodejs
- `pulumi.Output.json_dumps` in Python
This PR extends program-gen for TypeScript, C# and Python to start
emitting these JSON serialization functions (when necessary). The PR
special-cases the `toJSON` PCL function when rewriting applies so that
nested outputs aren't rewritted.
Example PCL program and generated results:
> Also check out the downstream codegen tests to see improved generated
examples
```
resource vpc "aws:ec2:Vpc" {
cidrBlock = "10.100.0.0/16"
instanceTenancy = "default"
}
resource policy "aws:iam/policy:Policy" {
description = "test"
policy = toJSON({
"Version" = "2012-10-17"
"Interpolated" = "arn:${vpc.arn}:value"
"Value" = vpc.id
})
}
```
### Generated TypeScript Before
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("vpc", {
cidrBlock: "10.100.0.0/16",
instanceTenancy: "default",
});
const policy = new aws.iam.Policy("policy", {
description: "test",
policy: pulumi.all([vpc.arn, vpc.id]).apply(([arn, id]) => JSON.stringify({
Version: "2012-10-17",
Interpolated: `arn:${arn}:value`,
Value: id,
})),
});
```
### Generated TypeScript After
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("vpc", {
cidrBlock: "10.100.0.0/16",
instanceTenancy: "default",
});
const policy = new aws.iam.Policy("policy", {
description: "test",
policy: pulumi.jsonStringify({
Version: "2012-10-17",
Interpolated: pulumi.interpolate`arn:${vpc.arn}:value`,
Value: vpc.id,
}),
});
```
### Generated Python Before
```python
import pulumi
import json
import pulumi_aws as aws
vpc = aws.ec2.Vpc("vpc",
cidr_block="10.100.0.0/16",
instance_tenancy="default")
policy = aws.iam.Policy("policy",
description="test",
policy=pulumi.Output.all(vpc.arn, vpc.id).apply(lambda arn, id: json.dumps({
"Version": "2012-10-17",
"Interpolated": f"arn:{arn}:value",
"Value": id,
})))
```
### Generated Python After
```python
import pulumi
import json
import pulumi_aws as aws
vpc = aws.ec2.Vpc("vpc",
cidr_block="10.100.0.0/16",
instance_tenancy="default")
policy = aws.iam.Policy("policy",
description="test",
policy=pulumi.Output.json_dumps({
"Version": "2012-10-17",
"Interpolated": vpc.arn.apply(lambda arn: f"arn:{arn}:value"),
"Value": vpc.id,
}))
```
### Generated C# Before
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var vpc = new Aws.Ec2.Vpc("vpc", new()
{
CidrBlock = "10.100.0.0/16",
InstanceTenancy = "default",
});
var policy = new Aws.Iam.Policy("policy", new()
{
Description = "test",
PolicyDocument = Output.Tuple(vpc.Arn, vpc.Id).Apply(values =>
{
var arn = values.Item1;
var id = values.Item2;
return JsonSerializer.Serialize(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Interpolated"] = $"arn:{arn}:value",
["Value"] = id,
});
}),
});
});
```
### Generated C# After
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var vpc = new Aws.Ec2.Vpc("vpc", new()
{
CidrBlock = "10.100.0.0/16",
InstanceTenancy = "default",
});
var policy = new Aws.Iam.Policy("policy", new()
{
Description = "test",
PolicyDocument = Output.JsonSerialize(Output.Create(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Interpolated"] = vpc.Arn.Apply(arn => $"arn:{arn}:value"),
["Value"] = vpc.Id,
})),
});
});
```
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-02-20 15:48:46 +00:00
|
|
|
if model.ContainsOutputs(expr.Args[0].Type()) {
|
|
|
|
g.Fgenf(w, "pulumi.jsonStringify(%v)", expr.Args[0])
|
|
|
|
} else {
|
|
|
|
g.Fgenf(w, "JSON.stringify(%v)", expr.Args[0])
|
|
|
|
}
|
2021-09-10 18:40:38 +00:00
|
|
|
case "sha1":
|
|
|
|
g.Fgenf(w, "crypto.createHash('sha1').update(%v).digest('hex')", expr.Args[0])
|
2022-03-16 00:05:36 +00:00
|
|
|
case "stack":
|
|
|
|
g.Fgenf(w, "pulumi.getStack()")
|
|
|
|
case "project":
|
|
|
|
g.Fgenf(w, "pulumi.getProject()")
|
2024-08-19 03:58:19 +00:00
|
|
|
case "organization":
|
|
|
|
g.Fgenf(w, "pulumi.getOrganization()")
|
2022-03-16 00:05:36 +00:00
|
|
|
case "cwd":
|
|
|
|
g.Fgen(w, "process.cwd()")
|
2024-04-16 11:13:25 +00:00
|
|
|
case "getOutput":
|
|
|
|
g.Fgenf(w, "%s.getOutput(%v)", expr.Args[0], expr.Args[1])
|
2021-09-10 18:40:38 +00:00
|
|
|
|
2020-04-03 06:27:05 +00:00
|
|
|
default:
|
|
|
|
var rng hcl.Range
|
|
|
|
if expr.Syntax != nil {
|
|
|
|
rng = expr.Syntax.Range()
|
|
|
|
}
|
|
|
|
g.genNYI(w, "FunctionCallExpression: %v (%v)", expr.Name, rng)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v[%.v]", expr.Collection, expr.Key)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
2024-03-29 14:24:29 +00:00
|
|
|
func escapeRune(c rune) string {
|
|
|
|
if uint(c) <= 0xFF {
|
|
|
|
return fmt.Sprintf("\\x%02x", c)
|
|
|
|
} else if uint(c) <= 0xFFFF {
|
|
|
|
return fmt.Sprintf("\\u%04x", c)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("\\u{%x}", c)
|
|
|
|
}
|
|
|
|
|
2020-04-03 06:27:05 +00:00
|
|
|
func (g *generator) genStringLiteral(w io.Writer, v string) {
|
|
|
|
builder := strings.Builder{}
|
|
|
|
newlines := strings.Count(v, "\n")
|
|
|
|
if newlines == 0 || newlines == 1 && (v[0] == '\n' || v[len(v)-1] == '\n') {
|
|
|
|
// This string either does not contain newlines or contains a single leading or trailing newline, so we'll
|
|
|
|
// Generate a normal string literal. Quotes, backslashes, and newlines will be escaped in conformance with
|
|
|
|
// ECMA-262 11.8.4 ("String Literals").
|
|
|
|
builder.WriteRune('"')
|
|
|
|
for _, c := range v {
|
2024-03-29 14:24:29 +00:00
|
|
|
if c == '"' || c == '\\' {
|
|
|
|
builder.WriteRune('\\')
|
|
|
|
builder.WriteRune(c)
|
|
|
|
} else if c == '\n' {
|
2020-04-03 06:27:05 +00:00
|
|
|
builder.WriteString(`\n`)
|
2024-03-29 14:24:29 +00:00
|
|
|
} else if unicode.IsPrint(c) {
|
2020-04-03 06:27:05 +00:00
|
|
|
builder.WriteRune(c)
|
2024-03-29 14:24:29 +00:00
|
|
|
} else {
|
|
|
|
// This is a non-printable character. We'll emit an escape sequence for it.
|
|
|
|
builder.WriteString(escapeRune(c))
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
builder.WriteRune('"')
|
|
|
|
} else {
|
|
|
|
// This string does contain newlines, so we'll Generate a template string literal. "${", backquotes, and
|
|
|
|
// backslashes will be escaped in conformance with ECMA-262 11.8.6 ("Template Literal Lexical Components").
|
|
|
|
runes := []rune(v)
|
|
|
|
builder.WriteRune('`')
|
|
|
|
for i, c := range runes {
|
2024-03-29 14:24:29 +00:00
|
|
|
if c == '`' || c == '\\' {
|
|
|
|
builder.WriteRune('\\')
|
|
|
|
builder.WriteRune(c)
|
|
|
|
} else if c == '$' {
|
2020-04-03 06:27:05 +00:00
|
|
|
if i < len(runes)-1 && runes[i+1] == '{' {
|
|
|
|
builder.WriteRune('\\')
|
2024-03-29 14:24:29 +00:00
|
|
|
builder.WriteRune('$')
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
2024-03-29 14:24:29 +00:00
|
|
|
} else if c == '\n' {
|
|
|
|
builder.WriteRune('\n')
|
|
|
|
} else if unicode.IsPrint(c) {
|
|
|
|
builder.WriteRune(c)
|
|
|
|
} else {
|
|
|
|
// This is a non-printable character. We'll emit an escape sequence for it.
|
|
|
|
builder.WriteString(escapeRune(c))
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
builder.WriteRune('`')
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Fgenf(w, "%s", builder.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) {
|
2021-06-24 16:17:55 +00:00
|
|
|
typ := expr.Type()
|
|
|
|
if cns, ok := typ.(*model.ConstType); ok {
|
|
|
|
typ = cns.Type
|
|
|
|
}
|
|
|
|
|
|
|
|
switch typ {
|
2020-04-03 06:27:05 +00:00
|
|
|
case model.BoolType:
|
|
|
|
g.Fgenf(w, "%v", expr.Value.True())
|
2020-08-06 20:12:27 +00:00
|
|
|
case model.NoneType:
|
|
|
|
g.Fgen(w, "undefined")
|
2020-04-03 06:27:05 +00:00
|
|
|
case model.NumberType:
|
|
|
|
bf := expr.Value.AsBigFloat()
|
|
|
|
if i, acc := bf.Int64(); acc == big.Exact {
|
|
|
|
g.Fgenf(w, "%d", i)
|
|
|
|
} else {
|
|
|
|
f, _ := bf.Float64()
|
|
|
|
g.Fgenf(w, "%g", f)
|
|
|
|
}
|
|
|
|
case model.StringType:
|
|
|
|
g.genStringLiteral(w, expr.Value.AsString())
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected literal type in GenLiteralValueExpression: %v (%v)", expr.Type(),
|
|
|
|
expr.SyntaxNode().Range())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-04 22:04:35 +00:00
|
|
|
func (g *generator) literalKey(x model.Expression) (string, bool) {
|
|
|
|
strKey := ""
|
|
|
|
switch x := x.(type) {
|
|
|
|
case *model.LiteralValueExpression:
|
2021-06-24 16:17:55 +00:00
|
|
|
if model.StringType.AssignableFrom(x.Type()) {
|
2020-05-04 22:04:35 +00:00
|
|
|
strKey = x.Value.AsString()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
g.GenLiteralValueExpression(&buf, x)
|
|
|
|
return buf.String(), true
|
|
|
|
case *model.TemplateExpression:
|
|
|
|
if len(x.Parts) == 1 {
|
2021-06-24 16:17:55 +00:00
|
|
|
if lit, ok := x.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
|
2020-05-04 22:04:35 +00:00
|
|
|
strKey = lit.Value.AsString()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2023-07-18 06:47:37 +00:00
|
|
|
return "", false
|
2020-05-04 22:04:35 +00:00
|
|
|
default:
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
if isLegalIdentifier(strKey) {
|
|
|
|
return strKey, true
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%q", strKey), true
|
|
|
|
}
|
|
|
|
|
2020-04-03 06:27:05 +00:00
|
|
|
func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) {
|
|
|
|
if len(expr.Items) == 0 {
|
|
|
|
g.Fgen(w, "{}")
|
|
|
|
} else {
|
|
|
|
g.Fgen(w, "{")
|
|
|
|
g.Indented(func() {
|
|
|
|
for _, item := range expr.Items {
|
|
|
|
g.Fgenf(w, "\n%s", g.Indent)
|
2020-05-04 22:04:35 +00:00
|
|
|
if lit, ok := g.literalKey(item.Key); ok {
|
|
|
|
g.Fgenf(w, "%s", lit)
|
2020-04-03 06:27:05 +00:00
|
|
|
} else {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "[%.v]", item.Key)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, ": %.v,", item.Value)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
g.Fgenf(w, "\n%s}", g.Indent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) genRelativeTraversal(w io.Writer, traversal hcl.Traversal, parts []model.Traversable) {
|
|
|
|
for i, part := range traversal {
|
|
|
|
var key cty.Value
|
|
|
|
switch part := part.(type) {
|
|
|
|
case hcl.TraverseAttr:
|
|
|
|
key = cty.StringVal(part.Name)
|
|
|
|
case hcl.TraverseIndex:
|
|
|
|
key = part.Key
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected traversal part of type %T (%v)", part, part.SourceRange())
|
|
|
|
}
|
|
|
|
|
2022-11-28 18:56:04 +00:00
|
|
|
var indexPrefix string
|
2020-04-03 06:27:05 +00:00
|
|
|
if model.IsOptionalType(model.GetTraversableType(parts[i])) {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgen(w, "?")
|
2022-11-28 18:56:04 +00:00
|
|
|
// `expr?[expr]` is not valid typescript, since it looks like a ternary
|
|
|
|
// operator.
|
|
|
|
//
|
|
|
|
// Typescript solves this by inserting a `.` in before the `[`: `expr?.[expr]`
|
|
|
|
//
|
|
|
|
// We need to do the same when generating index based expressions.
|
|
|
|
indexPrefix = "."
|
|
|
|
}
|
|
|
|
|
|
|
|
genIndex := func(inner string, value interface{}) {
|
|
|
|
g.Fgenf(w, "%s["+inner+"]", indexPrefix, value)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch key.Type() {
|
|
|
|
case cty.String:
|
|
|
|
keyVal := key.AsString()
|
|
|
|
if isLegalIdentifier(keyVal) {
|
|
|
|
g.Fgenf(w, ".%s", keyVal)
|
|
|
|
} else {
|
2022-11-28 18:56:04 +00:00
|
|
|
genIndex("%q", keyVal)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
case cty.Number:
|
|
|
|
idx, _ := key.AsBigFloat().Int64()
|
2022-11-28 18:56:04 +00:00
|
|
|
genIndex("%d", idx)
|
2020-04-03 06:27:05 +00:00
|
|
|
default:
|
2022-11-28 18:56:04 +00:00
|
|
|
genIndex("%q", key.AsString())
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v", expr.Source)
|
2020-04-03 06:27:05 +00:00
|
|
|
g.genRelativeTraversal(w, expr.Traversal, expr.Parts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) {
|
2023-03-23 22:25:21 +00:00
|
|
|
rootName := makeValidIdentifier(expr.RootName)
|
2023-03-08 23:34:15 +00:00
|
|
|
if g.isComponent {
|
|
|
|
if expr.RootName == "this" {
|
|
|
|
// special case for parent: this
|
|
|
|
g.Fgenf(w, "%s", expr.RootName)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
configVars := map[string]*pcl.ConfigVariable{}
|
|
|
|
for _, configVar := range g.program.ConfigVariables() {
|
|
|
|
configVars[configVar.Name()] = configVar
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, isConfig := configVars[expr.RootName]; isConfig {
|
|
|
|
if _, configReference := expr.Parts[0].(*pcl.ConfigVariable); configReference {
|
2023-12-12 12:19:42 +00:00
|
|
|
rootName = "args." + expr.RootName
|
2023-03-08 23:34:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-16 23:44:34 +00:00
|
|
|
if _, ok := expr.Parts[0].(*model.SplatVariable); ok {
|
2020-04-03 06:27:05 +00:00
|
|
|
rootName = "__item"
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Fgen(w, rootName)
|
|
|
|
g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%.20v.map(__item => %.v)", expr.Source, expr.Each)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) {
|
|
|
|
if len(expr.Parts) == 1 {
|
2021-06-24 16:17:55 +00:00
|
|
|
if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
|
2020-04-03 06:27:05 +00:00
|
|
|
g.GenLiteralValueExpression(w, lit)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Fgen(w, "`")
|
|
|
|
for _, expr := range expr.Parts {
|
2021-06-24 16:17:55 +00:00
|
|
|
if lit, ok := expr.(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
|
2020-04-03 06:27:05 +00:00
|
|
|
g.Fgen(w, lit.Value.AsString())
|
|
|
|
} else {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "${%.v}", expr)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
g.Fgen(w, "`")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) {
|
|
|
|
g.genNYI(w, "TemplateJoinExpression")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) {
|
|
|
|
switch len(expr.Expressions) {
|
|
|
|
case 0:
|
|
|
|
g.Fgen(w, "[]")
|
|
|
|
case 1:
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "[%.v]", expr.Expressions[0])
|
2020-04-03 06:27:05 +00:00
|
|
|
default:
|
|
|
|
g.Fgen(w, "[")
|
|
|
|
g.Indented(func() {
|
|
|
|
for _, v := range expr.Expressions {
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "\n%s%.v,", g.Indent, v)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
g.Fgen(w, "\n", g.Indent, "]")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) {
|
2020-04-16 23:44:34 +00:00
|
|
|
opstr, precedence := "", g.GetPrecedence(expr)
|
2020-04-03 06:27:05 +00:00
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpLogicalNot:
|
|
|
|
opstr = "!"
|
|
|
|
case hclsyntax.OpNegate:
|
|
|
|
opstr = "-"
|
|
|
|
}
|
2020-04-16 23:44:34 +00:00
|
|
|
g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand)
|
2020-04-03 06:27:05 +00:00
|
|
|
}
|